Skip to content

Commit

Permalink
fix: instance expressions with double quotes not escaped / converted
Browse files Browse the repository at this point in the history
- like other functions, argument value(s) (instance name) should be
  allowed to be wrapped in single or double quotes. Previously only
  singles quotes worked.
  • Loading branch information
lindsay-stevens committed Jun 3, 2024
1 parent 1599d1f commit b529fa2
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 2 deletions.
7 changes: 5 additions & 2 deletions pyxform/parsing/instance_expression.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import re
from typing import TYPE_CHECKING

from pyxform.utils import BRACKETED_TAG_REGEX, EXPRESSION_LEXER, ExpLexerToken
from pyxform.utils import BRACKETED_TAG_REGEX, EXPRESSION_LEXER, ExpLexerToken, node

if TYPE_CHECKING:
from pyxform.survey import Survey
Expand Down Expand Up @@ -116,7 +116,10 @@ def replace_with_output(xml_text: str, context: "SurveyElement", survey: "Survey
lambda m: survey._var_repl_function(m, context),
old_str,
)
new_strings.append((start, end, old_str, f'<output value="{new_str}" />'))
# Generate a node so that character escapes are applied.
new_strings.append(
(start, end, old_str, node("output", value=new_str).toxml())
)
# Position-based replacement avoids strings which are substrings of other
# replacements being inserted incorrectly. Offset tracking deals with changing
# expression positions due to incremental replacement.
Expand Down
49 changes: 49 additions & 0 deletions tests/test_notes.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ def test_instance_expression__permutations(self):
| | c2 | b | Big |
| | c2 | s | Small |
"""
# It's a bit confusing, but although double quotes are literally HTML in entity
# form (i.e. `&quot;`) in the output, for pyxform test comparisons they get
# converted back, so the expected output strings are double quotes not `&quot;`.
cases = [
# A pyxform token.
Case(
Expand All @@ -90,12 +93,30 @@ def test_instance_expression__permutations(self):
xpq.body_input_label_output_value("note"),
{"instance('c1')/root/item[name = /test_name/q1 ]/label"},
),
# Instance expression with predicate using pyxform token and equals (double quotes).
Case(
"""instance("c1")/root/item[name = ${q1}]/label""",
xpq.body_input_label_output_value("note"),
{"""instance("c1")/root/item[name = /test_name/q1 ]/label"""},
),
# Instance expression with predicate using pyxform token and function.
Case(
"instance('c2')/root/item[contains(name, ${q2})]/label",
xpq.body_input_label_output_value("note"),
{"instance('c2')/root/item[contains(name, /test_name/q2 )]/label"},
),
# Instance expression with predicate using pyxform token and function (double quotes).
Case(
"""instance("c2")/root/item[contains("name", ${q2})]/label""",
xpq.body_input_label_output_value("note"),
{"""instance("c2")/root/item[contains("name", /test_name/q2 )]/label"""},
),
# Instance expression with predicate using pyxform token and function (mixed quotes).
Case(
"""instance('c2')/root/item[contains("name", ${q2})]/label""",
xpq.body_input_label_output_value("note"),
{"""instance('c2')/root/item[contains("name", /test_name/q2 )]/label"""},
),
# Instance expression with predicate using pyxform token and equals.
Case(
"instance('c2')/root/item[contains(name, instance('c1')/root/item[name = ${q1}]/label)]/label",
Expand All @@ -104,12 +125,40 @@ def test_instance_expression__permutations(self):
"instance('c2')/root/item[contains(name, instance('c1')/root/item[name = /test_name/q1 ]/label)]/label"
},
),
# Instance expression with predicate using pyxform token and equals (double quotes).
Case(
"""instance("c2")/root/item[contains(name, instance("c1")/root/item[name = ${q1}]/label)]/label""",
xpq.body_input_label_output_value("note"),
{
"""instance("c2")/root/item[contains(name, instance("c1")/root/item[name = /test_name/q1 ]/label)]/label"""
},
),
# Instance expression with predicate using pyxform token and equals (mixed quotes).
Case(
"""instance('c2')/root/item[contains(name, instance("c1")/root/item[name = ${q1}]/label)]/label""",
xpq.body_input_label_output_value("note"),
{
"""instance('c2')/root/item[contains(name, instance("c1")/root/item[name = /test_name/q1 ]/label)]/label"""
},
),
# Instance expression with predicate not using a pyxform token.
Case(
"instance('c1')/root/item[name = 'y']/label",
xpq.body_input_label_output_value("note"),
{"instance('c1')/root/item[name = 'y']/label"},
),
# Instance expression with predicate not using a pyxform token (double quotes).
Case(
"""instance("c1")/root/item[name = "y"]/label""",
xpq.body_input_label_output_value("note"),
{"""instance("c1")/root/item[name = "y"]/label"""},
),
# Instance expression with predicate not using a pyxform token (mixed quotes).
Case(
"""instance("c1")/root/item[name = 'y']/label""",
xpq.body_input_label_output_value("note"),
{"""instance("c1")/root/item[name = 'y']/label"""},
),
]
wrap_scenarios = ("{}", "Text {}", "{} text", "Text {} text")
# All cases together in one.
Expand Down

0 comments on commit b529fa2

Please sign in to comment.