@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/README.md +50 -1
- package/dist/index.cjs +282 -1
- package/dist/index.cjs.map +6 -4
- package/dist/index.mjs +282 -1
- package/dist/index.mjs.map +6 -4
- package/dist/types/index.d.ts +27 -2
- package/dist/types/src/csv/convert.d.ts +31 -0
- package/dist/types/src/csv/parse.d.ts +40 -0
- package/dist/types/src/types.d.ts +42 -1
- package/package.json +1 -1
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=
|
|
1852
|
+
//# debugId=D5F9B2A423DC266864756E2164756E21
|