toon-formatter 1.0.1 → 1.0.3

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/src/csv.js CHANGED
@@ -3,19 +3,15 @@
3
3
  */
4
4
 
5
5
  import Papa from 'papaparse';
6
- import { jsonToToon, toonToJson } from './json.js';
6
+ import { jsonToToonSync, toonToJsonSync } from './json.js';
7
+ import { extractCsvFromString } from './utils.js';
7
8
 
8
9
  /**
9
- * Converts CSV to TOON format
10
- * @param {string} csvString - CSV formatted string
11
- * @returns {Promise<string>} TOON formatted 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
- export function csvToToon(csvString) {
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 = jsonToToon(jsonObject);
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
- * Converts CSV to TOON format (synchronous version)
46
- * @param {string} csvString - CSV formatted string
47
- * @returns {string} TOON formatted 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
- export function csvToToonSync(csvString) {
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 jsonToToon(jsonObject);
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 TOON to CSV format
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 toonToCsv(toonString) {
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 = toonToJson(toonString);
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-converter
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, csvToToonSync, toonToCsv } from './csv.js';
16
- import { validateToonString } from './validator.js';
17
- import { encodeXmlReservedChars, splitByDelimiter, parseValue, formatValue } from './utils.js';
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, csvToToonSync, toonToCsv,
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 fromXml(xmlString) {
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 fromCsv(csvString) {
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 fromCsvSync(csvString) {
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 jsonToToon(data, key = '', depth = 0) {
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
- // ---- YAML-STYLE ARRAY (nested objects present) ----
89
+ // ---- STANDARD ARRAY OF OBJECTS ----
65
90
  const lines = [];
66
91
  lines.push(`${indent}${key}[${length}]:`);
67
-
68
- data.forEach(row => {
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
- if (key) lines.push(`${indent}${key}:`);
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 TOON to JSON format
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 {*} Parsed JSON data
123
+ * @returns {Object} JSON object
124
+ * @throws {Error} If TOON string is invalid
100
125
  */
101
- export function toonToJson(toonString) {
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
+ }