Skip to content

fast-xml-parser ≥5.3.8 breaks ProblemEditor OLX builder/parser (blocks #3175) #3178

Description

@brian-smith-tcril

Note

This issue was researched and drafted by Claude (via Claude Code), and reviewed by @brian-smith-tcril before filing.

Summary

fast-xml-parser introduces two separate behavior changes in its XML builder — in the patch release 5.3.8 and the patch release 5.7.3 — that break the Problem Editor's OLX serialization/parsing. Both are breaking changes shipped in non-major releases, i.e. semver violations. Our package.json declares "fast-xml-parser": "^5.0.0", an honest range that (correctly, per semver) permits these releases, so a from-scratch package-lock.json regeneration floats the dependency up to the latest 5.x and the ProblemEditor test suites start failing. This currently blocks the security update in #3175 (bump to 5.7.0) and any lockfile regeneration.

The committed lockfile currently snapshots 5.3.6, which is why CI is green today. That snapshot is a known-working resolution, not a version policy — a lockfile regen should reproduce working behavior, and would if the dependency followed semver. It doesn't here, so any regen legitimately resolves to a broken version.

Impact

6 test failures surface on a fresh regen, across the two OLX suites:

src/editors/containers/ProblemEditor/data/ReactStateOLXParser.test.js (4 failures) — empty elements serialize as [object Object]:

  • Test numerical response with feedback and hints problem type
  • Test numerical response with isAnswerRange true
  • Test string response with feedback and hints problem type
  • Test string response with feedback and hints, multiple answers

Example:

- Expected: <textline size="20"></textline>
+ Received: <textline size="20">[object Object]</textline>

src/editors/containers/ProblemEditor/data/OLXParser.test.js (2 failures) — apostrophes are entity-encoded as &apos;:

  • parseMultipleChoiceAnswers() › given multiple choice olx with hex numbers and leading zeros › should not parse hex numbers and leading zeros
  • parseQuestions() › given olx with html entities › should not encode html entities

Example:

- Expected: font-family: 'courier new'
+ Received: font-family: &apos;courier new&apos;

Root cause — two distinct regressions

We bisected each failure to an exact release:

Regression First broken version Attributed change
[object Object] for empty nodes 5.3.8 c13a961 — "handle non-array input for XML builder when preserveOrder is true"
'&apos; encoding 5.7.3 f9c9a2c — "update builder to 1.1.7", pulling in fast-xml-builder fddfaf31 — "escape quotes in attribute value"

Version-by-version (both ProblemEditor suites):

Version ReactStateOLXParser OLXParser
5.3.7
5.3.8 ❌ (4)
5.7.2 ❌ (4)
5.7.3 ❌ (4) ❌ (2)
5.10.1 ❌ (4) ❌ (2)

Regression 1 — [object Object] (5.3.8)

The ordered (preserveOrder: true) builder's arrToStr gained a non-array guard in commit c13a961 (v5.3.7...v5.3.8):

  function arrToStr(arr, options, jPath, indentation) {
      let xmlStr = "";
      let isPreviousElementTag = false;
+     if (!Array.isArray(arr)) {
+         // Non-array values (e.g. string tag values) should be treated as text content
+         if (arr !== undefined && arr !== null) {
+             let text = arr.toString();
+             text = replaceEntitiesValue(text, options);
+             return text;
+         }
+         return "";
+     }
      for (let i = 0; i < arr.length; i++) {

Our OLX code builds empty nodes as a bare object rather than the array preserveOrder expects:

  • ReactStateOLXParser.js:380textline: { '#text': '' }
  • ReactStateOLXParser.js:492formulaequationinput: { '#text': '' }

Before 5.3.8, arrToStr iterated for (i=0; i<arr.length; …) over that object; .length is undefined, so zero iterations ran and it returned "". Since 5.3.8 the new branch runs arr.toString(), and ({ '#text': '' }).toString() is "[object Object]". This is a latent bug in our code (we pass an object where preserveOrder requires an array) that the library previously tolerated silently.

Regression 2 — &apos; encoding (5.7.3)

fast-xml-parser commit f9c9a2c ("update builder to 1.1.7", in v5.7.2...v5.7.3) bumped the bundled fast-xml-builder from 1.1.6 → 1.1.7. The actual behavior change is fast-xml-builder commit fddfaf31 — "escape quotes in attribute value" — which added:

export function escapeAttribute(val) {
  return String(val).replace(/"/g, '&quot;').replace(/'/g, '&apos;')
}

and applied it to attribute values in the preserveOrder builder (all three attr_to_str* paths in src/orderedJs2Xml.js). It's a security fix — escaping " prevents attribute-injection payloads like " onClick="alert(1) — but it also escapes ', and crucially it runs unconditionally, independent of processEntities.

Our builder sets processEntities: false (OLXParser.js:127) precisely to avoid entity encoding, but escapeAttribute bypasses that opt-out. Our OLX carries apostrophes inside a style attribute (style="font-family: 'courier new', courier;"), so they now serialize as &apos;. &apos; is valid XML, but it changes our OLX output and breaks the OLXParser round-trip expectations.

Recommended fixes

  1. Regression 1 (correct fix): wrap the empty nodes in arrays so they conform to preserveOrdertextline: [{ '#text': '' }], formulaequationinput: [{ '#text': '' }]. This is version-agnostic (passes on all versions) and is a genuine bug fix.
  2. Regression 2 (decision needed): the escapeAttribute change is intentional upstream security hardening and runs unconditionally (no opt-out), so the realistic path is to accept &apos; as valid output and update the OLXParser expectations accordingly.

Fix #1 is required for the security bump in #3175 (5.7.0) — Regression 1 (5.3.8) already affects that version. Fix #2 only becomes necessary at 5.7.3+, so it is additionally required to move to the latest release.

Metadata

Metadata

Assignees

No one assigned

    Labels

    help wantedReady to be picked up by anyone in the community

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions