arc-1 1.1.2 → 1.3.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.
- package/README.md +16 -6
- package/bin/start-cf.sh +32 -0
- package/dist/adt/atc-batch.d.ts +71 -0
- package/dist/adt/atc-batch.d.ts.map +1 -0
- package/dist/adt/atc-batch.js +182 -0
- package/dist/adt/atc-batch.js.map +1 -0
- package/dist/adt/atc.d.ts +34 -0
- package/dist/adt/atc.d.ts.map +1 -1
- package/dist/adt/atc.js +58 -12
- package/dist/adt/atc.js.map +1 -1
- package/dist/adt/aunit.d.ts.map +1 -1
- package/dist/adt/aunit.js +50 -7
- package/dist/adt/aunit.js.map +1 -1
- package/dist/adt/bounded-response.d.ts +12 -0
- package/dist/adt/bounded-response.d.ts.map +1 -0
- package/dist/adt/bounded-response.js +242 -0
- package/dist/adt/bounded-response.js.map +1 -0
- package/dist/adt/ci-quality-xml.d.ts +44 -0
- package/dist/adt/ci-quality-xml.d.ts.map +1 -0
- package/dist/adt/ci-quality-xml.js +98 -0
- package/dist/adt/ci-quality-xml.js.map +1 -0
- package/dist/adt/ci-quality.d.ts +64 -0
- package/dist/adt/ci-quality.d.ts.map +1 -0
- package/dist/adt/ci-quality.js +132 -0
- package/dist/adt/ci-quality.js.map +1 -0
- package/dist/adt/client.d.ts +55 -34
- package/dist/adt/client.d.ts.map +1 -1
- package/dist/adt/client.js +130 -169
- package/dist/adt/client.js.map +1 -1
- package/dist/adt/config.d.ts +6 -0
- package/dist/adt/config.d.ts.map +1 -1
- package/dist/adt/config.js +3 -0
- package/dist/adt/config.js.map +1 -1
- package/dist/adt/data-result-context.d.ts +46 -0
- package/dist/adt/data-result-context.d.ts.map +1 -0
- package/dist/adt/data-result-context.js +118 -0
- package/dist/adt/data-result-context.js.map +1 -0
- package/dist/adt/data-source-name.d.ts +60 -0
- package/dist/adt/data-source-name.d.ts.map +1 -0
- package/dist/adt/data-source-name.js +156 -0
- package/dist/adt/data-source-name.js.map +1 -0
- package/dist/adt/data-source-policy.d.ts +139 -0
- package/dist/adt/data-source-policy.d.ts.map +1 -0
- package/dist/adt/data-source-policy.js +759 -0
- package/dist/adt/data-source-policy.js.map +1 -0
- package/dist/adt/ddic-xml.d.ts +78 -4
- package/dist/adt/ddic-xml.d.ts.map +1 -1
- package/dist/adt/ddic-xml.js +647 -54
- package/dist/adt/ddic-xml.js.map +1 -1
- package/dist/adt/devtools.d.ts +1 -0
- package/dist/adt/devtools.d.ts.map +1 -1
- package/dist/adt/devtools.js +24 -4
- package/dist/adt/devtools.js.map +1 -1
- package/dist/adt/errors.d.ts +10 -0
- package/dist/adt/errors.d.ts.map +1 -1
- package/dist/adt/errors.js +17 -0
- package/dist/adt/errors.js.map +1 -1
- package/dist/adt/http-deadline.d.ts +10 -0
- package/dist/adt/http-deadline.d.ts.map +1 -1
- package/dist/adt/http-deadline.js +7 -0
- package/dist/adt/http-deadline.js.map +1 -1
- package/dist/adt/http.d.ts +6 -3
- package/dist/adt/http.d.ts.map +1 -1
- package/dist/adt/http.js +94 -55
- package/dist/adt/http.js.map +1 -1
- package/dist/adt/internal-data-operations.d.ts +56 -0
- package/dist/adt/internal-data-operations.d.ts.map +1 -0
- package/dist/adt/internal-data-operations.js +99 -0
- package/dist/adt/internal-data-operations.js.map +1 -0
- package/dist/adt/package-contents.d.ts +12 -0
- package/dist/adt/package-contents.d.ts.map +1 -0
- package/dist/adt/package-contents.js +19 -0
- package/dist/adt/package-contents.js.map +1 -0
- package/dist/adt/relation-objects.d.ts +385 -0
- package/dist/adt/relation-objects.d.ts.map +1 -0
- package/dist/adt/relation-objects.js +262 -0
- package/dist/adt/relation-objects.js.map +1 -0
- package/dist/adt/repository-relations.d.ts +41 -0
- package/dist/adt/repository-relations.d.ts.map +1 -0
- package/dist/adt/repository-relations.js +246 -0
- package/dist/adt/repository-relations.js.map +1 -0
- package/dist/adt/request-attempt-budget.d.ts +26 -0
- package/dist/adt/request-attempt-budget.d.ts.map +1 -0
- package/dist/adt/request-attempt-budget.js +63 -0
- package/dist/adt/request-attempt-budget.js.map +1 -0
- package/dist/adt/result-limits.d.ts +3 -0
- package/dist/adt/result-limits.d.ts.map +1 -0
- package/dist/adt/result-limits.js +7 -0
- package/dist/adt/result-limits.js.map +1 -0
- package/dist/adt/safety.d.ts +2 -0
- package/dist/adt/safety.d.ts.map +1 -1
- package/dist/adt/safety.js +6 -0
- package/dist/adt/safety.js.map +1 -1
- package/dist/adt/server-driven.d.ts +6 -1
- package/dist/adt/server-driven.d.ts.map +1 -1
- package/dist/adt/server-driven.js +24 -20
- package/dist/adt/server-driven.js.map +1 -1
- package/dist/adt/sql-source-analyzer.d.ts +12 -0
- package/dist/adt/sql-source-analyzer.d.ts.map +1 -0
- package/dist/adt/sql-source-analyzer.js +158 -0
- package/dist/adt/sql-source-analyzer.js.map +1 -0
- package/dist/adt/table-query.d.ts +43 -0
- package/dist/adt/table-query.d.ts.map +1 -0
- package/dist/adt/table-query.js +160 -0
- package/dist/adt/table-query.js.map +1 -0
- package/dist/adt/text-elements.d.ts +48 -0
- package/dist/adt/text-elements.d.ts.map +1 -0
- package/dist/adt/text-elements.js +116 -0
- package/dist/adt/text-elements.js.map +1 -0
- package/dist/adt/types.d.ts +16 -0
- package/dist/adt/types.d.ts.map +1 -1
- package/dist/adt/uiad.d.ts +19 -0
- package/dist/adt/uiad.d.ts.map +1 -0
- package/dist/adt/uiad.js +173 -0
- package/dist/adt/uiad.js.map +1 -0
- package/dist/adt/xml-parser.d.ts +12 -15
- package/dist/adt/xml-parser.d.ts.map +1 -1
- package/dist/adt/xml-parser.js +44 -12
- package/dist/adt/xml-parser.js.map +1 -1
- package/dist/authz/policy.d.ts.map +1 -1
- package/dist/authz/policy.js +4 -1
- package/dist/authz/policy.js.map +1 -1
- package/dist/cache/cache.d.ts +3 -23
- package/dist/cache/cache.d.ts.map +1 -1
- package/dist/cache/cache.js +2 -3
- package/dist/cache/cache.js.map +1 -1
- package/dist/cache/caching-layer.d.ts +6 -25
- package/dist/cache/caching-layer.d.ts.map +1 -1
- package/dist/cache/caching-layer.js +3 -45
- package/dist/cache/caching-layer.js.map +1 -1
- package/dist/cache/memory.d.ts +1 -4
- package/dist/cache/memory.d.ts.map +1 -1
- package/dist/cache/memory.js +1 -10
- package/dist/cache/memory.js.map +1 -1
- package/dist/cache/sqlite.d.ts +1 -3
- package/dist/cache/sqlite.d.ts.map +1 -1
- package/dist/cache/sqlite.js +2 -17
- package/dist/cache/sqlite.js.map +1 -1
- package/dist/cli-checks.d.ts +3 -0
- package/dist/cli-checks.d.ts.map +1 -1
- package/dist/cli-checks.js +16 -0
- package/dist/cli-checks.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +7 -2
- package/dist/cli.js.map +1 -1
- package/dist/context/compressor.d.ts +2 -3
- package/dist/context/compressor.d.ts.map +1 -1
- package/dist/context/compressor.js +30 -49
- package/dist/context/compressor.js.map +1 -1
- package/dist/context/contract.d.ts +5 -0
- package/dist/context/contract.d.ts.map +1 -1
- package/dist/context/contract.js +2 -2
- package/dist/context/contract.js.map +1 -1
- package/dist/context/deps.d.ts +11 -0
- package/dist/context/deps.d.ts.map +1 -1
- package/dist/context/deps.js +2 -2
- package/dist/context/deps.js.map +1 -1
- package/dist/context/parse-cache.d.ts +23 -0
- package/dist/context/parse-cache.d.ts.map +1 -0
- package/dist/context/parse-cache.js +71 -0
- package/dist/context/parse-cache.js.map +1 -0
- package/dist/context/relation-walk.d.ts +48 -0
- package/dist/context/relation-walk.d.ts.map +1 -0
- package/dist/context/relation-walk.js +137 -0
- package/dist/context/relation-walk.js.map +1 -0
- package/dist/context/types.d.ts +4 -4
- package/dist/context/types.d.ts.map +1 -1
- package/dist/handlers/activate.d.ts +1 -1
- package/dist/handlers/activate.d.ts.map +1 -1
- package/dist/handlers/activate.js +33 -56
- package/dist/handlers/activate.js.map +1 -1
- package/dist/handlers/activation-results.d.ts +3 -0
- package/dist/handlers/activation-results.d.ts.map +1 -0
- package/dist/handlers/activation-results.js +8 -0
- package/dist/handlers/activation-results.js.map +1 -0
- package/dist/handlers/context.d.ts.map +1 -1
- package/dist/handlers/context.js +8 -32
- package/dist/handlers/context.js.map +1 -1
- package/dist/handlers/data-element-fields.d.ts +26 -0
- package/dist/handlers/data-element-fields.d.ts.map +1 -0
- package/dist/handlers/data-element-fields.js +27 -0
- package/dist/handlers/data-element-fields.js.map +1 -0
- package/dist/handlers/diagnose-ci.d.ts +9 -0
- package/dist/handlers/diagnose-ci.d.ts.map +1 -0
- package/dist/handlers/diagnose-ci.js +120 -0
- package/dist/handlers/diagnose-ci.js.map +1 -0
- package/dist/handlers/diagnose-fields.d.ts +51 -0
- package/dist/handlers/diagnose-fields.d.ts.map +1 -0
- package/dist/handlers/diagnose-fields.js +48 -0
- package/dist/handlers/diagnose-fields.js.map +1 -0
- package/dist/handlers/diagnose.d.ts +4 -1
- package/dist/handlers/diagnose.d.ts.map +1 -1
- package/dist/handlers/diagnose.js +41 -6
- package/dist/handlers/diagnose.js.map +1 -1
- package/dist/handlers/dispatch.d.ts +3 -1
- package/dist/handlers/dispatch.d.ts.map +1 -1
- package/dist/handlers/dispatch.js +96 -11
- package/dist/handlers/dispatch.js.map +1 -1
- package/dist/handlers/feature-cache.d.ts +4 -3
- package/dist/handlers/feature-cache.d.ts.map +1 -1
- package/dist/handlers/feature-cache.js +4 -3
- package/dist/handlers/feature-cache.js.map +1 -1
- package/dist/handlers/hyperfocused.js +1 -1
- package/dist/handlers/live-relations.d.ts +5 -0
- package/dist/handlers/live-relations.d.ts.map +1 -0
- package/dist/handlers/live-relations.js +89 -0
- package/dist/handlers/live-relations.js.map +1 -0
- package/dist/handlers/manage.d.ts.map +1 -1
- package/dist/handlers/manage.js +7 -4
- package/dist/handlers/manage.js.map +1 -1
- package/dist/handlers/navigate.d.ts +1 -1
- package/dist/handlers/navigate.d.ts.map +1 -1
- package/dist/handlers/navigate.js +33 -7
- package/dist/handlers/navigate.js.map +1 -1
- package/dist/handlers/object-types.d.ts +6 -4
- package/dist/handlers/object-types.d.ts.map +1 -1
- package/dist/handlers/object-types.js +45 -10
- package/dist/handlers/object-types.js.map +1 -1
- package/dist/handlers/query-errors.d.ts +9 -1
- package/dist/handlers/query-errors.d.ts.map +1 -1
- package/dist/handlers/query-errors.js +40 -28
- package/dist/handlers/query-errors.js.map +1 -1
- package/dist/handlers/query.d.ts +2 -2
- package/dist/handlers/query.d.ts.map +1 -1
- package/dist/handlers/query.js +15 -22
- package/dist/handlers/query.js.map +1 -1
- package/dist/handlers/read.d.ts +1 -1
- package/dist/handlers/read.d.ts.map +1 -1
- package/dist/handlers/read.js +112 -23
- package/dist/handlers/read.js.map +1 -1
- package/dist/handlers/relation-input.d.ts +42 -0
- package/dist/handlers/relation-input.d.ts.map +1 -0
- package/dist/handlers/relation-input.js +22 -0
- package/dist/handlers/relation-input.js.map +1 -0
- package/dist/handlers/relation-tool.d.ts +5 -0
- package/dist/handlers/relation-tool.d.ts.map +1 -0
- package/dist/handlers/relation-tool.js +36 -0
- package/dist/handlers/relation-tool.js.map +1 -0
- package/dist/handlers/schemas.d.ts +119 -62
- package/dist/handlers/schemas.d.ts.map +1 -1
- package/dist/handlers/schemas.js +150 -13
- package/dist/handlers/schemas.js.map +1 -1
- package/dist/handlers/search.d.ts +1 -1
- package/dist/handlers/search.d.ts.map +1 -1
- package/dist/handlers/search.js +43 -8
- package/dist/handlers/search.js.map +1 -1
- package/dist/handlers/tool-descriptions.d.ts.map +1 -1
- package/dist/handlers/tool-descriptions.js +12 -13
- package/dist/handlers/tool-descriptions.js.map +1 -1
- package/dist/handlers/tool-registry.d.ts +1 -0
- package/dist/handlers/tool-registry.d.ts.map +1 -1
- package/dist/handlers/tool-registry.js +4 -0
- package/dist/handlers/tool-registry.js.map +1 -1
- package/dist/handlers/tools.d.ts +2 -3
- package/dist/handlers/tools.d.ts.map +1 -1
- package/dist/handlers/tools.js +139 -84
- package/dist/handlers/tools.js.map +1 -1
- package/dist/handlers/where-used.d.ts +2 -0
- package/dist/handlers/where-used.d.ts.map +1 -1
- package/dist/handlers/where-used.js +23 -5
- package/dist/handlers/where-used.js.map +1 -1
- package/dist/handlers/write/batch-results.d.ts +28 -0
- package/dist/handlers/write/batch-results.d.ts.map +1 -0
- package/dist/handlers/write/batch-results.js +106 -0
- package/dist/handlers/write/batch-results.js.map +1 -0
- package/dist/handlers/write/create-transport.d.ts +7 -0
- package/dist/handlers/write/create-transport.d.ts.map +1 -0
- package/dist/handlers/write/create-transport.js +38 -0
- package/dist/handlers/write/create-transport.js.map +1 -0
- package/dist/handlers/write/create.d.ts.map +1 -1
- package/dist/handlers/write/create.js +276 -413
- package/dist/handlers/write/create.js.map +1 -1
- package/dist/handlers/write/uiad.d.ts +8 -0
- package/dist/handlers/write/uiad.d.ts.map +1 -0
- package/dist/handlers/write/uiad.js +115 -0
- package/dist/handlers/write/uiad.js.map +1 -0
- package/dist/handlers/write/update-delete.d.ts +6 -6
- package/dist/handlers/write/update-delete.d.ts.map +1 -1
- package/dist/handlers/write/update-delete.js +46 -15
- package/dist/handlers/write/update-delete.js.map +1 -1
- package/dist/handlers/write-helpers.d.ts +0 -6
- package/dist/handlers/write-helpers.d.ts.map +1 -1
- package/dist/handlers/write-helpers.js +88 -80
- package/dist/handlers/write-helpers.js.map +1 -1
- package/dist/handlers/write.d.ts.map +1 -1
- package/dist/handlers/write.js +5 -1
- package/dist/handlers/write.js.map +1 -1
- package/dist/public/index.d.ts +1 -1
- package/dist/public/index.d.ts.map +1 -1
- package/dist/public/index.js +1 -1
- package/dist/public/index.js.map +1 -1
- package/dist/public/ui/app.js +0 -2
- package/dist/server/audit.d.ts +42 -1
- package/dist/server/audit.d.ts.map +1 -1
- package/dist/server/audit.js.map +1 -1
- package/dist/server/config.d.ts +4 -0
- package/dist/server/config.d.ts.map +1 -1
- package/dist/server/config.js +67 -0
- package/dist/server/config.js.map +1 -1
- package/dist/server/context.d.ts +5 -0
- package/dist/server/context.d.ts.map +1 -1
- package/dist/server/context.js.map +1 -1
- package/dist/server/destination-registry.d.ts +10 -2
- package/dist/server/destination-registry.d.ts.map +1 -1
- package/dist/server/destination-registry.js +12 -3
- package/dist/server/destination-registry.js.map +1 -1
- package/dist/server/effective-policy-log.d.ts +2 -1
- package/dist/server/effective-policy-log.d.ts.map +1 -1
- package/dist/server/effective-policy-log.js +18 -2
- package/dist/server/effective-policy-log.js.map +1 -1
- package/dist/server/http.d.ts +1 -1
- package/dist/server/http.d.ts.map +1 -1
- package/dist/server/http.js +1 -0
- package/dist/server/http.js.map +1 -1
- package/dist/server/logger.d.ts.map +1 -1
- package/dist/server/logger.js +4 -1
- package/dist/server/logger.js.map +1 -1
- package/dist/server/multi-target-runtime.d.ts.map +1 -1
- package/dist/server/multi-target-runtime.js +7 -2
- package/dist/server/multi-target-runtime.js.map +1 -1
- package/dist/server/multi-target-tools.d.ts.map +1 -1
- package/dist/server/multi-target-tools.js +3 -1
- package/dist/server/multi-target-tools.js.map +1 -1
- package/dist/server/runtime-memory.d.ts +13 -0
- package/dist/server/runtime-memory.d.ts.map +1 -0
- package/dist/server/runtime-memory.js +42 -0
- package/dist/server/runtime-memory.js.map +1 -0
- package/dist/server/server-instructions.d.ts +2 -0
- package/dist/server/server-instructions.d.ts.map +1 -0
- package/dist/server/server-instructions.js +33 -0
- package/dist/server/server-instructions.js.map +1 -0
- package/dist/server/server.d.ts +3 -2
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +42 -78
- package/dist/server/server.js.map +1 -1
- package/dist/server/shutdown.d.ts +6 -0
- package/dist/server/shutdown.d.ts.map +1 -0
- package/dist/server/shutdown.js +52 -0
- package/dist/server/shutdown.js.map +1 -0
- package/dist/server/sinks/btp-auditlog.d.ts +10 -8
- package/dist/server/sinks/btp-auditlog.d.ts.map +1 -1
- package/dist/server/sinks/btp-auditlog.js +86 -68
- package/dist/server/sinks/btp-auditlog.js.map +1 -1
- package/dist/server/sinks/file.d.ts +1 -1
- package/dist/server/sinks/file.d.ts.map +1 -1
- package/dist/server/sinks/file.js +12 -21
- package/dist/server/sinks/file.js.map +1 -1
- package/dist/server/types.d.ts +8 -0
- package/dist/server/types.d.ts.map +1 -1
- package/dist/server/types.js +4 -0
- package/dist/server/types.js.map +1 -1
- package/dist/server/ui-state.d.ts.map +1 -1
- package/dist/server/ui-state.js +1 -0
- package/dist/server/ui-state.js.map +1 -1
- package/dist/server/ui.js +0 -2
- package/dist/server/ui.js.map +1 -1
- package/package.json +15 -13
package/dist/adt/ddic-xml.js
CHANGED
|
@@ -37,7 +37,7 @@ export function normalizeSrvbBindingType(input) {
|
|
|
37
37
|
category = '0';
|
|
38
38
|
return { type: 'ODATA', odataVersion, category };
|
|
39
39
|
}
|
|
40
|
-
const DTEL_MAX_LABEL_LENGTHS = {
|
|
40
|
+
export const DTEL_MAX_LABEL_LENGTHS = {
|
|
41
41
|
short: 10,
|
|
42
42
|
medium: 20,
|
|
43
43
|
long: 40,
|
|
@@ -116,7 +116,10 @@ function formatLength(value, width) {
|
|
|
116
116
|
}
|
|
117
117
|
return ''.padStart(width, '0');
|
|
118
118
|
}
|
|
119
|
-
function formatLabelLength(label, maxLength) {
|
|
119
|
+
function formatLabelLength(label, maxLength, explicitLength) {
|
|
120
|
+
if (explicitLength !== undefined && explicitLength !== null && String(explicitLength).trim() !== '') {
|
|
121
|
+
return formatLength(explicitLength, 2);
|
|
122
|
+
}
|
|
120
123
|
if (!label)
|
|
121
124
|
return String(maxLength).padStart(2, '0');
|
|
122
125
|
return String(Math.min(label.length, maxLength)).padStart(2, '0');
|
|
@@ -326,25 +329,25 @@ export function buildDataElementXml(params) {
|
|
|
326
329
|
<dtel:dataTypeLength>${formatLength(params.length, 6)}</dtel:dataTypeLength>
|
|
327
330
|
<dtel:dataTypeDecimals>${formatLength(params.decimals, 6)}</dtel:dataTypeDecimals>
|
|
328
331
|
<dtel:shortFieldLabel>${escapeXmlAttr(shortLabel)}</dtel:shortFieldLabel>
|
|
329
|
-
<dtel:shortFieldLength>${formatLabelLength(shortLabel, DTEL_MAX_LABEL_LENGTHS.short)}</dtel:shortFieldLength>
|
|
332
|
+
<dtel:shortFieldLength>${formatLabelLength(shortLabel, DTEL_MAX_LABEL_LENGTHS.short, params.shortLength)}</dtel:shortFieldLength>
|
|
330
333
|
<dtel:shortFieldMaxLength>${String(DTEL_MAX_LABEL_LENGTHS.short).padStart(2, '0')}</dtel:shortFieldMaxLength>
|
|
331
334
|
<dtel:mediumFieldLabel>${escapeXmlAttr(mediumLabel)}</dtel:mediumFieldLabel>
|
|
332
|
-
<dtel:mediumFieldLength>${formatLabelLength(mediumLabel, DTEL_MAX_LABEL_LENGTHS.medium)}</dtel:mediumFieldLength>
|
|
335
|
+
<dtel:mediumFieldLength>${formatLabelLength(mediumLabel, DTEL_MAX_LABEL_LENGTHS.medium, params.mediumLength)}</dtel:mediumFieldLength>
|
|
333
336
|
<dtel:mediumFieldMaxLength>${DTEL_MAX_LABEL_LENGTHS.medium}</dtel:mediumFieldMaxLength>
|
|
334
337
|
<dtel:longFieldLabel>${escapeXmlAttr(longLabel)}</dtel:longFieldLabel>
|
|
335
|
-
<dtel:longFieldLength>${formatLabelLength(longLabel, DTEL_MAX_LABEL_LENGTHS.long)}</dtel:longFieldLength>
|
|
338
|
+
<dtel:longFieldLength>${formatLabelLength(longLabel, DTEL_MAX_LABEL_LENGTHS.long, params.longLength)}</dtel:longFieldLength>
|
|
336
339
|
<dtel:longFieldMaxLength>${DTEL_MAX_LABEL_LENGTHS.long}</dtel:longFieldMaxLength>
|
|
337
340
|
<dtel:headingFieldLabel>${escapeXmlAttr(headingLabel)}</dtel:headingFieldLabel>
|
|
338
|
-
<dtel:headingFieldLength>${formatLabelLength(headingLabel, DTEL_MAX_LABEL_LENGTHS.heading)}</dtel:headingFieldLength>
|
|
341
|
+
<dtel:headingFieldLength>${formatLabelLength(headingLabel, DTEL_MAX_LABEL_LENGTHS.heading, params.headingLength)}</dtel:headingFieldLength>
|
|
339
342
|
<dtel:headingFieldMaxLength>${DTEL_MAX_LABEL_LENGTHS.heading}</dtel:headingFieldMaxLength>
|
|
340
343
|
<dtel:searchHelp>${escapeXmlAttr(params.searchHelp ?? '')}</dtel:searchHelp>
|
|
341
344
|
<dtel:searchHelpParameter>${escapeXmlAttr(params.searchHelpParameter ?? '')}</dtel:searchHelpParameter>
|
|
342
345
|
<dtel:setGetParameter>${escapeXmlAttr(params.setGetParameter ?? '')}</dtel:setGetParameter>
|
|
343
346
|
<dtel:defaultComponentName>${escapeXmlAttr(params.defaultComponentName ?? '')}</dtel:defaultComponentName>
|
|
344
|
-
<dtel:deactivateInputHistory
|
|
347
|
+
<dtel:deactivateInputHistory>${boolToXml(params.deactivateInputHistory)}</dtel:deactivateInputHistory>
|
|
345
348
|
<dtel:changeDocument>${boolToXml(params.changeDocument)}</dtel:changeDocument>
|
|
346
|
-
<dtel:leftToRightDirection
|
|
347
|
-
<dtel:deactivateBIDIFiltering
|
|
349
|
+
<dtel:leftToRightDirection>${boolToXml(params.leftToRightDirection)}</dtel:leftToRightDirection>
|
|
350
|
+
<dtel:deactivateBIDIFiltering>${boolToXml(params.deactivateBIDIFiltering)}</dtel:deactivateBIDIFiltering>
|
|
348
351
|
</dtel:dataElement>
|
|
349
352
|
</blue:wbobj>`;
|
|
350
353
|
}
|
|
@@ -427,69 +430,659 @@ export function buildServiceBindingXml(params) {
|
|
|
427
430
|
* an `<sktd:id>` and a Base64-encoded `<sktd:text>`. We extract all of them
|
|
428
431
|
* and return a combined Markdown document with element headings.
|
|
429
432
|
*/
|
|
430
|
-
export function decodeKtdText(envelopeXml) {
|
|
433
|
+
export function decodeKtdText(envelopeXml, options = {}) {
|
|
431
434
|
// Extract all <sktd:element> blocks with their id and text
|
|
432
435
|
const elementPattern = /<sktd:element[^>]*>[\s\S]*?<sktd:id>([^<]*)<\/sktd:id>[\s\S]*?<\/sktd:element>/g;
|
|
433
436
|
const elements = [];
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
const textMatch = elementBlock.match(/<sktd:text[^>]*>([\s\S]*?)<\/sktd:text>/);
|
|
439
|
-
const base64 = textMatch?.[1]?.trim() ?? '';
|
|
440
|
-
let decoded = '';
|
|
441
|
-
if (base64) {
|
|
442
|
-
try {
|
|
443
|
-
decoded = Buffer.from(base64, 'base64').toString('utf-8');
|
|
444
|
-
}
|
|
445
|
-
catch {
|
|
446
|
-
decoded = '';
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
if (decoded) {
|
|
450
|
-
elements.push({ id, text: decoded });
|
|
451
|
-
}
|
|
437
|
+
for (const match of envelopeXml.matchAll(elementPattern)) {
|
|
438
|
+
const text = decodeKtdElementText(match[0]);
|
|
439
|
+
if (text)
|
|
440
|
+
elements.push({ id: match[1].trim(), text });
|
|
452
441
|
}
|
|
453
442
|
if (elements.length === 0) {
|
|
454
443
|
// Fallback: try extracting a single <sktd:text> without element structure
|
|
455
|
-
const
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
444
|
+
const text = decodeKtdElementText(envelopeXml) ?? '';
|
|
445
|
+
return options.routeSafe === false ? text : escapeKtdBodyMetaMarkers(text);
|
|
446
|
+
}
|
|
447
|
+
// A lone documented node still needs its routing heading when writable empty siblings
|
|
448
|
+
// exist. Otherwise appending one of the ids from formatKtdNodeIndex creates a
|
|
449
|
+
// non-empty preamble that the inverse writer must refuse. A genuinely single-target
|
|
450
|
+
// document keeps the compact, backwards-compatible bare body.
|
|
451
|
+
const allElements = findKtdElements(envelopeXml);
|
|
452
|
+
const documentedId = elements[0]?.id;
|
|
453
|
+
const hasOtherWritableTarget = allElements.some((element) => element.id && element.id !== documentedId && canWriteKtdLongText(element.xml));
|
|
454
|
+
if (elements.length === 1 && !hasOtherWritableTarget) {
|
|
455
|
+
const routes = ktdRoutes(envelopeXml, allElements);
|
|
456
|
+
if (options.routeSafe === false)
|
|
457
|
+
return elements[0].text;
|
|
458
|
+
const routeEscaped = elements[0].id ? escapeKtdBodyRouteHeadings(elements[0].text, routes) : elements[0].text;
|
|
459
|
+
const escaped = escapeKtdBodyMetaMarkers(routeEscaped);
|
|
460
|
+
// Keep the backwards-compatible bare body unless escaping exposed a line that the
|
|
461
|
+
// section parser would otherwise consume as a route. Metadata-marker escapes are
|
|
462
|
+
// independently reversible on the unaddressed path and need no outer heading.
|
|
463
|
+
return routeEscaped === elements[0].text ? escaped : `## ${elements[0].id}\n\n${escaped}`;
|
|
464
|
+
}
|
|
465
|
+
// Multiple elements: format as structured Markdown with element headings. Escape
|
|
466
|
+
// body headings that would otherwise be parsed as routing syntax on write. Prefixing
|
|
467
|
+
// one backslash is reversible even when the stored line already starts with one.
|
|
468
|
+
const routes = ktdRoutes(envelopeXml, allElements);
|
|
469
|
+
return elements
|
|
470
|
+
.map((e) => `## ${e.id}\n\n${options.routeSafe === false ? e.text : escapeKtdBodyMetaMarkers(escapeKtdBodyRouteHeadings(e.text, routes))}`)
|
|
471
|
+
.join('\n\n');
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Compact index of EVERY writable node of a KTD, documented or not — the only way to learn a
|
|
475
|
+
* node's address by reading, since `decodeKtdText` renders sections only for nodes that hold
|
|
476
|
+
* text. Empty string when the document has no writable node.
|
|
477
|
+
*
|
|
478
|
+
* Each node is listed by the spelling that resolves back to it: its node name, or its full id
|
|
479
|
+
* when the name would resolve elsewhere (a BDEF's root-entity node is named like the object
|
|
480
|
+
* itself, and that name resolves to the root) or is shared by several nodes. Names are grouped
|
|
481
|
+
* under their <base> and <TYPE> so a 90-node document costs a few lines, not 90 URIs.
|
|
482
|
+
*/
|
|
483
|
+
export function formatKtdNodeIndex(envelopeXml) {
|
|
484
|
+
const all = findKtdElements(envelopeXml);
|
|
485
|
+
const routes = ktdRoutes(envelopeXml, all);
|
|
486
|
+
if (routes.duplicateIds.size > 0) {
|
|
487
|
+
return (`SAPWrite unavailable: duplicate node ids (${[...routes.duplicateIds].join(', ')}). ` +
|
|
488
|
+
'The documentation remains readable, but these elements cannot be addressed separately.');
|
|
489
|
+
}
|
|
490
|
+
const writable = all.filter((element) => element.id && canWriteKtdLongText(element.xml));
|
|
491
|
+
if (writable.length === 0)
|
|
492
|
+
return '';
|
|
493
|
+
const roots = [];
|
|
494
|
+
const namesByBaseAndType = new Map();
|
|
495
|
+
// Listed on their own line, so the tokens above stay exactly what a caller may copy back —
|
|
496
|
+
// a marker glued onto a name would itself be a heading that resolves to nothing.
|
|
497
|
+
const empty = [];
|
|
498
|
+
for (const element of writable) {
|
|
499
|
+
const id = element.id;
|
|
500
|
+
const label = addressableKtdRoute(routes, element);
|
|
501
|
+
if (!elementBase64(element.xml))
|
|
502
|
+
empty.push(label);
|
|
503
|
+
const typeAt = id.indexOf('#type=');
|
|
504
|
+
const nameAt = typeAt < 0 ? -1 : id.indexOf(';name=', typeAt);
|
|
505
|
+
if (typeAt < 0 || nameAt < 0) {
|
|
506
|
+
roots.push(label);
|
|
507
|
+
continue;
|
|
463
508
|
}
|
|
464
|
-
|
|
465
|
-
|
|
509
|
+
const base = id.slice(0, typeAt);
|
|
510
|
+
const type = id.slice(typeAt + '#type='.length, nameAt);
|
|
511
|
+
const byType = namesByBaseAndType.get(base) ?? new Map();
|
|
512
|
+
byType.set(type, [...(byType.get(type) ?? []), label]);
|
|
513
|
+
namesByBaseAndType.set(base, byType);
|
|
514
|
+
}
|
|
515
|
+
const lines = [
|
|
516
|
+
`Nodes: ${writable.length}${empty.length ? ` (${empty.length} with no text yet, listed under "empty")` : ''}. ` +
|
|
517
|
+
'Address one with a "## <name>" section in SAPWrite "source", or as shortTexts[].node — every name below is ' +
|
|
518
|
+
'accepted verbatim. An update only touches the nodes it addresses; the rest keep their current text. ' +
|
|
519
|
+
'For a root-only H2 edit, keep the complete SAPRead context; when only the root has text, you can also omit its H2.',
|
|
520
|
+
];
|
|
521
|
+
for (const root of roots)
|
|
522
|
+
lines.push(`root: ${root}`);
|
|
523
|
+
for (const [base, byType] of namesByBaseAndType) {
|
|
524
|
+
lines.push(`base: ${base}`);
|
|
525
|
+
for (const [type, names] of byType)
|
|
526
|
+
lines.push(`${type} (${names.length}): ${names.join(', ')}`);
|
|
527
|
+
}
|
|
528
|
+
if (empty.length > 0)
|
|
529
|
+
lines.push(`empty (${empty.length}): ${empty.join(', ')}`);
|
|
530
|
+
return lines.join('\n');
|
|
531
|
+
}
|
|
532
|
+
/** Compare in-place rewrites and label changed nodes for create, update, and preview reports. */
|
|
533
|
+
export function summarizeKtdChanges(before, after) {
|
|
534
|
+
// Positional: a rewrite replaces element blocks in place and never adds or removes one, so
|
|
535
|
+
// index i is the same node on both sides — even for elements sharing or lacking an id.
|
|
536
|
+
const previous = findKtdElements(before);
|
|
537
|
+
const current = findKtdElements(after);
|
|
538
|
+
// The legacy write fallback can replace one envelope-level text slot without any elements.
|
|
539
|
+
if (previous.length === 0 && current.length === 0 && decodeKtdElementText(after) !== undefined) {
|
|
540
|
+
return before === after ? { changed: [], untouched: 1 } : { changed: ['(document body)'], untouched: 0 };
|
|
541
|
+
}
|
|
542
|
+
const routes = ktdRoutes(after, current);
|
|
543
|
+
const changed = [];
|
|
544
|
+
let untouched = 0;
|
|
545
|
+
current.forEach((element, index) => {
|
|
546
|
+
if (previous[index]?.xml === element.xml)
|
|
547
|
+
untouched += 1;
|
|
548
|
+
else
|
|
549
|
+
changed.push(element.id ? addressableKtdRoute(routes, element) : `(element ${index + 1})`);
|
|
550
|
+
});
|
|
551
|
+
return { changed, untouched };
|
|
552
|
+
}
|
|
553
|
+
/** The same routing feedback for a preview, an update, or a post-create documentation write. */
|
|
554
|
+
export function formatKtdWriteReport(before, after, report, dryRun = false) {
|
|
555
|
+
const { changed, untouched } = summarizeKtdChanges(before, after);
|
|
556
|
+
const changedList = changed.length > 0 ? `:\n${changed.map((node) => ` ${node}`).join('\n')}` : '.';
|
|
557
|
+
const proseNote = report.proseHeadings.length > 0
|
|
558
|
+
? `\nHeadings kept as prose inside their node (not node routes): ${report.proseHeadings.join(', ')}`
|
|
559
|
+
: '';
|
|
560
|
+
return (`${dryRun ? 'Would change' : 'Changed'} ${changed.length} node(s); ${untouched} node(s) ` +
|
|
561
|
+
`${dryRun ? 'would keep' : 'kept'} their current text${changedList}${proseNote}`);
|
|
562
|
+
}
|
|
563
|
+
/**
|
|
564
|
+
* The spelling a caller can copy back for this node: its name when that name resolves to this
|
|
565
|
+
* very element, otherwise the full id. Duplicate IDs are display-only; their read context warns
|
|
566
|
+
* that writes are unavailable.
|
|
567
|
+
*/
|
|
568
|
+
function addressableKtdRoute(routes, element) {
|
|
569
|
+
const name = ktdNodeNameSpellings(element.id)[0];
|
|
570
|
+
const candidates = ktdRouteCandidates(routes, name);
|
|
571
|
+
return candidates.length === 1 && candidates[0] === element ? name : element.id;
|
|
572
|
+
}
|
|
573
|
+
/** Exact transport line separating writable KTD Markdown from ARC-1 read-only context. */
|
|
574
|
+
export const KTD_META_MARKER = '<!-- arc1:ktd-meta — read-only context below; SAPWrite ignores it -->';
|
|
575
|
+
/** A Markdown level-two heading line. Greedy capture avoids quadratic matching on long input. */
|
|
576
|
+
const KTD_HEADING_LINE = /^##[ \t]+(.*)$/m;
|
|
577
|
+
/**
|
|
578
|
+
* Remove the read-only context that SAPRead appends after `KTD_META_MARKER`.
|
|
579
|
+
*
|
|
580
|
+
* A route-safe SAPRead escapes the same line when it belongs to a stored body. When a complete
|
|
581
|
+
* read contains both that escaped body line and an appended trailer, the final unescaped marker
|
|
582
|
+
* is therefore the delimiter. Using the final marker also keeps older complete reads lossless if
|
|
583
|
+
* their body still contains an unescaped occurrence. A heading below the delimiter is refused
|
|
584
|
+
* because it is most likely a node section appended in the wrong place.
|
|
585
|
+
*/
|
|
586
|
+
export function stripKtdMetaTrailer(markdown) {
|
|
587
|
+
let markerAt = -1;
|
|
588
|
+
let lineStart = 0;
|
|
589
|
+
while (lineStart <= markdown.length) {
|
|
590
|
+
const newlineAt = markdown.indexOf('\n', lineStart);
|
|
591
|
+
const lineEnd = newlineAt < 0 ? markdown.length : newlineAt;
|
|
592
|
+
const line = markdown.slice(lineStart, lineEnd).replace(/\r$/, '');
|
|
593
|
+
if (line === KTD_META_MARKER) {
|
|
594
|
+
markerAt = lineStart;
|
|
466
595
|
}
|
|
596
|
+
if (newlineAt < 0)
|
|
597
|
+
break;
|
|
598
|
+
lineStart = newlineAt + 1;
|
|
467
599
|
}
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
600
|
+
if (markerAt < 0)
|
|
601
|
+
return markdown;
|
|
602
|
+
const trailer = markdown.slice(markerAt + KTD_META_MARKER.length);
|
|
603
|
+
const strayHeading = trailer.match(KTD_HEADING_LINE);
|
|
604
|
+
if (strayHeading) {
|
|
605
|
+
throw new Error(`KTD documentation update has a "## ${strayHeading[1].trim()}" section below the read-only SAPRead ` +
|
|
606
|
+
`marker "${KTD_META_MARKER}". Move the section above that line, or remove the marker and its context.`);
|
|
471
607
|
}
|
|
472
|
-
|
|
473
|
-
return elements.map((e) => `## ${e.id}\n\n${e.text}`).join('\n\n');
|
|
608
|
+
return markdown.slice(0, markerAt).trimEnd();
|
|
474
609
|
}
|
|
475
610
|
/**
|
|
476
|
-
* Replace the <sktd:text>
|
|
477
|
-
* preserving all other attributes and elements (responsible,
|
|
611
|
+
* Replace the per-node <sktd:text> bodies of a <sktd:docu> envelope with
|
|
612
|
+
* base64(markdown), preserving all other attributes and elements (responsible,
|
|
613
|
+
* packageRef, refObject, and every node the body does not address).
|
|
614
|
+
*
|
|
615
|
+
* ADDRESSING: a KTD holds one <sktd:element> per documented node — the object
|
|
616
|
+
* root plus one per documentable element (BDEF actions, savers, entities, …).
|
|
617
|
+
* `decodeKtdText` renders those as `## <node id>` sections, and this function
|
|
618
|
+
* consumes that exact-id section format to write each section back to its node.
|
|
619
|
+
* A body that addresses no node used to be written into whichever element came
|
|
620
|
+
* first, which silently overwrote the root and dropped every other node's edit.
|
|
478
621
|
*
|
|
479
622
|
* The returned XML is suitable for a PUT to the KTD object URL with
|
|
480
623
|
* content-type `application/vnd.sap.adt.sktdv2+xml`.
|
|
481
624
|
*/
|
|
482
|
-
export function rewriteKtdText(envelopeXml, markdown) {
|
|
483
|
-
const
|
|
484
|
-
|
|
485
|
-
if (
|
|
486
|
-
|
|
625
|
+
export function rewriteKtdText(envelopeXml, markdown, report) {
|
|
626
|
+
const hasReadOnlyContext = markdown.split(/\r?\n/).includes(KTD_META_MARKER);
|
|
627
|
+
markdown = stripKtdMetaTrailer(markdown);
|
|
628
|
+
if (!markdown.trim()) {
|
|
629
|
+
throw new Error('KTD documentation update has an empty body. ARC-1 will not erase documentation from a bodyless ' +
|
|
630
|
+
'update; to clear one node, address it with "## <node id>" followed by an empty section.');
|
|
487
631
|
}
|
|
488
|
-
|
|
489
|
-
const
|
|
490
|
-
|
|
491
|
-
|
|
632
|
+
const elements = findKtdElements(envelopeXml);
|
|
633
|
+
const routes = ktdRoutes(envelopeXml, elements);
|
|
634
|
+
assertWritableKtdRoutes(routes);
|
|
635
|
+
const documented = elements.filter((element) => elementBase64(element.xml));
|
|
636
|
+
const root = rootKtdElement(envelopeXml, elements);
|
|
637
|
+
const unaddressedTarget = documented.length <= 1 ? (documented[0] ?? root) : undefined;
|
|
638
|
+
// A route-safe SAPRead prefixes one backslash to body H2s that would otherwise
|
|
639
|
+
// be parsed as node boundaries. Its presence makes a lone root route unambiguous,
|
|
640
|
+
// even when that read had no metadata context to append. Compute the inverse once
|
|
641
|
+
// as well so the unaddressed/recovery path cannot persist the transport escape.
|
|
642
|
+
const routeUnescapedMarkdown = unescapeKtdBodyRouteHeadings(markdown, routes);
|
|
643
|
+
const hasBodyRouteEscape = routeUnescapedMarkdown !== markdown;
|
|
644
|
+
const unescapedMarkdown = unescapeKtdBodyMetaMarkers(routeUnescapedMarkdown);
|
|
645
|
+
const perElement = splitKtdMarkdownByElementId(markdown, routes, report);
|
|
646
|
+
if (perElement) {
|
|
647
|
+
// The root id is a bare ABAP object name, so a lone `## ZI_FOO` is also a very
|
|
648
|
+
// natural document title. When the unaddressed fallback already resolves to that
|
|
649
|
+
// root, consuming the line as routing syntax would silently delete the title.
|
|
650
|
+
// A complete SAPRead result is unambiguous because it carries the metadata marker;
|
|
651
|
+
// several addressed nodes are likewise unambiguous. Refuse the remaining collision.
|
|
652
|
+
if (!hasReadOnlyContext &&
|
|
653
|
+
!hasBodyRouteEscape &&
|
|
654
|
+
root &&
|
|
655
|
+
root.id.toUpperCase() === envelopeKtdName(envelopeXml).toUpperCase() &&
|
|
656
|
+
unaddressedTarget === root &&
|
|
657
|
+
perElement.size === 1 &&
|
|
658
|
+
perElement.has(root.id)) {
|
|
659
|
+
throw new Error(`KTD heading "## ${root.id}" is ambiguous: it can be the root-node route or a visible Markdown title. ` +
|
|
660
|
+
`ARC-1 will not silently remove it. Omit that heading to update the sole root body, use "# ${root.id}" ` +
|
|
661
|
+
'for a visible title, or start a multi-node edit from the complete SAPRead result.');
|
|
662
|
+
}
|
|
663
|
+
return rewriteKtdElementTexts(envelopeXml, elements, perElement);
|
|
492
664
|
}
|
|
665
|
+
// An unaddressed body still has one unambiguous destination whenever at most one
|
|
666
|
+
// node currently holds text — that is exactly what `decodeKtdText` rendered, and
|
|
667
|
+
// it is the shape a freshly created KTD comes back in. More than one documented
|
|
668
|
+
// node, though, and the body would overwrite one and silently drop the others.
|
|
669
|
+
if (documented.length > 1) {
|
|
670
|
+
const addressable = documented.map((element) => element.id).filter(Boolean);
|
|
671
|
+
throw new Error(`KTD documentation update addresses no node. "${envelopeKtdName(envelopeXml)}" documents ` +
|
|
672
|
+
`${documented.length} nodes, so the body must address them with "## <node id>" headings. ` +
|
|
673
|
+
`Documented node ids in the version this write would modify:\n${addressable.map((id) => ` ${id}`).join('\n')}`);
|
|
674
|
+
}
|
|
675
|
+
const target = unaddressedTarget;
|
|
676
|
+
if (target) {
|
|
677
|
+
return (envelopeXml.slice(0, target.start) + setKtdElementText(target, unescapedMarkdown) + envelopeXml.slice(target.end));
|
|
678
|
+
}
|
|
679
|
+
// No <sktd:element> at all — rewrite the lone <sktd:text> wherever it sits.
|
|
680
|
+
const spliced = spliceKtdTextBody(envelopeXml, unescapedMarkdown);
|
|
681
|
+
if (spliced !== undefined)
|
|
682
|
+
return spliced;
|
|
493
683
|
throw new Error('KTD envelope missing <sktd:text> element — cannot update documentation body.');
|
|
494
684
|
}
|
|
685
|
+
/** Eclipse ADT applies this limit to its KTD short-text input widget. */
|
|
686
|
+
export const KTD_SHORT_TEXT_MAX_LENGTH = 60;
|
|
687
|
+
/**
|
|
688
|
+
* Apply optional Markdown bodies and optional short texts to one server-provided envelope.
|
|
689
|
+
* All mutations stay local until validation succeeds and the caller performs the PUT.
|
|
690
|
+
*/
|
|
691
|
+
export function rewriteKtdDocument(envelopeXml, markdown, shortTexts, report) {
|
|
692
|
+
const assignments = shortTexts ?? [];
|
|
693
|
+
if (markdown === undefined && assignments.length === 0) {
|
|
694
|
+
throw new Error('KTD documentation update has nothing to write: pass "source" (node bodies), "shortTexts", or both.');
|
|
695
|
+
}
|
|
696
|
+
const withBodies = markdown === undefined ? envelopeXml : rewriteKtdText(envelopeXml, markdown, report);
|
|
697
|
+
return assignments.length === 0 ? withBodies : rewriteKtdShortTexts(withBodies, assignments);
|
|
698
|
+
}
|
|
699
|
+
/** Base64 payload currently held by an element, '' when it is empty or absent. */
|
|
700
|
+
function elementBase64(elementXml) {
|
|
701
|
+
return elementXml.match(/<sktd:text[^>]*>([\s\S]*?)<\/sktd:text>/)?.[1]?.trim() ?? '';
|
|
702
|
+
}
|
|
703
|
+
/** Mirror Eclipse KtdElementUtil.canHaveLongText while retaining old envelopes with no flags. */
|
|
704
|
+
function canWriteKtdLongText(elementXml) {
|
|
705
|
+
const obligation = elementXml.match(/\bsktd:longTextObligation="([^"]*)"/)?.[1]?.toLowerCase();
|
|
706
|
+
if (obligation)
|
|
707
|
+
return obligation === 'mandatory' || obligation === 'optional';
|
|
708
|
+
const canHaveDocumentation = elementXml.match(/\bsktd:canHaveDocumentation="([^"]*)"/)?.[1]?.toLowerCase();
|
|
709
|
+
if (canHaveDocumentation)
|
|
710
|
+
return canHaveDocumentation === 'true';
|
|
711
|
+
// Older/simplified envelopes do not always carry either optional attribute. The
|
|
712
|
+
// server-provided text slot remains the strongest backwards-compatible evidence.
|
|
713
|
+
return /<sktd:text(?:\s|\/|>)/.test(elementXml);
|
|
714
|
+
}
|
|
715
|
+
const KTD_SHORT_TEXT_TAG = /<sktd:shortText\b[^>]*\/?>/;
|
|
716
|
+
const KTD_SHORT_TEXT_VALUE = /\bsktd:text="([^"]*)"/;
|
|
717
|
+
const KTD_SHORT_TEXT_OBLIGATION = /\bsktd:obligation="([^"]*)"/;
|
|
718
|
+
/** Existing short texts, each labelled with a reference that resolves back to its node. */
|
|
719
|
+
export function formatKtdShortTexts(envelopeXml) {
|
|
720
|
+
const elements = findKtdElements(envelopeXml);
|
|
721
|
+
const routes = ktdRoutes(envelopeXml, elements);
|
|
722
|
+
const lines = elements
|
|
723
|
+
.map((element) => {
|
|
724
|
+
const tag = element.xml.match(KTD_SHORT_TEXT_TAG)?.[0];
|
|
725
|
+
const encoded = tag?.match(KTD_SHORT_TEXT_VALUE)?.[1] ?? '';
|
|
726
|
+
if (!element.id || !encoded)
|
|
727
|
+
return undefined;
|
|
728
|
+
const text = Buffer.from(encoded, 'base64').toString('utf-8').replace(/\s+/g, ' ').trim();
|
|
729
|
+
if (!text)
|
|
730
|
+
return undefined;
|
|
731
|
+
const obligation = tag?.match(KTD_SHORT_TEXT_OBLIGATION)?.[1] || 'unspecified';
|
|
732
|
+
return ` ${addressableKtdRoute(routes, element)} [${obligation}]: ${text}`;
|
|
733
|
+
})
|
|
734
|
+
.filter((line) => Boolean(line));
|
|
735
|
+
return lines.length === 0
|
|
736
|
+
? ''
|
|
737
|
+
: [
|
|
738
|
+
routes.duplicateIds.size > 0
|
|
739
|
+
? 'Short texts (read-only; duplicate node IDs prevent updates):'
|
|
740
|
+
: 'Short texts (read-only; update with SAPWrite shortTexts=[{node,text}] using the name shown):',
|
|
741
|
+
...lines,
|
|
742
|
+
].join('\n');
|
|
743
|
+
}
|
|
744
|
+
/** Validate all assignments, then splice only their existing short-text value attributes. */
|
|
745
|
+
function rewriteKtdShortTexts(envelopeXml, assignments) {
|
|
746
|
+
const elements = findKtdElements(envelopeXml);
|
|
747
|
+
// The same resolver the "## " section headings use, so one spelling addresses a node
|
|
748
|
+
// everywhere: exact id, case variant, or the node name the SAPRead index prints.
|
|
749
|
+
const routes = ktdRoutes(envelopeXml, elements);
|
|
750
|
+
assertWritableKtdRoutes(routes);
|
|
751
|
+
const resolved = new Map();
|
|
752
|
+
for (const assignment of assignments) {
|
|
753
|
+
const requestedId = assignment.node.trim();
|
|
754
|
+
const element = resolveKtdRoute(routes, requestedId);
|
|
755
|
+
if (!element)
|
|
756
|
+
throw unknownKtdNodeError([requestedId], routes);
|
|
757
|
+
const key = element.id;
|
|
758
|
+
if (resolved.has(key)) {
|
|
759
|
+
throw new Error(`KTD node "${element.id}" appears twice in shortTexts — keep one entry per node.`);
|
|
760
|
+
}
|
|
761
|
+
const text = assignment.text.replace(/\s+/g, ' ').trim();
|
|
762
|
+
if (text.length > KTD_SHORT_TEXT_MAX_LENGTH) {
|
|
763
|
+
throw new Error(`Short text for KTD node "${element.id}" is ${text.length} characters; Eclipse ADT allows ` +
|
|
764
|
+
`${KTD_SHORT_TEXT_MAX_LENGTH}.`);
|
|
765
|
+
}
|
|
766
|
+
const tag = element.xml.match(KTD_SHORT_TEXT_TAG)?.[0];
|
|
767
|
+
if (!tag || !KTD_SHORT_TEXT_VALUE.test(tag)) {
|
|
768
|
+
throw new Error(`KTD node "${element.id}" has no writable <sktd:shortText sktd:text="…"> value; ARC-1 will not synthesize one.`);
|
|
769
|
+
}
|
|
770
|
+
const obligation = tag.match(KTD_SHORT_TEXT_OBLIGATION)?.[1]?.toLowerCase();
|
|
771
|
+
if (obligation !== 'mandatory' && obligation !== 'optional') {
|
|
772
|
+
throw new Error(`KTD node "${element.id}" does not take a writable short text ` +
|
|
773
|
+
`(sktd:obligation="${obligation ?? 'missing'}"; expected "mandatory" or "optional").`);
|
|
774
|
+
}
|
|
775
|
+
resolved.set(key, { element, text });
|
|
776
|
+
}
|
|
777
|
+
let rewritten = envelopeXml;
|
|
778
|
+
for (const { element, text } of [...resolved.values()].sort((left, right) => right.element.start - left.element.start)) {
|
|
779
|
+
const encoded = text ? Buffer.from(text, 'utf-8').toString('base64') : '';
|
|
780
|
+
const replacement = element.xml.replace(KTD_SHORT_TEXT_TAG, (tag) => tag.replace(KTD_SHORT_TEXT_VALUE, `sktd:text="${encoded}"`));
|
|
781
|
+
rewritten = rewritten.slice(0, element.start) + replacement + rewritten.slice(element.end);
|
|
782
|
+
}
|
|
783
|
+
return rewritten;
|
|
784
|
+
}
|
|
785
|
+
/**
|
|
786
|
+
* The element documenting the object itself, for an envelope where nothing is
|
|
787
|
+
* documented yet. Live reads show the root node's `<sktd:id>` repeating the
|
|
788
|
+
* object name from `<sktd:docu adtcore:name>`; when nothing matches, fall back to
|
|
789
|
+
* document order, which is where an unaddressed body has always gone.
|
|
790
|
+
*/
|
|
791
|
+
function rootKtdElement(envelopeXml, elements) {
|
|
792
|
+
const name = envelopeKtdName(envelopeXml);
|
|
793
|
+
return (elements.find((element) => element.id === name) ??
|
|
794
|
+
elements.find((element) => element.id.toUpperCase() === name.toUpperCase()) ??
|
|
795
|
+
elements[0]);
|
|
796
|
+
}
|
|
797
|
+
/** `<sktd:element>` blocks, paired or self-closing, in document order. */
|
|
798
|
+
function findKtdElements(envelopeXml) {
|
|
799
|
+
const pattern = /<sktd:element\b(?:[^>]*\/>|[\s\S]*?<\/sktd:element>)/g;
|
|
800
|
+
const elements = [];
|
|
801
|
+
let match;
|
|
802
|
+
while ((match = pattern.exec(envelopeXml)) !== null) {
|
|
803
|
+
const xml = match[0];
|
|
804
|
+
elements.push({
|
|
805
|
+
id: xml.match(/<sktd:id>([\s\S]*?)<\/sktd:id>/)?.[1]?.trim() ?? '',
|
|
806
|
+
start: match.index,
|
|
807
|
+
end: match.index + xml.length,
|
|
808
|
+
xml,
|
|
809
|
+
});
|
|
810
|
+
}
|
|
811
|
+
return elements;
|
|
812
|
+
}
|
|
813
|
+
function envelopeKtdName(envelopeXml) {
|
|
814
|
+
return envelopeXml.match(/<sktd:docu\b[^>]*\badtcore:name="([^"]*)"/)?.[1] ?? 'this KTD';
|
|
815
|
+
}
|
|
816
|
+
/**
|
|
817
|
+
* Every spelling a node name is seen in: percent-decoded (`%_OWN`, what the index prints) and raw
|
|
818
|
+
* as SAP encodes it on the wire (`%25_OWN`, what every `## <full id>` heading carries). One entry
|
|
819
|
+
* when they coincide. Both must be routes, or the spelling a caller copies from one surface is
|
|
820
|
+
* prose on the other.
|
|
821
|
+
* SAP's optional displayName is a presentation label, not an address; never route by it.
|
|
822
|
+
*/
|
|
823
|
+
function ktdNodeNameSpellings(id) {
|
|
824
|
+
const at = id.indexOf(';name=');
|
|
825
|
+
const raw = at < 0 ? id : id.slice(at + ';name='.length);
|
|
826
|
+
try {
|
|
827
|
+
return [...new Set([decodeURIComponent(raw), raw])];
|
|
828
|
+
}
|
|
829
|
+
catch {
|
|
830
|
+
return [raw];
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
function ktdRoutes(envelopeXml, elements) {
|
|
834
|
+
const byId = new Map();
|
|
835
|
+
const duplicateIds = new Set();
|
|
836
|
+
const byFoldedId = new Map();
|
|
837
|
+
const byName = new Map();
|
|
838
|
+
const qualifiers = new Set();
|
|
839
|
+
const register = (map, key, element) => {
|
|
840
|
+
const candidates = map.get(key) ?? [];
|
|
841
|
+
if (!candidates.includes(element))
|
|
842
|
+
candidates.push(element);
|
|
843
|
+
map.set(key, candidates);
|
|
844
|
+
};
|
|
845
|
+
for (const element of elements) {
|
|
846
|
+
if (!element.id)
|
|
847
|
+
continue;
|
|
848
|
+
if (byId.has(element.id))
|
|
849
|
+
duplicateIds.add(element.id);
|
|
850
|
+
else
|
|
851
|
+
byId.set(element.id, element);
|
|
852
|
+
register(byFoldedId, element.id.toUpperCase(), element);
|
|
853
|
+
for (const spelling of ktdNodeNameSpellings(element.id)) {
|
|
854
|
+
const nameKey = spelling.toUpperCase();
|
|
855
|
+
register(byName, nameKey, element);
|
|
856
|
+
const dot = spelling.lastIndexOf('.');
|
|
857
|
+
if (dot > 0)
|
|
858
|
+
qualifiers.add(spelling.slice(0, dot).toUpperCase());
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
const objectName = envelopeKtdName(envelopeXml);
|
|
862
|
+
return {
|
|
863
|
+
objectName,
|
|
864
|
+
byId,
|
|
865
|
+
duplicateIds,
|
|
866
|
+
byFoldedId,
|
|
867
|
+
byName,
|
|
868
|
+
qualifiers,
|
|
869
|
+
namespace: objectName.match(/^\/[^/]+\//)?.[0].toUpperCase() ?? '',
|
|
870
|
+
};
|
|
871
|
+
}
|
|
872
|
+
/** Ambiguous envelopes remain inspectable; only mutation requires unique exact IDs. */
|
|
873
|
+
function assertWritableKtdRoutes(routes) {
|
|
874
|
+
if (routes.duplicateIds.size > 0) {
|
|
875
|
+
throw new Error(`KTD envelope contains duplicate node ids (${[...routes.duplicateIds].join(', ')}); ARC-1 cannot address these elements separately.`);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
/**
|
|
879
|
+
* Shared lookup precedence: exact ID, then case-insensitive ID, then indexed node name.
|
|
880
|
+
* Keep ambiguous candidates so writers can refuse them and readers can choose a full-ID label.
|
|
881
|
+
*/
|
|
882
|
+
function ktdRouteCandidates(routes, heading) {
|
|
883
|
+
const exact = routes.duplicateIds.has(heading) ? undefined : routes.byId.get(heading);
|
|
884
|
+
if (exact)
|
|
885
|
+
return [exact];
|
|
886
|
+
const key = heading.toUpperCase();
|
|
887
|
+
return routes.byFoldedId.get(key) ?? routes.byName.get(key) ?? [];
|
|
888
|
+
}
|
|
889
|
+
/** Writes require one target; display labels can fall back to the full ID without throwing. */
|
|
890
|
+
function resolveKtdRoute(routes, heading) {
|
|
891
|
+
const candidates = ktdRouteCandidates(routes, heading);
|
|
892
|
+
if (candidates.length > 1) {
|
|
893
|
+
throw new Error(`KTD node "${heading}" is ambiguous in "${routes.objectName}" — ${candidates.length} nodes carry ` +
|
|
894
|
+
`that reference. Address one by its exact full id:\n${candidates.map((element) => ` ${element.id}`).join('\n')}`);
|
|
895
|
+
}
|
|
896
|
+
return candidates[0];
|
|
897
|
+
}
|
|
898
|
+
/**
|
|
899
|
+
* Whether an unresolved heading is unmistakably meant as a node reference — an ADT URI, a `#type=`
|
|
900
|
+
* fragment, a name qualified by an entity this document knows, a name in the documented object's
|
|
901
|
+
* own namespace, or an index label copied with its "(empty)" marker. Such a heading is a typo or
|
|
902
|
+
* a node that does not exist, and folding it into the previous node's text is exactly the silent
|
|
903
|
+
* corruption this guard exists to prevent. Ordinary prose headings ("## Shape", "## Where the data
|
|
904
|
+
* comes from") match none of these; a prose heading that does can be kept as prose with a leading
|
|
905
|
+
* backslash (`\## …`), the same escape the route-safe read uses.
|
|
906
|
+
*/
|
|
907
|
+
function looksLikeKtdRoute(routes, heading) {
|
|
908
|
+
const key = heading.toUpperCase();
|
|
909
|
+
if (key.startsWith('/SAP/BC/ADT/') || key.includes('#TYPE=') || /\(empty\)$/i.test(heading))
|
|
910
|
+
return true;
|
|
911
|
+
if (routes.namespace && key.startsWith(routes.namespace))
|
|
912
|
+
return true;
|
|
913
|
+
return [...routes.qualifiers].some((qualifier) => key.startsWith(`${qualifier}.`));
|
|
914
|
+
}
|
|
915
|
+
/**
|
|
916
|
+
* Refusal for a node reference that matches nothing. Lists the known nodes by the spelling the
|
|
917
|
+
* SAPRead index prints (a name, or the full id where only that resolves), so a typo can be matched
|
|
918
|
+
* against the list by eye instead of against ~140-character URIs.
|
|
919
|
+
*/
|
|
920
|
+
function unknownKtdNodeError(refs, routes) {
|
|
921
|
+
const subject = refs.length === 1
|
|
922
|
+
? `KTD node "${refs[0]}" does not`
|
|
923
|
+
: `KTD nodes ${refs.map((ref) => `"${ref}"`).join(', ')} do not`;
|
|
924
|
+
const known = [...routes.byId.values()].map((element) => ` ${addressableKtdRoute(routes, element)}`);
|
|
925
|
+
return new Error(`${subject} exist in this document. SAP creates one <sktd:element> per documentable element of ` +
|
|
926
|
+
'the referenced object; ARC-1 will not invent one. A heading meant as prose can be kept as prose with a ' +
|
|
927
|
+
'leading backslash ("\\## …"). Known node ids (any spelling below is accepted):\n' +
|
|
928
|
+
known.join('\n'));
|
|
929
|
+
}
|
|
930
|
+
/**
|
|
931
|
+
* Split a Markdown body in the section format emitted by `decodeKtdText`.
|
|
932
|
+
*
|
|
933
|
+
* A line is a node boundary when it is `## ` followed by something `resolveKtdRoute` maps to an
|
|
934
|
+
* element of this envelope: its exact id, a case variant, or the node NAME SAPRead's index prints.
|
|
935
|
+
* Names resolve against EVERY node, documented or not — resolving them against the pending ones
|
|
936
|
+
* only made a second write to the same node silently fold its section into the previous node's
|
|
937
|
+
* body. A heading that is unmistakably a node reference but matches nothing aborts the write.
|
|
938
|
+
* Ordinary Markdown headings inside a node's documentation survive the round-trip untouched.
|
|
939
|
+
*
|
|
940
|
+
* Returns undefined when the body addresses no node (single-node KTD, or a freshly
|
|
941
|
+
* created one); the caller then treats the whole body as that one node's text.
|
|
942
|
+
*/
|
|
943
|
+
function splitKtdMarkdownByElementId(markdown, routes, report) {
|
|
944
|
+
const lines = markdown.split(/\r?\n/);
|
|
945
|
+
const headings = [];
|
|
946
|
+
const unknown = [];
|
|
947
|
+
lines.forEach((line, index) => {
|
|
948
|
+
const id = line.match(KTD_HEADING_LINE)?.[1].trim();
|
|
949
|
+
if (!id)
|
|
950
|
+
return;
|
|
951
|
+
const resolved = resolveKtdRoute(routes, id);
|
|
952
|
+
if (resolved)
|
|
953
|
+
headings.push({ line: index, id: resolved.id });
|
|
954
|
+
else if (routes.byId.size > 0 && looksLikeKtdRoute(routes, id))
|
|
955
|
+
unknown.push(id);
|
|
956
|
+
// Nothing distinguishes a typo in a bare node name from a prose heading when the document's
|
|
957
|
+
// names carry no qualifier (DDLS fields, for instance) — so the caller is told what stayed prose.
|
|
958
|
+
else if (routes.byId.size > 0)
|
|
959
|
+
report?.proseHeadings.push(id);
|
|
960
|
+
});
|
|
961
|
+
if (unknown.length > 0)
|
|
962
|
+
throw unknownKtdNodeError(unknown, routes);
|
|
963
|
+
if (headings.length === 0)
|
|
964
|
+
return undefined;
|
|
965
|
+
const preamble = lines.slice(0, headings[0].line).join('\n').trim();
|
|
966
|
+
if (preamble) {
|
|
967
|
+
throw new Error(`KTD documentation update has text before its first "## <node id>" heading (the one for ` +
|
|
968
|
+
`"${headings[0].id}"). Every line must belong to a node, so move that text under a heading. ` +
|
|
969
|
+
`Stray text starts: "${preamble.slice(0, 80)}"`);
|
|
970
|
+
}
|
|
971
|
+
const bodies = new Map();
|
|
972
|
+
headings.forEach((heading, index) => {
|
|
973
|
+
const until = index + 1 < headings.length ? headings[index + 1].line : lines.length;
|
|
974
|
+
if (bodies.has(heading.id)) {
|
|
975
|
+
throw new Error(`KTD node "${heading.id}" appears twice in the update body — keep one section per node.`);
|
|
976
|
+
}
|
|
977
|
+
const body = lines
|
|
978
|
+
.slice(heading.line + 1, until)
|
|
979
|
+
.join('\n')
|
|
980
|
+
.trim();
|
|
981
|
+
bodies.set(heading.id, unescapeKtdBodyMetaMarkers(unescapeKtdBodyRouteHeadings(body, routes)));
|
|
982
|
+
});
|
|
983
|
+
return bodies;
|
|
984
|
+
}
|
|
985
|
+
/**
|
|
986
|
+
* A heading shape reserved by the section parser, whether it resolves or aborts the write.
|
|
987
|
+
* Node NAMES are reserved too, so a prose heading that happens to equal one is escaped on read
|
|
988
|
+
* and restored on write instead of being consumed as a route.
|
|
989
|
+
*/
|
|
990
|
+
function isKtdRouteHeadingId(id, routes) {
|
|
991
|
+
const key = id.toUpperCase();
|
|
992
|
+
return routes.byFoldedId.has(key) || routes.byName.has(key) || looksLikeKtdRoute(routes, id);
|
|
993
|
+
}
|
|
994
|
+
/**
|
|
995
|
+
* Protect a stored body line that is indistinguishable from KTD section routing.
|
|
996
|
+
*
|
|
997
|
+
* One leading backslash is added to every run of zero or more existing backslashes,
|
|
998
|
+
* so the inverse can remove exactly the transport escape and preserve the stored text.
|
|
999
|
+
*/
|
|
1000
|
+
function escapeKtdBodyRouteHeadings(markdown, routes) {
|
|
1001
|
+
return markdown.replace(/^(\\*)(##[ \t]+.*)$/gm, (line, _slashes, heading) => {
|
|
1002
|
+
const id = heading.match(KTD_HEADING_LINE)?.[1].trim();
|
|
1003
|
+
return id && isKtdRouteHeadingId(id, routes) ? `\\${line}` : line;
|
|
1004
|
+
});
|
|
1005
|
+
}
|
|
1006
|
+
/** Remove the one transport escape added by `escapeKtdBodyRouteHeadings`. */
|
|
1007
|
+
function unescapeKtdBodyRouteHeadings(markdown, routes) {
|
|
1008
|
+
return markdown.replace(/^\\(\\*##[ \t]+.*)$/gm, (line, escapedHeading) => {
|
|
1009
|
+
const heading = escapedHeading.replace(/^\\*/, '');
|
|
1010
|
+
const id = heading.match(KTD_HEADING_LINE)?.[1].trim();
|
|
1011
|
+
return id && isKtdRouteHeadingId(id, routes) ? escapedHeading : line;
|
|
1012
|
+
});
|
|
1013
|
+
}
|
|
1014
|
+
/** Escape an exact metadata-marker body line without losing existing leading backslashes. */
|
|
1015
|
+
function escapeKtdBodyMetaMarkers(markdown) {
|
|
1016
|
+
const marker = escapeRegExp(KTD_META_MARKER);
|
|
1017
|
+
return markdown.replace(new RegExp(`^(\\\\*)${marker}(\\r?)$`, 'gm'), (line) => `\\${line}`);
|
|
1018
|
+
}
|
|
1019
|
+
/** Remove the one transport escape added by `escapeKtdBodyMetaMarkers`. */
|
|
1020
|
+
function unescapeKtdBodyMetaMarkers(markdown) {
|
|
1021
|
+
const marker = escapeRegExp(KTD_META_MARKER);
|
|
1022
|
+
return markdown.replace(new RegExp(`^\\\\(\\\\*${marker})(\\r?)$`, 'gm'), (_line, escapedMarker, carriageReturn) => `${escapedMarker}${carriageReturn}`);
|
|
1023
|
+
}
|
|
1024
|
+
function escapeRegExp(value) {
|
|
1025
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
1026
|
+
}
|
|
1027
|
+
/** Splice new base64 bodies into the addressed elements, leaving every other byte alone. */
|
|
1028
|
+
function rewriteKtdElementTexts(envelopeXml, elements, bodies) {
|
|
1029
|
+
let rewritten = envelopeXml;
|
|
1030
|
+
// Back to front, so the elements still ahead keep their offsets.
|
|
1031
|
+
for (const element of [...elements].reverse()) {
|
|
1032
|
+
const body = bodies.get(element.id);
|
|
1033
|
+
if (body === undefined)
|
|
1034
|
+
continue;
|
|
1035
|
+
rewritten = rewritten.slice(0, element.start) + setKtdElementText(element, body) + rewritten.slice(element.end);
|
|
1036
|
+
}
|
|
1037
|
+
return rewritten;
|
|
1038
|
+
}
|
|
1039
|
+
function setKtdElementText(element, markdown) {
|
|
1040
|
+
const current = decodeKtdElementText(element.xml);
|
|
1041
|
+
// A complete SAPRead result includes documented nodes even when SAP marks one of
|
|
1042
|
+
// them non-writable. Preserve an unchanged section byte-for-byte; only an attempted
|
|
1043
|
+
// mutation needs the writability gate below.
|
|
1044
|
+
if (current !== undefined && normalizeKtdBodyForComparison(current) === normalizeKtdBodyForComparison(markdown)) {
|
|
1045
|
+
return element.xml;
|
|
1046
|
+
}
|
|
1047
|
+
const spliced = spliceKtdTextBody(element.xml, markdown);
|
|
1048
|
+
if (spliced === undefined) {
|
|
1049
|
+
throw new Error(`KTD node "${element.id}" has no <sktd:text> element to write into. SAP gives every documentable ` +
|
|
1050
|
+
`node an (initially empty) <sktd:text/>, so an element without one is not a documentation target; ` +
|
|
1051
|
+
`ARC-1 does not synthesize one.`);
|
|
1052
|
+
}
|
|
1053
|
+
if (!canWriteKtdLongText(element.xml)) {
|
|
1054
|
+
throw new Error(`KTD node "${element.id}" does not accept long-text documentation according to SAP's ` +
|
|
1055
|
+
'canHaveDocumentation/longTextObligation contract.');
|
|
1056
|
+
}
|
|
1057
|
+
return spliced;
|
|
1058
|
+
}
|
|
1059
|
+
/** Decode one existing text slot; undefined means the element has no slot at all. */
|
|
1060
|
+
function decodeKtdElementText(elementXml) {
|
|
1061
|
+
const paired = elementXml.match(/<sktd:text[^>]*>([\s\S]*?)<\/sktd:text>/);
|
|
1062
|
+
if (paired) {
|
|
1063
|
+
const base64 = paired[1].trim();
|
|
1064
|
+
return base64 ? Buffer.from(base64, 'base64').toString('utf-8') : '';
|
|
1065
|
+
}
|
|
1066
|
+
return /<sktd:text(?:\s[^>]*)?\/>/.test(elementXml) ? '' : undefined;
|
|
1067
|
+
}
|
|
1068
|
+
/** Section separators normalize outer whitespace and CRLF; compare the semantic body. */
|
|
1069
|
+
function normalizeKtdBodyForComparison(markdown) {
|
|
1070
|
+
return markdown.replace(/\r\n/g, '\n').trim();
|
|
1071
|
+
}
|
|
1072
|
+
/**
|
|
1073
|
+
* Swap the payload of the first <sktd:text> in `xml` — paired or self-closing — for
|
|
1074
|
+
* base64(markdown). Returns undefined when `xml` holds no <sktd:text> at all, so each
|
|
1075
|
+
* caller raises the error that fits its own context. `<sktd:shortText sktd:text="…"/>`
|
|
1076
|
+
* never matches: the literal `<sktd:text` requires the element name, not the attribute.
|
|
1077
|
+
*/
|
|
1078
|
+
function spliceKtdTextBody(xml, markdown) {
|
|
1079
|
+
const base64 = Buffer.from(markdown, 'utf-8').toString('base64');
|
|
1080
|
+
const paired = /(<sktd:text[^>]*>)([\s\S]*?)(<\/sktd:text>)/;
|
|
1081
|
+
if (paired.test(xml))
|
|
1082
|
+
return xml.replace(paired, `$1${base64}$3`);
|
|
1083
|
+
const selfClosing = /<sktd:text([^>]*)\/>/;
|
|
1084
|
+
if (selfClosing.test(xml))
|
|
1085
|
+
return xml.replace(selfClosing, `<sktd:text$1>${base64}</sktd:text>`);
|
|
1086
|
+
return undefined;
|
|
1087
|
+
}
|
|
495
1088
|
//# sourceMappingURL=ddic-xml.js.map
|