@ricsam/excel-to-formula-engine 0.1.1 → 0.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/dist/index.mjs CHANGED
@@ -1538,6 +1538,238 @@ function coalesceCellsIntoAreas(sheetName, cells) {
1538
1538
  return areas;
1539
1539
  }
1540
1540
 
1541
+ // src/csv/parse.ts
1542
+ var CANDIDATE_DELIMITERS = [",", "\t", ";", "|"];
1543
+ function parseDelimitedText(text, delimiter) {
1544
+ const rows = [];
1545
+ let row = [];
1546
+ let field = "";
1547
+ let inQuotes = false;
1548
+ let fieldWasQuoted = false;
1549
+ let rowHasContent = false;
1550
+ const endField = () => {
1551
+ row.push(field);
1552
+ field = "";
1553
+ fieldWasQuoted = false;
1554
+ rowHasContent = true;
1555
+ };
1556
+ const endRow = () => {
1557
+ endField();
1558
+ rows.push(row);
1559
+ row = [];
1560
+ rowHasContent = false;
1561
+ };
1562
+ for (let i = 0;i < text.length; i++) {
1563
+ const char = text[i];
1564
+ if (inQuotes) {
1565
+ if (char === '"') {
1566
+ if (text[i + 1] === '"') {
1567
+ field += '"';
1568
+ i++;
1569
+ } else {
1570
+ inQuotes = false;
1571
+ }
1572
+ } else {
1573
+ field += char;
1574
+ }
1575
+ continue;
1576
+ }
1577
+ if (char === '"' && field.length === 0) {
1578
+ inQuotes = true;
1579
+ fieldWasQuoted = true;
1580
+ continue;
1581
+ }
1582
+ if (char === delimiter) {
1583
+ endField();
1584
+ continue;
1585
+ }
1586
+ if (char === "\r") {
1587
+ if (text[i + 1] === `
1588
+ `)
1589
+ i++;
1590
+ endRow();
1591
+ continue;
1592
+ }
1593
+ if (char === `
1594
+ `) {
1595
+ endRow();
1596
+ continue;
1597
+ }
1598
+ field += char;
1599
+ }
1600
+ if (field.length > 0 || fieldWasQuoted || rowHasContent) {
1601
+ endRow();
1602
+ }
1603
+ return rows;
1604
+ }
1605
+ function detectDelimiter(text, candidates = CANDIDATE_DELIMITERS) {
1606
+ const lines = takeSampleLines(text, 20);
1607
+ if (lines.length === 0)
1608
+ return ",";
1609
+ let best;
1610
+ for (const delimiter of candidates) {
1611
+ const counts = lines.map((line) => countOutsideQuotes(line, delimiter));
1612
+ const present = counts.filter((count) => count > 0);
1613
+ if (present.length === 0)
1614
+ continue;
1615
+ const first = counts[0];
1616
+ const consistent = counts.filter((count) => count === first).length;
1617
+ const score = consistent / counts.length + Math.min(first, 50) / 1000;
1618
+ if (!best || score > best.score) {
1619
+ best = { delimiter, score };
1620
+ }
1621
+ }
1622
+ return best?.delimiter ?? ",";
1623
+ }
1624
+ function takeSampleLines(text, limit) {
1625
+ const lines = [];
1626
+ let current = "";
1627
+ let inQuotes = false;
1628
+ for (let i = 0;i < text.length && lines.length < limit; i++) {
1629
+ const char = text[i];
1630
+ if (char === '"') {
1631
+ if (inQuotes && text[i + 1] === '"') {
1632
+ current += '""';
1633
+ i++;
1634
+ continue;
1635
+ }
1636
+ inQuotes = !inQuotes;
1637
+ current += char;
1638
+ continue;
1639
+ }
1640
+ if (!inQuotes && (char === `
1641
+ ` || char === "\r")) {
1642
+ if (char === "\r" && text[i + 1] === `
1643
+ `)
1644
+ i++;
1645
+ if (current.trim().length > 0)
1646
+ lines.push(current);
1647
+ current = "";
1648
+ continue;
1649
+ }
1650
+ current += char;
1651
+ }
1652
+ if (current.trim().length > 0 && lines.length < limit)
1653
+ lines.push(current);
1654
+ return lines;
1655
+ }
1656
+ function countOutsideQuotes(line, delimiter) {
1657
+ let count = 0;
1658
+ let inQuotes = false;
1659
+ for (let i = 0;i < line.length; i++) {
1660
+ const char = line[i];
1661
+ if (char === '"') {
1662
+ if (inQuotes && line[i + 1] === '"') {
1663
+ i++;
1664
+ continue;
1665
+ }
1666
+ inQuotes = !inQuotes;
1667
+ continue;
1668
+ }
1669
+ if (!inQuotes && char === delimiter)
1670
+ count++;
1671
+ }
1672
+ return count;
1673
+ }
1674
+ function stripBom(text) {
1675
+ return text.charCodeAt(0) === 65279 ? text.slice(1) : text;
1676
+ }
1677
+
1678
+ // src/csv/convert.ts
1679
+ var MAX_CELLS = 2000000;
1680
+ function convertDelimitedText(text, options = {}) {
1681
+ const {
1682
+ sheetName = "Sheet1",
1683
+ fileName,
1684
+ parseNumbers = true,
1685
+ parseBooleans = true,
1686
+ trimFields = false
1687
+ } = options;
1688
+ const diagnostics = [];
1689
+ const body = stripBom(text);
1690
+ const delimiter = options.delimiter ?? detectDelimiter(body);
1691
+ const rows = parseDelimitedText(body, delimiter);
1692
+ const widest = rows.reduce((max, row) => Math.max(max, row.length), 0);
1693
+ if (rows.length * widest > MAX_CELLS) {
1694
+ throw new Error(`The file has about ${rows.length * widest} cells, more than the ` + `${MAX_CELLS} this importer will load at once.`);
1695
+ }
1696
+ const content = new Map;
1697
+ for (let rowIndex = 0;rowIndex < rows.length; rowIndex++) {
1698
+ const row = rows[rowIndex];
1699
+ for (let colIndex = 0;colIndex < row.length; colIndex++) {
1700
+ const raw = trimFields ? row[colIndex].trim() : row[colIndex];
1701
+ if (raw.length === 0)
1702
+ continue;
1703
+ const reference = `${columnIndexToLabel(colIndex)}${rowIndex + 1}`;
1704
+ content.set(reference, toCellValue(raw, {
1705
+ parseNumbers,
1706
+ parseBooleans,
1707
+ sheetName,
1708
+ reference,
1709
+ diagnostics
1710
+ }));
1711
+ }
1712
+ }
1713
+ const data = {
1714
+ sheets: [{ name: sheetName, content }],
1715
+ workbookMetadata: {
1716
+ fileName,
1717
+ sheetOrder: [sheetName],
1718
+ diagnostics: diagnostics.length > 0 ? diagnostics : undefined
1719
+ }
1720
+ };
1721
+ return { data, diagnostics };
1722
+ }
1723
+ function toCellValue(raw, context) {
1724
+ if (raw.startsWith("=") && raw.length > 1) {
1725
+ const translated = translateFormula(raw, {
1726
+ sheetName: context.sheetName,
1727
+ cellReference: context.reference
1728
+ });
1729
+ context.diagnostics.push(...translated.diagnostics);
1730
+ return translated.formula;
1731
+ }
1732
+ if (context.parseBooleans) {
1733
+ const upper = raw.toUpperCase();
1734
+ if (upper === "TRUE")
1735
+ return true;
1736
+ if (upper === "FALSE")
1737
+ return false;
1738
+ }
1739
+ if (context.parseNumbers) {
1740
+ const number = toNumber(raw);
1741
+ if (number !== undefined)
1742
+ return number;
1743
+ }
1744
+ return raw;
1745
+ }
1746
+ function toNumber(raw) {
1747
+ const text = raw.trim();
1748
+ if (text.length === 0)
1749
+ return;
1750
+ if (!/^[+-]?(\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?$/.test(text)) {
1751
+ return;
1752
+ }
1753
+ const digits = text.replace(/^[+-]/, "");
1754
+ if (/^0\d/.test(digits))
1755
+ return;
1756
+ const value = Number(text);
1757
+ if (!Number.isFinite(value))
1758
+ return;
1759
+ if (!text.includes(".") && !/[eE]/.test(text) && !Number.isSafeInteger(value)) {
1760
+ return;
1761
+ }
1762
+ return value;
1763
+ }
1764
+ async function readTextFrom(source) {
1765
+ if (typeof source === "string")
1766
+ return source;
1767
+ if (source instanceof Uint8Array || source instanceof ArrayBuffer) {
1768
+ return new TextDecoder("utf-8").decode(source);
1769
+ }
1770
+ return source.text();
1771
+ }
1772
+
1541
1773
  // index.ts
1542
1774
  async function excelToFormulaEngine(source, options) {
1543
1775
  const { data } = await convertWorkbook(source, withFileName(source, options));
@@ -1553,10 +1785,55 @@ function withFileName(source, options) {
1553
1785
  const name = typeof File !== "undefined" && source instanceof File ? source.name : undefined;
1554
1786
  return name ? { ...options, fileName: name } : { ...options };
1555
1787
  }
1788
+ async function csvToFormulaEngine(source, options) {
1789
+ const { data } = await csvToFormulaEngineDetailed(source, options);
1790
+ return data;
1791
+ }
1792
+ async function csvToFormulaEngineDetailed(source, options) {
1793
+ const text = await readTextFrom(source);
1794
+ const resolved = withCsvFileName(source, options);
1795
+ return convertDelimitedText(text, resolved);
1796
+ }
1797
+ async function spreadsheetToFormulaEngine(source, options) {
1798
+ const { data } = await spreadsheetToFormulaEngineDetailed(source, options);
1799
+ return data;
1800
+ }
1801
+ async function spreadsheetToFormulaEngineDetailed(source, options) {
1802
+ const bytes = await toBytes2(source);
1803
+ if (looksLikeZip(bytes)) {
1804
+ return convertWorkbook(bytes, withFileName(source, options));
1805
+ }
1806
+ return csvToFormulaEngineDetailed(bytes, withCsvFileName(source, options));
1807
+ }
1808
+ function looksLikeZip(bytes) {
1809
+ return bytes.length >= 4 && bytes[0] === 80 && bytes[1] === 75 && bytes[2] === 3 && bytes[3] === 4;
1810
+ }
1811
+ async function toBytes2(source) {
1812
+ if (source instanceof Uint8Array)
1813
+ return source;
1814
+ if (source instanceof ArrayBuffer)
1815
+ return new Uint8Array(source);
1816
+ return new Uint8Array(await source.arrayBuffer());
1817
+ }
1818
+ function withCsvFileName(source, options) {
1819
+ const name = typeof File !== "undefined" && source instanceof File ? source.name : undefined;
1820
+ const fileName = options?.fileName ?? name;
1821
+ if (!fileName)
1822
+ return { ...options };
1823
+ return {
1824
+ ...options,
1825
+ fileName,
1826
+ sheetName: options?.sheetName ?? (fileName.replace(/\.(csv|tsv|tab|txt)$/i, "").trim() || "Sheet1")
1827
+ };
1828
+ }
1556
1829
  export {
1557
1830
  columnIndexToLabel,
1558
1831
  columnLabelToIndex,
1832
+ convertDelimitedText,
1559
1833
  convertWorkbook,
1834
+ csvToFormulaEngine,
1835
+ csvToFormulaEngineDetailed,
1836
+ detectDelimiter,
1560
1837
  excelSerialToIso,
1561
1838
  excelToFormulaEngine,
1562
1839
  excelToFormulaEngineDetailed,
@@ -1564,8 +1841,12 @@ export {
1564
1841
  offsetFormulaReferences,
1565
1842
  parseA1,
1566
1843
  parseA1Range,
1844
+ parseDelimitedText,
1845
+ spreadsheetToFormulaEngine,
1846
+ spreadsheetToFormulaEngineDetailed,
1847
+ stripBom,
1567
1848
  toA1,
1568
1849
  translateFormula
1569
1850
  };
1570
1851
 
1571
- //# debugId=0B0A199FAC38549264756E2164756E21
1852
+ //# debugId=D5F9B2A423DC266864756E2164756E21