toon-formatter 2.2.1 → 2.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.
@@ -7,6 +7,7 @@ import { yamlToJson, yamlToJsonSync, jsonToYaml, jsonToYamlSync } from './yaml.j
7
7
  import { xmlToJson, xmlToJsonSync, jsonToXml, jsonToXmlSync } from './xml.js';
8
8
  import { csvToJson, csvToJsonSync, jsonToCsv, jsonToCsvSync } from './csv.js';
9
9
  import { validateJsonString, validateJsonStringSync } from './validator.js';
10
+ import { dataManager, dataManagerAsync, extractJsonFromString, extractXmlFromString, extractCsvFromString } from '../utils.js';
10
11
 
11
12
  export class JsonConverter {
12
13
  /**
@@ -110,8 +111,9 @@ export class JsonConverter {
110
111
  */
111
112
  toToon(jsonData, options = {}) {
112
113
  const { conversionMode = 'no_encryption' } = options;
114
+ const optimizedConverterFn = dataManager(jsonToToonSync, extractJsonFromString);
113
115
  return this._convertWithEncryption(
114
- (data) => jsonToToonSync(data),
116
+ (data) => optimizedConverterFn(data),
115
117
  jsonData,
116
118
  conversionMode
117
119
  );
@@ -126,8 +128,9 @@ export class JsonConverter {
126
128
  */
127
129
  async toToonAsync(jsonData, options = {}) {
128
130
  const { conversionMode = 'no_encryption' } = options;
131
+ const optimizedConverterFn = dataManagerAsync(jsonToToon, extractJsonFromString);
129
132
  return this._convertWithEncryptionAsync(
130
- async (data) => jsonToToon(data),
133
+ async (data) => optimizedConverterFn(data),
131
134
  jsonData,
132
135
  conversionMode
133
136
  );
@@ -178,8 +181,9 @@ export class JsonConverter {
178
181
  */
179
182
  toYaml(jsonData, options = {}) {
180
183
  const { conversionMode = 'no_encryption' } = options;
184
+ const optimizedConverterFn = dataManager(jsonToYamlSync, extractJsonFromString);
181
185
  return this._convertWithEncryption(
182
- (data) => jsonToYamlSync(data),
186
+ (data) => optimizedConverterFn(data),
183
187
  jsonData,
184
188
  conversionMode
185
189
  );
@@ -194,8 +198,9 @@ export class JsonConverter {
194
198
  */
195
199
  async toYamlAsync(jsonData, options = {}) {
196
200
  const { conversionMode = 'no_encryption' } = options;
201
+ const optimizedConverterFn = dataManagerAsync(jsonToYaml, extractJsonFromString);
197
202
  return this._convertWithEncryptionAsync(
198
- async (data) => jsonToYaml(data),
203
+ async (data) => optimizedConverterFn(data),
199
204
  jsonData,
200
205
  conversionMode
201
206
  );
@@ -212,8 +217,9 @@ export class JsonConverter {
212
217
  */
213
218
  fromXml(xmlString, options = {}) {
214
219
  const { conversionMode = 'no_encryption' } = options;
220
+ const optimizedConverterFn = dataManager(xmlToJsonSync, extractXmlFromString);
215
221
  return this._convertWithEncryption(
216
- (data) => xmlToJsonSync(data),
222
+ (data) => optimizedConverterFn(data),
217
223
  xmlString,
218
224
  conversionMode
219
225
  );
@@ -228,8 +234,9 @@ export class JsonConverter {
228
234
  */
229
235
  async fromXmlAsync(xmlString, options = {}) {
230
236
  const { conversionMode = 'no_encryption' } = options;
237
+ const optimizedConverterFn = dataManagerAsync(xmlToJson, extractXmlFromString);
231
238
  return this._convertWithEncryptionAsync(
232
- async (data) => xmlToJson(data),
239
+ async (data) => optimizedConverterFn(data),
233
240
  xmlString,
234
241
  conversionMode
235
242
  );
@@ -244,8 +251,9 @@ export class JsonConverter {
244
251
  */
245
252
  toXml(jsonData, options = {}) {
246
253
  const { conversionMode = 'no_encryption' } = options;
254
+ const optimizedConverterFn = dataManager(jsonToXmlSync, extractJsonFromString);
247
255
  return this._convertWithEncryption(
248
- (data) => jsonToXmlSync(data),
256
+ (data) => optimizedConverterFn(data),
249
257
  jsonData,
250
258
  conversionMode
251
259
  );
@@ -260,8 +268,9 @@ export class JsonConverter {
260
268
  */
261
269
  async toXmlAsync(jsonData, options = {}) {
262
270
  const { conversionMode = 'no_encryption' } = options;
271
+ const optimizedConverterFn = dataManagerAsync(jsonToXml, extractJsonFromString);
263
272
  return this._convertWithEncryptionAsync(
264
- async (data) => jsonToXml(data),
273
+ async (data) => optimizedConverterFn(data),
265
274
  jsonData,
266
275
  conversionMode
267
276
  );
@@ -278,8 +287,9 @@ export class JsonConverter {
278
287
  */
279
288
  fromCsv(csvString, options = {}) {
280
289
  const { conversionMode = 'no_encryption' } = options;
290
+ const optimizedConverterFn = dataManager(csvToJsonSync, extractCsvFromString);
281
291
  return this._convertWithEncryption(
282
- (data) => csvToJsonSync(data),
292
+ (data) => optimizedConverterFn(data),
283
293
  csvString,
284
294
  conversionMode
285
295
  );
@@ -294,8 +304,9 @@ export class JsonConverter {
294
304
  */
295
305
  async fromCsvAsync(csvString, options = {}) {
296
306
  const { conversionMode = 'no_encryption' } = options;
307
+ const optimizedConverterFn = dataManagerAsync(csvToJson, extractCsvFromString);
297
308
  return this._convertWithEncryptionAsync(
298
- async (data) => csvToJson(data),
309
+ async (data) => optimizedConverterFn(data),
299
310
  csvString,
300
311
  conversionMode
301
312
  );
@@ -310,8 +321,9 @@ export class JsonConverter {
310
321
  */
311
322
  toCsv(jsonData, options = {}) {
312
323
  const { conversionMode = 'no_encryption' } = options;
324
+ const optimizedConverterFn = dataManager(jsonToCsvSync, extractJsonFromString);
313
325
  return this._convertWithEncryption(
314
- (data) => jsonToCsvSync(data),
326
+ (data) => optimizedConverterFn(data),
315
327
  jsonData,
316
328
  conversionMode
317
329
  );
@@ -326,8 +338,9 @@ export class JsonConverter {
326
338
  */
327
339
  async toCsvAsync(jsonData, options = {}) {
328
340
  const { conversionMode = 'no_encryption' } = options;
341
+ const optimizedConverterFn = dataManagerAsync(jsonToCsv, extractJsonFromString);
329
342
  return this._convertWithEncryptionAsync(
330
- async (data) => jsonToCsv(data),
343
+ async (data) => optimizedConverterFn(data),
331
344
  jsonData,
332
345
  conversionMode
333
346
  );
@@ -383,7 +396,8 @@ export class JsonConverter {
383
396
  * @returns {string} TOON formatted string
384
397
  */
385
398
  static toToon(jsonData) {
386
- return jsonToToonSync(jsonData);
399
+ const optimizedConverterFn = dataManager(jsonToToonSync, extractJsonFromString);
400
+ return optimizedConverterFn(jsonData);
387
401
  }
388
402
 
389
403
  /**
@@ -392,7 +406,8 @@ export class JsonConverter {
392
406
  * @returns {Promise<string>} TOON formatted string
393
407
  */
394
408
  static async toToonAsync(jsonData) {
395
- return jsonToToon(jsonData);
409
+ const optimizedConverterFn = dataManagerAsync(jsonToToon, extractJsonFromString);
410
+ return optimizedConverterFn(jsonData);
396
411
  }
397
412
 
398
413
  /**
@@ -421,7 +436,8 @@ export class JsonConverter {
421
436
  * @returns {string} YAML formatted string
422
437
  */
423
438
  static toYaml(jsonData) {
424
- return jsonToYamlSync(jsonData);
439
+ const optimizedConverterFn = dataManager(jsonToYamlSync, extractJsonFromString);
440
+ return optimizedConverterFn(jsonData);
425
441
  }
426
442
 
427
443
  /**
@@ -430,7 +446,8 @@ export class JsonConverter {
430
446
  * @returns {Promise<string>} YAML formatted string
431
447
  */
432
448
  static async toYamlAsync(jsonData) {
433
- return jsonToYaml(jsonData);
449
+ const optimizedConverterFn = dataManagerAsync(jsonToYaml, extractJsonFromString);
450
+ return optimizedConverterFn(jsonData);
434
451
  }
435
452
 
436
453
  /**
@@ -439,7 +456,8 @@ export class JsonConverter {
439
456
  * @returns {Object|string} JSON object or string
440
457
  */
441
458
  static fromXml(xmlString) {
442
- return xmlToJsonSync(xmlString);
459
+ const optimizedConverterFn = dataManager(xmlToJsonSync, extractXmlFromString);
460
+ return optimizedConverterFn(xmlString);
443
461
  }
444
462
 
445
463
  /**
@@ -448,7 +466,8 @@ export class JsonConverter {
448
466
  * @returns {Promise<Object|string>} JSON object or string
449
467
  */
450
468
  static async fromXmlAsync(xmlString) {
451
- return xmlToJson(xmlString);
469
+ const optimizedConverterFn = dataManagerAsync(xmlToJson, extractXmlFromString);
470
+ return optimizedConverterFn(xmlString);
452
471
  }
453
472
 
454
473
  /**
@@ -457,7 +476,8 @@ export class JsonConverter {
457
476
  * @returns {string} XML formatted string
458
477
  */
459
478
  static toXml(jsonData) {
460
- return jsonToXmlSync(jsonData);
479
+ const optimizedConverterFn = dataManager(jsonToXmlSync, extractJsonFromString);
480
+ return optimizedConverterFn(jsonData);
461
481
  }
462
482
 
463
483
  /**
@@ -466,7 +486,8 @@ export class JsonConverter {
466
486
  * @returns {Promise<string>} XML formatted string
467
487
  */
468
488
  static async toXmlAsync(jsonData) {
469
- return jsonToXml(jsonData);
489
+ const optimizedConverterFn = dataManagerAsync(jsonToXml, extractJsonFromString);
490
+ return optimizedConverterFn(jsonData);
470
491
  }
471
492
 
472
493
  /**
@@ -475,7 +496,8 @@ export class JsonConverter {
475
496
  * @returns {Array<Object>|string} JSON object or string
476
497
  */
477
498
  static fromCsv(csvString) {
478
- return csvToJsonSync(csvString);
499
+ const optimizedConverterFn = dataManager(csvToJsonSync, extractCsvFromString);
500
+ return optimizedConverterFn(csvString);
479
501
  }
480
502
 
481
503
  /**
@@ -484,7 +506,8 @@ export class JsonConverter {
484
506
  * @returns {Promise<Array<Object>|string>} JSON object or string
485
507
  */
486
508
  static async fromCsvAsync(csvString) {
487
- return csvToJson(csvString);
509
+ const optimizedConverterFn = dataManagerAsync(csvToJson, extractCsvFromString);
510
+ return optimizedConverterFn(csvString);
488
511
  }
489
512
 
490
513
  /**
@@ -493,7 +516,8 @@ export class JsonConverter {
493
516
  * @returns {string} CSV formatted string
494
517
  */
495
518
  static toCsv(jsonData) {
496
- return jsonToCsvSync(jsonData);
519
+ const optimizedConverterFn = dataManager(jsonToCsvSync, extractJsonFromString);
520
+ return optimizedConverterFn(jsonData);
497
521
  }
498
522
 
499
523
  /**
@@ -502,7 +526,8 @@ export class JsonConverter {
502
526
  * @returns {Promise<string>} CSV formatted string
503
527
  */
504
528
  static async toCsvAsync(jsonData) {
505
- return jsonToCsv(jsonData);
529
+ const optimizedConverterFn = dataManagerAsync(jsonToCsv, extractJsonFromString);
530
+ return await optimizedConverterFn(jsonData);
506
531
  }
507
532
 
508
533
  /**
package/src/utils.js CHANGED
@@ -2,6 +2,8 @@
2
2
  * Utility functions for TOON conversion
3
3
  */
4
4
 
5
+ import { EXPENSIVE_WORDS } from './constants.js';
6
+
5
7
  /**
6
8
  * Encodes XML reserved characters to prevent parsing errors
7
9
  * @param {string} rawXmlString - Raw XML string
@@ -439,3 +441,303 @@ export function buildTag(key, value) {
439
441
  return `<${sanitizedKey}>${value}</${sanitizedKey}>`;
440
442
  }
441
443
  }
444
+
445
+ // ============================================================================
446
+ // SMART CODE OPTIMIZATION FUNCTIONS
447
+ // ============================================================================
448
+
449
+ /**
450
+ * Detects if a string is likely code.
451
+ * Uses heuristics including command patterns, keywords, and structure.
452
+ * @param {string} value - String to check
453
+ * @returns {boolean} True if the string appears to be code
454
+ */
455
+ export function isCode(value) {
456
+ if (typeof value !== 'string' || value.length < 5) return false;
457
+
458
+ const trimmed = value.trim();
459
+
460
+ // Single-line command patterns
461
+ const isSingleLineCommand = (
462
+ /^(npm|yarn|pnpm|pip|pip3|brew|apt|gem|go|cargo|composer|mvn|gradle|dotnet|conda)\s+/.test(trimmed) ||
463
+ /^(git|docker|kubectl|curl|wget|ssh|scp|rsync|sudo)\s+/.test(trimmed) ||
464
+ /^(node|python|python3|ruby|java|go|rust)\s+/.test(trimmed) ||
465
+ trimmed.startsWith('$') ||
466
+ trimmed.startsWith('>') ||
467
+ trimmed.startsWith('#!')
468
+ );
469
+
470
+ if (isSingleLineCommand) return true;
471
+
472
+ // Multi-line code detection
473
+ const hasMultipleLines = /\n/.test(trimmed);
474
+ const hasCodePatterns = /import|require\(|function |const |let |var |class |def |async |=>|\[|\];|print\(|console\.log\(/.test(trimmed);
475
+ const startsWithShebang = trimmed.startsWith('#!');
476
+
477
+ return hasMultipleLines && (hasCodePatterns || startsWithShebang);
478
+ }
479
+
480
+ /**
481
+ * Extracts code blocks from text, separated by double newlines.
482
+ * @param {string} text - Text containing potential code blocks
483
+ * @returns {Array<{code: string, start: number, end: number}>} Array of code block objects
484
+ */
485
+ export function extractCodeBlocks(text) {
486
+ if (typeof text !== 'string') return [];
487
+
488
+ const results = [];
489
+ let currentPos = 0;
490
+
491
+ while (true) {
492
+ const nextBreak = text.indexOf('\n\n', currentPos);
493
+ let chunk, chunkEnd, nextStart;
494
+
495
+ if (nextBreak !== -1) {
496
+ chunk = text.substring(currentPos, nextBreak);
497
+ chunkEnd = nextBreak;
498
+ nextStart = nextBreak + 2; // skip \n\n
499
+ } else {
500
+ chunk = text.substring(currentPos);
501
+ chunkEnd = text.length;
502
+ nextStart = text.length;
503
+ }
504
+
505
+ const cleanChunk = chunk.trim();
506
+
507
+ if (cleanChunk && isCode(cleanChunk)) {
508
+ results.push({
509
+ code: cleanChunk,
510
+ start: currentPos,
511
+ end: chunkEnd
512
+ });
513
+ }
514
+
515
+ currentPos = nextStart;
516
+ if (currentPos >= text.length) break;
517
+ }
518
+
519
+ return results;
520
+ }
521
+
522
+ /**
523
+ * Reduces a code block by removing comments and compressing whitespace.
524
+ * @param {string} codeBlock - Code to reduce
525
+ * @returns {string} Reduced code
526
+ */
527
+ export function reduceCodeBlock(codeBlock) {
528
+ let reduced = codeBlock;
529
+
530
+ // Remove double newlines
531
+ reduced = reduced.replace(/\n\n/g, '\n');
532
+
533
+ // Remove single-line comments (# for Python, shell, etc.)
534
+ reduced = reduced.replace(/#.*/g, '');
535
+
536
+ // Remove single-line comments (// for JS, Java, etc.)
537
+ reduced = reduced.replace(/\/\/.*/g, '');
538
+
539
+ // Remove trailing whitespace from each line
540
+ reduced = reduced.replace(/\s*\n/g, '\n');
541
+
542
+ // Remove any remaining double newlines created by comment removal
543
+ reduced = reduced.replace(/\n\n/g, '\n');
544
+
545
+ return reduced.trim();
546
+ }
547
+
548
+ /**
549
+ * Replaces verbose phrases with token-efficient abbreviations.
550
+ * Case-insensitive replacement using EXPENSIVE_WORDS dictionary.
551
+ * @param {string} text - Text to optimize
552
+ * @returns {string} Optimized text
553
+ */
554
+ export function alterExpensiveWords(text) {
555
+ if (typeof text !== 'string') return text;
556
+
557
+ const keys = Object.keys(EXPENSIVE_WORDS);
558
+ if (keys.length === 0) return text;
559
+
560
+ // Build regex pattern from all keys
561
+ const pattern = new RegExp(
562
+ '\\b(' + keys.map(k => k.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|') + ')\\b',
563
+ 'gi'
564
+ );
565
+
566
+ return text.replace(pattern, (match) => {
567
+ return EXPENSIVE_WORDS[match.toLowerCase()] || match;
568
+ });
569
+ }
570
+
571
+ /**
572
+ * Higher-order function that wraps converters with Smart Code Optimization.
573
+ *
574
+ * Preprocessing steps:
575
+ * 1. Extract code blocks and replace with placeholders
576
+ * 2. Extract data blocks (JSON/XML/CSV) and replace with placeholders
577
+ * 3. Apply expensive word replacements to remaining text
578
+ * 4. Convert data blocks using the converter function
579
+ * 5. Re-insert converted data blocks
580
+ * 6. Reduce and re-insert code blocks
581
+ *
582
+ * @param {Function} converterFn - The converter function to wrap
583
+ * @param {Function} extractFn - Function to extract data blocks (e.g., extractJsonFromString)
584
+ * @returns {Function} Wrapped converter function
585
+ */
586
+ export function dataManager(converterFn, extractFn) {
587
+ return function (data, ...args) {
588
+ if (typeof data !== 'string') {
589
+ return converterFn(data, ...args);
590
+ }
591
+
592
+ let processedData = data;
593
+
594
+ // Step 1: Extract code blocks and replace with placeholders
595
+ const codeBlocks = extractCodeBlocks(processedData);
596
+
597
+ // Replace from end to start to preserve indices
598
+ for (let i = codeBlocks.length - 1; i >= 0; i--) {
599
+ const block = codeBlocks[i];
600
+ processedData = processedData.substring(0, block.start) +
601
+ `#code#${i}#code#` +
602
+ processedData.substring(block.end);
603
+ }
604
+
605
+ // Step 2: Extract data blocks and replace with placeholders
606
+ const dataBlocks = [];
607
+ let iterationCount = 0;
608
+ const maxIterations = 100;
609
+
610
+ while (iterationCount < maxIterations) {
611
+ const block = extractFn(processedData);
612
+ if (!block) break;
613
+
614
+ dataBlocks.push(block);
615
+ processedData = processedData.replace(block, `#data#${iterationCount}#data#`);
616
+ iterationCount++;
617
+ }
618
+
619
+ // Step 3: Apply expensive word replacements to remaining text
620
+ processedData = alterExpensiveWords(processedData);
621
+
622
+ // Step 4: Compress double newlines
623
+ processedData = processedData.replace(/\n\n/g, '\n');
624
+
625
+ // Step 5: Convert and re-insert data blocks
626
+ let convertedData = processedData.trim();
627
+
628
+ if (dataBlocks.length > 0) {
629
+ // Special Case: If input was 100% one data block and no code blocks, return raw result
630
+ if (dataBlocks.length === 1 && convertedData === '#data#0#data#' && codeBlocks.length === 0) {
631
+ return converterFn(dataBlocks[0].trim(), ...args);
632
+ }
633
+
634
+ for (let i = dataBlocks.length - 1; i >= 0; i--) {
635
+ const block = dataBlocks[i];
636
+ let convertedBlock = converterFn(block.trim(), ...args);
637
+
638
+ // If the converter returned an object/array, stringify it for insertion
639
+ if (typeof convertedBlock !== 'string') {
640
+ try {
641
+ convertedBlock = JSON.stringify(convertedBlock);
642
+ } catch (e) {
643
+ convertedBlock = String(convertedBlock);
644
+ }
645
+ }
646
+
647
+ convertedData = convertedData.replace(`#data#${i}#data#`, convertedBlock);
648
+ }
649
+ } else {
650
+ // No data blocks found, convert the whole thing
651
+ convertedData = converterFn(convertedData, ...args);
652
+ }
653
+
654
+ // Step 6: Reduce and re-insert code blocks
655
+ for (let i = codeBlocks.length - 1; i >= 0; i--) {
656
+ const reducedCode = reduceCodeBlock(codeBlocks[i].code);
657
+ convertedData = convertedData.replace(`#code#${i}#code#`, reducedCode);
658
+ }
659
+
660
+ return convertedData;
661
+ };
662
+ }
663
+
664
+ /**
665
+ * Asynchronous version of dataManager.
666
+ *
667
+ * @param {Function} converterFn - The ASYNC converter function to wrap
668
+ * @param {Function} extractFn - Function to extract data blocks
669
+ * @returns {Function} Wrapped ASYNC converter function
670
+ */
671
+ export function dataManagerAsync(converterFn, extractFn) {
672
+ return async function (data, ...args) {
673
+ if (typeof data !== 'string') {
674
+ return await converterFn(data, ...args);
675
+ }
676
+
677
+ let processedData = data;
678
+
679
+ // Step 1: Extract code blocks and replace with placeholders
680
+ const codeBlocks = extractCodeBlocks(processedData);
681
+ for (let i = codeBlocks.length - 1; i >= 0; i--) {
682
+ const block = codeBlocks[i];
683
+ processedData = processedData.substring(0, block.start) +
684
+ `#code#${i}#code#` +
685
+ processedData.substring(block.end);
686
+ }
687
+
688
+ // Step 2: Extract data blocks and replace with placeholders
689
+ const dataBlocks = [];
690
+ let iterationCount = 0;
691
+ const maxIterations = 100;
692
+
693
+ while (iterationCount < maxIterations) {
694
+ const block = extractFn(processedData);
695
+ if (!block) break;
696
+
697
+ dataBlocks.push(block);
698
+ processedData = processedData.replace(block, `#data#${iterationCount}#data#`);
699
+ iterationCount++;
700
+ }
701
+
702
+ // Step 3: Apply expensive word replacements
703
+ processedData = alterExpensiveWords(processedData);
704
+
705
+ // Step 4: Compress double newlines
706
+ processedData = processedData.replace(/\n\n/g, '\n');
707
+
708
+ // Step 5: Convert and re-insert data blocks
709
+ let convertedData = processedData.trim();
710
+
711
+ if (dataBlocks.length > 0) {
712
+ // Special Case: If input was 100% one data block and no code blocks, return raw result
713
+ if (dataBlocks.length === 1 && convertedData === '#data#0#data#' && codeBlocks.length === 0) {
714
+ return await converterFn(dataBlocks[0].trim(), ...args);
715
+ }
716
+
717
+ for (let i = dataBlocks.length - 1; i >= 0; i--) {
718
+ const block = dataBlocks[i];
719
+ let convertedBlock = await converterFn(block.trim(), ...args);
720
+
721
+ if (typeof convertedBlock !== 'string') {
722
+ try {
723
+ convertedBlock = JSON.stringify(convertedBlock);
724
+ } catch (e) {
725
+ convertedBlock = String(convertedBlock);
726
+ }
727
+ }
728
+
729
+ convertedData = convertedData.replace(`#data#${i}#data#`, convertedBlock);
730
+ }
731
+ } else {
732
+ convertedData = await converterFn(convertedData, ...args);
733
+ }
734
+
735
+ // Step 6: Reduce and re-insert code blocks
736
+ for (let i = codeBlocks.length - 1; i >= 0; i--) {
737
+ const reducedCode = reduceCodeBlock(codeBlocks[i].code);
738
+ convertedData = convertedData.replace(`#code#${i}#code#`, reducedCode);
739
+ }
740
+
741
+ return convertedData;
742
+ };
743
+ }