diff --git a/bases/rsptx/book_server_api/routers/coach.py b/bases/rsptx/book_server_api/routers/coach.py index e056790e5..b670f24b6 100644 --- a/bases/rsptx/book_server_api/routers/coach.py +++ b/bases/rsptx/book_server_api/routers/coach.py @@ -170,6 +170,25 @@ def extract_parsons_solution(parsonsexample_code): return "\n".join(clean_lines) +def _escape_parsons_block_markup(block_markup): + """ + The generated "---"-separated block markup is about to be embedded into a +
element and re-parsed from innerHTML by
+ parsons.js, so a bare "<" in the code (e.g. "if hours <= 1:") would be
+ misread as the start of an HTML tag. HTML-escape it -- the Parsons widget
+ renders entities back to their literal characters, the same way it already
+ handles the escaped DB-authored markup (see extract_parsons_code).
+
+ "<" is the only character that actually breaks parsing here -- ">" is plain
+ data in element content and "&" only matters before a valid entity name --
+ but html.escape covers "&"/">" too, which round-trip harmlessly through the
+ widget. A previous version instead inserted a space after "<", which turned
+ every "<="/"<" in the code into "< ="/"< ". The generated code never
+ contains pre-existing entities, so escaping is safe to apply unconditionally.
+ """
+ return html.escape(block_markup, quote=False)
+
+
def _extract_suffix_code_from_htmlsrc(htmlsrc):
"""
Book-authored activecode questions never get question_json.suffix_code
@@ -518,7 +537,7 @@ def parsons_help(
[],
{},
)
- example_block = re.sub(r"<(?=\S)", "< ", example_block)
+ example_block = _escape_parsons_block_markup(example_block)
parsons_html = f"""