@cdklabs/cdk-construct-connect-datalake 0.0.7 → 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 (113) 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/dist-cjs/index.js +9 -9
  5. package/node_modules/@aws-sdk/client-cloudformation/dist-es/waiters/waitForChangeSetCreateComplete.js +2 -2
  6. package/node_modules/@aws-sdk/client-cloudformation/dist-es/waiters/waitForStackCreateComplete.js +2 -2
  7. package/node_modules/@aws-sdk/client-cloudformation/dist-es/waiters/waitForStackDeleteComplete.js +2 -2
  8. package/node_modules/@aws-sdk/client-cloudformation/dist-es/waiters/waitForStackExists.js +2 -2
  9. package/node_modules/@aws-sdk/client-cloudformation/dist-es/waiters/waitForStackImportComplete.js +2 -2
  10. package/node_modules/@aws-sdk/client-cloudformation/dist-es/waiters/waitForStackRefactorCreateComplete.js +1 -1
  11. package/node_modules/@aws-sdk/client-cloudformation/dist-es/waiters/waitForStackRefactorExecuteComplete.js +1 -1
  12. package/node_modules/@aws-sdk/client-cloudformation/dist-es/waiters/waitForStackRollbackComplete.js +2 -2
  13. package/node_modules/@aws-sdk/client-cloudformation/dist-es/waiters/waitForStackUpdateComplete.js +2 -2
  14. package/node_modules/@aws-sdk/client-cloudformation/dist-types/CloudFormation.d.ts +11 -10
  15. package/node_modules/@aws-sdk/client-cloudformation/dist-types/ts3.4/CloudFormation.d.ts +13 -10
  16. package/node_modules/@aws-sdk/client-cloudformation/dist-types/ts3.4/waiters/waitForChangeSetCreateComplete.d.ts +9 -3
  17. package/node_modules/@aws-sdk/client-cloudformation/dist-types/ts3.4/waiters/waitForStackCreateComplete.d.ts +9 -3
  18. package/node_modules/@aws-sdk/client-cloudformation/dist-types/ts3.4/waiters/waitForStackDeleteComplete.d.ts +11 -3
  19. package/node_modules/@aws-sdk/client-cloudformation/dist-types/ts3.4/waiters/waitForStackExists.d.ts +9 -3
  20. package/node_modules/@aws-sdk/client-cloudformation/dist-types/ts3.4/waiters/waitForStackImportComplete.d.ts +9 -3
  21. package/node_modules/@aws-sdk/client-cloudformation/dist-types/ts3.4/waiters/waitForStackRefactorCreateComplete.d.ts +11 -3
  22. package/node_modules/@aws-sdk/client-cloudformation/dist-types/ts3.4/waiters/waitForStackRefactorExecuteComplete.d.ts +11 -3
  23. package/node_modules/@aws-sdk/client-cloudformation/dist-types/ts3.4/waiters/waitForStackRollbackComplete.d.ts +9 -3
  24. package/node_modules/@aws-sdk/client-cloudformation/dist-types/ts3.4/waiters/waitForStackUpdateComplete.d.ts +9 -3
  25. package/node_modules/@aws-sdk/client-cloudformation/dist-types/ts3.4/waiters/waitForTypeRegistrationComplete.d.ts +11 -3
  26. package/node_modules/@aws-sdk/client-cloudformation/dist-types/waiters/waitForChangeSetCreateComplete.d.ts +4 -3
  27. package/node_modules/@aws-sdk/client-cloudformation/dist-types/waiters/waitForStackCreateComplete.d.ts +4 -3
  28. package/node_modules/@aws-sdk/client-cloudformation/dist-types/waiters/waitForStackDeleteComplete.d.ts +4 -3
  29. package/node_modules/@aws-sdk/client-cloudformation/dist-types/waiters/waitForStackExists.d.ts +4 -3
  30. package/node_modules/@aws-sdk/client-cloudformation/dist-types/waiters/waitForStackImportComplete.d.ts +4 -3
  31. package/node_modules/@aws-sdk/client-cloudformation/dist-types/waiters/waitForStackRefactorCreateComplete.d.ts +4 -3
  32. package/node_modules/@aws-sdk/client-cloudformation/dist-types/waiters/waitForStackRefactorExecuteComplete.d.ts +4 -3
  33. package/node_modules/@aws-sdk/client-cloudformation/dist-types/waiters/waitForStackRollbackComplete.d.ts +4 -3
  34. package/node_modules/@aws-sdk/client-cloudformation/dist-types/waiters/waitForStackUpdateComplete.d.ts +4 -3
  35. package/node_modules/@aws-sdk/client-cloudformation/dist-types/waiters/waitForTypeRegistrationComplete.d.ts +4 -3
  36. package/node_modules/@aws-sdk/client-cloudformation/package.json +8 -8
  37. package/node_modules/@aws-sdk/client-connect/package.json +7 -7
  38. package/node_modules/@aws-sdk/client-glue/dist-cjs/schemas/schemas_0.js +13 -12
  39. package/node_modules/@aws-sdk/client-glue/dist-es/schemas/schemas_0.js +13 -12
  40. package/node_modules/@aws-sdk/client-glue/dist-types/commands/GetDataQualityRulesetEvaluationRunCommand.d.ts +1 -0
  41. package/node_modules/@aws-sdk/client-glue/dist-types/commands/GetPartitionCommand.d.ts +7 -0
  42. package/node_modules/@aws-sdk/client-glue/dist-types/commands/GetPartitionsCommand.d.ts +7 -0
  43. package/node_modules/@aws-sdk/client-glue/dist-types/commands/GetTableVersionCommand.d.ts +7 -0
  44. package/node_modules/@aws-sdk/client-glue/dist-types/commands/GetTableVersionsCommand.d.ts +7 -0
  45. package/node_modules/@aws-sdk/client-glue/dist-types/commands/ListDataQualityRulesetEvaluationRunsCommand.d.ts +1 -0
  46. package/node_modules/@aws-sdk/client-glue/dist-types/commands/StartDataQualityRulesetEvaluationRunCommand.d.ts +1 -0
  47. package/node_modules/@aws-sdk/client-glue/dist-types/models/models_1.d.ts +5 -0
  48. package/node_modules/@aws-sdk/client-glue/dist-types/models/models_2.d.ts +25 -0
  49. package/node_modules/@aws-sdk/client-glue/dist-types/ts3.4/models/models_1.d.ts +1 -0
  50. package/node_modules/@aws-sdk/client-glue/dist-types/ts3.4/models/models_2.d.ts +5 -0
  51. package/node_modules/@aws-sdk/client-glue/package.json +7 -7
  52. package/node_modules/@aws-sdk/client-lakeformation/package.json +7 -7
  53. package/node_modules/@aws-sdk/client-ram/package.json +7 -7
  54. package/node_modules/@aws-sdk/client-sts/package.json +8 -8
  55. package/node_modules/@aws-sdk/core/dist-cjs/index.js +15 -6
  56. package/node_modules/@aws-sdk/core/dist-cjs/submodules/client/index.js +15 -6
  57. package/node_modules/@aws-sdk/core/dist-es/submodules/client/emitWarningIfUnsupportedVersion.js +15 -6
  58. package/node_modules/@aws-sdk/core/dist-types/submodules/client/emitWarningIfUnsupportedVersion.d.ts +1 -2
  59. package/node_modules/@aws-sdk/core/package.json +3 -3
  60. package/node_modules/@aws-sdk/credential-provider-env/package.json +2 -2
  61. package/node_modules/@aws-sdk/credential-provider-http/package.json +2 -2
  62. package/node_modules/@aws-sdk/credential-provider-ini/package.json +9 -9
  63. package/node_modules/@aws-sdk/credential-provider-login/package.json +3 -3
  64. package/node_modules/@aws-sdk/credential-provider-node/package.json +7 -7
  65. package/node_modules/@aws-sdk/credential-provider-process/package.json +2 -2
  66. package/node_modules/@aws-sdk/credential-provider-sso/package.json +4 -4
  67. package/node_modules/@aws-sdk/credential-provider-web-identity/package.json +3 -3
  68. package/node_modules/@aws-sdk/middleware-sdk-s3/package.json +2 -2
  69. package/node_modules/@aws-sdk/middleware-user-agent/package.json +3 -3
  70. package/node_modules/@aws-sdk/nested-clients/package.json +7 -7
  71. package/node_modules/@aws-sdk/signature-v4-multi-region/package.json +2 -2
  72. package/node_modules/@aws-sdk/token-providers/package.json +3 -3
  73. package/node_modules/@aws-sdk/util-user-agent-node/package.json +2 -2
  74. package/node_modules/@aws-sdk/xml-builder/dist-cjs/xml-external/nodable_entities.js +336 -0
  75. package/node_modules/@aws-sdk/xml-builder/dist-cjs/xml-parser.js +26 -0
  76. package/node_modules/@aws-sdk/xml-builder/dist-es/xml-external/nodable_entities.js +332 -0
  77. package/node_modules/@aws-sdk/xml-builder/dist-es/xml-parser.js +26 -0
  78. package/node_modules/@aws-sdk/xml-builder/dist-types/ts3.4/xml-external/nodable_entities.d.ts +66 -0
  79. package/node_modules/@aws-sdk/xml-builder/dist-types/xml-external/nodable_entities.d.ts +65 -0
  80. package/node_modules/@aws-sdk/xml-builder/package.json +3 -2
  81. package/node_modules/@smithy/middleware-retry/package.json +2 -2
  82. package/node_modules/@smithy/util-retry/dist-cjs/index.js +7 -3
  83. package/node_modules/@smithy/util-retry/dist-es/AdaptiveRetryStrategy.js +5 -2
  84. package/node_modules/@smithy/util-retry/dist-es/DefaultRateLimiter.js +2 -1
  85. package/node_modules/@smithy/util-retry/package.json +2 -2
  86. package/node_modules/fast-xml-builder/CHANGELOG.md +24 -0
  87. package/node_modules/fast-xml-builder/README.md +53 -2
  88. package/node_modules/fast-xml-builder/lib/fxb.cjs +1 -1
  89. package/node_modules/fast-xml-builder/lib/fxb.d.cts +91 -1
  90. package/node_modules/fast-xml-builder/lib/fxb.min.js +1 -1
  91. package/node_modules/fast-xml-builder/lib/fxb.min.js.map +1 -1
  92. package/node_modules/fast-xml-builder/package.json +3 -2
  93. package/node_modules/fast-xml-builder/src/fxb.d.ts +93 -3
  94. package/node_modules/fast-xml-builder/src/fxb.js +106 -40
  95. package/node_modules/fast-xml-builder/src/orderedJs2Xml.js +95 -42
  96. package/node_modules/fast-xml-builder/src/util.js +16 -0
  97. package/node_modules/fast-xml-parser/CHANGELOG.md +8 -0
  98. package/node_modules/fast-xml-parser/lib/fxp.cjs +1 -1
  99. package/node_modules/fast-xml-parser/lib/fxp.min.js +1 -1
  100. package/node_modules/fast-xml-parser/lib/fxp.min.js.map +1 -1
  101. package/node_modules/fast-xml-parser/lib/fxparser.min.js +1 -1
  102. package/node_modules/fast-xml-parser/lib/fxparser.min.js.map +1 -1
  103. package/node_modules/fast-xml-parser/package.json +1 -1
  104. package/node_modules/fast-xml-parser/src/xmlparser/OrderedObjParser.js +16 -11
  105. package/node_modules/fast-xml-parser/src/xmlparser/XMLParser.js +2 -2
  106. package/node_modules/strnum/CHANGELOG.md +3 -0
  107. package/node_modules/strnum/package.json +3 -2
  108. package/node_modules/strnum/strnum.js +12 -10
  109. package/node_modules/xml-naming/README.md +189 -0
  110. package/node_modules/xml-naming/package.json +54 -0
  111. package/node_modules/xml-naming/src/index.d.ts +74 -0
  112. package/node_modules/xml-naming/src/index.js +270 -0
  113. package/package.json +10 -10
@@ -3,6 +3,8 @@
3
3
  import buildFromOrderedJs from './orderedJs2Xml.js';
4
4
  import getIgnoreAttributesFn from "./ignoreAttributes.js";
5
5
  import { Expression, Matcher } from 'path-expression-matcher';
6
+ import { safeComment, safeCdata, escapeAttribute } from './util.js';
7
+ import { qName } from 'xml-naming';
6
8
 
7
9
  const defaultOptions = {
8
10
  attributeNamePrefix: '@_',
@@ -37,7 +39,11 @@ const defaultOptions = {
37
39
  // transformAttributeName: false,
38
40
  oneListGroup: false,
39
41
  maxNestedTags: 100,
40
- 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.
41
47
  };
42
48
 
43
49
  export default function Builder(options) {
@@ -94,6 +100,44 @@ export default function Builder(options) {
94
100
  }
95
101
  }
96
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
+
97
141
  Builder.prototype.build = function (jObj) {
98
142
  if (this.options.preserveOrder) {
99
143
  return buildFromOrderedJs(jObj, this.options);
@@ -105,11 +149,12 @@ Builder.prototype.build = function (jObj) {
105
149
  }
106
150
  // Initialize matcher for path tracking
107
151
  const matcher = new Matcher();
108
- return this.j2x(jObj, 0, matcher).val;
152
+ const xmlVersion = detectXmlVersionFromObj(jObj, this.options);
153
+ return this.j2x(jObj, 0, matcher, xmlVersion).val;
109
154
  }
110
155
  };
111
156
 
112
- Builder.prototype.j2x = function (jObj, level, matcher) {
157
+ Builder.prototype.j2x = function (jObj, level, matcher, xmlVersion) {
113
158
  let attrStr = '';
114
159
  let val = '';
115
160
  if (this.options.maxNestedTags && matcher.getDepth() >= this.options.maxNestedTags) {
@@ -123,6 +168,22 @@ Builder.prototype.j2x = function (jObj, level, matcher) {
123
168
 
124
169
  for (let key in jObj) {
125
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
+
126
187
  if (typeof jObj[key] === 'undefined') {
127
188
  // supress undefined node only if it is not an attribute
128
189
  if (this.isAttribute(key)) {
@@ -132,21 +193,22 @@ Builder.prototype.j2x = function (jObj, level, matcher) {
132
193
  // null attribute should be ignored by the attribute list, but should not cause the tag closing
133
194
  if (this.isAttribute(key)) {
134
195
  val += '';
135
- } else if (key === this.options.cdataPropName) {
196
+ } else if (resolvedKey === this.options.cdataPropName || resolvedKey === this.options.commentPropName) {
136
197
  val += '';
137
- } else if (key[0] === '?') {
138
- val += this.indentate(level) + '<' + key + '?' + this.tagEndChar;
198
+ } else if (resolvedKey[0] === '?') {
199
+ val += this.indentate(level) + '<' + resolvedKey + '?' + this.tagEndChar;
139
200
  } else {
140
- val += this.indentate(level) + '<' + key + '/' + this.tagEndChar;
201
+ val += this.indentate(level) + '<' + resolvedKey + '/' + this.tagEndChar;
141
202
  }
142
- // val += this.indentate(level) + '<' + key + '/' + this.tagEndChar;
143
203
  } else if (jObj[key] instanceof Date) {
144
- val += this.buildTextValNode(jObj[key], key, '', level, matcher);
204
+ val += this.buildTextValNode(jObj[key], resolvedKey, '', level, matcher);
145
205
  } else if (typeof jObj[key] !== 'object') {
146
206
  //premitive type
147
207
  const attr = this.isAttribute(key);
148
208
  if (attr && !this.ignoreAttributesFn(attr, jPath)) {
149
- 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);
150
212
  } else if (!attr) {
151
213
  //tag value
152
214
  if (key === this.options.textNodeName) {
@@ -154,7 +216,7 @@ Builder.prototype.j2x = function (jObj, level, matcher) {
154
216
  val += this.replaceEntitiesValue(newval);
155
217
  } else {
156
218
  // Check if this is a stopNode before building
157
- matcher.push(key);
219
+ matcher.push(resolvedKey);
158
220
  const isStopNode = this.checkStopNode(matcher);
159
221
  matcher.pop();
160
222
 
@@ -162,12 +224,12 @@ Builder.prototype.j2x = function (jObj, level, matcher) {
162
224
  // Build as raw content without encoding
163
225
  const textValue = '' + jObj[key];
164
226
  if (textValue === '') {
165
- val += this.indentate(level) + '<' + key + this.closeTag(key) + this.tagEndChar;
227
+ val += this.indentate(level) + '<' + resolvedKey + this.closeTag(resolvedKey) + this.tagEndChar;
166
228
  } else {
167
- val += this.indentate(level) + '<' + key + '>' + textValue + '</' + key + this.tagEndChar;
229
+ val += this.indentate(level) + '<' + resolvedKey + '>' + textValue + '</' + resolvedKey + this.tagEndChar;
168
230
  }
169
231
  } else {
170
- val += this.buildTextValNode(jObj[key], key, '', level, matcher);
232
+ val += this.buildTextValNode(jObj[key], resolvedKey, '', level, matcher);
171
233
  }
172
234
  }
173
235
  }
@@ -181,14 +243,13 @@ Builder.prototype.j2x = function (jObj, level, matcher) {
181
243
  if (typeof item === 'undefined') {
182
244
  // supress undefined node
183
245
  } else if (item === null) {
184
- if (key[0] === "?") val += this.indentate(level) + '<' + key + '?' + this.tagEndChar;
185
- else val += this.indentate(level) + '<' + key + '/' + this.tagEndChar;
186
- // 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;
187
248
  } else if (typeof item === 'object') {
188
249
  if (this.options.oneListGroup) {
189
250
  // Push tag to matcher before recursive call
190
- matcher.push(key);
191
- const result = this.j2x(item, level + 1, matcher);
251
+ matcher.push(resolvedKey);
252
+ const result = this.j2x(item, level + 1, matcher, xmlVersion);
192
253
  // Pop tag from matcher after recursive call
193
254
  matcher.pop();
194
255
 
@@ -197,16 +258,16 @@ Builder.prototype.j2x = function (jObj, level, matcher) {
197
258
  listTagAttr += result.attrStr
198
259
  }
199
260
  } else {
200
- listTagVal += this.processTextOrObjNode(item, key, level, matcher)
261
+ listTagVal += this.processTextOrObjNode(item, resolvedKey, level, matcher, xmlVersion)
201
262
  }
202
263
  } else {
203
264
  if (this.options.oneListGroup) {
204
- let textValue = this.options.tagValueProcessor(key, item);
265
+ let textValue = this.options.tagValueProcessor(resolvedKey, item);
205
266
  textValue = this.replaceEntitiesValue(textValue);
206
267
  listTagVal += textValue;
207
268
  } else {
208
269
  // Check if this is a stopNode before building
209
- matcher.push(key);
270
+ matcher.push(resolvedKey);
210
271
  const isStopNode = this.checkStopNode(matcher);
211
272
  matcher.pop();
212
273
 
@@ -214,18 +275,18 @@ Builder.prototype.j2x = function (jObj, level, matcher) {
214
275
  // Build as raw content without encoding
215
276
  const textValue = '' + item;
216
277
  if (textValue === '') {
217
- listTagVal += this.indentate(level) + '<' + key + this.closeTag(key) + this.tagEndChar;
278
+ listTagVal += this.indentate(level) + '<' + resolvedKey + this.closeTag(resolvedKey) + this.tagEndChar;
218
279
  } else {
219
- listTagVal += this.indentate(level) + '<' + key + '>' + textValue + '</' + key + this.tagEndChar;
280
+ listTagVal += this.indentate(level) + '<' + resolvedKey + '>' + textValue + '</' + resolvedKey + this.tagEndChar;
220
281
  }
221
282
  } else {
222
- listTagVal += this.buildTextValNode(item, key, '', level, matcher);
283
+ listTagVal += this.buildTextValNode(item, resolvedKey, '', level, matcher);
223
284
  }
224
285
  }
225
286
  }
226
287
  }
227
288
  if (this.options.oneListGroup) {
228
- listTagVal = this.buildObjectNode(listTagVal, key, listTagAttr, level);
289
+ listTagVal = this.buildObjectNode(listTagVal, resolvedKey, listTagAttr, level);
229
290
  }
230
291
  val += listTagVal;
231
292
  } else {
@@ -234,10 +295,12 @@ Builder.prototype.j2x = function (jObj, level, matcher) {
234
295
  const Ks = Object.keys(jObj[key]);
235
296
  const L = Ks.length;
236
297
  for (let j = 0; j < L; j++) {
237
- 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);
238
301
  }
239
302
  } else {
240
- val += this.processTextOrObjNode(jObj[key], key, level, matcher)
303
+ val += this.processTextOrObjNode(jObj[key], resolvedKey, level, matcher, xmlVersion)
241
304
  }
242
305
  }
243
306
  }
@@ -251,10 +314,10 @@ Builder.prototype.buildAttrPairStr = function (attrName, val, isStopNode) {
251
314
  }
252
315
  if (this.options.suppressBooleanAttributes && val === "true") {
253
316
  return ' ' + attrName;
254
- } else return ' ' + attrName + '="' + val + '"';
317
+ } else return ' ' + attrName + '="' + escapeAttribute(val) + '"';
255
318
  }
256
319
 
257
- function processTextOrObjNode(object, key, level, matcher) {
320
+ function processTextOrObjNode(object, key, level, matcher, xmlVersion) {
258
321
  // Extract attributes to pass to matcher
259
322
  const attrValues = this.extractAttributes(object);
260
323
 
@@ -272,11 +335,15 @@ function processTextOrObjNode(object, key, level, matcher) {
272
335
  return this.buildObjectNode(rawContent, key, attrStr, level);
273
336
  }
274
337
 
275
- const result = this.j2x(object, level + 1, matcher);
338
+ const result = this.j2x(object, level + 1, matcher, xmlVersion);
276
339
  // Pop tag from matcher after recursion
277
340
  matcher.pop();
278
341
 
279
- 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) {
280
347
  return this.buildTextValNode(object[this.options.textNodeName], key, result.attrStr, level, matcher);
281
348
  } else {
282
349
  return this.buildObjectNode(result.val, key, result.attrStr, level);
@@ -299,7 +366,7 @@ Builder.prototype.extractAttributes = function (obj) {
299
366
  const cleanKey = attrKey.startsWith(this.options.attributeNamePrefix)
300
367
  ? attrKey.substring(this.options.attributeNamePrefix.length)
301
368
  : attrKey;
302
- attrValues[cleanKey] = attrGroup[attrKey];
369
+ attrValues[cleanKey] = escapeAttribute(attrGroup[attrKey]);
303
370
  hasAttrs = true;
304
371
  }
305
372
  } else {
@@ -308,7 +375,7 @@ Builder.prototype.extractAttributes = function (obj) {
308
375
  if (!Object.prototype.hasOwnProperty.call(obj, key)) continue;
309
376
  const attr = this.isAttribute(key);
310
377
  if (attr) {
311
- attrValues[attr] = obj[key];
378
+ attrValues[attr] = escapeAttribute(obj[key]);
312
379
  hasAttrs = true;
313
380
  }
314
381
  }
@@ -425,8 +492,10 @@ Builder.prototype.buildObjectNode = function (val, key, attrStr, level) {
425
492
  else {
426
493
  return this.indentate(level) + '<' + key + attrStr + this.closeTag(key) + this.tagEndChar;
427
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;
428
498
  } else {
429
-
430
499
  let tagEndExp = '</' + key + this.tagEndChar;
431
500
  let piClosingChar = "";
432
501
 
@@ -479,19 +548,16 @@ function buildEmptyObjNode(val, key, attrStr, level) {
479
548
  if (key[0] === "?") return this.indentate(level) + '<' + key + attrStr + '?' + this.tagEndChar;
480
549
  else {
481
550
  return this.indentate(level) + '<' + key + attrStr + '/' + this.tagEndChar;
482
- // return this.buildTagStr(level,key, attrStr);
483
551
  }
484
552
  }
485
553
  }
486
554
 
487
555
  Builder.prototype.buildTextValNode = function (val, key, attrStr, level, matcher) {
488
556
  if (this.options.cdataPropName !== false && key === this.options.cdataPropName) {
489
- const safeVal = String(val).replace(/\]\]>/g, ']]]]><![CDATA[>');
557
+ const safeVal = safeCdata(val);
490
558
  return this.indentate(level) + `<![CDATA[${safeVal}]]>` + this.newLine;
491
559
  } else if (this.options.commentPropName !== false && key === this.options.commentPropName) {
492
- const safeVal = String(val)
493
- .replace(/--/g, '- -') // -- is illegal anywhere in comment content
494
- .replace(/-$/, '- '); // trailing - would form -- with the closing -->
560
+ const safeVal = safeComment(val);
495
561
  return this.indentate(level) + `<!--${safeVal}-->` + this.newLine;
496
562
  } else if (key[0] === "?") {//PI tag
497
563
  return this.indentate(level) + '<' + key + attrStr + '?' + this.tagEndChar;
@@ -1,16 +1,57 @@
1
1
  import { Expression, Matcher } from 'path-expression-matcher';
2
+ import { safeComment, safeCdata, escapeAttribute } from "./util.js";
3
+ import { qName } from 'xml-naming';
2
4
 
3
5
  const EOL = "\n";
4
6
 
5
7
  /**
6
- *
7
- * @param {array} jArray
8
- * @param {any} options
9
- * @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
10
51
  */
11
52
  export default function toXml(jArray, options) {
12
53
  let indentation = "";
13
- if (options.format && options.indentBy.length > 0) {
54
+ if (options.format) {
14
55
  indentation = EOL;
15
56
  }
16
57
 
@@ -27,13 +68,16 @@ export default function toXml(jArray, options) {
27
68
  }
28
69
  }
29
70
 
71
+ // Detect XML version for use in name validation
72
+ const xmlVersion = detectXmlVersionFromArray(jArray, options);
73
+
30
74
  // Initialize matcher for path tracking
31
75
  const matcher = new Matcher();
32
76
 
33
- return arrToStr(jArray, options, indentation, matcher, stopNodeExpressions);
77
+ return arrToStr(jArray, options, indentation, matcher, stopNodeExpressions, xmlVersion);
34
78
  }
35
79
 
36
- function arrToStr(arr, options, indentation, matcher, stopNodeExpressions) {
80
+ function arrToStr(arr, options, indentation, matcher, stopNodeExpressions, xmlVersion) {
37
81
  let xmlStr = "";
38
82
  let isPreviousElementTag = false;
39
83
 
@@ -53,20 +97,32 @@ function arrToStr(arr, options, indentation, matcher, stopNodeExpressions) {
53
97
 
54
98
  for (let i = 0; i < arr.length; i++) {
55
99
  const tagObj = arr[i];
56
- const tagName = propName(tagObj);
57
- 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);
58
114
 
59
115
  // Extract attributes from ":@" property
60
116
  const attrValues = extractAttributeValues(tagObj[":@"], options);
61
117
 
62
- // Push tag to matcher WITH attributes
118
+ // Push resolved tag to matcher WITH attributes
63
119
  matcher.push(tagName, attrValues);
64
120
 
65
121
  // Check if this is a stop node using Expression matching
66
122
  const isStopNode = checkStopNode(matcher, stopNodeExpressions);
67
123
 
68
124
  if (tagName === options.textNodeName) {
69
- let tagText = tagObj[tagName];
125
+ let tagText = tagObj[rawTagName];
70
126
  if (!isStopNode) {
71
127
  tagText = options.tagValueProcessor(tagName, tagText);
72
128
  tagText = replaceEntitiesValue(tagText, options);
@@ -82,27 +138,25 @@ function arrToStr(arr, options, indentation, matcher, stopNodeExpressions) {
82
138
  if (isPreviousElementTag) {
83
139
  xmlStr += indentation;
84
140
  }
85
- const val = tagObj[tagName][0][options.textNodeName];
86
- const safeVal = String(val).replace(/\]\]>/g, ']]]]><![CDATA[>');
141
+ const val = tagObj[rawTagName][0][options.textNodeName];
142
+ const safeVal = safeCdata(val);
87
143
  xmlStr += `<![CDATA[${safeVal}]]>`;
88
144
  isPreviousElementTag = false;
89
145
  matcher.pop();
90
146
  continue;
91
147
  } else if (tagName === options.commentPropName) {
92
- const val = tagObj[tagName][0][options.textNodeName]
93
- const safeVal = String(val)
94
- .replace(/--/g, '- -') // -- is illegal anywhere in comment content
95
- .replace(/-$/, '- '); // trailing - would form -- with the closing -->
148
+ const val = tagObj[rawTagName][0][options.textNodeName];
149
+ const safeVal = safeComment(val);
96
150
  xmlStr += indentation + `<!--${safeVal}-->`;
97
151
  isPreviousElementTag = true;
98
152
  matcher.pop();
99
153
  continue;
100
154
  } else if (tagName[0] === "?") {
101
- const attStr = attr_to_str(tagObj[":@"], options, isStopNode);
155
+ const attStr = attr_to_str(tagObj[":@"], options, isStopNode, matcher, xmlVersion);
102
156
  const tempInd = tagName === "?xml" ? "" : indentation;
103
- let piTextNodeName = tagObj[tagName][0][options.textNodeName];
104
- piTextNodeName = piTextNodeName.length !== 0 ? " " + piTextNodeName : ""; //remove extra spacing
105
- 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}?>`;
106
160
  isPreviousElementTag = true;
107
161
  matcher.pop();
108
162
  continue;
@@ -114,16 +168,15 @@ function arrToStr(arr, options, indentation, matcher, stopNodeExpressions) {
114
168
  }
115
169
 
116
170
  // Pass isStopNode to attr_to_str so attributes are also not processed for stopNodes
117
- const attStr = attr_to_str(tagObj[":@"], options, isStopNode);
171
+ const attStr = attr_to_str(tagObj[":@"], options, isStopNode, matcher, xmlVersion);
118
172
  const tagStart = indentation + `<${tagName}${attStr}`;
119
173
 
120
174
  // If this is a stopNode, get raw content without processing
121
175
  let tagValue;
122
176
  if (isStopNode) {
123
- tagValue = getRawContent(tagObj[tagName], options);
177
+ tagValue = getRawContent(tagObj[rawTagName], options);
124
178
  } else {
125
-
126
- tagValue = arrToStr(tagObj[tagName], options, newIdentation, matcher, stopNodeExpressions);
179
+ tagValue = arrToStr(tagObj[rawTagName], options, newIdentation, matcher, stopNodeExpressions, xmlVersion);
127
180
  }
128
181
 
129
182
  if (options.unpairedTags.indexOf(tagName) !== -1) {
@@ -167,7 +220,7 @@ function extractAttributeValues(attrMap, options) {
167
220
  const cleanAttrName = attr.startsWith(options.attributeNamePrefix)
168
221
  ? attr.substr(options.attributeNamePrefix.length)
169
222
  : attr;
170
- attrValues[cleanAttrName] = attrMap[attr];
223
+ attrValues[cleanAttrName] = escapeAttribute(attrMap[attr]);
171
224
  hasAttrs = true;
172
225
  }
173
226
 
@@ -205,9 +258,7 @@ function getRawContent(arr, options) {
205
258
  // Processing instruction - skip for stopNodes
206
259
  continue;
207
260
  } else if (tagName) {
208
- // Nested tags within stopNode
209
- // Recursively get raw content and reconstruct the tag
210
- // For stopNodes, we don't process attributes either
261
+ // Nested tags within stopNode — no sanitizeName, content is raw
211
262
  const attStr = attr_to_str_raw(item[":@"], options);
212
263
  const nestedContent = getRawContent(item[tagName], options);
213
264
 
@@ -234,7 +285,7 @@ function attr_to_str_raw(attrMap, options) {
234
285
  if (attrVal === true && options.suppressBooleanAttributes) {
235
286
  attrStr += ` ${attr.substr(options.attributeNamePrefix.length)}`;
236
287
  } else {
237
- attrStr += ` ${attr.substr(options.attributeNamePrefix.length)}="${attrVal}"`;
288
+ attrStr += ` ${attr.substr(options.attributeNamePrefix.length)}="${escapeAttribute(attrVal)}"`;
238
289
  }
239
290
  }
240
291
  }
@@ -250,13 +301,23 @@ function propName(obj) {
250
301
  }
251
302
  }
252
303
 
253
- 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) {
254
309
  let attrStr = "";
255
310
  if (attrMap && !options.ignoreAttributes) {
256
311
  for (let attr in attrMap) {
257
312
  if (!Object.prototype.hasOwnProperty.call(attrMap, attr)) continue;
258
- let attrVal;
259
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;
260
321
  if (isStopNode) {
261
322
  // For stopNodes, use raw value without any processing
262
323
  attrVal = attrMap[attr];
@@ -267,9 +328,9 @@ function attr_to_str(attrMap, options, isStopNode) {
267
328
  }
268
329
 
269
330
  if (attrVal === true && options.suppressBooleanAttributes) {
270
- attrStr += ` ${attr.substr(options.attributeNamePrefix.length)}`;
331
+ attrStr += ` ${resolvedAttrName}`;
271
332
  } else {
272
- attrStr += ` ${attr.substr(options.attributeNamePrefix.length)}="${attrVal}"`;
333
+ attrStr += ` ${resolvedAttrName}="${escapeAttribute(attrVal)}"`;
273
334
  }
274
335
  }
275
336
  }
@@ -295,12 +356,4 @@ function replaceEntitiesValue(textValue, options) {
295
356
  }
296
357
  }
297
358
  return textValue;
298
- }
299
-
300
- function cdataVal(val) {
301
-
302
- }
303
-
304
- function commentVal(val) {
305
-
306
359
  }
@@ -0,0 +1,16 @@
1
+
2
+
3
+ export function safeComment(val) {
4
+ return String(val)
5
+ .replace(/--/g, '- -') // -- is illegal anywhere in comment content
6
+ .replace(/--/g, '- -') // handle the scenario when 2 consiucative dashes appears
7
+ .replace(/-$/, '- '); // trailing - would form -- with the closing -->
8
+ }
9
+
10
+ export function safeCdata(val) {
11
+ return String(val).replace(/\]\]>/g, ']]]]><![CDATA[>')
12
+ }
13
+
14
+ export function escapeAttribute(val) {
15
+ return String(val).replace(/"/g, '&quot;').replace(/'/g, '&apos;')
16
+ }
@@ -2,6 +2,14 @@
2
2
 
3
3
  Note: Due to some last quick changes on v4, detail of v4.5.3 & v4.5.4 are not updated here. v4.5.4x is the last tag of v4 in github repository. I'm extremely sorry for the confusion
4
4
 
5
+ **5.7.2 / 2026-04-25**
6
+ - allow numerical external entity for backward compatibility
7
+ - fix #705: attributesGroupName working with preserveOrder
8
+ - fix #817: stackoverflow when tag expression is very long
9
+
10
+ **5.7.1 / 2026-04-20**
11
+ - fix typo in CJS typing file
12
+
5
13
  **5.7.0 / 2026-04-17**
6
14
  - Use `@nodable/entities` v2.1.0
7
15
  - breaking changes