toon-formatter 2.0.1 → 2.2.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/CHANGELOG.md +39 -1
- package/README.md +211 -0
- package/bin/toon-formatter.js +3 -0
- package/package.json +17 -7
- package/src/cli.js +271 -0
- package/src/csv.js +19 -50
- package/src/csv_formatter/index.js +496 -0
- package/src/csv_formatter/validator.js +36 -0
- package/src/index.js +9 -1
- package/src/json.js +117 -67
- package/src/json_formatter/csv.js +145 -0
- package/src/json_formatter/index.js +525 -0
- package/src/json_formatter/validator.js +29 -0
- package/src/json_formatter/xml.js +206 -0
- package/src/json_formatter/yaml.js +81 -0
- package/src/utils.js +262 -64
- package/src/xml.js +24 -79
- package/src/xml_formatter/csv.js +122 -0
- package/src/xml_formatter/index.js +488 -0
- package/src/xml_formatter/validator.js +53 -0
- package/src/yaml_formatter/csv.js +101 -0
- package/src/yaml_formatter/index.js +542 -0
- package/src/yaml_formatter/validator.js +31 -0
- package/src/yaml_formatter/xml.js +116 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* XML <-> JSON Converter (for JsonConverter)
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { encodeXmlReservedChars, extractJsonFromString, extractXmlFromString, buildTag } from '../utils.js';
|
|
6
|
+
|
|
7
|
+
// --- Internal Helper Logic ---
|
|
8
|
+
|
|
9
|
+
function xmlDomToJson(xml) {
|
|
10
|
+
let obj = {};
|
|
11
|
+
|
|
12
|
+
if (xml.nodeType === 1) { // Element node
|
|
13
|
+
if (xml.attributes && xml.attributes.length > 0) {
|
|
14
|
+
obj["@attributes"] = {};
|
|
15
|
+
for (let j = 0; j < xml.attributes.length; j++) {
|
|
16
|
+
const attribute = xml.attributes.item(j);
|
|
17
|
+
obj["@attributes"][attribute.nodeName] = attribute.nodeValue;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
} else if (xml.nodeType === 3) { // Text node
|
|
21
|
+
const trimmedText = xml.nodeValue.trim();
|
|
22
|
+
return trimmedText === "" ? undefined : trimmedText;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (xml.hasChildNodes && xml.hasChildNodes()) {
|
|
26
|
+
for (let i = 0; i < xml.childNodes.length; i++) {
|
|
27
|
+
const item = xml.childNodes.item(i);
|
|
28
|
+
const nodeName = item.nodeName;
|
|
29
|
+
|
|
30
|
+
// Skip comment nodes
|
|
31
|
+
if (item.nodeType === 8) continue;
|
|
32
|
+
|
|
33
|
+
const childJson = xmlDomToJson(item);
|
|
34
|
+
|
|
35
|
+
if (childJson === undefined) continue;
|
|
36
|
+
|
|
37
|
+
if (obj[nodeName] === undefined) {
|
|
38
|
+
obj[nodeName] = childJson;
|
|
39
|
+
} else {
|
|
40
|
+
if (!Array.isArray(obj[nodeName])) {
|
|
41
|
+
const old = obj[nodeName];
|
|
42
|
+
obj[nodeName] = [];
|
|
43
|
+
obj[nodeName].push(old);
|
|
44
|
+
}
|
|
45
|
+
obj[nodeName].push(childJson);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Special case: if object only has #text and no attributes/children, return text directly
|
|
51
|
+
const keys = Object.keys(obj);
|
|
52
|
+
if (keys.length === 1 && keys[0] === '#text' && !obj['@attributes']) {
|
|
53
|
+
return obj['#text'];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return obj;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// --- Exports ---
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Convert XML string to JSON object (Sync)
|
|
63
|
+
* @param {string} xmlString
|
|
64
|
+
* @returns {Object} JSON object
|
|
65
|
+
*/
|
|
66
|
+
export function xmlToJsonSync(xmlString) {
|
|
67
|
+
if (!xmlString || typeof xmlString !== 'string') {
|
|
68
|
+
throw new Error('Input must be a non-empty string');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
let convertedText = xmlString;
|
|
72
|
+
let iterationCount = 0;
|
|
73
|
+
const maxIterations = 100;
|
|
74
|
+
let wasModified = false;
|
|
75
|
+
|
|
76
|
+
const firstExtract = extractXmlFromString(xmlString);
|
|
77
|
+
if (firstExtract === xmlString.trim()) {
|
|
78
|
+
return parseXmlStringDirectly(xmlString);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
while (iterationCount < maxIterations) {
|
|
82
|
+
const xmlBlock = extractXmlFromString(convertedText);
|
|
83
|
+
if (!xmlBlock) break;
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
const jsonObject = parseXmlStringDirectly(xmlBlock);
|
|
87
|
+
const jsonOutput = JSON.stringify(jsonObject);
|
|
88
|
+
convertedText = convertedText.replace(xmlBlock, jsonOutput);
|
|
89
|
+
wasModified = true;
|
|
90
|
+
iterationCount++;
|
|
91
|
+
} catch (e) {
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (wasModified) return convertedText;
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
return parseXmlStringDirectly(xmlString);
|
|
100
|
+
} catch (e) {
|
|
101
|
+
return xmlString;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Helper for strict parsing to object
|
|
106
|
+
function parseXmlStringDirectly(xmlString) {
|
|
107
|
+
let Parser;
|
|
108
|
+
if (typeof DOMParser !== 'undefined') {
|
|
109
|
+
Parser = DOMParser;
|
|
110
|
+
} else {
|
|
111
|
+
throw new Error('DOMParser is not available. Please polyfill global.DOMParser for synchronous XML conversion in Node.js.');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const parser = new Parser();
|
|
115
|
+
const xmlDoc = parser.parseFromString(
|
|
116
|
+
encodeXmlReservedChars(xmlString),
|
|
117
|
+
'application/xml'
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
const parserError = xmlDoc.querySelector ? xmlDoc.querySelector('parsererror') :
|
|
121
|
+
(xmlDoc.documentElement && xmlDoc.documentElement.nodeName === 'parsererror' ? xmlDoc.documentElement : null);
|
|
122
|
+
|
|
123
|
+
if (parserError) {
|
|
124
|
+
throw new Error(parserError.textContent || 'XML parsing error');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const rootElement = xmlDoc.documentElement;
|
|
128
|
+
const data = {};
|
|
129
|
+
data[rootElement.nodeName] = xmlDomToJson(rootElement);
|
|
130
|
+
return data;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Convert XML string to JSON object (Async)
|
|
135
|
+
* @param {string} xmlString
|
|
136
|
+
* @returns {Promise<Object>} JSON object
|
|
137
|
+
*/
|
|
138
|
+
export async function xmlToJson(xmlString) {
|
|
139
|
+
if (typeof DOMParser === 'undefined') {
|
|
140
|
+
try {
|
|
141
|
+
const { DOMParser: NodeDOMParser } = await import('xmldom');
|
|
142
|
+
global.DOMParser = NodeDOMParser;
|
|
143
|
+
} catch (e) { }
|
|
144
|
+
}
|
|
145
|
+
return xmlToJsonSync(xmlString);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Convert JSON object to XML string (Sync)
|
|
150
|
+
* @param {Object|string} data - JSON data or mixed text
|
|
151
|
+
* @returns {string} XML string
|
|
152
|
+
*/
|
|
153
|
+
export function jsonToXmlSync(data) {
|
|
154
|
+
if (typeof data === 'string') {
|
|
155
|
+
let convertedText = data;
|
|
156
|
+
let iterationCount = 0;
|
|
157
|
+
const maxIterations = 100;
|
|
158
|
+
let wasModified = false;
|
|
159
|
+
|
|
160
|
+
const firstExtract = extractJsonFromString(data);
|
|
161
|
+
if (firstExtract && firstExtract === data.trim()) {
|
|
162
|
+
try {
|
|
163
|
+
const obj = JSON.parse(firstExtract);
|
|
164
|
+
let xml = "";
|
|
165
|
+
for (const k in obj) xml += buildTag(k, obj[k]);
|
|
166
|
+
return xml;
|
|
167
|
+
} catch (e) { }
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
while (iterationCount < maxIterations) {
|
|
171
|
+
const jsonString = extractJsonFromString(convertedText);
|
|
172
|
+
if (!jsonString) break;
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
const jsonObject = JSON.parse(jsonString);
|
|
176
|
+
let xmlOutput = "";
|
|
177
|
+
for (const k in jsonObject) xmlOutput += buildTag(k, jsonObject[k]);
|
|
178
|
+
convertedText = convertedText.replace(jsonString, xmlOutput);
|
|
179
|
+
wasModified = true;
|
|
180
|
+
iterationCount++;
|
|
181
|
+
} catch (e) { break; }
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (wasModified) return convertedText;
|
|
185
|
+
|
|
186
|
+
try {
|
|
187
|
+
const obj = JSON.parse(data);
|
|
188
|
+
let xml = "";
|
|
189
|
+
for (const k in obj) xml += buildTag(k, obj[k]);
|
|
190
|
+
return xml;
|
|
191
|
+
} catch (e) { return data; }
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
let xml = "";
|
|
195
|
+
for (const k in data) xml += buildTag(k, data[k]);
|
|
196
|
+
return xml;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Convert JSON object to XML string (Async)
|
|
201
|
+
* @param {Object|string} jsonObject
|
|
202
|
+
* @returns {Promise<string>} XML string
|
|
203
|
+
*/
|
|
204
|
+
export async function jsonToXml(jsonObject) {
|
|
205
|
+
return jsonToXmlSync(jsonObject);
|
|
206
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* YAML <-> JSON Converter (for JsonConverter)
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import yaml from 'js-yaml';
|
|
6
|
+
import { extractJsonFromString } from '../utils.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Convert YAML string to JSON object (Sync)
|
|
10
|
+
* @param {string} yamlString
|
|
11
|
+
* @returns {Object} JSON object
|
|
12
|
+
*/
|
|
13
|
+
export function yamlToJsonSync(yamlString) {
|
|
14
|
+
if (!yamlString || typeof yamlString !== 'string') {
|
|
15
|
+
throw new Error('Input must be a non-empty string');
|
|
16
|
+
}
|
|
17
|
+
const result = yaml.load(yamlString);
|
|
18
|
+
if (result === undefined) return null;
|
|
19
|
+
return result;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Convert YAML string to JSON object (Async)
|
|
24
|
+
* @param {string} yamlString
|
|
25
|
+
* @returns {Promise<Object>} JSON object
|
|
26
|
+
*/
|
|
27
|
+
export async function yamlToJson(yamlString) {
|
|
28
|
+
return yamlToJsonSync(yamlString);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Convert JSON object to YAML string (Sync)
|
|
34
|
+
* @param {Object} jsonObject
|
|
35
|
+
* @returns {string} YAML string
|
|
36
|
+
*/
|
|
37
|
+
export function jsonToYamlSync(data) {
|
|
38
|
+
if (typeof data === 'string') {
|
|
39
|
+
let convertedText = data;
|
|
40
|
+
let iterationCount = 0;
|
|
41
|
+
const maxIterations = 100;
|
|
42
|
+
let wasModified = false;
|
|
43
|
+
|
|
44
|
+
const firstExtract = extractJsonFromString(data);
|
|
45
|
+
if (firstExtract && firstExtract === data.trim()) {
|
|
46
|
+
try {
|
|
47
|
+
const obj = JSON.parse(firstExtract);
|
|
48
|
+
return yaml.dump(obj);
|
|
49
|
+
} catch (e) { }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
while (iterationCount < maxIterations) {
|
|
53
|
+
const jsonString = extractJsonFromString(convertedText);
|
|
54
|
+
if (!jsonString) break;
|
|
55
|
+
try {
|
|
56
|
+
const jsonObject = JSON.parse(jsonString);
|
|
57
|
+
const yamlOutput = yaml.dump(jsonObject).trim();
|
|
58
|
+
convertedText = convertedText.replace(jsonString, yamlOutput);
|
|
59
|
+
wasModified = true;
|
|
60
|
+
iterationCount++;
|
|
61
|
+
} catch (e) { break; }
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (wasModified) return convertedText;
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
const obj = JSON.parse(data);
|
|
68
|
+
return yaml.dump(obj);
|
|
69
|
+
} catch (e) { return data; }
|
|
70
|
+
}
|
|
71
|
+
return yaml.dump(data);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Convert JSON object to YAML string (Async)
|
|
76
|
+
* @param {Object} jsonObject
|
|
77
|
+
* @returns {Promise<string>} YAML string
|
|
78
|
+
*/
|
|
79
|
+
export async function jsonToYaml(jsonObject) {
|
|
80
|
+
return jsonToYamlSync(jsonObject);
|
|
81
|
+
}
|