toon-formatter 1.0.2 → 1.1.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/ENHANCEMENTS.md +124 -0
- package/README.md +398 -54
- package/package.json +1 -1
- package/src/csv.js +66 -24
- package/src/index.js +103 -22
- package/src/json.js +65 -25
- package/src/utils.js +155 -0
- package/src/validator.js +73 -11
- package/src/xml.js +86 -34
- package/src/yaml.js +27 -8
- package/test/basic.test.js +17 -17
- package/test/converters.test.js +9 -9
package/src/csv.js
CHANGED
|
@@ -3,19 +3,15 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import Papa from 'papaparse';
|
|
6
|
-
import {
|
|
6
|
+
import { jsonToToonSync, toonToJsonSync } from './json.js';
|
|
7
|
+
import { extractCsvFromString } from './utils.js';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
|
-
*
|
|
10
|
-
* @param {string} csvString
|
|
11
|
-
* @returns {Promise<string>}
|
|
12
|
-
* @throws {Error} If CSV is invalid
|
|
10
|
+
* Internal core function to convert pure CSV string to TOON (Async)
|
|
11
|
+
* @param {string} csvString
|
|
12
|
+
* @returns {Promise<string>}
|
|
13
13
|
*/
|
|
14
|
-
|
|
15
|
-
if (!csvString || typeof csvString !== 'string') {
|
|
16
|
-
throw new Error('Input must be a non-empty string');
|
|
17
|
-
}
|
|
18
|
-
|
|
14
|
+
function parseCsvToToon(csvString) {
|
|
19
15
|
return new Promise((resolve, reject) => {
|
|
20
16
|
Papa.parse(csvString, {
|
|
21
17
|
header: true,
|
|
@@ -28,7 +24,7 @@ export function csvToToon(csvString) {
|
|
|
28
24
|
throw new Error("CSV parsing failed — cannot convert.");
|
|
29
25
|
}
|
|
30
26
|
|
|
31
|
-
const toonString =
|
|
27
|
+
const toonString = jsonToToonSync(jsonObject);
|
|
32
28
|
resolve(toonString);
|
|
33
29
|
} catch (error) {
|
|
34
30
|
reject(error);
|
|
@@ -42,16 +38,11 @@ export function csvToToon(csvString) {
|
|
|
42
38
|
}
|
|
43
39
|
|
|
44
40
|
/**
|
|
45
|
-
*
|
|
46
|
-
* @param {string} csvString
|
|
47
|
-
* @returns {string}
|
|
48
|
-
* @throws {Error} If CSV is invalid
|
|
41
|
+
* Internal core function to convert pure CSV string to TOON (Sync)
|
|
42
|
+
* @param {string} csvString
|
|
43
|
+
* @returns {string}
|
|
49
44
|
*/
|
|
50
|
-
|
|
51
|
-
if (!csvString || typeof csvString !== 'string') {
|
|
52
|
-
throw new Error('Input must be a non-empty string');
|
|
53
|
-
}
|
|
54
|
-
|
|
45
|
+
function parseCsvToToonSync(csvString) {
|
|
55
46
|
const results = Papa.parse(csvString, {
|
|
56
47
|
header: true,
|
|
57
48
|
dynamicTyping: true,
|
|
@@ -67,21 +58,63 @@ export function csvToToonSync(csvString) {
|
|
|
67
58
|
throw new Error("CSV parsing failed — cannot convert.");
|
|
68
59
|
}
|
|
69
60
|
|
|
70
|
-
return
|
|
61
|
+
return jsonToToonSync(jsonObject);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Converts CSV (or mixed text with CSV) to TOON format (Async)
|
|
66
|
+
* @param {string} csvString - CSV formatted string or mixed text
|
|
67
|
+
* @returns {Promise<string>} TOON formatted string
|
|
68
|
+
* @throws {Error} If CSV is invalid
|
|
69
|
+
*/
|
|
70
|
+
export async function csvToToon(csvString) {
|
|
71
|
+
return csvToToonSync(csvString);
|
|
71
72
|
}
|
|
72
73
|
|
|
73
74
|
/**
|
|
74
|
-
* Converts
|
|
75
|
+
* Converts CSV (or mixed text with CSV) to TOON format (synchronous version)
|
|
76
|
+
* @param {string} csvString - CSV formatted string or mixed text
|
|
77
|
+
* @returns {string} TOON formatted string
|
|
78
|
+
* @throws {Error} If CSV is invalid
|
|
79
|
+
*/
|
|
80
|
+
export function csvToToonSync(csvString) {
|
|
81
|
+
if (!csvString || typeof csvString !== 'string') {
|
|
82
|
+
throw new Error('Input must be a non-empty string');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
let convertedText = csvString;
|
|
86
|
+
let iterationCount = 0;
|
|
87
|
+
const maxIterations = 100;
|
|
88
|
+
|
|
89
|
+
while (iterationCount < maxIterations) {
|
|
90
|
+
const csvBlock = extractCsvFromString(convertedText);
|
|
91
|
+
if (!csvBlock) break;
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
const toonString = parseCsvToToonSync(csvBlock);
|
|
95
|
+
const toonOutput = toonString.trim();
|
|
96
|
+
convertedText = convertedText.replace(csvBlock, toonOutput);
|
|
97
|
+
iterationCount++;
|
|
98
|
+
} catch (e) {
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return convertedText;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Converts TOON to CSV format (Synchronous)
|
|
75
108
|
* @param {string} toonString - TOON formatted string
|
|
76
109
|
* @returns {string} CSV formatted string
|
|
77
110
|
* @throws {Error} If TOON is invalid
|
|
78
111
|
*/
|
|
79
|
-
export function
|
|
112
|
+
export function toonToCsvSync(toonString) {
|
|
80
113
|
if (!toonString || typeof toonString !== 'string') {
|
|
81
114
|
throw new Error('Input must be a non-empty string');
|
|
82
115
|
}
|
|
83
116
|
|
|
84
|
-
const jsonObject =
|
|
117
|
+
const jsonObject = toonToJsonSync(toonString);
|
|
85
118
|
|
|
86
119
|
const csvString = Papa.unparse(jsonObject, {
|
|
87
120
|
header: true,
|
|
@@ -90,3 +123,12 @@ export function toonToCsv(toonString) {
|
|
|
90
123
|
|
|
91
124
|
return csvString;
|
|
92
125
|
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Converts TOON to CSV format (Async)
|
|
129
|
+
* @param {string} toonString - TOON formatted string
|
|
130
|
+
* @returns {Promise<string>} CSV formatted string
|
|
131
|
+
*/
|
|
132
|
+
export async function toonToCsv(toonString) {
|
|
133
|
+
return toonToCsvSync(toonString);
|
|
134
|
+
}
|
package/src/index.js
CHANGED
|
@@ -6,24 +6,33 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Reduce LLM token costs by up to 40% using TOON format.
|
|
8
8
|
*
|
|
9
|
-
* @module toon-
|
|
9
|
+
* @module toon-formatter
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import { jsonToToon, toonToJson } from './json.js';
|
|
13
|
-
import { yamlToToon, toonToYaml } from './yaml.js';
|
|
14
|
-
import { xmlToToon, toonToXml } from './xml.js';
|
|
15
|
-
import { csvToToon,
|
|
16
|
-
import { validateToonString } from './validator.js';
|
|
17
|
-
import {
|
|
12
|
+
import { jsonToToonSync, jsonToToon, toonToJsonSync, toonToJson } from './json.js';
|
|
13
|
+
import { yamlToToonSync, yamlToToon, toonToYamlSync, toonToYaml } from './yaml.js';
|
|
14
|
+
import { xmlToToonSync, xmlToToon, toonToXmlSync, toonToXml } from './xml.js';
|
|
15
|
+
import { csvToToonSync, csvToToon, toonToCsvSync, toonToCsv } from './csv.js';
|
|
16
|
+
import { validateToonString, validateToonStringSync } from './validator.js';
|
|
17
|
+
import {
|
|
18
|
+
encodeXmlReservedChars,
|
|
19
|
+
splitByDelimiter,
|
|
20
|
+
parseValue,
|
|
21
|
+
formatValue,
|
|
22
|
+
extractJsonFromString,
|
|
23
|
+
extractXmlFromString,
|
|
24
|
+
extractCsvFromString
|
|
25
|
+
} from './utils.js';
|
|
18
26
|
|
|
19
27
|
// Exports
|
|
20
28
|
export {
|
|
21
|
-
jsonToToon, toonToJson,
|
|
22
|
-
yamlToToon, toonToYaml,
|
|
23
|
-
xmlToToon, toonToXml,
|
|
24
|
-
csvToToon,
|
|
25
|
-
validateToonString,
|
|
26
|
-
encodeXmlReservedChars, splitByDelimiter, parseValue, formatValue
|
|
29
|
+
jsonToToonSync, jsonToToon, toonToJsonSync, toonToJson,
|
|
30
|
+
yamlToToonSync, yamlToToon, toonToYamlSync, toonToYaml,
|
|
31
|
+
xmlToToonSync, xmlToToon, toonToXmlSync, toonToXml,
|
|
32
|
+
csvToToonSync, csvToToon, toonToCsvSync, toonToCsv,
|
|
33
|
+
validateToonString, validateToonStringSync,
|
|
34
|
+
encodeXmlReservedChars, splitByDelimiter, parseValue, formatValue,
|
|
35
|
+
extractJsonFromString, extractXmlFromString, extractCsvFromString
|
|
27
36
|
};
|
|
28
37
|
|
|
29
38
|
/**
|
|
@@ -31,56 +40,110 @@ export {
|
|
|
31
40
|
*/
|
|
32
41
|
export class ToonConverter {
|
|
33
42
|
/**
|
|
34
|
-
* Convert JSON to TOON
|
|
43
|
+
* Convert JSON to TOON (Sync)
|
|
35
44
|
* @param {*} jsonData - JSON data (object, array, or primitive)
|
|
36
45
|
* @returns {string} TOON formatted string
|
|
37
46
|
*/
|
|
38
47
|
static fromJson(jsonData) {
|
|
48
|
+
return jsonToToonSync(jsonData);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Convert JSON to TOON (Async)
|
|
53
|
+
* @param {*} jsonData - JSON data
|
|
54
|
+
* @returns {Promise<string>} TOON formatted string
|
|
55
|
+
*/
|
|
56
|
+
static async fromJsonAsync(jsonData) {
|
|
39
57
|
return jsonToToon(jsonData);
|
|
40
58
|
}
|
|
41
59
|
|
|
42
60
|
/**
|
|
43
|
-
* Convert TOON to JSON
|
|
61
|
+
* Convert TOON to JSON (Sync)
|
|
44
62
|
* @param {string} toonString - TOON formatted string
|
|
45
63
|
* @returns {*} Parsed JSON data
|
|
46
64
|
*/
|
|
47
65
|
static toJson(toonString) {
|
|
66
|
+
return toonToJsonSync(toonString);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Convert TOON to JSON (Async)
|
|
71
|
+
* @param {string} toonString - TOON formatted string
|
|
72
|
+
* @returns {Promise<*>} Parsed JSON data
|
|
73
|
+
*/
|
|
74
|
+
static async toJsonAsync(toonString) {
|
|
48
75
|
return toonToJson(toonString);
|
|
49
76
|
}
|
|
50
77
|
|
|
51
78
|
/**
|
|
52
|
-
* Convert YAML to TOON
|
|
79
|
+
* Convert YAML to TOON (Sync)
|
|
53
80
|
* @param {string} yamlString - YAML formatted string
|
|
54
81
|
* @returns {string} TOON formatted string
|
|
55
82
|
*/
|
|
56
83
|
static fromYaml(yamlString) {
|
|
84
|
+
return yamlToToonSync(yamlString);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Convert YAML to TOON (Async)
|
|
89
|
+
* @param {string} yamlString - YAML formatted string
|
|
90
|
+
* @returns {Promise<string>} TOON formatted string
|
|
91
|
+
*/
|
|
92
|
+
static async fromYamlAsync(yamlString) {
|
|
57
93
|
return yamlToToon(yamlString);
|
|
58
94
|
}
|
|
59
95
|
|
|
60
96
|
/**
|
|
61
|
-
* Convert TOON to YAML
|
|
97
|
+
* Convert TOON to YAML (Sync)
|
|
62
98
|
* @param {string} toonString - TOON formatted string
|
|
63
99
|
* @returns {string} YAML formatted string
|
|
64
100
|
*/
|
|
65
101
|
static toYaml(toonString) {
|
|
102
|
+
return toonToYamlSync(toonString);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Convert TOON to YAML (Async)
|
|
107
|
+
* @param {string} toonString - TOON formatted string
|
|
108
|
+
* @returns {Promise<string>} YAML formatted string
|
|
109
|
+
*/
|
|
110
|
+
static async toYamlAsync(toonString) {
|
|
66
111
|
return toonToYaml(toonString);
|
|
67
112
|
}
|
|
68
113
|
|
|
114
|
+
/**
|
|
115
|
+
* Convert XML to TOON (Sync)
|
|
116
|
+
* @param {string} xmlString - XML formatted string
|
|
117
|
+
* @returns {string} TOON formatted string
|
|
118
|
+
*/
|
|
119
|
+
static fromXml(xmlString) {
|
|
120
|
+
return xmlToToonSync(xmlString);
|
|
121
|
+
}
|
|
122
|
+
|
|
69
123
|
/**
|
|
70
124
|
* Convert XML to TOON (Async)
|
|
71
125
|
* @param {string} xmlString - XML formatted string
|
|
72
126
|
* @returns {Promise<string>} TOON formatted string
|
|
73
127
|
*/
|
|
74
|
-
static async
|
|
128
|
+
static async fromXmlAsync(xmlString) {
|
|
75
129
|
return xmlToToon(xmlString);
|
|
76
130
|
}
|
|
77
131
|
|
|
78
132
|
/**
|
|
79
|
-
* Convert TOON to XML
|
|
133
|
+
* Convert TOON to XML (Sync)
|
|
80
134
|
* @param {string} toonString - TOON formatted string
|
|
81
135
|
* @returns {string} XML formatted string
|
|
82
136
|
*/
|
|
83
137
|
static toXml(toonString) {
|
|
138
|
+
return toonToXmlSync(toonString);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Convert TOON to XML (Async)
|
|
143
|
+
* @param {string} toonString - TOON formatted string
|
|
144
|
+
* @returns {Promise<string>} XML formatted string
|
|
145
|
+
*/
|
|
146
|
+
static async toXmlAsync(toonString) {
|
|
84
147
|
return toonToXml(toonString);
|
|
85
148
|
}
|
|
86
149
|
|
|
@@ -89,7 +152,7 @@ export class ToonConverter {
|
|
|
89
152
|
* @param {string} csvString - CSV formatted string
|
|
90
153
|
* @returns {Promise<string>} TOON formatted string
|
|
91
154
|
*/
|
|
92
|
-
static async
|
|
155
|
+
static async fromCsvAsync(csvString) {
|
|
93
156
|
return csvToToon(csvString);
|
|
94
157
|
}
|
|
95
158
|
|
|
@@ -98,16 +161,25 @@ export class ToonConverter {
|
|
|
98
161
|
* @param {string} csvString - CSV formatted string
|
|
99
162
|
* @returns {string} TOON formatted string
|
|
100
163
|
*/
|
|
101
|
-
static
|
|
164
|
+
static fromCsv(csvString) {
|
|
102
165
|
return csvToToonSync(csvString);
|
|
103
166
|
}
|
|
104
167
|
|
|
105
168
|
/**
|
|
106
|
-
* Convert TOON to CSV
|
|
169
|
+
* Convert TOON to CSV (Sync)
|
|
107
170
|
* @param {string} toonString - TOON formatted string
|
|
108
171
|
* @returns {string} CSV formatted string
|
|
109
172
|
*/
|
|
110
173
|
static toCsv(toonString) {
|
|
174
|
+
return toonToCsvSync(toonString);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Convert TOON to CSV (Async)
|
|
179
|
+
* @param {string} toonString - TOON formatted string
|
|
180
|
+
* @returns {Promise<string>} CSV formatted string
|
|
181
|
+
*/
|
|
182
|
+
static async toCsvAsync(toonString) {
|
|
111
183
|
return toonToCsv(toonString);
|
|
112
184
|
}
|
|
113
185
|
|
|
@@ -117,6 +189,15 @@ export class ToonConverter {
|
|
|
117
189
|
* @returns {{isValid: boolean, error: string|null}} Validation result
|
|
118
190
|
*/
|
|
119
191
|
static validate(toonString) {
|
|
192
|
+
return validateToonStringSync(toonString);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Validate a TOON string (Async)
|
|
197
|
+
* @param {string} toonString - TOON formatted string
|
|
198
|
+
* @returns {{isValid: boolean, error: string|null}} Validation result
|
|
199
|
+
*/
|
|
200
|
+
static async validateAsync(toonString) {
|
|
120
201
|
return validateToonString(toonString);
|
|
121
202
|
}
|
|
122
203
|
}
|
package/src/json.js
CHANGED
|
@@ -2,16 +2,41 @@
|
|
|
2
2
|
* JSON ↔ TOON Converter
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { formatValue, parseValue, splitByDelimiter } from './utils.js';
|
|
5
|
+
import { formatValue, parseValue, splitByDelimiter, extractJsonFromString } from './utils.js';
|
|
6
|
+
import { validateToonStringSync } from './validator.js';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
|
-
* Converts JSON to TOON format
|
|
9
|
+
* Converts JSON to TOON format (Synchronous)
|
|
9
10
|
* @param {*} data - JSON data to convert
|
|
10
11
|
* @param {string} key - Current key name (for recursion)
|
|
11
12
|
* @param {number} depth - Current indentation depth
|
|
12
13
|
* @returns {string} TOON formatted string
|
|
13
14
|
*/
|
|
14
|
-
export function
|
|
15
|
+
export function jsonToToonSync(data, key = '', depth = 0) {
|
|
16
|
+
// Handle String Input (Potential JSON string or Mixed Text)
|
|
17
|
+
if (typeof data === 'string' && key === '' && depth === 0) {
|
|
18
|
+
let convertedText = data;
|
|
19
|
+
let iterationCount = 0;
|
|
20
|
+
const maxIterations = 100;
|
|
21
|
+
|
|
22
|
+
while (iterationCount < maxIterations) {
|
|
23
|
+
const jsonString = extractJsonFromString(convertedText);
|
|
24
|
+
if (!jsonString) break;
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
const jsonObject = JSON.parse(jsonString);
|
|
28
|
+
// Recursively call jsonToToonSync with the object
|
|
29
|
+
const toonString = jsonToToonSync(jsonObject);
|
|
30
|
+
const toonOutput = toonString.trim();
|
|
31
|
+
convertedText = convertedText.replace(jsonString, toonOutput);
|
|
32
|
+
iterationCount++;
|
|
33
|
+
} catch (e) {
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return convertedText;
|
|
38
|
+
}
|
|
39
|
+
|
|
15
40
|
const indent = ' '.repeat(depth);
|
|
16
41
|
const nextIndent = ' '.repeat(depth + 1);
|
|
17
42
|
|
|
@@ -61,44 +86,50 @@ export function jsonToToon(data, key = '', depth = 0) {
|
|
|
61
86
|
return lines.join('\n');
|
|
62
87
|
}
|
|
63
88
|
|
|
64
|
-
// ----
|
|
89
|
+
// ---- STANDARD ARRAY OF OBJECTS ----
|
|
65
90
|
const lines = [];
|
|
66
91
|
lines.push(`${indent}${key}[${length}]:`);
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
lines.push(`${nextIndent}-`); // item marker
|
|
70
|
-
for (const f of fields) {
|
|
71
|
-
const child = row[f];
|
|
72
|
-
const block = jsonToToon(child, f, depth + 2);
|
|
73
|
-
lines.push(block);
|
|
74
|
-
}
|
|
92
|
+
data.forEach(item => {
|
|
93
|
+
lines.push(jsonToToonSync(item, '', depth + 1));
|
|
75
94
|
});
|
|
76
|
-
|
|
77
95
|
return lines.join('\n');
|
|
78
96
|
}
|
|
79
97
|
|
|
80
98
|
// ---- Object ----
|
|
81
99
|
const lines = [];
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
for (const childKey in data) {
|
|
86
|
-
if (Object.prototype.hasOwnProperty.call(data, childKey)) {
|
|
87
|
-
const child = data[childKey];
|
|
88
|
-
const block = jsonToToon(child, childKey, depth + 1);
|
|
89
|
-
lines.push(block);
|
|
90
|
-
}
|
|
100
|
+
if (key) {
|
|
101
|
+
lines.push(`${indent}${key}:`);
|
|
91
102
|
}
|
|
92
103
|
|
|
104
|
+
Object.keys(data).forEach(k => {
|
|
105
|
+
lines.push(jsonToToonSync(data[k], k, key ? depth + 1 : depth));
|
|
106
|
+
});
|
|
107
|
+
|
|
93
108
|
return lines.join('\n');
|
|
94
109
|
}
|
|
95
110
|
|
|
96
111
|
/**
|
|
97
|
-
* Converts
|
|
112
|
+
* Converts JSON to TOON format (Async)
|
|
113
|
+
* @param {*} data - JSON data to convert
|
|
114
|
+
* @returns {Promise<string>} TOON formatted string
|
|
115
|
+
*/
|
|
116
|
+
export async function jsonToToon(data) {
|
|
117
|
+
return jsonToToonSync(data);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Converts TOON to JSON format (Synchronous)
|
|
98
122
|
* @param {string} toonString - TOON formatted string
|
|
99
|
-
* @returns {
|
|
123
|
+
* @returns {Object} JSON object
|
|
124
|
+
* @throws {Error} If TOON string is invalid
|
|
100
125
|
*/
|
|
101
|
-
export function
|
|
126
|
+
export function toonToJsonSync(toonString) {
|
|
127
|
+
// Validate TOON string before conversion
|
|
128
|
+
const validationStatus = validateToonStringSync(toonString);
|
|
129
|
+
if (!validationStatus.isValid) {
|
|
130
|
+
throw new Error(`Invalid TOON: ${validationStatus.error}`);
|
|
131
|
+
}
|
|
132
|
+
|
|
102
133
|
const lines = toonString.split('\n');
|
|
103
134
|
let root = {};
|
|
104
135
|
let stack = [];
|
|
@@ -295,3 +326,12 @@ export function toonToJson(toonString) {
|
|
|
295
326
|
|
|
296
327
|
return root;
|
|
297
328
|
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Converts TOON to JSON format (Async)
|
|
332
|
+
* @param {string} toonString - TOON formatted string
|
|
333
|
+
* @returns {Promise<Object>} Parsed JSON data
|
|
334
|
+
*/
|
|
335
|
+
export async function toonToJson(toonString) {
|
|
336
|
+
return toonToJsonSync(toonString);
|
|
337
|
+
}
|
package/src/utils.js
CHANGED
|
@@ -79,3 +79,158 @@ export function formatValue(v) {
|
|
|
79
79
|
if (typeof v === "string") return `"${v.replace(/"/g, '\\"')}"`;
|
|
80
80
|
return v; // number, boolean
|
|
81
81
|
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Extracts JSON from mixed text
|
|
85
|
+
* @param {string} text - Text containing JSON
|
|
86
|
+
* @returns {string|null} Extracted JSON string or null
|
|
87
|
+
*/
|
|
88
|
+
export function extractJsonFromString(text) {
|
|
89
|
+
if (!text || typeof text !== 'string') return null;
|
|
90
|
+
|
|
91
|
+
let startIndex = -1;
|
|
92
|
+
|
|
93
|
+
// Find first potential start
|
|
94
|
+
for (let i = 0; i < text.length; i++) {
|
|
95
|
+
if (text[i] === '{' || text[i] === '[') {
|
|
96
|
+
// Ignore if preceded by non-whitespace (e.g. key[2])
|
|
97
|
+
if (i > 0 && /\S/.test(text[i - 1])) {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
startIndex = i;
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (startIndex === -1) return null;
|
|
106
|
+
|
|
107
|
+
let balance = 0;
|
|
108
|
+
let inQuote = false;
|
|
109
|
+
let escape = false;
|
|
110
|
+
|
|
111
|
+
for (let i = startIndex; i < text.length; i++) {
|
|
112
|
+
const char = text[i];
|
|
113
|
+
|
|
114
|
+
if (escape) {
|
|
115
|
+
escape = false;
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (char === '\\') {
|
|
120
|
+
escape = true;
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (char === '"') {
|
|
125
|
+
inQuote = !inQuote;
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (!inQuote) {
|
|
130
|
+
if (char === '{' || char === '[') {
|
|
131
|
+
balance++;
|
|
132
|
+
} else if (char === '}' || char === ']') {
|
|
133
|
+
balance--;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (balance === 0) {
|
|
137
|
+
// Potential end
|
|
138
|
+
const candidate = text.substring(startIndex, i + 1);
|
|
139
|
+
try {
|
|
140
|
+
JSON.parse(candidate);
|
|
141
|
+
return candidate;
|
|
142
|
+
} catch (e) {
|
|
143
|
+
// Continue scanning if parse fails
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Extracts XML from mixed text
|
|
154
|
+
* @param {string} text - Text containing XML
|
|
155
|
+
* @returns {string|null} Extracted XML string or null
|
|
156
|
+
*/
|
|
157
|
+
export function extractXmlFromString(text) {
|
|
158
|
+
if (!text || typeof text !== 'string') return null;
|
|
159
|
+
|
|
160
|
+
// Find first start tag
|
|
161
|
+
const startTagRegex = /<([a-zA-Z0-9_:-]+)(?:\s[^>]*)?\>/;
|
|
162
|
+
const match = text.match(startTagRegex);
|
|
163
|
+
|
|
164
|
+
if (!match) return null;
|
|
165
|
+
|
|
166
|
+
const startIndex = match.index;
|
|
167
|
+
const rootTagName = match[1];
|
|
168
|
+
|
|
169
|
+
const fullMatch = match[0];
|
|
170
|
+
if (fullMatch.endsWith('/>')) {
|
|
171
|
+
return fullMatch;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
let balance = 0;
|
|
175
|
+
|
|
176
|
+
const tagRegex = /<\/?([a-zA-Z0-9_:-]+)(?:\s[^>]*)?\/?\>/g;
|
|
177
|
+
tagRegex.lastIndex = startIndex;
|
|
178
|
+
|
|
179
|
+
let matchTag;
|
|
180
|
+
while ((matchTag = tagRegex.exec(text)) !== null) {
|
|
181
|
+
const fullTag = matchTag[0];
|
|
182
|
+
const tagName = matchTag[1];
|
|
183
|
+
|
|
184
|
+
if (tagName !== rootTagName) continue;
|
|
185
|
+
|
|
186
|
+
if (fullTag.startsWith('</')) {
|
|
187
|
+
balance--;
|
|
188
|
+
} else if (!fullTag.endsWith('/>')) {
|
|
189
|
+
balance++;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (balance === 0) {
|
|
193
|
+
return text.substring(startIndex, matchTag.index + fullTag.length);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Extracts CSV from mixed text
|
|
202
|
+
* @param {string} text - Text containing CSV
|
|
203
|
+
* @returns {string|null} Extracted CSV string or null
|
|
204
|
+
*/
|
|
205
|
+
export function extractCsvFromString(text) {
|
|
206
|
+
if (!text || typeof text !== 'string') return null;
|
|
207
|
+
|
|
208
|
+
const lines = text.split('\n');
|
|
209
|
+
let startLineIndex = -1;
|
|
210
|
+
|
|
211
|
+
for (let i = 0; i < lines.length; i++) {
|
|
212
|
+
const line = lines[i];
|
|
213
|
+
const commaCount = (line.match(/,/g) || []).length;
|
|
214
|
+
if (commaCount > 0) {
|
|
215
|
+
startLineIndex = i;
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (startLineIndex === -1) return null;
|
|
221
|
+
|
|
222
|
+
const resultLines = [];
|
|
223
|
+
|
|
224
|
+
for (let i = startLineIndex; i < lines.length; i++) {
|
|
225
|
+
const line = lines[i];
|
|
226
|
+
if (line.trim() === '') continue;
|
|
227
|
+
|
|
228
|
+
const commaCount = (line.match(/,/g) || []).length;
|
|
229
|
+
if (commaCount === 0) {
|
|
230
|
+
break;
|
|
231
|
+
}
|
|
232
|
+
resultLines.push(line);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return resultLines.join('\n').trim();
|
|
236
|
+
}
|