xml-to-html-converter 0.2.0 → 0.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/dist/index.cjs +258 -0
- package/dist/index.d.cts +26 -0
- package/package.json +5 -3
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var src_exports = {};
|
|
22
|
+
__export(src_exports, {
|
|
23
|
+
isMalformed: () => isMalformed,
|
|
24
|
+
render: () => render,
|
|
25
|
+
scaffold: () => scaffold
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(src_exports);
|
|
28
|
+
|
|
29
|
+
// src/modules/render/render.ts
|
|
30
|
+
function render(nodes) {
|
|
31
|
+
return nodes.map(renderNode).join("");
|
|
32
|
+
}
|
|
33
|
+
function renderNode(node) {
|
|
34
|
+
if (node.role === "textLeaf") return node.raw;
|
|
35
|
+
if (node.role === "comment") return node.raw;
|
|
36
|
+
if (node.role === "processingInstruction") return "";
|
|
37
|
+
if (node.role === "doctype") return "";
|
|
38
|
+
if (node.role === "closeTag") return "";
|
|
39
|
+
const tag = node.xmlTag ?? "";
|
|
40
|
+
const attrs = buildDataAttrs(node);
|
|
41
|
+
const attrsHtml = attrs ? ` ${attrs}` : "";
|
|
42
|
+
if (node.role === "selfTag") {
|
|
43
|
+
return `<div data-tag="${tag}"${attrsHtml}></div>`;
|
|
44
|
+
}
|
|
45
|
+
const children = render(node.children ?? []);
|
|
46
|
+
return `<div data-tag="${tag}"${attrsHtml}>${children}</div>`;
|
|
47
|
+
}
|
|
48
|
+
function buildDataAttrs(node) {
|
|
49
|
+
if (!node.xmlAttributes || node.xmlAttributes.length === 0) return "";
|
|
50
|
+
return node.xmlAttributes.map(({ name, value }) => `data-attrs-${name}="${value}"`).join(" ");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// src/modules/scaffold/scaffold.ts
|
|
54
|
+
function parseXmlAttributes(xmlInner) {
|
|
55
|
+
const attributes = [];
|
|
56
|
+
let i = 0;
|
|
57
|
+
const s = xmlInner.trim();
|
|
58
|
+
while (i < s.length) {
|
|
59
|
+
while (i < s.length && /\s/.test(s[i])) i++;
|
|
60
|
+
if (i >= s.length) break;
|
|
61
|
+
const nameStart = i;
|
|
62
|
+
while (i < s.length && s[i] !== "=" && !/\s/.test(s[i])) i++;
|
|
63
|
+
const name = s.slice(nameStart, i).trim();
|
|
64
|
+
if (!name) break;
|
|
65
|
+
while (i < s.length && /\s/.test(s[i])) i++;
|
|
66
|
+
if (s[i] !== "=") break;
|
|
67
|
+
i++;
|
|
68
|
+
while (i < s.length && /\s/.test(s[i])) i++;
|
|
69
|
+
const quote = s[i];
|
|
70
|
+
if (quote !== '"' && quote !== "'") break;
|
|
71
|
+
i++;
|
|
72
|
+
const valueStart = i;
|
|
73
|
+
while (i < s.length && s[i] !== quote) i++;
|
|
74
|
+
const value = s.slice(valueStart, i);
|
|
75
|
+
i++;
|
|
76
|
+
attributes.push({ name, value });
|
|
77
|
+
}
|
|
78
|
+
return attributes.length > 0 ? attributes : void 0;
|
|
79
|
+
}
|
|
80
|
+
var MAX_DEPTH = 500;
|
|
81
|
+
function scaffold(xml) {
|
|
82
|
+
const counter = { value: 0 };
|
|
83
|
+
const { xmlNodes } = collectXmlNodes(xml, 0, null, counter, 0);
|
|
84
|
+
return xmlNodes;
|
|
85
|
+
}
|
|
86
|
+
function collectXmlNodes(xml, position, parentTag, counter, depth) {
|
|
87
|
+
if (depth > MAX_DEPTH) return { xmlNodes: [], position, closed: false };
|
|
88
|
+
const xmlNodes = [];
|
|
89
|
+
while (position < xml.length) {
|
|
90
|
+
const xmlNodeData = extractXmlNodes(xml, position);
|
|
91
|
+
if (xmlNodeData.role === "textLeaf" && xmlNodeData.raw.trim() === "") {
|
|
92
|
+
position = xmlNodeData.end;
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (xmlNodeData.role === "closeTag") {
|
|
96
|
+
if (xmlNodeData.tag === parentTag)
|
|
97
|
+
return { xmlNodes, position: xmlNodeData.end, closed: true };
|
|
98
|
+
xmlNodes.push({
|
|
99
|
+
role: "closeTag",
|
|
100
|
+
raw: xmlNodeData.raw,
|
|
101
|
+
xmlTag: xmlNodeData.tag || void 0,
|
|
102
|
+
xmlInner: xmlNodeData.xmlInner,
|
|
103
|
+
globalIndex: counter.value++,
|
|
104
|
+
localIndex: xmlNodes.length,
|
|
105
|
+
malformed: true
|
|
106
|
+
});
|
|
107
|
+
position = xmlNodeData.end;
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (xmlNodeData.role === "openTag" && !xmlNodeData.malformed) {
|
|
111
|
+
const globalIndex = counter.value++;
|
|
112
|
+
const localIndex = xmlNodes.length;
|
|
113
|
+
const nested = collectXmlNodes(
|
|
114
|
+
xml,
|
|
115
|
+
xmlNodeData.end,
|
|
116
|
+
xmlNodeData.tag,
|
|
117
|
+
counter,
|
|
118
|
+
depth + 1
|
|
119
|
+
);
|
|
120
|
+
const xmlNode2 = {
|
|
121
|
+
role: "openTag",
|
|
122
|
+
raw: xmlNodeData.raw,
|
|
123
|
+
xmlTag: xmlNodeData.tag || void 0,
|
|
124
|
+
xmlInner: xmlNodeData.xmlInner,
|
|
125
|
+
xmlAttributes: xmlNodeData.xmlAttributes,
|
|
126
|
+
globalIndex,
|
|
127
|
+
localIndex,
|
|
128
|
+
children: nested.xmlNodes
|
|
129
|
+
};
|
|
130
|
+
if (!nested.closed) xmlNode2.malformed = true;
|
|
131
|
+
xmlNodes.push(xmlNode2);
|
|
132
|
+
position = nested.position;
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
const xmlNode = {
|
|
136
|
+
role: xmlNodeData.role,
|
|
137
|
+
raw: xmlNodeData.raw,
|
|
138
|
+
xmlTag: xmlNodeData.tag || void 0,
|
|
139
|
+
xmlInner: xmlNodeData.xmlInner,
|
|
140
|
+
xmlAttributes: xmlNodeData.xmlAttributes,
|
|
141
|
+
globalIndex: counter.value++,
|
|
142
|
+
localIndex: xmlNodes.length
|
|
143
|
+
};
|
|
144
|
+
if (xmlNodeData.malformed) xmlNode.malformed = true;
|
|
145
|
+
if (xmlNodeData.role === "openTag") xmlNode.children = [];
|
|
146
|
+
xmlNodes.push(xmlNode);
|
|
147
|
+
position = xmlNodeData.end;
|
|
148
|
+
}
|
|
149
|
+
return { xmlNodes, position, closed: parentTag === null };
|
|
150
|
+
}
|
|
151
|
+
function findTagClose(xml, position) {
|
|
152
|
+
let i = position;
|
|
153
|
+
while (i < xml.length) {
|
|
154
|
+
const ch = xml[i];
|
|
155
|
+
if (ch === '"' || ch === "'") {
|
|
156
|
+
const closeQuote = xml.indexOf(ch, i + 1);
|
|
157
|
+
i = closeQuote === -1 ? xml.length : closeQuote + 1;
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
if (ch === ">") return i;
|
|
161
|
+
i++;
|
|
162
|
+
}
|
|
163
|
+
return -1;
|
|
164
|
+
}
|
|
165
|
+
function extractXmlNodes(xml, position) {
|
|
166
|
+
if (xml[position] !== "<") {
|
|
167
|
+
const end2 = xml.indexOf("<", position);
|
|
168
|
+
return {
|
|
169
|
+
raw: xml.slice(position, end2 === -1 ? xml.length : end2),
|
|
170
|
+
role: "textLeaf",
|
|
171
|
+
tag: "",
|
|
172
|
+
end: end2 === -1 ? xml.length : end2
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
if (xml[position + 1] === "?") {
|
|
176
|
+
const end2 = xml.indexOf("?>", position + 2);
|
|
177
|
+
return end2 === -1 ? {
|
|
178
|
+
raw: xml.slice(position),
|
|
179
|
+
role: "processingInstruction",
|
|
180
|
+
tag: "",
|
|
181
|
+
end: xml.length
|
|
182
|
+
} : {
|
|
183
|
+
raw: xml.slice(position, end2 + 2),
|
|
184
|
+
role: "processingInstruction",
|
|
185
|
+
tag: "",
|
|
186
|
+
end: end2 + 2
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
if (xml[position + 1] === "!" && xml[position + 2] === "[") {
|
|
190
|
+
const end2 = xml.indexOf("]]>", position + 3);
|
|
191
|
+
return end2 === -1 ? { raw: xml.slice(position), role: "textLeaf", tag: "", end: xml.length } : {
|
|
192
|
+
raw: xml.slice(position, end2 + 3),
|
|
193
|
+
role: "textLeaf",
|
|
194
|
+
tag: "",
|
|
195
|
+
end: end2 + 3
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
if (xml[position + 1] === "!" && xml[position + 2] === "-" && xml[position + 3] === "-") {
|
|
199
|
+
const end2 = xml.indexOf("-->", position + 4);
|
|
200
|
+
return end2 === -1 ? { raw: xml.slice(position), role: "comment", tag: "", end: xml.length } : {
|
|
201
|
+
raw: xml.slice(position, end2 + 3),
|
|
202
|
+
role: "comment",
|
|
203
|
+
tag: "",
|
|
204
|
+
end: end2 + 3
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
if (xml.startsWith("<!DOCTYPE", position)) {
|
|
208
|
+
const bracketOpen = xml.indexOf("[", position);
|
|
209
|
+
const firstClose = xml.indexOf(">", position);
|
|
210
|
+
const hasBracket = bracketOpen !== -1 && bracketOpen < firstClose;
|
|
211
|
+
if (hasBracket) {
|
|
212
|
+
const bracketClose = xml.indexOf("]>", bracketOpen);
|
|
213
|
+
const end3 = bracketClose === -1 ? xml.length : bracketClose + 2;
|
|
214
|
+
return { raw: xml.slice(position, end3), role: "doctype", tag: "", end: end3 };
|
|
215
|
+
}
|
|
216
|
+
const end2 = firstClose === -1 ? xml.length : firstClose + 1;
|
|
217
|
+
return { raw: xml.slice(position, end2), role: "doctype", tag: "", end: end2 };
|
|
218
|
+
}
|
|
219
|
+
const closeAt = findTagClose(xml, position + 1);
|
|
220
|
+
if (closeAt === -1)
|
|
221
|
+
return {
|
|
222
|
+
raw: xml.slice(position),
|
|
223
|
+
role: "openTag",
|
|
224
|
+
tag: "",
|
|
225
|
+
end: xml.length,
|
|
226
|
+
malformed: true
|
|
227
|
+
};
|
|
228
|
+
const raw = xml.slice(position, closeAt + 1);
|
|
229
|
+
const end = closeAt + 1;
|
|
230
|
+
const inner = xml.slice(position + 1, closeAt).trim();
|
|
231
|
+
if (inner.startsWith("/")) {
|
|
232
|
+
const tag2 = inner.slice(1).trim().split(/\s/)[0] ?? "";
|
|
233
|
+
const xmlInner2 = inner.slice(1).trim().slice(tag2.length).trim() || void 0;
|
|
234
|
+
return { raw, role: "closeTag", tag: tag2, xmlInner: xmlInner2, end };
|
|
235
|
+
}
|
|
236
|
+
if (inner.endsWith("/")) {
|
|
237
|
+
const trimmed = inner.slice(0, -1).trim();
|
|
238
|
+
const tag2 = trimmed.split(/\s/)[0] ?? "";
|
|
239
|
+
const xmlInner2 = trimmed.slice(tag2.length).trim() || void 0;
|
|
240
|
+
const xmlAttributes2 = xmlInner2 ? parseXmlAttributes(xmlInner2) : void 0;
|
|
241
|
+
return { raw, role: "selfTag", tag: tag2, xmlInner: xmlInner2, xmlAttributes: xmlAttributes2, end };
|
|
242
|
+
}
|
|
243
|
+
const tag = inner.split(/\s/)[0] ?? "";
|
|
244
|
+
const xmlInner = inner.slice(tag.length).trim() || void 0;
|
|
245
|
+
const xmlAttributes = xmlInner ? parseXmlAttributes(xmlInner) : void 0;
|
|
246
|
+
return { raw, role: "openTag", tag, xmlInner, xmlAttributes, end };
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// src/modules/scaffold/types.ts
|
|
250
|
+
function isMalformed(node) {
|
|
251
|
+
return node.malformed === true;
|
|
252
|
+
}
|
|
253
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
254
|
+
0 && (module.exports = {
|
|
255
|
+
isMalformed,
|
|
256
|
+
render,
|
|
257
|
+
scaffold
|
|
258
|
+
});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
interface XmlAttribute {
|
|
2
|
+
name: string;
|
|
3
|
+
value: string;
|
|
4
|
+
}
|
|
5
|
+
type XmlNodeRole = "closeTag" | "comment" | "doctype" | "openTag" | "processingInstruction" | "selfTag" | "textLeaf";
|
|
6
|
+
interface XmlNode {
|
|
7
|
+
role: XmlNodeRole;
|
|
8
|
+
raw: string;
|
|
9
|
+
xmlTag?: string;
|
|
10
|
+
xmlInner?: string;
|
|
11
|
+
xmlAttributes?: XmlAttribute[];
|
|
12
|
+
globalIndex: number;
|
|
13
|
+
localIndex: number;
|
|
14
|
+
children?: XmlNode[];
|
|
15
|
+
malformed?: true;
|
|
16
|
+
}
|
|
17
|
+
type MalformedXmlNode = XmlNode & {
|
|
18
|
+
malformed: true;
|
|
19
|
+
};
|
|
20
|
+
declare function isMalformed(node: XmlNode): node is MalformedXmlNode;
|
|
21
|
+
|
|
22
|
+
declare function render(nodes: XmlNode[]): string;
|
|
23
|
+
|
|
24
|
+
declare function scaffold(xml: string): XmlNode[];
|
|
25
|
+
|
|
26
|
+
export { type MalformedXmlNode, type XmlAttribute, type XmlNode, type XmlNodeRole, isMalformed, render, scaffold };
|
package/package.json
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "xml-to-html-converter",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Zero dependency XML to HTML converter for Node environments",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "./dist/index.
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
7
8
|
"types": "./dist/index.d.ts",
|
|
8
9
|
"exports": {
|
|
9
10
|
".": {
|
|
10
11
|
"types": "./dist/index.d.ts",
|
|
11
|
-
"import": "./dist/index.js"
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
12
14
|
}
|
|
13
15
|
},
|
|
14
16
|
"files": [
|