@cdklabs/cdk-construct-connect-datalake 0.0.8 → 0.0.9

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.
Files changed (36) hide show
  1. package/.jsii +9 -9
  2. package/.jsii.tabl.json +1 -1
  3. package/lib/index.js +1 -1
  4. package/node_modules/@aws-sdk/client-cloudformation/package.json +1 -1
  5. package/node_modules/@aws-sdk/client-connect/package.json +1 -1
  6. package/node_modules/@aws-sdk/client-glue/dist-cjs/schemas/schemas_0.js +5 -4
  7. package/node_modules/@aws-sdk/client-glue/dist-es/schemas/schemas_0.js +5 -4
  8. package/node_modules/@aws-sdk/client-glue/dist-types/commands/GetDataQualityRulesetEvaluationRunCommand.d.ts +1 -0
  9. package/node_modules/@aws-sdk/client-glue/dist-types/commands/ListDataQualityRulesetEvaluationRunsCommand.d.ts +1 -0
  10. package/node_modules/@aws-sdk/client-glue/dist-types/commands/StartDataQualityRulesetEvaluationRunCommand.d.ts +1 -0
  11. package/node_modules/@aws-sdk/client-glue/dist-types/models/models_1.d.ts +5 -0
  12. package/node_modules/@aws-sdk/client-glue/dist-types/models/models_2.d.ts +5 -0
  13. package/node_modules/@aws-sdk/client-glue/dist-types/ts3.4/models/models_1.d.ts +1 -0
  14. package/node_modules/@aws-sdk/client-glue/dist-types/ts3.4/models/models_2.d.ts +1 -0
  15. package/node_modules/@aws-sdk/client-glue/package.json +1 -1
  16. package/node_modules/@aws-sdk/client-lakeformation/package.json +1 -1
  17. package/node_modules/@aws-sdk/client-ram/package.json +1 -1
  18. package/node_modules/@aws-sdk/client-sts/package.json +1 -1
  19. package/node_modules/fast-xml-builder/CHANGELOG.md +11 -0
  20. package/node_modules/fast-xml-builder/README.md +53 -2
  21. package/node_modules/fast-xml-builder/lib/fxb.cjs +1 -1
  22. package/node_modules/fast-xml-builder/lib/fxb.d.cts +91 -1
  23. package/node_modules/fast-xml-builder/lib/fxb.min.js +1 -1
  24. package/node_modules/fast-xml-builder/lib/fxb.min.js.map +1 -1
  25. package/node_modules/fast-xml-builder/package.json +3 -2
  26. package/node_modules/fast-xml-builder/src/fxb.d.ts +93 -3
  27. package/node_modules/fast-xml-builder/src/fxb.js +100 -33
  28. package/node_modules/fast-xml-builder/src/orderedJs2Xml.js +91 -37
  29. package/node_modules/strnum/CHANGELOG.md +3 -0
  30. package/node_modules/strnum/package.json +3 -2
  31. package/node_modules/strnum/strnum.js +12 -10
  32. package/node_modules/xml-naming/README.md +189 -0
  33. package/node_modules/xml-naming/package.json +54 -0
  34. package/node_modules/xml-naming/src/index.d.ts +74 -0
  35. package/node_modules/xml-naming/src/index.js +270 -0
  36. package/package.json +10 -10
@@ -4,6 +4,7 @@ import buildFromOrderedJs from './orderedJs2Xml.js';
4
4
  import getIgnoreAttributesFn from "./ignoreAttributes.js";
5
5
  import { Expression, Matcher } from 'path-expression-matcher';
6
6
  import { safeComment, safeCdata, escapeAttribute } from './util.js';
7
+ import { qName } from 'xml-naming';
7
8
 
8
9
  const defaultOptions = {
9
10
  attributeNamePrefix: '@_',
@@ -38,7 +39,11 @@ const defaultOptions = {
38
39
  // transformAttributeName: false,
39
40
  oneListGroup: false,
40
41
  maxNestedTags: 100,
41
- jPath: true // When true, callbacks receive string jPath; when false, receive Matcher instance
42
+ jPath: true, // When true, callbacks receive string jPath; when false, receive Matcher instance
43
+ sanitizeName: false // false = allow all names as-is (default, backward-compatible).
44
+ // Set to a function (name, { isAttribute, matcher }) => string to
45
+ // validate/sanitize tag and attribute names. Throw inside the function
46
+ // to reject an invalid name.
42
47
  };
43
48
 
44
49
  export default function Builder(options) {
@@ -95,6 +100,44 @@ export default function Builder(options) {
95
100
  }
96
101
  }
97
102
 
103
+ /**
104
+ * Detect XML version from the ?xml declaration at the root of a plain-object input.
105
+ * Checks both attributesGroupName and flat attribute forms.
106
+ * Returns '1.0' if no declaration is found.
107
+ */
108
+ function detectXmlVersionFromObj(jObj, options) {
109
+ const decl = jObj['?xml'];
110
+ if (decl && typeof decl === 'object') {
111
+ // attributesGroupName path e.g. { '$$': { '@_version': '1.1' } }
112
+ if (options.attributesGroupName && decl[options.attributesGroupName]) {
113
+ const v = decl[options.attributesGroupName][options.attributeNamePrefix + 'version'];
114
+ if (v) return v;
115
+ }
116
+ // flat attribute path e.g. { '@_version': '1.1' }
117
+ const v = decl[options.attributeNamePrefix + 'version'];
118
+ if (v) return v;
119
+ }
120
+ return '1.0';
121
+ }
122
+
123
+ /**
124
+ * Resolve a tag or attribute name through sanitizeName if configured.
125
+ * Validation via xml-naming's qName is performed first; the sanitizeName
126
+ * callback is invoked only when the name is invalid. If sanitizeName is
127
+ * false (default), no validation occurs and the name is used as-is.
128
+ *
129
+ * @param {string} name - raw name from the JS object
130
+ * @param {boolean} isAttribute - true when resolving an attribute name
131
+ * @param {object} options
132
+ * @param {Matcher} matcher - current matcher state (readonly from callback perspective)
133
+ * @param {string} xmlVersion - '1.0' or '1.1', forwarded to xml-naming
134
+ */
135
+ function resolveTagName(name, isAttribute, options, matcher, xmlVersion) {
136
+ if (!options.sanitizeName) return name;
137
+ if (qName(name, { xmlVersion })) return name;
138
+ return options.sanitizeName(name, { isAttribute, matcher: matcher.readOnly() });
139
+ }
140
+
98
141
  Builder.prototype.build = function (jObj) {
99
142
  if (this.options.preserveOrder) {
100
143
  return buildFromOrderedJs(jObj, this.options);
@@ -106,11 +149,12 @@ Builder.prototype.build = function (jObj) {
106
149
  }
107
150
  // Initialize matcher for path tracking
108
151
  const matcher = new Matcher();
109
- return this.j2x(jObj, 0, matcher).val;
152
+ const xmlVersion = detectXmlVersionFromObj(jObj, this.options);
153
+ return this.j2x(jObj, 0, matcher, xmlVersion).val;
110
154
  }
111
155
  };
112
156
 
113
- Builder.prototype.j2x = function (jObj, level, matcher) {
157
+ Builder.prototype.j2x = function (jObj, level, matcher, xmlVersion) {
114
158
  let attrStr = '';
115
159
  let val = '';
116
160
  if (this.options.maxNestedTags && matcher.getDepth() >= this.options.maxNestedTags) {
@@ -124,6 +168,22 @@ Builder.prototype.j2x = function (jObj, level, matcher) {
124
168
 
125
169
  for (let key in jObj) {
126
170
  if (!Object.prototype.hasOwnProperty.call(jObj, key)) continue;
171
+
172
+ // Resolve the key through sanitizeName before any use.
173
+ // Special keys (textNodeName, cdataPropName, commentPropName, attributeNamePrefix,
174
+ // attributesGroupName, "?" PI tags) are exempt — they are builder-internal conventions,
175
+ // not user-supplied XML names.
176
+ const isSpecialKey = key === this.options.textNodeName
177
+ || key === this.options.cdataPropName
178
+ || key === this.options.commentPropName
179
+ || (this.options.attributesGroupName && key === this.options.attributesGroupName)
180
+ || this.isAttribute(key)
181
+ || key[0] === '?';
182
+
183
+ const resolvedKey = isSpecialKey
184
+ ? key
185
+ : resolveTagName(key, false, this.options, matcher, xmlVersion);
186
+
127
187
  if (typeof jObj[key] === 'undefined') {
128
188
  // supress undefined node only if it is not an attribute
129
189
  if (this.isAttribute(key)) {
@@ -133,21 +193,22 @@ Builder.prototype.j2x = function (jObj, level, matcher) {
133
193
  // null attribute should be ignored by the attribute list, but should not cause the tag closing
134
194
  if (this.isAttribute(key)) {
135
195
  val += '';
136
- } else if (key === this.options.cdataPropName || key === this.options.commentPropName) {
196
+ } else if (resolvedKey === this.options.cdataPropName || resolvedKey === this.options.commentPropName) {
137
197
  val += '';
138
- } else if (key[0] === '?') {
139
- val += this.indentate(level) + '<' + key + '?' + this.tagEndChar;
198
+ } else if (resolvedKey[0] === '?') {
199
+ val += this.indentate(level) + '<' + resolvedKey + '?' + this.tagEndChar;
140
200
  } else {
141
- val += this.indentate(level) + '<' + key + '/' + this.tagEndChar;
201
+ val += this.indentate(level) + '<' + resolvedKey + '/' + this.tagEndChar;
142
202
  }
143
- // val += this.indentate(level) + '<' + key + '/' + this.tagEndChar;
144
203
  } else if (jObj[key] instanceof Date) {
145
- val += this.buildTextValNode(jObj[key], key, '', level, matcher);
204
+ val += this.buildTextValNode(jObj[key], resolvedKey, '', level, matcher);
146
205
  } else if (typeof jObj[key] !== 'object') {
147
206
  //premitive type
148
207
  const attr = this.isAttribute(key);
149
208
  if (attr && !this.ignoreAttributesFn(attr, jPath)) {
150
- attrStr += this.buildAttrPairStr(attr, '' + jObj[key], isCurrentStopNode);
209
+ // Resolve the attribute name through sanitizeName
210
+ const resolvedAttr = resolveTagName(attr, true, this.options, matcher, xmlVersion);
211
+ attrStr += this.buildAttrPairStr(resolvedAttr, '' + jObj[key], isCurrentStopNode);
151
212
  } else if (!attr) {
152
213
  //tag value
153
214
  if (key === this.options.textNodeName) {
@@ -155,7 +216,7 @@ Builder.prototype.j2x = function (jObj, level, matcher) {
155
216
  val += this.replaceEntitiesValue(newval);
156
217
  } else {
157
218
  // Check if this is a stopNode before building
158
- matcher.push(key);
219
+ matcher.push(resolvedKey);
159
220
  const isStopNode = this.checkStopNode(matcher);
160
221
  matcher.pop();
161
222
 
@@ -163,12 +224,12 @@ Builder.prototype.j2x = function (jObj, level, matcher) {
163
224
  // Build as raw content without encoding
164
225
  const textValue = '' + jObj[key];
165
226
  if (textValue === '') {
166
- val += this.indentate(level) + '<' + key + this.closeTag(key) + this.tagEndChar;
227
+ val += this.indentate(level) + '<' + resolvedKey + this.closeTag(resolvedKey) + this.tagEndChar;
167
228
  } else {
168
- val += this.indentate(level) + '<' + key + '>' + textValue + '</' + key + this.tagEndChar;
229
+ val += this.indentate(level) + '<' + resolvedKey + '>' + textValue + '</' + resolvedKey + this.tagEndChar;
169
230
  }
170
231
  } else {
171
- val += this.buildTextValNode(jObj[key], key, '', level, matcher);
232
+ val += this.buildTextValNode(jObj[key], resolvedKey, '', level, matcher);
172
233
  }
173
234
  }
174
235
  }
@@ -182,14 +243,13 @@ Builder.prototype.j2x = function (jObj, level, matcher) {
182
243
  if (typeof item === 'undefined') {
183
244
  // supress undefined node
184
245
  } else if (item === null) {
185
- if (key[0] === "?") val += this.indentate(level) + '<' + key + '?' + this.tagEndChar;
186
- else val += this.indentate(level) + '<' + key + '/' + this.tagEndChar;
187
- // val += this.indentate(level) + '<' + key + '/' + this.tagEndChar;
246
+ if (resolvedKey[0] === "?") val += this.indentate(level) + '<' + resolvedKey + '?' + this.tagEndChar;
247
+ else val += this.indentate(level) + '<' + resolvedKey + '/' + this.tagEndChar;
188
248
  } else if (typeof item === 'object') {
189
249
  if (this.options.oneListGroup) {
190
250
  // Push tag to matcher before recursive call
191
- matcher.push(key);
192
- const result = this.j2x(item, level + 1, matcher);
251
+ matcher.push(resolvedKey);
252
+ const result = this.j2x(item, level + 1, matcher, xmlVersion);
193
253
  // Pop tag from matcher after recursive call
194
254
  matcher.pop();
195
255
 
@@ -198,16 +258,16 @@ Builder.prototype.j2x = function (jObj, level, matcher) {
198
258
  listTagAttr += result.attrStr
199
259
  }
200
260
  } else {
201
- listTagVal += this.processTextOrObjNode(item, key, level, matcher)
261
+ listTagVal += this.processTextOrObjNode(item, resolvedKey, level, matcher, xmlVersion)
202
262
  }
203
263
  } else {
204
264
  if (this.options.oneListGroup) {
205
- let textValue = this.options.tagValueProcessor(key, item);
265
+ let textValue = this.options.tagValueProcessor(resolvedKey, item);
206
266
  textValue = this.replaceEntitiesValue(textValue);
207
267
  listTagVal += textValue;
208
268
  } else {
209
269
  // Check if this is a stopNode before building
210
- matcher.push(key);
270
+ matcher.push(resolvedKey);
211
271
  const isStopNode = this.checkStopNode(matcher);
212
272
  matcher.pop();
213
273
 
@@ -215,18 +275,18 @@ Builder.prototype.j2x = function (jObj, level, matcher) {
215
275
  // Build as raw content without encoding
216
276
  const textValue = '' + item;
217
277
  if (textValue === '') {
218
- listTagVal += this.indentate(level) + '<' + key + this.closeTag(key) + this.tagEndChar;
278
+ listTagVal += this.indentate(level) + '<' + resolvedKey + this.closeTag(resolvedKey) + this.tagEndChar;
219
279
  } else {
220
- listTagVal += this.indentate(level) + '<' + key + '>' + textValue + '</' + key + this.tagEndChar;
280
+ listTagVal += this.indentate(level) + '<' + resolvedKey + '>' + textValue + '</' + resolvedKey + this.tagEndChar;
221
281
  }
222
282
  } else {
223
- listTagVal += this.buildTextValNode(item, key, '', level, matcher);
283
+ listTagVal += this.buildTextValNode(item, resolvedKey, '', level, matcher);
224
284
  }
225
285
  }
226
286
  }
227
287
  }
228
288
  if (this.options.oneListGroup) {
229
- listTagVal = this.buildObjectNode(listTagVal, key, listTagAttr, level);
289
+ listTagVal = this.buildObjectNode(listTagVal, resolvedKey, listTagAttr, level);
230
290
  }
231
291
  val += listTagVal;
232
292
  } else {
@@ -235,10 +295,12 @@ Builder.prototype.j2x = function (jObj, level, matcher) {
235
295
  const Ks = Object.keys(jObj[key]);
236
296
  const L = Ks.length;
237
297
  for (let j = 0; j < L; j++) {
238
- attrStr += this.buildAttrPairStr(Ks[j], '' + jObj[key][Ks[j]], isCurrentStopNode);
298
+ // Resolve attribute names inside attributesGroupName
299
+ const resolvedAttr = resolveTagName(Ks[j], true, this.options, matcher, xmlVersion);
300
+ attrStr += this.buildAttrPairStr(resolvedAttr, '' + jObj[key][Ks[j]], isCurrentStopNode);
239
301
  }
240
302
  } else {
241
- val += this.processTextOrObjNode(jObj[key], key, level, matcher)
303
+ val += this.processTextOrObjNode(jObj[key], resolvedKey, level, matcher, xmlVersion)
242
304
  }
243
305
  }
244
306
  }
@@ -255,7 +317,7 @@ Builder.prototype.buildAttrPairStr = function (attrName, val, isStopNode) {
255
317
  } else return ' ' + attrName + '="' + escapeAttribute(val) + '"';
256
318
  }
257
319
 
258
- function processTextOrObjNode(object, key, level, matcher) {
320
+ function processTextOrObjNode(object, key, level, matcher, xmlVersion) {
259
321
  // Extract attributes to pass to matcher
260
322
  const attrValues = this.extractAttributes(object);
261
323
 
@@ -273,11 +335,15 @@ function processTextOrObjNode(object, key, level, matcher) {
273
335
  return this.buildObjectNode(rawContent, key, attrStr, level);
274
336
  }
275
337
 
276
- const result = this.j2x(object, level + 1, matcher);
338
+ const result = this.j2x(object, level + 1, matcher, xmlVersion);
277
339
  // Pop tag from matcher after recursion
278
340
  matcher.pop();
279
341
 
280
- if (object[this.options.textNodeName] !== undefined && Object.keys(object).length === 1) {
342
+ // PI/XML-declaration tags must never emit text content — route through
343
+ // buildTextValNode which correctly ignores the text node for "?" tags.
344
+ if (key[0] === '?') {
345
+ return this.buildTextValNode('', key, result.attrStr, level, matcher);
346
+ } else if (object[this.options.textNodeName] !== undefined && Object.keys(object).length === 1) {
281
347
  return this.buildTextValNode(object[this.options.textNodeName], key, result.attrStr, level, matcher);
282
348
  } else {
283
349
  return this.buildObjectNode(result.val, key, result.attrStr, level);
@@ -426,8 +492,10 @@ Builder.prototype.buildObjectNode = function (val, key, attrStr, level) {
426
492
  else {
427
493
  return this.indentate(level) + '<' + key + attrStr + this.closeTag(key) + this.tagEndChar;
428
494
  }
495
+ } else if (key[0] === "?") {
496
+ // PI/XML-declaration tags never have body content — treat them like empty.
497
+ return this.indentate(level) + '<' + key + attrStr + '?' + this.tagEndChar;
429
498
  } else {
430
-
431
499
  let tagEndExp = '</' + key + this.tagEndChar;
432
500
  let piClosingChar = "";
433
501
 
@@ -480,7 +548,6 @@ function buildEmptyObjNode(val, key, attrStr, level) {
480
548
  if (key[0] === "?") return this.indentate(level) + '<' + key + attrStr + '?' + this.tagEndChar;
481
549
  else {
482
550
  return this.indentate(level) + '<' + key + attrStr + '/' + this.tagEndChar;
483
- // return this.buildTagStr(level,key, attrStr);
484
551
  }
485
552
  }
486
553
  }
@@ -1,17 +1,57 @@
1
1
  import { Expression, Matcher } from 'path-expression-matcher';
2
2
  import { safeComment, safeCdata, escapeAttribute } from "./util.js";
3
+ import { qName } from 'xml-naming';
3
4
 
4
5
  const EOL = "\n";
5
6
 
6
7
  /**
7
- *
8
- * @param {array} jArray
9
- * @param {any} options
10
- * @returns
8
+ * Detect XML version from the first element of the ordered array input.
9
+ * The first element must be a ?xml processing instruction with a version attribute.
10
+ * Returns '1.0' if not found.
11
+ *
12
+ * @param {array} jArray
13
+ * @param {object} options
14
+ */
15
+ function detectXmlVersionFromArray(jArray, options) {
16
+ if (!Array.isArray(jArray) || jArray.length === 0) return '1.0';
17
+ const first = jArray[0];
18
+ const firstKey = propName(first);
19
+ if (firstKey === '?xml') {
20
+ const attrs = first[':@'];
21
+ if (attrs) {
22
+ const versionKey = options.attributeNamePrefix + 'version';
23
+ if (attrs[versionKey]) return attrs[versionKey];
24
+ }
25
+ }
26
+ return '1.0';
27
+ }
28
+
29
+ /**
30
+ * Resolve a tag or attribute name through sanitizeName if configured.
31
+ * Validation via xml-naming's qName is performed first; the sanitizeName
32
+ * callback is invoked only when the name is invalid. If sanitizeName is
33
+ * false (default), no validation occurs and the name is used as-is.
34
+ *
35
+ * @param {string} name - raw name from the JS object
36
+ * @param {boolean} isAttribute - true when resolving an attribute name
37
+ * @param {object} options
38
+ * @param {Matcher} matcher - current matcher state (readonly from callback perspective)
39
+ * @param {string} xmlVersion - '1.0' or '1.1', forwarded to xml-naming
40
+ */
41
+ function resolveTagName(name, isAttribute, options, matcher, xmlVersion) {
42
+ if (!options.sanitizeName) return name;
43
+ if (qName(name, { xmlVersion })) return name;
44
+ return options.sanitizeName(name, { isAttribute, matcher: matcher.readOnly() });
45
+ }
46
+
47
+ /**
48
+ * @param {array} jArray
49
+ * @param {any} options
50
+ * @returns
11
51
  */
12
52
  export default function toXml(jArray, options) {
13
53
  let indentation = "";
14
- if (options.format && options.indentBy.length > 0) {
54
+ if (options.format) {
15
55
  indentation = EOL;
16
56
  }
17
57
 
@@ -28,13 +68,16 @@ export default function toXml(jArray, options) {
28
68
  }
29
69
  }
30
70
 
71
+ // Detect XML version for use in name validation
72
+ const xmlVersion = detectXmlVersionFromArray(jArray, options);
73
+
31
74
  // Initialize matcher for path tracking
32
75
  const matcher = new Matcher();
33
76
 
34
- return arrToStr(jArray, options, indentation, matcher, stopNodeExpressions);
77
+ return arrToStr(jArray, options, indentation, matcher, stopNodeExpressions, xmlVersion);
35
78
  }
36
79
 
37
- function arrToStr(arr, options, indentation, matcher, stopNodeExpressions) {
80
+ function arrToStr(arr, options, indentation, matcher, stopNodeExpressions, xmlVersion) {
38
81
  let xmlStr = "";
39
82
  let isPreviousElementTag = false;
40
83
 
@@ -54,20 +97,32 @@ function arrToStr(arr, options, indentation, matcher, stopNodeExpressions) {
54
97
 
55
98
  for (let i = 0; i < arr.length; i++) {
56
99
  const tagObj = arr[i];
57
- const tagName = propName(tagObj);
58
- if (tagName === undefined) continue;
100
+ const rawTagName = propName(tagObj);
101
+ if (rawTagName === undefined) continue;
102
+
103
+ // Special names are exempt from sanitizeName: internal conventions and PI tags
104
+ // are not user-supplied XML element names.
105
+ const isSpecialName = rawTagName === options.textNodeName
106
+ || rawTagName === options.cdataPropName
107
+ || rawTagName === options.commentPropName
108
+ || rawTagName[0] === '?';
109
+
110
+ // Resolve tag name (may transform it; may throw for invalid names)
111
+ const tagName = isSpecialName
112
+ ? rawTagName
113
+ : resolveTagName(rawTagName, false, options, matcher, xmlVersion);
59
114
 
60
115
  // Extract attributes from ":@" property
61
116
  const attrValues = extractAttributeValues(tagObj[":@"], options);
62
117
 
63
- // Push tag to matcher WITH attributes
118
+ // Push resolved tag to matcher WITH attributes
64
119
  matcher.push(tagName, attrValues);
65
120
 
66
121
  // Check if this is a stop node using Expression matching
67
122
  const isStopNode = checkStopNode(matcher, stopNodeExpressions);
68
123
 
69
124
  if (tagName === options.textNodeName) {
70
- let tagText = tagObj[tagName];
125
+ let tagText = tagObj[rawTagName];
71
126
  if (!isStopNode) {
72
127
  tagText = options.tagValueProcessor(tagName, tagText);
73
128
  tagText = replaceEntitiesValue(tagText, options);
@@ -83,25 +138,25 @@ function arrToStr(arr, options, indentation, matcher, stopNodeExpressions) {
83
138
  if (isPreviousElementTag) {
84
139
  xmlStr += indentation;
85
140
  }
86
- const val = tagObj[tagName][0][options.textNodeName];
141
+ const val = tagObj[rawTagName][0][options.textNodeName];
87
142
  const safeVal = safeCdata(val);
88
143
  xmlStr += `<![CDATA[${safeVal}]]>`;
89
144
  isPreviousElementTag = false;
90
145
  matcher.pop();
91
146
  continue;
92
147
  } else if (tagName === options.commentPropName) {
93
- const val = tagObj[tagName][0][options.textNodeName]
94
- const safeVal = safeComment(val)
148
+ const val = tagObj[rawTagName][0][options.textNodeName];
149
+ const safeVal = safeComment(val);
95
150
  xmlStr += indentation + `<!--${safeVal}-->`;
96
151
  isPreviousElementTag = true;
97
152
  matcher.pop();
98
153
  continue;
99
154
  } else if (tagName[0] === "?") {
100
- const attStr = attr_to_str(tagObj[":@"], options, isStopNode);
155
+ const attStr = attr_to_str(tagObj[":@"], options, isStopNode, matcher, xmlVersion);
101
156
  const tempInd = tagName === "?xml" ? "" : indentation;
102
- let piTextNodeName = tagObj[tagName][0][options.textNodeName];
103
- piTextNodeName = piTextNodeName.length !== 0 ? " " + piTextNodeName : ""; //remove extra spacing
104
- xmlStr += tempInd + `<${tagName}${piTextNodeName}${attStr}?>`;
157
+ // Text node content on PI/XML declaration tags is intentionally ignored.
158
+ // Only attributes are valid on these tags per the XML spec.
159
+ xmlStr += tempInd + `<${tagName}${attStr}?>`;
105
160
  isPreviousElementTag = true;
106
161
  matcher.pop();
107
162
  continue;
@@ -113,16 +168,15 @@ function arrToStr(arr, options, indentation, matcher, stopNodeExpressions) {
113
168
  }
114
169
 
115
170
  // Pass isStopNode to attr_to_str so attributes are also not processed for stopNodes
116
- const attStr = attr_to_str(tagObj[":@"], options, isStopNode);
171
+ const attStr = attr_to_str(tagObj[":@"], options, isStopNode, matcher, xmlVersion);
117
172
  const tagStart = indentation + `<${tagName}${attStr}`;
118
173
 
119
174
  // If this is a stopNode, get raw content without processing
120
175
  let tagValue;
121
176
  if (isStopNode) {
122
- tagValue = getRawContent(tagObj[tagName], options);
177
+ tagValue = getRawContent(tagObj[rawTagName], options);
123
178
  } else {
124
-
125
- tagValue = arrToStr(tagObj[tagName], options, newIdentation, matcher, stopNodeExpressions);
179
+ tagValue = arrToStr(tagObj[rawTagName], options, newIdentation, matcher, stopNodeExpressions, xmlVersion);
126
180
  }
127
181
 
128
182
  if (options.unpairedTags.indexOf(tagName) !== -1) {
@@ -204,9 +258,7 @@ function getRawContent(arr, options) {
204
258
  // Processing instruction - skip for stopNodes
205
259
  continue;
206
260
  } else if (tagName) {
207
- // Nested tags within stopNode
208
- // Recursively get raw content and reconstruct the tag
209
- // For stopNodes, we don't process attributes either
261
+ // Nested tags within stopNode — no sanitizeName, content is raw
210
262
  const attStr = attr_to_str_raw(item[":@"], options);
211
263
  const nestedContent = getRawContent(item[tagName], options);
212
264
 
@@ -249,13 +301,23 @@ function propName(obj) {
249
301
  }
250
302
  }
251
303
 
252
- function attr_to_str(attrMap, options, isStopNode) {
304
+ /**
305
+ * Build attribute string, resolving attribute names through sanitizeName when configured.
306
+ * Accepts matcher so the callback has path context.
307
+ */
308
+ function attr_to_str(attrMap, options, isStopNode, matcher, xmlVersion) {
253
309
  let attrStr = "";
254
310
  if (attrMap && !options.ignoreAttributes) {
255
311
  for (let attr in attrMap) {
256
312
  if (!Object.prototype.hasOwnProperty.call(attrMap, attr)) continue;
257
- let attrVal;
258
313
 
314
+ // Strip prefix to get the clean XML attribute name, then optionally sanitize it
315
+ const cleanAttrName = attr.substr(options.attributeNamePrefix.length);
316
+ const resolvedAttrName = isStopNode
317
+ ? cleanAttrName // stopNodes are raw — skip sanitizeName for attr names too
318
+ : resolveTagName(cleanAttrName, true, options, matcher, xmlVersion);
319
+
320
+ let attrVal;
259
321
  if (isStopNode) {
260
322
  // For stopNodes, use raw value without any processing
261
323
  attrVal = attrMap[attr];
@@ -266,9 +328,9 @@ function attr_to_str(attrMap, options, isStopNode) {
266
328
  }
267
329
 
268
330
  if (attrVal === true && options.suppressBooleanAttributes) {
269
- attrStr += ` ${attr.substr(options.attributeNamePrefix.length)}`;
331
+ attrStr += ` ${resolvedAttrName}`;
270
332
  } else {
271
- attrStr += ` ${attr.substr(options.attributeNamePrefix.length)}="${escapeAttribute(attrVal)}"`;
333
+ attrStr += ` ${resolvedAttrName}="${escapeAttribute(attrVal)}"`;
272
334
  }
273
335
  }
274
336
  }
@@ -294,12 +356,4 @@ function replaceEntitiesValue(textValue, options) {
294
356
  }
295
357
  }
296
358
  return textValue;
297
- }
298
-
299
- function cdataVal(val) {
300
-
301
- }
302
-
303
- function commentVal(val) {
304
-
305
359
  }
@@ -1,4 +1,7 @@
1
1
 
2
+ **2.2.3 / 2026-04-07**
3
+ - remove unnecessary files from npm package
4
+
2
5
  **2.2.2 / 2026-03-23**
3
6
  - fix for space string
4
7
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "strnum",
3
- "version": "2.2.3",
3
+ "version": "2.3.0",
4
4
  "description": "Parse String to Number based on configuration",
5
5
  "type": "module",
6
6
  "main": "strnum.js",
@@ -17,7 +17,7 @@
17
17
  "type": "git",
18
18
  "url": "https://github.com/NaturalIntelligence/strnum"
19
19
  },
20
- "author": "Amit Gupta (https://amitkumargupta.work/)",
20
+ "author": "Amit Gupta (https://solothought.work/)",
21
21
  "license": "MIT",
22
22
  "funding": [
23
23
  {
@@ -26,6 +26,7 @@
26
26
  }
27
27
  ],
28
28
  "devDependencies": {
29
+ "@byspec/numbers": "^0.1.1",
29
30
  "jasmine": "^5.6.0"
30
31
  }
31
32
  }
@@ -1,12 +1,12 @@
1
1
  const hexRegex = /^[-+]?0x[a-fA-F0-9]+$/;
2
+ const binRegex = /^0b[01]+$/;
3
+ const octRegex = /^0o[0-7]+$/;
2
4
  const numRegex = /^([\-\+])?(0*)([0-9]*(\.[0-9]*)?)$/;
3
- // const octRegex = /^0x[a-z0-9]+/;
4
- // const binRegex = /0x[a-z0-9]+/;
5
-
6
5
 
7
6
  const consider = {
8
7
  hex: true,
9
- // oct: false,
8
+ binary: false,
9
+ octal: false,
10
10
  leadingZeros: true,
11
11
  decimalPoint: "\.",
12
12
  eNotation: true,
@@ -25,14 +25,14 @@ export default function toNumber(str, options = {}) {
25
25
  else if (trimmedStr === "0") return 0;
26
26
  else if (options.hex && hexRegex.test(trimmedStr)) {
27
27
  return parse_int(trimmedStr, 16);
28
- // }else if (options.oct && octRegex.test(str)) {
29
- // return Number.parseInt(val, 8);
28
+ } else if (options.binary && binRegex.test(trimmedStr)) {
29
+ return parse_int(trimmedStr, 2);
30
+ } else if (options.octal && octRegex.test(trimmedStr)) {
31
+ return parse_int(trimmedStr, 8);
30
32
  } else if (!isFinite(trimmedStr)) { //Infinity
31
33
  return handleInfinity(str, Number(trimmedStr), options);
32
34
  } else if (trimmedStr.includes('e') || trimmedStr.includes('E')) { //eNotation
33
35
  return resolveEnotation(str, trimmedStr, options);
34
- // }else if (options.parseBin && binRegex.test(str)) {
35
- // return Number.parseInt(val, 2);
36
36
  } else {
37
37
  //separate negative sign, leading zeros, and rest number
38
38
  const match = numRegex.exec(trimmedStr);
@@ -130,11 +130,13 @@ function trimZeros(numStr) {
130
130
  }
131
131
 
132
132
  function parse_int(numStr, base) {
133
- //polyfill
133
+ const str = numStr.trim();
134
+ if (base === 2 || base === 8) numStr = str.substring(2);
135
+
134
136
  if (parseInt) return parseInt(numStr, base);
135
137
  else if (Number.parseInt) return Number.parseInt(numStr, base);
136
138
  else if (window && window.parseInt) return window.parseInt(numStr, base);
137
- else throw new Error("parseInt, Number.parseInt, window.parseInt are not supported")
139
+ else throw new Error("parseInt, Number.parseInt, window.parseInt are not supported");
138
140
  }
139
141
 
140
142
  /**