@nodable/flexible-xml-parser 1.4.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -48,6 +48,10 @@ export function readClosingTagName(source) {
48
48
  */
49
49
  export function readTagExp(parser) {
50
50
  parser.source.markTokenStart(1);
51
+ // Absolute document offset where `exp` (tag name onward, right after '<')
52
+ // begins — captured before any reads so buildTagExpObj can compute each
53
+ // attribute's absolute document position from its offset within attrsExp.
54
+ const expStart = parser.source.startIndex;
51
55
  let inSingleQuotes = false;
52
56
  let inDoubleQuotes = false;
53
57
  let i;
@@ -77,7 +81,7 @@ export function readTagExp(parser) {
77
81
 
78
82
  const exp = parser.source.readStr(i);
79
83
  parser.source.updateBufferBoundary(i + 1);
80
- return buildTagExpObj(exp, parser);
84
+ return buildTagExpObj(exp, parser, expStart);
81
85
  }
82
86
 
83
87
  /**
@@ -90,6 +94,7 @@ export function readTagExp(parser) {
90
94
  */
91
95
  export function readPiExp(parser) {
92
96
  parser.source.markTokenStart(1);
97
+ const expStart = parser.source.startIndex;
93
98
  let inSingleQuotes = false;
94
99
  let inDoubleQuotes = false;
95
100
  let i;
@@ -127,7 +132,7 @@ export function readPiExp(parser) {
127
132
 
128
133
  const exp = parser.source.readStr(i);
129
134
  parser.source.updateBufferBoundary(i + 2);
130
- return buildTagExpObj(exp, parser);
135
+ return buildTagExpObj(exp, parser, expStart, true);
131
136
  }
132
137
 
133
138
  // ─── Internal helpers ─────────────────────────────────────────────────────────
@@ -135,16 +140,22 @@ export function readPiExp(parser) {
135
140
  /**
136
141
  * Parse a raw tag expression string into a structured tag descriptor.
137
142
  *
138
- * @param {string} exp - everything between '<' and '>' (exclusive)
143
+ * @param {string} exp - everything between '<' and '>' (exclusive)
139
144
  * @param {object} parser
140
- * @returns {{ tagName, selfClosing, rawAttributes, _attrsExp }}
145
+ * @param {number} [expStart] - absolute document offset where `exp` begins
146
+ * (i.e. right after '<' or '<?'). Used to compute each attribute's absolute
147
+ * document position (tagExp._attrsExpStart) for addAttribute()'s meta arg.
148
+ * Optional so callers that don't have/need it can omit it — attribute
149
+ * position metadata is simply unavailable in that case, not an error.
150
+ * @returns {{ tagName, selfClosing, rawAttributes, _attrsExp, _attrsExpStart }}
141
151
  */
142
- function buildTagExpObj(exp, parser) {
152
+ function buildTagExpObj(exp, parser, expStart, forceToReadAttrs = false) {
143
153
  const tagExp = {
144
154
  tagName: "",
145
155
  selfClosing: false,
146
156
  rawAttributes: Object.create(null),
147
157
  _attrsExp: "", // stored for two-pass attribute flushing in readOpeningTag
158
+ _attrsExpStart: undefined, // absolute document offset of _attrsExp's first char
148
159
  };
149
160
 
150
161
  const expLen = exp.length;
@@ -163,6 +174,7 @@ function buildTagExpObj(exp, parser) {
163
174
  if (isSpace(c)) {
164
175
  tagExp.tagName = exp.substring(0, i);
165
176
  attrsExp = exp.substring(i + 1);
177
+ if (expStart !== undefined) tagExp._attrsExpStart = expStart + i + 1;
166
178
  break;
167
179
  }
168
180
  }
@@ -171,13 +183,13 @@ function buildTagExpObj(exp, parser) {
171
183
  tagExp.tagName = tagExp.tagName.trimEnd();
172
184
  tagExp._attrsExp = attrsExp;
173
185
 
174
- if (!isQName(tagExp.tagName, parser.xmlVersion)) {
186
+ if (!isQName(tagExp.tagName, parser.xmlDec.version)) {
175
187
  throw new ParseError("Invalid tag name", ErrorCode.INVALID_TAG_NAME);
176
188
  }
177
189
 
178
190
  // Pass 1: collect raw attribute values for matcher.updateCurrent().
179
191
  // Pass 2 (flushAttributes) runs later in readOpeningTag, after updateCurrent().
180
- if (!parser.options.skip.attributes && attrsExp.length > 0) {
192
+ if (forceToReadAttrs || !parser.options.skip.attributes && attrsExp.length > 0) {
181
193
  collectRawAttributes(attrsExp, parser, tagExp);
182
194
  }
183
195
  // console.log(tagExp)
@@ -46,9 +46,7 @@ export function readPiTag(parser) {
46
46
  // Read version from the declaration and store it on the parser for validators.
47
47
  const version = tagExp.rawAttributes?.version;
48
48
  if (version === '1.1') {
49
- parser.xmlVersion = 1.1;
50
- } else {
51
- parser.xmlVersion = 1.0; // default
49
+ parser.xmlDec.version = 1.1;
52
50
  }
53
51
  }
54
52
 
@@ -57,15 +55,15 @@ export function readPiTag(parser) {
57
55
  // does for regular tags. PI tags are not pushed onto the matcher, so no
58
56
  // updateCurrent() call is needed here.
59
57
  if (!skipOptions.attributes) {
60
- flushAttributes(tagExp._attrsExp, parser);
58
+ flushAttributes(tagExp._attrsExp, parser, tagExp._attrsExpStart);
61
59
  }
62
60
 
63
61
  if (tagExp.tagName === "xml") {
64
62
  //TODO: verify it is very first tag else error
65
- if (!skipOptions.declaration) {
66
- parser.outputBuilder.addDeclaration("?xml");
63
+ if (!skipOptions.declaration) { //TODO: unnecessary. builder can ommit it from response if not needed
64
+ parser.outputBuilder.addDeclaration("?xml", parser.xmlDec);
67
65
  }
68
- } else if (!skipOptions.pi) {
66
+ } else if (!skipOptions.pi) { //TODO: unnecessary. builder can ommit it from response if not needed
69
67
  parser.outputBuilder.addInstruction("?" + tagExp.tagName);
70
68
  }
71
69
  }