diff --git a/drivers/python/age/builder.py b/drivers/python/age/builder.py index 08a40c252..b4f746e7a 100644 --- a/drivers/python/age/builder.py +++ b/drivers/python/age/builder.py @@ -105,9 +105,16 @@ def visitAgValue(self, ctx:AgtypeParser.AgValueContext): return valueCtx.accept(self) + @staticmethod + def _stripStringDelimiters(stringToken): + # The STRING token always has surrounding '"' delimiters per the + # Agtype grammar; slice rather than strip('"') so escaped quotes + # at the boundaries are preserved. + return stringToken.getText()[1:-1] + # Visit a parse tree produced by AgtypeParser#StringValue. def visitStringValue(self, ctx:AgtypeParser.StringValueContext): - return ctx.STRING().getText().strip('"') + return self._stripStringDelimiters(ctx.STRING()) # Visit a parse tree produced by AgtypeParser#IntegerValue. @@ -182,7 +189,7 @@ def visitPair(self, ctx:AgtypeParser.PairContext): raise AGTypeError(ctx.getText(), "Missing key in object pair") if agValNode is None: raise AGTypeError(ctx.getText(), "Missing value in object pair") - return (strNode.getText().strip('"') , agValNode) + return (self._stripStringDelimiters(strNode), agValNode) # Visit a parse tree produced by AgtypeParser#array. diff --git a/drivers/python/test_agtypes.py b/drivers/python/test_agtypes.py index 97f7972d1..7de0f5f52 100644 --- a/drivers/python/test_agtypes.py +++ b/drivers/python/test_agtypes.py @@ -245,6 +245,22 @@ def test_array_of_mixed_types(self): self.assertEqual(result[4], [1, 2, 3]) self.assertEqual(result[5], {"key": "val"}) + def test_string_value_preserves_inner_quotes(self): + """Issue #2418: preserve escaped boundary quotes when stripping. + + visitStringValue must strip only the outer delimiters; otherwise + values like '"foo \\"bar\\""' lose their trailing escaped quote. + """ + self.assertEqual(self.parse('"foo \\"bar\\""'), 'foo \\"bar\\"') + self.assertEqual(self.parse('"\\"leading"'), '\\"leading') + self.assertEqual(self.parse('"trailing\\""'), 'trailing\\"') + self.assertEqual(self.parse('""'), '') + # Same fix applies to visitPair() for object keys. + self.assertEqual( + self.parse('{"key\\"q": 1}'), + {'key\\"q': 1}, + ) + def test_malformed_vertex_raises_agtypeerror_or_recovers(self): """Issue #2367: Malformed agtype must raise AGTypeError or recover gracefully.""" from age.exceptions import AGTypeError