@graphty/graph-io 0.3.25 → 0.3.27
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 +49 -414
- package/dist/chunks/children-CL3Cy0ez.js.map +1 -1
- package/dist/chunks/choose-DrXtu_96.js +56 -0
- package/dist/chunks/choose-DrXtu_96.js.map +1 -0
- package/dist/chunks/constants-ikG85dT8.js +231 -0
- package/dist/chunks/constants-ikG85dT8.js.map +1 -0
- package/dist/chunks/{escape-B-tkk_Cl.js → escape-D1HPTUrP.js} +390 -99
- package/dist/chunks/escape-D1HPTUrP.js.map +1 -0
- package/dist/chunks/export-CHd4tQE2.js +4333 -0
- package/dist/chunks/export-CHd4tQE2.js.map +1 -0
- package/dist/chunks/{importer-eUREhBGj.js → exporter-D23CKNqF.js} +4969 -3201
- package/dist/chunks/exporter-D23CKNqF.js.map +1 -0
- package/dist/chunks/{exporter-DIeGJXAZ.js → exporter-DfWTgrti.js} +23 -21
- package/dist/chunks/exporter-DfWTgrti.js.map +1 -0
- package/dist/chunks/exporter-DhtW6FxK.js +3465 -0
- package/dist/chunks/exporter-DhtW6FxK.js.map +1 -0
- package/dist/chunks/{exporter-BGrsWamJ.js → exporter-IHv4nkCm.js} +38 -229
- package/dist/chunks/exporter-IHv4nkCm.js.map +1 -0
- package/dist/chunks/{importer-BWY2FFc5.js → importer-B1wmPQjr.js} +255 -77
- package/dist/chunks/importer-B1wmPQjr.js.map +1 -0
- package/dist/chunks/{importer-D_e7e7LX.js → importer-B2W_58pz.js} +399 -146
- package/dist/chunks/importer-B2W_58pz.js.map +1 -0
- package/dist/chunks/importer-BSALvkui.js +2811 -0
- package/dist/chunks/importer-BSALvkui.js.map +1 -0
- package/dist/chunks/{importer-vi3bSdtw.js → importer-BVRE--Un.js} +510 -77
- package/dist/chunks/importer-BVRE--Un.js.map +1 -0
- package/dist/chunks/{importer-DET1tg6s.js → importer-CIAzt-wo.js} +1689 -312
- package/dist/chunks/importer-CIAzt-wo.js.map +1 -0
- package/dist/chunks/{importer-DIrbbnAf.js → importer-DpEW5zeG.js} +585 -226
- package/dist/chunks/importer-DpEW5zeG.js.map +1 -0
- package/dist/chunks/{importer-zGLo8Dg_.js → importer-z_FL4VMb.js} +488 -161
- package/dist/chunks/importer-z_FL4VMb.js.map +1 -0
- package/dist/chunks/json-elements-BAmNBZok.js +1225 -0
- package/dist/chunks/json-elements-BAmNBZok.js.map +1 -0
- package/dist/chunks/ontology-export-Bab4eUA8.js +357 -0
- package/dist/chunks/ontology-export-Bab4eUA8.js.map +1 -0
- package/dist/chunks/{records-dbRkxwaq.js → records-CdlvL4RO.js} +298 -114
- package/dist/chunks/records-CdlvL4RO.js.map +1 -0
- package/dist/chunks/{text-Dr0Ifpag.js → text-DzXjAiTl.js} +29 -11
- package/dist/chunks/text-DzXjAiTl.js.map +1 -0
- package/dist/chunks/{writer-C7flM-Ih.js → writer-DxjgHwSe.js} +19 -1
- package/dist/chunks/writer-DxjgHwSe.js.map +1 -0
- package/dist/csv.js +952 -262
- package/dist/csv.js.map +1 -1
- package/dist/cx.js +6 -3
- package/dist/cx2.js +2 -2
- package/dist/cys.js +6 -3
- package/dist/dot.js +1 -1
- package/dist/gexf.js +554 -122
- package/dist/gexf.js.map +1 -1
- package/dist/gml.js +344 -87
- package/dist/gml.js.map +1 -1
- package/dist/graph-io.js +1105 -213
- package/dist/graph-io.js.map +1 -1
- package/dist/graphml.js +3 -2
- package/dist/graphml.js.map +1 -1
- package/dist/json.js +3 -2
- package/dist/neo4j.js +565 -158
- package/dist/neo4j.js.map +1 -1
- package/dist/obo.js +6 -3
- package/dist/pajek.js +1 -1
- package/dist/src/children.d.ts +18 -6
- package/dist/src/children.d.ts.map +1 -1
- package/dist/src/children.js +6 -3
- package/dist/src/children.js.map +1 -1
- package/dist/src/common/attributes.d.ts +29 -11
- package/dist/src/common/attributes.d.ts.map +1 -1
- package/dist/src/common/attributes.js +25 -7
- package/dist/src/common/attributes.js.map +1 -1
- package/dist/src/common/cell-budget.d.ts +9 -2
- package/dist/src/common/cell-budget.d.ts.map +1 -1
- package/dist/src/common/cell-budget.js +11 -3
- package/dist/src/common/cell-budget.js.map +1 -1
- package/dist/src/common/choose.d.ts +19 -0
- package/dist/src/common/choose.d.ts.map +1 -0
- package/dist/src/common/choose.js +72 -0
- package/dist/src/common/choose.js.map +1 -0
- package/dist/src/common/codes.d.ts +456 -66
- package/dist/src/common/codes.d.ts.map +1 -1
- package/dist/src/common/codes.js +458 -66
- package/dist/src/common/codes.js.map +1 -1
- package/dist/src/common/compare.d.ts +81 -0
- package/dist/src/common/compare.d.ts.map +1 -0
- package/dist/src/common/compare.js +394 -0
- package/dist/src/common/compare.js.map +1 -0
- package/dist/src/common/cx-export.d.ts +123 -0
- package/dist/src/common/cx-export.d.ts.map +1 -0
- package/dist/src/common/cx-export.js +334 -0
- package/dist/src/common/cx-export.js.map +1 -0
- package/dist/src/common/declared-types.d.ts +22 -2
- package/dist/src/common/declared-types.d.ts.map +1 -1
- package/dist/src/common/declared-types.js +21 -3
- package/dist/src/common/declared-types.js.map +1 -1
- package/dist/src/common/direction.d.ts +74 -50
- package/dist/src/common/direction.d.ts.map +1 -1
- package/dist/src/common/direction.js +47 -27
- package/dist/src/common/direction.js.map +1 -1
- package/dist/src/common/escape.d.ts +21 -9
- package/dist/src/common/escape.d.ts.map +1 -1
- package/dist/src/common/escape.js +39 -22
- package/dist/src/common/escape.js.map +1 -1
- package/dist/src/common/export.d.ts +144 -32
- package/dist/src/common/export.d.ts.map +1 -1
- package/dist/src/common/export.js +216 -42
- package/dist/src/common/export.js.map +1 -1
- package/dist/src/common/format.d.ts +23 -1
- package/dist/src/common/format.d.ts.map +1 -1
- package/dist/src/common/format.js +26 -1
- package/dist/src/common/format.js.map +1 -1
- package/dist/src/common/ids.d.ts +9 -2
- package/dist/src/common/ids.d.ts.map +1 -1
- package/dist/src/common/ids.js +6 -2
- package/dist/src/common/ids.js.map +1 -1
- package/dist/src/common/input.d.ts +122 -28
- package/dist/src/common/input.d.ts.map +1 -1
- package/dist/src/common/input.js +890 -124
- package/dist/src/common/input.js.map +1 -1
- package/dist/src/common/json-elements.d.ts +144 -20
- package/dist/src/common/json-elements.d.ts.map +1 -1
- package/dist/src/common/json-elements.js +869 -189
- package/dist/src/common/json-elements.js.map +1 -1
- package/dist/src/common/line-format.d.ts +90 -0
- package/dist/src/common/line-format.d.ts.map +1 -0
- package/dist/src/common/line-format.js +185 -0
- package/dist/src/common/line-format.js.map +1 -0
- package/dist/src/common/lists.d.ts +6 -1
- package/dist/src/common/lists.d.ts.map +1 -1
- package/dist/src/common/lists.js +3 -1
- package/dist/src/common/lists.js.map +1 -1
- package/dist/src/common/ontology-export.d.ts +167 -0
- package/dist/src/common/ontology-export.d.ts.map +1 -0
- package/dist/src/common/ontology-export.js +359 -0
- package/dist/src/common/ontology-export.js.map +1 -0
- package/dist/src/common/ontology.d.ts +25 -3
- package/dist/src/common/ontology.d.ts.map +1 -1
- package/dist/src/common/ontology.js +30 -9
- package/dist/src/common/ontology.js.map +1 -1
- package/dist/src/common/options.d.ts +62 -12
- package/dist/src/common/options.d.ts.map +1 -1
- package/dist/src/common/options.js +89 -11
- package/dist/src/common/options.js.map +1 -1
- package/dist/src/common/plural.d.ts +31 -0
- package/dist/src/common/plural.d.ts.map +1 -0
- package/dist/src/common/plural.js +46 -0
- package/dist/src/common/plural.js.map +1 -0
- package/dist/src/common/report.d.ts +72 -14
- package/dist/src/common/report.d.ts.map +1 -1
- package/dist/src/common/report.js +146 -22
- package/dist/src/common/report.js.map +1 -1
- package/dist/src/common/temporal.d.ts +22 -5
- package/dist/src/common/temporal.d.ts.map +1 -1
- package/dist/src/common/temporal.js +13 -2
- package/dist/src/common/temporal.js.map +1 -1
- package/dist/src/common/text.d.ts +39 -23
- package/dist/src/common/text.d.ts.map +1 -1
- package/dist/src/common/text.js +51 -21
- package/dist/src/common/text.js.map +1 -1
- package/dist/src/common/weights.d.ts +37 -4
- package/dist/src/common/weights.d.ts.map +1 -1
- package/dist/src/common/weights.js +61 -2
- package/dist/src/common/weights.js.map +1 -1
- package/dist/src/common/writer.d.ts +19 -3
- package/dist/src/common/writer.d.ts.map +1 -1
- package/dist/src/common/writer.js +30 -2
- package/dist/src/common/writer.js.map +1 -1
- package/dist/src/common/xml.d.ts +125 -9
- package/dist/src/common/xml.d.ts.map +1 -1
- package/dist/src/common/xml.js +402 -85
- package/dist/src/common/xml.js.map +1 -1
- package/dist/src/common/zip.d.ts +44 -4
- package/dist/src/common/zip.d.ts.map +1 -1
- package/dist/src/common/zip.js +134 -5
- package/dist/src/common/zip.js.map +1 -1
- package/dist/src/formats/csv/exporter.d.ts +80 -24
- package/dist/src/formats/csv/exporter.d.ts.map +1 -1
- package/dist/src/formats/csv/exporter.js +146 -33
- package/dist/src/formats/csv/exporter.js.map +1 -1
- package/dist/src/formats/csv/header.d.ts +38 -9
- package/dist/src/formats/csv/header.d.ts.map +1 -1
- package/dist/src/formats/csv/header.js +46 -10
- package/dist/src/formats/csv/header.js.map +1 -1
- package/dist/src/formats/csv/importer.d.ts +187 -37
- package/dist/src/formats/csv/importer.d.ts.map +1 -1
- package/dist/src/formats/csv/importer.js +835 -204
- package/dist/src/formats/csv/importer.js.map +1 -1
- package/dist/src/formats/csv/index.d.ts +100 -19
- package/dist/src/formats/csv/index.d.ts.map +1 -1
- package/dist/src/formats/csv/index.js +94 -21
- package/dist/src/formats/csv/index.js.map +1 -1
- package/dist/src/formats/csv/records.d.ts +95 -10
- package/dist/src/formats/csv/records.d.ts.map +1 -1
- package/dist/src/formats/csv/records.js +333 -122
- package/dist/src/formats/csv/records.js.map +1 -1
- package/dist/src/formats/csv/values.d.ts +1 -0
- package/dist/src/formats/csv/values.d.ts.map +1 -1
- package/dist/src/formats/csv/values.js +1 -0
- package/dist/src/formats/csv/values.js.map +1 -1
- package/dist/src/formats/cx/exporter.d.ts +102 -0
- package/dist/src/formats/cx/exporter.d.ts.map +1 -0
- package/dist/src/formats/cx/exporter.js +1217 -0
- package/dist/src/formats/cx/exporter.js.map +1 -0
- package/dist/src/formats/cx/importer.d.ts +79 -32
- package/dist/src/formats/cx/importer.d.ts.map +1 -1
- package/dist/src/formats/cx/importer.js +682 -287
- package/dist/src/formats/cx/importer.js.map +1 -1
- package/dist/src/formats/cx/index.d.ts +4 -3
- package/dist/src/formats/cx/index.d.ts.map +1 -1
- package/dist/src/formats/cx/index.js +4 -3
- package/dist/src/formats/cx/index.js.map +1 -1
- package/dist/src/formats/cx2/exporter.d.ts +29 -11
- package/dist/src/formats/cx2/exporter.d.ts.map +1 -1
- package/dist/src/formats/cx2/exporter.js +39 -268
- package/dist/src/formats/cx2/exporter.js.map +1 -1
- package/dist/src/formats/cx2/importer.d.ts +78 -28
- package/dist/src/formats/cx2/importer.d.ts.map +1 -1
- package/dist/src/formats/cx2/importer.js +227 -80
- package/dist/src/formats/cx2/importer.js.map +1 -1
- package/dist/src/formats/cx2/index.d.ts +2 -1
- package/dist/src/formats/cx2/index.d.ts.map +1 -1
- package/dist/src/formats/cx2/index.js +2 -1
- package/dist/src/formats/cx2/index.js.map +1 -1
- package/dist/src/formats/cys/constants.d.ts +66 -17
- package/dist/src/formats/cys/constants.d.ts.map +1 -1
- package/dist/src/formats/cys/constants.js +59 -18
- package/dist/src/formats/cys/constants.js.map +1 -1
- package/dist/src/formats/cys/exporter.d.ts +118 -0
- package/dist/src/formats/cys/exporter.d.ts.map +1 -0
- package/dist/src/formats/cys/exporter.js +1072 -0
- package/dist/src/formats/cys/exporter.js.map +1 -0
- package/dist/src/formats/cys/importer.d.ts +22 -8
- package/dist/src/formats/cys/importer.d.ts.map +1 -1
- package/dist/src/formats/cys/importer.js +510 -149
- package/dist/src/formats/cys/importer.js.map +1 -1
- package/dist/src/formats/cys/index.d.ts +4 -2
- package/dist/src/formats/cys/index.d.ts.map +1 -1
- package/dist/src/formats/cys/index.js +4 -2
- package/dist/src/formats/cys/index.js.map +1 -1
- package/dist/src/formats/cys/session.d.ts +46 -7
- package/dist/src/formats/cys/session.d.ts.map +1 -1
- package/dist/src/formats/cys/session.js +101 -27
- package/dist/src/formats/cys/session.js.map +1 -1
- package/dist/src/formats/cys/tables.d.ts +9 -2
- package/dist/src/formats/cys/tables.d.ts.map +1 -1
- package/dist/src/formats/cys/tables.js +29 -7
- package/dist/src/formats/cys/tables.js.map +1 -1
- package/dist/src/formats/dot/exporter.d.ts +42 -14
- package/dist/src/formats/dot/exporter.d.ts.map +1 -1
- package/dist/src/formats/dot/exporter.js +56 -29
- package/dist/src/formats/dot/exporter.js.map +1 -1
- package/dist/src/formats/dot/importer.d.ts +91 -29
- package/dist/src/formats/dot/importer.d.ts.map +1 -1
- package/dist/src/formats/dot/importer.js +190 -39
- package/dist/src/formats/dot/importer.js.map +1 -1
- package/dist/src/formats/dot/index.d.ts +2 -1
- package/dist/src/formats/dot/index.d.ts.map +1 -1
- package/dist/src/formats/dot/index.js +2 -1
- package/dist/src/formats/dot/index.js.map +1 -1
- package/dist/src/formats/dot/names.d.ts +44 -11
- package/dist/src/formats/dot/names.d.ts.map +1 -1
- package/dist/src/formats/dot/names.js +44 -11
- package/dist/src/formats/dot/names.js.map +1 -1
- package/dist/src/formats/dot/tokenizer.d.ts +9 -2
- package/dist/src/formats/dot/tokenizer.d.ts.map +1 -1
- package/dist/src/formats/dot/tokenizer.js +20 -5
- package/dist/src/formats/dot/tokenizer.js.map +1 -1
- package/dist/src/formats/gexf/exporter.d.ts +46 -15
- package/dist/src/formats/gexf/exporter.d.ts.map +1 -1
- package/dist/src/formats/gexf/exporter.js +45 -15
- package/dist/src/formats/gexf/exporter.js.map +1 -1
- package/dist/src/formats/gexf/importer.d.ts +87 -23
- package/dist/src/formats/gexf/importer.d.ts.map +1 -1
- package/dist/src/formats/gexf/importer.js +471 -90
- package/dist/src/formats/gexf/importer.js.map +1 -1
- package/dist/src/formats/gexf/index.d.ts +84 -21
- package/dist/src/formats/gexf/index.d.ts.map +1 -1
- package/dist/src/formats/gexf/index.js +80 -23
- package/dist/src/formats/gexf/index.js.map +1 -1
- package/dist/src/formats/gexf/schema.d.ts +67 -15
- package/dist/src/formats/gexf/schema.d.ts.map +1 -1
- package/dist/src/formats/gexf/schema.js +59 -13
- package/dist/src/formats/gexf/schema.js.map +1 -1
- package/dist/src/formats/gml/exporter.d.ts +69 -21
- package/dist/src/formats/gml/exporter.d.ts.map +1 -1
- package/dist/src/formats/gml/exporter.js +74 -29
- package/dist/src/formats/gml/exporter.js.map +1 -1
- package/dist/src/formats/gml/importer.d.ts +116 -25
- package/dist/src/formats/gml/importer.d.ts.map +1 -1
- package/dist/src/formats/gml/importer.js +290 -61
- package/dist/src/formats/gml/importer.js.map +1 -1
- package/dist/src/formats/gml/index.d.ts +87 -24
- package/dist/src/formats/gml/index.d.ts.map +1 -1
- package/dist/src/formats/gml/index.js +79 -26
- package/dist/src/formats/gml/index.js.map +1 -1
- package/dist/src/formats/gml/syntax.d.ts +67 -18
- package/dist/src/formats/gml/syntax.d.ts.map +1 -1
- package/dist/src/formats/gml/syntax.js +110 -28
- package/dist/src/formats/gml/syntax.js.map +1 -1
- package/dist/src/formats/graphml/constants.d.ts +174 -44
- package/dist/src/formats/graphml/constants.d.ts.map +1 -1
- package/dist/src/formats/graphml/constants.js +165 -44
- package/dist/src/formats/graphml/constants.js.map +1 -1
- package/dist/src/formats/graphml/exporter.d.ts +17 -9
- package/dist/src/formats/graphml/exporter.d.ts.map +1 -1
- package/dist/src/formats/graphml/exporter.js +33 -12
- package/dist/src/formats/graphml/exporter.js.map +1 -1
- package/dist/src/formats/graphml/importer.d.ts +19 -7
- package/dist/src/formats/graphml/importer.d.ts.map +1 -1
- package/dist/src/formats/graphml/importer.js +466 -39
- package/dist/src/formats/graphml/importer.js.map +1 -1
- package/dist/src/formats/graphml/index.d.ts +2 -1
- package/dist/src/formats/graphml/index.d.ts.map +1 -1
- package/dist/src/formats/graphml/index.js +2 -1
- package/dist/src/formats/graphml/index.js.map +1 -1
- package/dist/src/formats/graphml/tree.d.ts +6 -2
- package/dist/src/formats/graphml/tree.d.ts.map +1 -1
- package/dist/src/formats/graphml/tree.js +6 -2
- package/dist/src/formats/graphml/tree.js.map +1 -1
- package/dist/src/formats/graphml/yfiles.d.ts +8 -3
- package/dist/src/formats/graphml/yfiles.d.ts.map +1 -1
- package/dist/src/formats/graphml/yfiles.js +36 -15
- package/dist/src/formats/graphml/yfiles.js.map +1 -1
- package/dist/src/formats/json/dialect.d.ts +88 -24
- package/dist/src/formats/json/dialect.d.ts.map +1 -1
- package/dist/src/formats/json/dialect.js +109 -19
- package/dist/src/formats/json/dialect.js.map +1 -1
- package/dist/src/formats/json/exporter.d.ts +134 -22
- package/dist/src/formats/json/exporter.d.ts.map +1 -1
- package/dist/src/formats/json/exporter.js +135 -54
- package/dist/src/formats/json/exporter.js.map +1 -1
- package/dist/src/formats/json/importer.d.ts +173 -52
- package/dist/src/formats/json/importer.d.ts.map +1 -1
- package/dist/src/formats/json/importer.js +899 -220
- package/dist/src/formats/json/importer.js.map +1 -1
- package/dist/src/formats/json/index.d.ts +3 -2
- package/dist/src/formats/json/index.d.ts.map +1 -1
- package/dist/src/formats/json/index.js +3 -2
- package/dist/src/formats/json/index.js.map +1 -1
- package/dist/src/formats/json/obographs-export.d.ts +68 -0
- package/dist/src/formats/json/obographs-export.d.ts.map +1 -0
- package/dist/src/formats/json/obographs-export.js +941 -0
- package/dist/src/formats/json/obographs-export.js.map +1 -0
- package/dist/src/formats/json/obographs.d.ts +26 -0
- package/dist/src/formats/json/obographs.d.ts.map +1 -1
- package/dist/src/formats/json/obographs.js +297 -80
- package/dist/src/formats/json/obographs.js.map +1 -1
- package/dist/src/formats/neo4j/exporter.d.ts +109 -21
- package/dist/src/formats/neo4j/exporter.d.ts.map +1 -1
- package/dist/src/formats/neo4j/exporter.js +239 -86
- package/dist/src/formats/neo4j/exporter.js.map +1 -1
- package/dist/src/formats/neo4j/header.d.ts +17 -4
- package/dist/src/formats/neo4j/header.d.ts.map +1 -1
- package/dist/src/formats/neo4j/header.js +36 -4
- package/dist/src/formats/neo4j/header.js.map +1 -1
- package/dist/src/formats/neo4j/importer.d.ts +107 -27
- package/dist/src/formats/neo4j/importer.d.ts.map +1 -1
- package/dist/src/formats/neo4j/importer.js +415 -63
- package/dist/src/formats/neo4j/importer.js.map +1 -1
- package/dist/src/formats/neo4j/index.d.ts +73 -23
- package/dist/src/formats/neo4j/index.d.ts.map +1 -1
- package/dist/src/formats/neo4j/index.js +65 -25
- package/dist/src/formats/neo4j/index.js.map +1 -1
- package/dist/src/formats/obo/exporter.d.ts +150 -0
- package/dist/src/formats/obo/exporter.d.ts.map +1 -0
- package/dist/src/formats/obo/exporter.js +1736 -0
- package/dist/src/formats/obo/exporter.js.map +1 -0
- package/dist/src/formats/obo/importer.d.ts +95 -19
- package/dist/src/formats/obo/importer.d.ts.map +1 -1
- package/dist/src/formats/obo/importer.js +475 -104
- package/dist/src/formats/obo/importer.js.map +1 -1
- package/dist/src/formats/obo/index.d.ts +6 -3
- package/dist/src/formats/obo/index.d.ts.map +1 -1
- package/dist/src/formats/obo/index.js +6 -3
- package/dist/src/formats/obo/index.js.map +1 -1
- package/dist/src/formats/obo/syntax.d.ts +108 -4
- package/dist/src/formats/obo/syntax.d.ts.map +1 -1
- package/dist/src/formats/obo/syntax.js +211 -34
- package/dist/src/formats/obo/syntax.js.map +1 -1
- package/dist/src/formats/pajek/exporter.d.ts +54 -16
- package/dist/src/formats/pajek/exporter.d.ts.map +1 -1
- package/dist/src/formats/pajek/exporter.js +67 -35
- package/dist/src/formats/pajek/exporter.js.map +1 -1
- package/dist/src/formats/pajek/importer.d.ts +84 -22
- package/dist/src/formats/pajek/importer.d.ts.map +1 -1
- package/dist/src/formats/pajek/importer.js +303 -83
- package/dist/src/formats/pajek/importer.js.map +1 -1
- package/dist/src/formats/pajek/index.d.ts +2 -1
- package/dist/src/formats/pajek/index.d.ts.map +1 -1
- package/dist/src/formats/pajek/index.js +2 -1
- package/dist/src/formats/pajek/index.js.map +1 -1
- package/dist/src/formats/pajek/syntax.d.ts +77 -14
- package/dist/src/formats/pajek/syntax.d.ts.map +1 -1
- package/dist/src/formats/pajek/syntax.js +186 -54
- package/dist/src/formats/pajek/syntax.js.map +1 -1
- package/dist/src/formats/xgmml/columns.d.ts +1 -0
- package/dist/src/formats/xgmml/columns.d.ts.map +1 -1
- package/dist/src/formats/xgmml/columns.js +4 -0
- package/dist/src/formats/xgmml/columns.js.map +1 -1
- package/dist/src/formats/xgmml/constants.d.ts +186 -56
- package/dist/src/formats/xgmml/constants.d.ts.map +1 -1
- package/dist/src/formats/xgmml/constants.js +181 -57
- package/dist/src/formats/xgmml/constants.js.map +1 -1
- package/dist/src/formats/xgmml/document.d.ts +65 -11
- package/dist/src/formats/xgmml/document.d.ts.map +1 -1
- package/dist/src/formats/xgmml/document.js +157 -28
- package/dist/src/formats/xgmml/document.js.map +1 -1
- package/dist/src/formats/xgmml/emit.d.ts +61 -3
- package/dist/src/formats/xgmml/emit.d.ts.map +1 -1
- package/dist/src/formats/xgmml/emit.js +129 -26
- package/dist/src/formats/xgmml/emit.js.map +1 -1
- package/dist/src/formats/xgmml/exporter.d.ts +13 -6
- package/dist/src/formats/xgmml/exporter.d.ts.map +1 -1
- package/dist/src/formats/xgmml/exporter.js +21 -22
- package/dist/src/formats/xgmml/exporter.js.map +1 -1
- package/dist/src/formats/xgmml/importer.d.ts +33 -12
- package/dist/src/formats/xgmml/importer.d.ts.map +1 -1
- package/dist/src/formats/xgmml/importer.js +35 -72
- package/dist/src/formats/xgmml/importer.js.map +1 -1
- package/dist/src/formats/xgmml/index.d.ts +1 -0
- package/dist/src/formats/xgmml/index.d.ts.map +1 -1
- package/dist/src/formats/xgmml/index.js +1 -0
- package/dist/src/formats/xgmml/index.js.map +1 -1
- package/dist/src/formats/xgmml/values.d.ts +8 -1
- package/dist/src/formats/xgmml/values.d.ts.map +1 -1
- package/dist/src/formats/xgmml/values.js +8 -3
- package/dist/src/formats/xgmml/values.js.map +1 -1
- package/dist/src/index.d.ts +23 -18
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +28 -16
- package/dist/src/index.js.map +1 -1
- package/dist/src/registry.d.ts +478 -92
- package/dist/src/registry.d.ts.map +1 -1
- package/dist/src/registry.js +807 -88
- package/dist/src/registry.js.map +1 -1
- package/dist/src/sniff.d.ts +61 -19
- package/dist/src/sniff.d.ts.map +1 -1
- package/dist/src/sniff.js +121 -29
- package/dist/src/sniff.js.map +1 -1
- package/dist/src/types.d.ts +362 -112
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/types.js +28 -7
- package/dist/src/types.js.map +1 -1
- package/dist/xgmml.js +2 -2
- package/docs/samples/README.md +25 -0
- package/docs/samples/got-edges.csv +353 -0
- package/docs/samples/got-network.graphml +1384 -0
- package/docs/samples/got-nodes.csv +108 -0
- package/docs/samples/got.cx +930 -0
- package/docs/samples/got.cx2 +465 -0
- package/docs/samples/got.gexf +471 -0
- package/docs/samples/got.gml +2650 -0
- package/docs/samples/got.json +2546 -0
- package/docs/samples/got.net +461 -0
- package/docs/samples/movies-nodes.csv +9 -0
- package/docs/samples/movies-rels.csv +8 -0
- package/docs/samples/networks.cys +0 -0
- package/docs/samples/proteins.xgmml +9 -0
- package/docs/samples/teams.gv +24 -0
- package/docs/samples/vehicles.obo +25 -0
- package/package.json +9 -4
- package/src/children.ts +18 -6
- package/src/common/attributes.ts +29 -11
- package/src/common/cell-budget.ts +11 -3
- package/src/common/choose.ts +89 -0
- package/src/common/codes.ts +490 -66
- package/src/common/compare.ts +472 -0
- package/src/common/cx-export.ts +432 -0
- package/src/common/declared-types.ts +40 -6
- package/src/common/direction.ts +78 -51
- package/src/common/escape.ts +39 -22
- package/src/common/export.ts +284 -47
- package/src/common/format.ts +30 -1
- package/src/common/ids.ts +14 -3
- package/src/common/input.ts +1057 -134
- package/src/common/json-elements.ts +1035 -199
- package/src/common/line-format.ts +249 -0
- package/src/common/lists.ts +7 -2
- package/src/common/ontology-export.ts +461 -0
- package/src/common/ontology.ts +31 -9
- package/src/common/options.ts +110 -15
- package/src/common/plural.ts +49 -0
- package/src/common/report.ts +168 -25
- package/src/common/temporal.ts +22 -5
- package/src/common/text.ts +62 -23
- package/src/common/weights.ts +74 -4
- package/src/common/writer.ts +37 -3
- package/src/common/xml.ts +479 -90
- package/src/common/zip.ts +169 -7
- package/src/formats/csv/exporter.ts +202 -47
- package/src/formats/csv/header.ts +50 -11
- package/src/formats/csv/importer.ts +1133 -245
- package/src/formats/csv/index.ts +106 -19
- package/src/formats/csv/records.ts +396 -122
- package/src/formats/csv/values.ts +1 -0
- package/src/formats/cx/exporter.ts +1483 -0
- package/src/formats/cx/importer.ts +890 -302
- package/src/formats/cx/index.ts +4 -3
- package/src/formats/cx2/exporter.ts +62 -339
- package/src/formats/cx2/importer.ts +318 -87
- package/src/formats/cx2/index.ts +2 -1
- package/src/formats/cys/constants.ts +62 -17
- package/src/formats/cys/exporter.ts +1368 -0
- package/src/formats/cys/importer.ts +659 -157
- package/src/formats/cys/index.ts +4 -2
- package/src/formats/cys/session.ts +142 -34
- package/src/formats/cys/tables.ts +47 -9
- package/src/formats/dot/exporter.ts +83 -37
- package/src/formats/dot/importer.ts +248 -54
- package/src/formats/dot/index.ts +2 -1
- package/src/formats/dot/names.ts +44 -11
- package/src/formats/dot/tokenizer.ts +25 -6
- package/src/formats/gexf/exporter.ts +57 -18
- package/src/formats/gexf/importer.ts +636 -105
- package/src/formats/gexf/index.ts +90 -21
- package/src/formats/gexf/schema.ts +67 -15
- package/src/formats/gml/exporter.ts +90 -36
- package/src/formats/gml/importer.ts +388 -67
- package/src/formats/gml/index.ts +85 -24
- package/src/formats/gml/syntax.ts +113 -28
- package/src/formats/graphml/constants.ts +178 -44
- package/src/formats/graphml/exporter.ts +52 -18
- package/src/formats/graphml/importer.ts +640 -60
- package/src/formats/graphml/index.ts +2 -1
- package/src/formats/graphml/tree.ts +6 -2
- package/src/formats/graphml/yfiles.ts +37 -15
- package/src/formats/json/dialect.ts +128 -29
- package/src/formats/json/exporter.ts +205 -68
- package/src/formats/json/importer.ts +1163 -253
- package/src/formats/json/index.ts +3 -1
- package/src/formats/json/obographs-export.ts +1197 -0
- package/src/formats/json/obographs.ts +353 -92
- package/src/formats/neo4j/exporter.ts +321 -102
- package/src/formats/neo4j/header.ts +44 -6
- package/src/formats/neo4j/importer.ts +512 -73
- package/src/formats/neo4j/index.ts +75 -23
- package/src/formats/obo/exporter.ts +2210 -0
- package/src/formats/obo/importer.ts +622 -109
- package/src/formats/obo/index.ts +6 -3
- package/src/formats/obo/syntax.ts +232 -37
- package/src/formats/pajek/exporter.ts +90 -42
- package/src/formats/pajek/importer.ts +407 -93
- package/src/formats/pajek/index.ts +2 -1
- package/src/formats/pajek/syntax.ts +216 -55
- package/src/formats/xgmml/columns.ts +10 -0
- package/src/formats/xgmml/constants.ts +183 -56
- package/src/formats/xgmml/document.ts +212 -34
- package/src/formats/xgmml/emit.ts +175 -34
- package/src/formats/xgmml/exporter.ts +36 -28
- package/src/formats/xgmml/importer.ts +64 -86
- package/src/formats/xgmml/index.ts +1 -0
- package/src/formats/xgmml/values.ts +12 -4
- package/src/index.ts +81 -11
- package/src/registry.ts +1121 -135
- package/src/sniff.ts +163 -32
- package/src/types.ts +385 -114
- package/dist/chunks/escape-B-tkk_Cl.js.map +0 -1
- package/dist/chunks/export-Bh60Dv-n.js +0 -582
- package/dist/chunks/export-Bh60Dv-n.js.map +0 -1
- package/dist/chunks/exporter-BGrsWamJ.js.map +0 -1
- package/dist/chunks/exporter-DIeGJXAZ.js.map +0 -1
- package/dist/chunks/importer-BWY2FFc5.js.map +0 -1
- package/dist/chunks/importer-DD-xv9_Y.js +0 -1471
- package/dist/chunks/importer-DD-xv9_Y.js.map +0 -1
- package/dist/chunks/importer-DET1tg6s.js.map +0 -1
- package/dist/chunks/importer-DEcKhsmQ.js +0 -1614
- package/dist/chunks/importer-DEcKhsmQ.js.map +0 -1
- package/dist/chunks/importer-DIrbbnAf.js.map +0 -1
- package/dist/chunks/importer-D_e7e7LX.js.map +0 -1
- package/dist/chunks/importer-eUREhBGj.js.map +0 -1
- package/dist/chunks/importer-vi3bSdtw.js.map +0 -1
- package/dist/chunks/importer-zGLo8Dg_.js.map +0 -1
- package/dist/chunks/json-elements-DDS8N2Dd.js +0 -779
- package/dist/chunks/json-elements-DDS8N2Dd.js.map +0 -1
- package/dist/chunks/ontology-BnrJ4I98.js +0 -113
- package/dist/chunks/ontology-BnrJ4I98.js.map +0 -1
- package/dist/chunks/records-dbRkxwaq.js.map +0 -1
- package/dist/chunks/report-B1z4WT9e.js +0 -2372
- package/dist/chunks/report-B1z4WT9e.js.map +0 -1
- package/dist/chunks/text-Dr0Ifpag.js.map +0 -1
- package/dist/chunks/weights-Dzba96G3.js +0 -176
- package/dist/chunks/weights-Dzba96G3.js.map +0 -1
- package/dist/chunks/writer-C7flM-Ih.js.map +0 -1
|
@@ -16,17 +16,26 @@
|
|
|
16
16
|
|
|
17
17
|
import { GraphFormatError } from "@graphty/graph-format";
|
|
18
18
|
|
|
19
|
-
import { type ReadOptions, textChunks } from "../../common/input.js";
|
|
19
|
+
import { MAX_TEXT_LENGTH, type ReadOptions, textChunks, tooLarge } from "../../common/input.js";
|
|
20
20
|
import { type ImportReportBuilder } from "../../common/report.js";
|
|
21
21
|
import { type ImportInput } from "../../types.js";
|
|
22
22
|
|
|
23
|
-
/**
|
|
23
|
+
/**
|
|
24
|
+
* A quoted field is never closed; the import aborts (everything after it would be one cell).
|
|
25
|
+
* @category Issue and loss codes
|
|
26
|
+
*/
|
|
24
27
|
export const UNCLOSED_QUOTE_CODE = "E_CSV_UNCLOSED_QUOTE";
|
|
25
28
|
|
|
26
|
-
/**
|
|
29
|
+
/**
|
|
30
|
+
* A closing quote is followed by text other than a delimiter or a line break; the import aborts.
|
|
31
|
+
* @category Issue and loss codes
|
|
32
|
+
*/
|
|
27
33
|
export const BAD_QUOTE_CODE = "E_CSV_QUOTE";
|
|
28
34
|
|
|
29
|
-
/**
|
|
35
|
+
/**
|
|
36
|
+
* The delimiters tried, in priority order, when none is given.
|
|
37
|
+
* @category Plugin helpers
|
|
38
|
+
*/
|
|
30
39
|
export const DELIMITER_CANDIDATES: readonly string[] = Object.freeze([",", "\t", ";", "|", " "]);
|
|
31
40
|
|
|
32
41
|
/** Rows the delimiter sniff looks at. */
|
|
@@ -37,6 +46,11 @@ const PREVIEW_CHARS = 64 * 1024;
|
|
|
37
46
|
|
|
38
47
|
const LF = 10;
|
|
39
48
|
const CR = 13;
|
|
49
|
+
const TAB = 9;
|
|
50
|
+
const SPACE = 32;
|
|
51
|
+
|
|
52
|
+
/** An Excel delimiter directive: a first line `sep=;` names the delimiter and is not a record. */
|
|
53
|
+
const SEP_DIRECTIVE = /^sep=([^\r\n])(?:\r\n|\n|\r|$)/i;
|
|
40
54
|
|
|
41
55
|
/** Tokeniser state: at the start of a field. */
|
|
42
56
|
const START = 0;
|
|
@@ -53,7 +67,10 @@ const COMMENT = 5;
|
|
|
53
67
|
|
|
54
68
|
type State = typeof START | typeof UNQUOTED | typeof QUOTED | typeof CLOSING | typeof AFTER_QUOTED | typeof COMMENT;
|
|
55
69
|
|
|
56
|
-
/**
|
|
70
|
+
/**
|
|
71
|
+
* The delimiter and quote of a reader.
|
|
72
|
+
* @category Plugin helpers
|
|
73
|
+
*/
|
|
57
74
|
export interface RecordSyntax {
|
|
58
75
|
/** The field delimiter, one character; null sniffs it from the first rows. */
|
|
59
76
|
readonly delimiter: string | null;
|
|
@@ -72,6 +89,20 @@ export interface RecordSyntax {
|
|
|
72
89
|
* other text (see sniffDelimiter). False by default: that text then aborts the read.
|
|
73
90
|
*/
|
|
74
91
|
readonly skipQuoteErrors?: boolean | undefined;
|
|
92
|
+
/**
|
|
93
|
+
* Whether a space delimiter means the whitespace dialect of SNAP / KONECT edge lists: any run of
|
|
94
|
+
* spaces and tabs separates two fields, and whitespace at the start and end of a line is
|
|
95
|
+
* ignored. False by default (every space is a delimiter).
|
|
96
|
+
*/
|
|
97
|
+
readonly collapseSpaces?: boolean | undefined;
|
|
98
|
+
/** Whether an Excel `sep=X` first line names the delimiter for the sniff. False by default. */
|
|
99
|
+
readonly sepDirective?: boolean | undefined;
|
|
100
|
+
/**
|
|
101
|
+
* Called with the sniff preview (leading comment lines removed) and the line it starts on,
|
|
102
|
+
* before any record is scanned, so a caller can refuse input that is not delimited text at all.
|
|
103
|
+
* Only called when the delimiter is sniffed.
|
|
104
|
+
*/
|
|
105
|
+
readonly inspect?: ((preview: string, line: number) => void) | undefined;
|
|
75
106
|
}
|
|
76
107
|
|
|
77
108
|
/**
|
|
@@ -79,6 +110,7 @@ export interface RecordSyntax {
|
|
|
79
110
|
* must differ.
|
|
80
111
|
* @param syntax - the delimiter (or null for sniffing) and quote
|
|
81
112
|
* @returns the syntax unchanged; E_UNSUPPORTED when invalid
|
|
113
|
+
* @category Plugin helpers
|
|
82
114
|
*/
|
|
83
115
|
export function checkRecordSyntax(syntax: RecordSyntax): RecordSyntax {
|
|
84
116
|
for (const [option, value] of [
|
|
@@ -110,13 +142,14 @@ export function checkRecordSyntax(syntax: RecordSyntax): RecordSyntax {
|
|
|
110
142
|
* (classic Mac files); `\n` otherwise.
|
|
111
143
|
* @param text - the preview text
|
|
112
144
|
* @returns "\n" or "\r"
|
|
145
|
+
* @category Plugin helpers
|
|
113
146
|
*/
|
|
114
147
|
export function sniffNewline(text: string): "\n" | "\r" {
|
|
115
148
|
return !text.includes("\n") && text.includes("\r") ? "\r" : "\n";
|
|
116
149
|
}
|
|
117
150
|
|
|
118
151
|
/**
|
|
119
|
-
* Split a text into records synchronously (the first `maxRows` of them),
|
|
152
|
+
* Split a text into records synchronously (the first `maxRows` of them), honoring quotes; for
|
|
120
153
|
* the delimiter sniff and the registry's head sniff, where the input is a bounded preview.
|
|
121
154
|
* @param text - the text
|
|
122
155
|
* @param delimiter - the delimiter
|
|
@@ -124,86 +157,177 @@ export function sniffNewline(text: string): "\n" | "\r" {
|
|
|
124
157
|
* @param maxRows - the most rows to return
|
|
125
158
|
* @param strictQuotes - return null when a closing quote is followed by text other than the
|
|
126
159
|
* delimiter or a line break (the import would abort under this delimiter)
|
|
160
|
+
* @param collapse - the whitespace dialect (RecordSyntax.collapseSpaces) for a space delimiter
|
|
127
161
|
* @returns the rows as cell arrays (blank lines skipped), or null (see strictQuotes)
|
|
162
|
+
* @category Plugin helpers
|
|
128
163
|
*/
|
|
129
|
-
function splitRecords(
|
|
164
|
+
export function splitRecords(
|
|
130
165
|
text: string,
|
|
131
166
|
delimiter: string,
|
|
132
167
|
quote: string,
|
|
133
168
|
maxRows: number,
|
|
134
169
|
strictQuotes = false,
|
|
170
|
+
collapse = false,
|
|
135
171
|
): string[][] | null {
|
|
136
|
-
const
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
const n = text.length;
|
|
144
|
-
for (let i = 0; i < n && rows.length < maxRows; i++) {
|
|
145
|
-
const c = text.charCodeAt(i);
|
|
146
|
-
if (state === QUOTED) {
|
|
147
|
-
if (c === quoteCode) {
|
|
148
|
-
field += text.slice(segment, i);
|
|
149
|
-
state = CLOSING;
|
|
150
|
-
}
|
|
151
|
-
continue;
|
|
172
|
+
const delimiterCode = delimiter.codePointAt(0);
|
|
173
|
+
const splitter = new RecordSplitter(text, quote, delimiterCode, collapse && delimiterCode === SPACE, strictQuotes);
|
|
174
|
+
let i = 0;
|
|
175
|
+
while (i < text.length && splitter.rows.length < maxRows) {
|
|
176
|
+
i = splitter.step(i);
|
|
177
|
+
if (i < 0) {
|
|
178
|
+
return null;
|
|
152
179
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
180
|
+
}
|
|
181
|
+
if (splitter.rows.length < maxRows) {
|
|
182
|
+
splitter.finish();
|
|
183
|
+
}
|
|
184
|
+
return splitter.rows;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** The state machine of splitRecords(): one character at a time, quotes honored. */
|
|
188
|
+
class RecordSplitter {
|
|
189
|
+
/** The rows read so far. */
|
|
190
|
+
readonly rows: string[][] = [];
|
|
191
|
+
|
|
192
|
+
private cells: string[] = [];
|
|
193
|
+
|
|
194
|
+
private state: State = START;
|
|
195
|
+
|
|
196
|
+
/** Where the unread part of the current field starts. */
|
|
197
|
+
private segment = 0;
|
|
198
|
+
|
|
199
|
+
private field = "";
|
|
200
|
+
|
|
201
|
+
private readonly quoteCode: number | undefined;
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Create a splitter.
|
|
205
|
+
* @param text - the text
|
|
206
|
+
* @param quote - the quote character
|
|
207
|
+
* @param delimiterCode - the delimiter's code
|
|
208
|
+
* @param whitespace - whether runs of spaces and tabs are one separator
|
|
209
|
+
* @param strictQuotes - whether text after a closing quote gives up
|
|
210
|
+
*/
|
|
211
|
+
constructor(
|
|
212
|
+
private readonly text: string,
|
|
213
|
+
private readonly quote: string,
|
|
214
|
+
private readonly delimiterCode: number | undefined,
|
|
215
|
+
private readonly whitespace: boolean,
|
|
216
|
+
private readonly strictQuotes: boolean,
|
|
217
|
+
) {
|
|
218
|
+
this.quoteCode = quote.codePointAt(0);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Read one character.
|
|
223
|
+
* @param i - its index
|
|
224
|
+
* @returns the index of the next character to read, or -1 to give up (strictQuotes)
|
|
225
|
+
*/
|
|
226
|
+
step(i: number): number {
|
|
227
|
+
const c = this.text.codePointAt(i);
|
|
228
|
+
if (this.state === QUOTED) {
|
|
229
|
+
if (c === this.quoteCode) {
|
|
230
|
+
this.field += this.text.slice(this.segment, i);
|
|
231
|
+
this.state = CLOSING;
|
|
162
232
|
}
|
|
163
|
-
|
|
164
|
-
segment = i;
|
|
233
|
+
return i + 1;
|
|
165
234
|
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
cells.push(field);
|
|
172
|
-
field = "";
|
|
173
|
-
state = START;
|
|
174
|
-
} else {
|
|
175
|
-
if (state !== START || cells.length > 0) {
|
|
176
|
-
cells.push(field);
|
|
177
|
-
rows.push(cells);
|
|
178
|
-
cells = [];
|
|
179
|
-
field = "";
|
|
180
|
-
}
|
|
181
|
-
state = START;
|
|
182
|
-
if (c === CR && text.charCodeAt(i + 1) === LF) {
|
|
183
|
-
i++;
|
|
184
|
-
}
|
|
235
|
+
const isDelimiter = c === this.delimiterCode || (this.whitespace && c === TAB);
|
|
236
|
+
if (this.state === CLOSING) {
|
|
237
|
+
const next = this.afterQuote(i, c, isDelimiter || c === LF || c === CR);
|
|
238
|
+
if (next !== null) {
|
|
239
|
+
return next;
|
|
185
240
|
}
|
|
186
|
-
segment = i + 1;
|
|
187
|
-
continue;
|
|
188
241
|
}
|
|
189
|
-
if (
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
242
|
+
if (isDelimiter || c === LF || c === CR) {
|
|
243
|
+
return this.separator(i, isDelimiter);
|
|
244
|
+
}
|
|
245
|
+
if (this.state === START) {
|
|
246
|
+
const quoted = c === this.quoteCode;
|
|
247
|
+
this.state = quoted ? QUOTED : UNQUOTED;
|
|
248
|
+
this.segment = quoted ? i + 1 : i;
|
|
249
|
+
}
|
|
250
|
+
return i + 1;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Read the character after a quote that may close a quoted field: a second quote is a quote
|
|
255
|
+
* character in the field; anything else closes it.
|
|
256
|
+
* @param i - its index
|
|
257
|
+
* @param c - its code
|
|
258
|
+
* @param separates - whether it is a delimiter or a line break
|
|
259
|
+
* @returns the index of the next character to read, -1 to give up (strictQuotes), or null to
|
|
260
|
+
* read this character as the one after the field
|
|
261
|
+
*/
|
|
262
|
+
private afterQuote(i: number, c: number | undefined, separates: boolean): number | null {
|
|
263
|
+
if (c === this.quoteCode) {
|
|
264
|
+
this.field += this.quote;
|
|
265
|
+
this.segment = i + 1;
|
|
266
|
+
this.state = QUOTED;
|
|
267
|
+
return i + 1;
|
|
268
|
+
}
|
|
269
|
+
if (this.strictQuotes && !separates) {
|
|
270
|
+
return -1;
|
|
271
|
+
}
|
|
272
|
+
this.state = AFTER_QUOTED;
|
|
273
|
+
this.segment = i;
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Read a delimiter or a line break: end the field, and at a line break the row.
|
|
279
|
+
* @param i - its index
|
|
280
|
+
* @param isDelimiter - whether it is a delimiter
|
|
281
|
+
* @returns the index of the next character to read
|
|
282
|
+
*/
|
|
283
|
+
private separator(i: number, isDelimiter: boolean): number {
|
|
284
|
+
if (this.state === UNQUOTED || this.state === AFTER_QUOTED) {
|
|
285
|
+
this.field += this.text.slice(this.segment, i);
|
|
286
|
+
}
|
|
287
|
+
let next = i + 1;
|
|
288
|
+
if (isDelimiter && this.whitespace && this.state === START) {
|
|
289
|
+
// a run of whitespace, or indentation: one separator
|
|
290
|
+
} else if (isDelimiter) {
|
|
291
|
+
this.cells.push(this.field);
|
|
292
|
+
this.field = "";
|
|
293
|
+
this.state = START;
|
|
294
|
+
} else {
|
|
295
|
+
this.endRow();
|
|
296
|
+
this.state = START;
|
|
297
|
+
if (this.text.codePointAt(i) === CR && this.text.codePointAt(i + 1) === LF) {
|
|
298
|
+
next = i + 2;
|
|
196
299
|
}
|
|
197
300
|
}
|
|
301
|
+
this.segment = next;
|
|
302
|
+
return next;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/** End the row at a line break; a blank line is no row. */
|
|
306
|
+
private endRow(): void {
|
|
307
|
+
if (this.state === START && this.cells.length === 0) {
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
if (!(this.whitespace && this.state === START)) {
|
|
311
|
+
this.cells.push(this.field);
|
|
312
|
+
}
|
|
313
|
+
this.rows.push(this.cells);
|
|
314
|
+
this.cells = [];
|
|
315
|
+
this.field = "";
|
|
198
316
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
317
|
+
|
|
318
|
+
/** End the text: the last row, when it has no line break after it. */
|
|
319
|
+
finish(): void {
|
|
320
|
+
if (this.state === START && this.cells.length === 0) {
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
if (this.state === UNQUOTED || this.state === AFTER_QUOTED || this.state === QUOTED) {
|
|
324
|
+
this.field += this.text.slice(this.segment);
|
|
325
|
+
}
|
|
326
|
+
if (!(this.whitespace && this.state === START)) {
|
|
327
|
+
this.cells.push(this.field);
|
|
202
328
|
}
|
|
203
|
-
|
|
204
|
-
rows.push(cells);
|
|
329
|
+
this.rows.push(this.cells);
|
|
205
330
|
}
|
|
206
|
-
return rows;
|
|
207
331
|
}
|
|
208
332
|
|
|
209
333
|
/**
|
|
@@ -212,30 +336,37 @@ function splitRecords(
|
|
|
212
336
|
* @param text - the preview text
|
|
213
337
|
* @param comments - the comment characters
|
|
214
338
|
* @returns the text from the first non-comment line on
|
|
339
|
+
* @category Plugin helpers
|
|
215
340
|
*/
|
|
216
|
-
function stripLeadingComments(text: string, comments: readonly string[]): string {
|
|
341
|
+
export function stripLeadingComments(text: string, comments: readonly string[]): string {
|
|
217
342
|
if (comments.length === 0) {
|
|
218
343
|
return text;
|
|
219
344
|
}
|
|
220
345
|
let at = 0;
|
|
221
|
-
while (at < text.length
|
|
346
|
+
while (at < text.length) {
|
|
347
|
+
if (text[at] === "\n" || text[at] === "\r") {
|
|
348
|
+
// a blank line among the leading comments (the scanner skips it too)
|
|
349
|
+
at++;
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
if (!comments.includes(text[at])) {
|
|
353
|
+
break;
|
|
354
|
+
}
|
|
222
355
|
const lf = text.indexOf("\n", at);
|
|
223
356
|
const cr = text.indexOf("\r", at);
|
|
224
357
|
const end = Math.min(lf < 0 ? Infinity : lf, cr < 0 ? Infinity : cr);
|
|
225
358
|
if (end === Infinity) {
|
|
226
359
|
return "";
|
|
227
360
|
}
|
|
228
|
-
at = end
|
|
229
|
-
if (text[at - 1] === "\r" && text[at] === "\n") {
|
|
230
|
-
at++;
|
|
231
|
-
}
|
|
361
|
+
at = end;
|
|
232
362
|
}
|
|
233
363
|
return at === 0 ? text : text.slice(at);
|
|
234
364
|
}
|
|
235
365
|
|
|
236
366
|
/**
|
|
237
367
|
* Sniff the delimiter of a text: every candidate is tried over the first rows and the one whose
|
|
238
|
-
* field count is most consistent across rows wins (
|
|
368
|
+
* field count is most consistent across rows wins (a tie goes to the earlier candidate, so a
|
|
369
|
+
* space inside comma-separated cells never beats the comma); a
|
|
239
370
|
* candidate that yields fewer than two fields per row on average is never chosen. `null` when no
|
|
240
371
|
* candidate qualifies (a single-column file).
|
|
241
372
|
* @param text - the preview text
|
|
@@ -247,7 +378,9 @@ function stripLeadingComments(text: string, comments: readonly string[]): string
|
|
|
247
378
|
* by other text. Only for inputs whose field counts say nothing (an adjacency table's rows vary in
|
|
248
379
|
* width): elsewhere a stray character after a quote would make a worse candidate win silently
|
|
249
380
|
* instead of aborting the read
|
|
381
|
+
* @param collapse - read a space candidate in the whitespace dialect (RecordSyntax.collapseSpaces)
|
|
250
382
|
* @returns the delimiter, or null
|
|
383
|
+
* @category Plugin helpers
|
|
251
384
|
*/
|
|
252
385
|
export function sniffDelimiter(
|
|
253
386
|
text: string,
|
|
@@ -255,16 +388,16 @@ export function sniffDelimiter(
|
|
|
255
388
|
candidates: readonly string[] = DELIMITER_CANDIDATES,
|
|
256
389
|
quote = '"',
|
|
257
390
|
skipQuoteErrors = false,
|
|
391
|
+
collapse = false,
|
|
258
392
|
): string | null {
|
|
259
393
|
let best: string | null = null;
|
|
260
394
|
let bestDelta = Infinity;
|
|
261
|
-
let bestAverage = 0;
|
|
262
395
|
const terminated = text.endsWith("\n") || text.endsWith("\r");
|
|
263
396
|
for (const delimiter of candidates) {
|
|
264
397
|
if (delimiter === quote || delimiter === newline) {
|
|
265
398
|
continue;
|
|
266
399
|
}
|
|
267
|
-
const split = splitRecords(text, delimiter, quote, PREVIEW_ROWS, skipQuoteErrors);
|
|
400
|
+
const split = splitRecords(text, delimiter, quote, PREVIEW_ROWS, skipQuoteErrors, collapse);
|
|
268
401
|
if (split === null) {
|
|
269
402
|
// a quoted cell this delimiter does not close: it is not the file's delimiter
|
|
270
403
|
continue;
|
|
@@ -287,10 +420,9 @@ export function sniffDelimiter(
|
|
|
287
420
|
}
|
|
288
421
|
}
|
|
289
422
|
const average = total / rows.length;
|
|
290
|
-
if (average > 1.99 &&
|
|
423
|
+
if (average > 1.99 && delta < bestDelta) {
|
|
291
424
|
best = delimiter;
|
|
292
425
|
bestDelta = delta;
|
|
293
|
-
bestAverage = average;
|
|
294
426
|
}
|
|
295
427
|
}
|
|
296
428
|
return best;
|
|
@@ -312,6 +444,7 @@ function isBlankRow(row: readonly string[]): boolean {
|
|
|
312
444
|
* are skipped. The arrays are reused between records. When the syntax gives no delimiter, the
|
|
313
445
|
* first rows (PREVIEW_ROWS, or PREVIEW_CHARS characters) are buffered and the delimiter sniffed
|
|
314
446
|
* from them before the first record is yielded.
|
|
447
|
+
* @category Plugin helpers
|
|
315
448
|
*/
|
|
316
449
|
export class RecordReader implements AsyncIterable<number> {
|
|
317
450
|
/** The cells of the record most recently yielded; only the first `count` entries are valid. */
|
|
@@ -323,9 +456,18 @@ export class RecordReader implements AsyncIterable<number> {
|
|
|
323
456
|
/** The leading comment lines (without their line breaks), in order; empty without `syntax.comments`. */
|
|
324
457
|
readonly leadingComments: string[] = [];
|
|
325
458
|
|
|
459
|
+
/** The 1-based line of each leading comment, parallel to `leadingComments`. */
|
|
460
|
+
readonly leadingCommentLines: number[] = [];
|
|
461
|
+
|
|
462
|
+
/** The line of the first quote inside an unquoted field (kept as text, RFC 4180 forbids it); 0 for none. */
|
|
463
|
+
strayQuoteLine = 0;
|
|
464
|
+
|
|
465
|
+
/** Whether the sniff took the delimiter from an Excel `sep=X` first line (yielded as the first record). */
|
|
466
|
+
sepDirective = false;
|
|
467
|
+
|
|
326
468
|
private readonly input: ImportInput;
|
|
327
469
|
|
|
328
|
-
|
|
470
|
+
readonly report: ImportReportBuilder;
|
|
329
471
|
|
|
330
472
|
private readonly readOptions: ReadOptions;
|
|
331
473
|
|
|
@@ -398,12 +540,15 @@ export class RecordReader implements AsyncIterable<number> {
|
|
|
398
540
|
/**
|
|
399
541
|
* Record a text-after-quote error (fatal).
|
|
400
542
|
* @param line - the line the field started on
|
|
543
|
+
* @param field - the 1-based position of the field in its record
|
|
401
544
|
* @returns never
|
|
402
545
|
*/
|
|
403
|
-
badQuote(line: number): never {
|
|
404
|
-
return this.report.fail(
|
|
405
|
-
|
|
406
|
-
|
|
546
|
+
badQuote(line: number, field: number): never {
|
|
547
|
+
return this.report.fail(
|
|
548
|
+
BAD_QUOTE_CODE,
|
|
549
|
+
`text after the closing quote of quoted field ${field} (a quote inside a quoted field is written as two quotes)`,
|
|
550
|
+
{ line },
|
|
551
|
+
);
|
|
407
552
|
}
|
|
408
553
|
|
|
409
554
|
/**
|
|
@@ -435,6 +580,11 @@ export class RecordReader implements AsyncIterable<number> {
|
|
|
435
580
|
let breaks = 0;
|
|
436
581
|
let lastWasCr = false;
|
|
437
582
|
let sniffed = false;
|
|
583
|
+
// the leading comment lines are left out of the sniff, so they do not count toward its rows
|
|
584
|
+
const commentCodes = (this.syntax.comments ?? []).map((c) => c.codePointAt(0));
|
|
585
|
+
let leading = commentCodes.length > 0;
|
|
586
|
+
let lineStart = true;
|
|
587
|
+
let inComment = false;
|
|
438
588
|
for await (const chunk of source) {
|
|
439
589
|
if (sniffed) {
|
|
440
590
|
yield chunk;
|
|
@@ -444,12 +594,18 @@ export class RecordReader implements AsyncIterable<number> {
|
|
|
444
594
|
length += chunk.length;
|
|
445
595
|
for (let i = 0; i < chunk.length && breaks < PREVIEW_ROWS; i++) {
|
|
446
596
|
const c = chunk.charCodeAt(i);
|
|
447
|
-
if (c === LF) {
|
|
448
|
-
if (!lastWasCr) {
|
|
597
|
+
if (c === LF || c === CR) {
|
|
598
|
+
if (!(c === LF && lastWasCr) && !inComment) {
|
|
449
599
|
breaks++;
|
|
450
600
|
}
|
|
451
|
-
|
|
452
|
-
|
|
601
|
+
inComment = false;
|
|
602
|
+
lineStart = true;
|
|
603
|
+
} else {
|
|
604
|
+
if (lineStart && leading) {
|
|
605
|
+
inComment = commentCodes.includes(c);
|
|
606
|
+
leading = inComment;
|
|
607
|
+
}
|
|
608
|
+
lineStart = false;
|
|
453
609
|
}
|
|
454
610
|
lastWasCr = c === CR;
|
|
455
611
|
}
|
|
@@ -476,13 +632,30 @@ export class RecordReader implements AsyncIterable<number> {
|
|
|
476
632
|
* @param text - the preview
|
|
477
633
|
*/
|
|
478
634
|
private sniff(text: string): void {
|
|
479
|
-
const candidates = this.syntax.candidates ?? DELIMITER_CANDIDATES;
|
|
480
635
|
// the sniff is a heuristic over the first rows: a single chunk holding a huge quoted cell
|
|
481
636
|
// is capped so the candidate scans stay bounded
|
|
482
|
-
const
|
|
637
|
+
const preview = text.slice(0, PREVIEW_CHARS);
|
|
638
|
+
const body = stripLeadingComments(preview, this.syntax.comments ?? []);
|
|
639
|
+
if (this.syntax.inspect !== undefined) {
|
|
640
|
+
const skipped = preview.slice(0, preview.length - body.length).match(/\r\n|\r|\n/g)?.length ?? 0;
|
|
641
|
+
this.syntax.inspect(body, skipped + 1);
|
|
642
|
+
}
|
|
643
|
+
const directive = this.syntax.sepDirective === true ? SEP_DIRECTIVE.exec(text) : null;
|
|
644
|
+
if (directive !== null && directive[1] !== this.syntax.quote) {
|
|
645
|
+
this.delimiterText = directive[1];
|
|
646
|
+
this.sepDirective = true;
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
const candidates = this.syntax.candidates ?? DELIMITER_CANDIDATES;
|
|
483
650
|
this.delimiterText =
|
|
484
|
-
sniffDelimiter(
|
|
485
|
-
|
|
651
|
+
sniffDelimiter(
|
|
652
|
+
body,
|
|
653
|
+
sniffNewline(body),
|
|
654
|
+
candidates,
|
|
655
|
+
this.syntax.quote,
|
|
656
|
+
this.syntax.skipQuoteErrors,
|
|
657
|
+
this.syntax.collapseSpaces,
|
|
658
|
+
) ?? candidates[0];
|
|
486
659
|
}
|
|
487
660
|
}
|
|
488
661
|
|
|
@@ -515,6 +688,9 @@ class RecordScanner {
|
|
|
515
688
|
|
|
516
689
|
private delimiterCode = -1;
|
|
517
690
|
|
|
691
|
+
/** The whitespace dialect: the delimiter is a space and RecordSyntax.collapseSpaces is set. */
|
|
692
|
+
private collapse = false;
|
|
693
|
+
|
|
518
694
|
private state: State = START;
|
|
519
695
|
|
|
520
696
|
private field = "";
|
|
@@ -525,7 +701,7 @@ class RecordScanner {
|
|
|
525
701
|
|
|
526
702
|
private lastWasCr = false;
|
|
527
703
|
|
|
528
|
-
/** Whether no record has started yet (comment lines are
|
|
704
|
+
/** Whether no record has started yet (comment lines are recognized until one does). */
|
|
529
705
|
private leading: boolean;
|
|
530
706
|
|
|
531
707
|
private comment = "";
|
|
@@ -552,6 +728,7 @@ class RecordScanner {
|
|
|
552
728
|
setDelimiter(code: number): void {
|
|
553
729
|
if (this.delimiterCode < 0) {
|
|
554
730
|
this.delimiterCode = code;
|
|
731
|
+
this.collapse = code === SPACE && this.reader.syntax.collapseSpaces === true;
|
|
555
732
|
}
|
|
556
733
|
}
|
|
557
734
|
|
|
@@ -570,8 +747,9 @@ class RecordScanner {
|
|
|
570
747
|
const c = chunk.charCodeAt(i);
|
|
571
748
|
if (this.state === COMMENT) {
|
|
572
749
|
if (c === LF || c === CR) {
|
|
573
|
-
this.comment
|
|
750
|
+
this.comment = this.grow(this.comment, chunk.slice(this.segment, i));
|
|
574
751
|
this.reader.leadingComments.push(this.comment);
|
|
752
|
+
this.reader.leadingCommentLines.push(this.line);
|
|
575
753
|
this.comment = "";
|
|
576
754
|
this.state = START;
|
|
577
755
|
this.endLine(c);
|
|
@@ -591,29 +769,30 @@ class RecordScanner {
|
|
|
591
769
|
i = n;
|
|
592
770
|
break;
|
|
593
771
|
}
|
|
594
|
-
this.field
|
|
772
|
+
this.field = this.grow(this.field, chunk.slice(this.segment, q));
|
|
595
773
|
this.state = CLOSING;
|
|
596
774
|
this.lastWasCr = false;
|
|
597
775
|
i = q + 1;
|
|
598
776
|
continue;
|
|
599
777
|
}
|
|
778
|
+
const isDelimiter = c === delimiterCode || (this.collapse && c === TAB);
|
|
600
779
|
if (this.state === CLOSING) {
|
|
601
780
|
if (c === quoteCode) {
|
|
602
|
-
this.field
|
|
781
|
+
this.field = this.grow(this.field, this.quoteText);
|
|
603
782
|
this.segment = i + 1;
|
|
604
783
|
this.state = QUOTED;
|
|
605
784
|
this.lastWasCr = false;
|
|
606
785
|
i++;
|
|
607
786
|
continue;
|
|
608
787
|
}
|
|
609
|
-
if (
|
|
610
|
-
this.reader.badQuote(this.startLine);
|
|
788
|
+
if (!isDelimiter && c !== LF && c !== CR) {
|
|
789
|
+
this.reader.badQuote(this.startLine, this.count + 1);
|
|
611
790
|
}
|
|
612
791
|
this.state = AFTER_QUOTED;
|
|
613
792
|
this.segment = i;
|
|
614
793
|
// fall through to the delimiter / line-end handling below without consuming c
|
|
615
794
|
}
|
|
616
|
-
if (
|
|
795
|
+
if (isDelimiter || c === LF || c === CR) {
|
|
617
796
|
if (c === LF && this.lastWasCr) {
|
|
618
797
|
// the second half of a CRLF already ended the record
|
|
619
798
|
this.lastWasCr = false;
|
|
@@ -622,9 +801,16 @@ class RecordScanner {
|
|
|
622
801
|
continue;
|
|
623
802
|
}
|
|
624
803
|
if (this.state === UNQUOTED) {
|
|
625
|
-
this.field
|
|
804
|
+
this.field = this.grow(this.field, chunk.slice(this.segment, i));
|
|
805
|
+
}
|
|
806
|
+
if (isDelimiter && this.collapse && this.state === START) {
|
|
807
|
+
// a run of whitespace, or indentation: one separator
|
|
808
|
+
this.lastWasCr = false;
|
|
809
|
+
this.segment = i + 1;
|
|
810
|
+
i++;
|
|
811
|
+
continue;
|
|
626
812
|
}
|
|
627
|
-
if (
|
|
813
|
+
if (isDelimiter) {
|
|
628
814
|
this.push();
|
|
629
815
|
this.state = START;
|
|
630
816
|
this.lastWasCr = false;
|
|
@@ -632,9 +818,11 @@ class RecordScanner {
|
|
|
632
818
|
i++;
|
|
633
819
|
continue;
|
|
634
820
|
}
|
|
635
|
-
|
|
636
|
-
if (!
|
|
821
|
+
// a line ending right after a whitespace run in the whitespace dialect ends no field
|
|
822
|
+
if (!(this.state === START && (this.count === 0 || this.collapse))) {
|
|
637
823
|
this.push();
|
|
824
|
+
}
|
|
825
|
+
if (this.count > 0) {
|
|
638
826
|
this.ready = true;
|
|
639
827
|
this.recordLine = this.startLine;
|
|
640
828
|
}
|
|
@@ -664,6 +852,8 @@ class RecordScanner {
|
|
|
664
852
|
this.state = UNQUOTED;
|
|
665
853
|
this.segment = i;
|
|
666
854
|
}
|
|
855
|
+
} else if (c === quoteCode && this.state === UNQUOTED && this.reader.strayQuoteLine === 0) {
|
|
856
|
+
this.reader.strayQuoteLine = this.startLine;
|
|
667
857
|
}
|
|
668
858
|
i++;
|
|
669
859
|
}
|
|
@@ -680,9 +870,9 @@ class RecordScanner {
|
|
|
680
870
|
return;
|
|
681
871
|
}
|
|
682
872
|
if (this.state === COMMENT) {
|
|
683
|
-
this.comment
|
|
873
|
+
this.comment = this.grow(this.comment, chunk.slice(this.segment, n));
|
|
684
874
|
} else if (this.state === QUOTED || this.state === UNQUOTED) {
|
|
685
|
-
this.field
|
|
875
|
+
this.field = this.grow(this.field, chunk.slice(this.segment, n));
|
|
686
876
|
}
|
|
687
877
|
this.segment = n;
|
|
688
878
|
}
|
|
@@ -696,13 +886,16 @@ class RecordScanner {
|
|
|
696
886
|
const { state } = this;
|
|
697
887
|
if (state === COMMENT) {
|
|
698
888
|
this.reader.leadingComments.push(this.comment);
|
|
889
|
+
this.reader.leadingCommentLines.push(this.line);
|
|
699
890
|
return false;
|
|
700
891
|
}
|
|
701
892
|
if (state === QUOTED) {
|
|
702
893
|
this.reader.unclosedQuote(this.startLine);
|
|
703
894
|
}
|
|
704
895
|
if (state !== START || this.count > 0) {
|
|
705
|
-
this.
|
|
896
|
+
if (!(state === START && this.collapse)) {
|
|
897
|
+
this.push();
|
|
898
|
+
}
|
|
706
899
|
this.recordLine = this.startLine;
|
|
707
900
|
return true;
|
|
708
901
|
}
|
|
@@ -716,23 +909,46 @@ class RecordScanner {
|
|
|
716
909
|
* @param to - the index after the run
|
|
717
910
|
*/
|
|
718
911
|
private countLineBreaks(chunk: string, from: number, to: number): void {
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
912
|
+
// indexOf, not a charCodeAt loop: a quoted cell may be hundreds of MiB, and a per-character
|
|
913
|
+
// loop over it is the whole cost of reading it (and many times that under coverage)
|
|
914
|
+
const run = chunk.slice(from, to);
|
|
915
|
+
let { line } = this;
|
|
916
|
+
// the index of the last CR seen; -1 is a CR just before the run, -2 none
|
|
917
|
+
let lastCr = this.lastWasCr ? -1 : -2;
|
|
918
|
+
let lf = run.indexOf("\n");
|
|
919
|
+
let cr = run.indexOf("\r");
|
|
920
|
+
while (lf >= 0 || cr >= 0) {
|
|
921
|
+
if (lf < 0 || (cr >= 0 && cr < lf)) {
|
|
728
922
|
line++;
|
|
729
|
-
|
|
923
|
+
lastCr = cr;
|
|
924
|
+
cr = run.indexOf("\r", cr + 1);
|
|
730
925
|
} else {
|
|
731
|
-
|
|
926
|
+
if (lf !== lastCr + 1) {
|
|
927
|
+
line++;
|
|
928
|
+
}
|
|
929
|
+
lf = run.indexOf("\n", lf + 1);
|
|
732
930
|
}
|
|
733
931
|
}
|
|
734
932
|
this.line = line;
|
|
735
|
-
this.lastWasCr =
|
|
933
|
+
this.lastWasCr = lastCr === run.length - 1;
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
/**
|
|
937
|
+
* Append to the field or comment in progress, failing with E_TOO_LARGE instead of a RangeError
|
|
938
|
+
* when the result would be longer than one JavaScript string can hold.
|
|
939
|
+
* @param text - the text so far
|
|
940
|
+
* @param piece - the text to append
|
|
941
|
+
* @returns the joined text
|
|
942
|
+
*/
|
|
943
|
+
private grow(text: string, piece: string): string {
|
|
944
|
+
if (text.length + piece.length > MAX_TEXT_LENGTH) {
|
|
945
|
+
tooLarge(
|
|
946
|
+
this.reader.report,
|
|
947
|
+
`a cell is longer than ${MAX_TEXT_LENGTH} characters, the most one JavaScript string holds`,
|
|
948
|
+
this.startLine,
|
|
949
|
+
);
|
|
950
|
+
}
|
|
951
|
+
return text + piece;
|
|
736
952
|
}
|
|
737
953
|
|
|
738
954
|
/** Store the field in progress as the next cell. */
|
|
@@ -758,7 +974,10 @@ class RecordScanner {
|
|
|
758
974
|
}
|
|
759
975
|
}
|
|
760
976
|
|
|
761
|
-
/**
|
|
977
|
+
/**
|
|
978
|
+
* What the CSV importer's reader needs besides the input.
|
|
979
|
+
* @category Plugin helpers
|
|
980
|
+
*/
|
|
762
981
|
export interface CsvReaderOptions extends ReadOptions {
|
|
763
982
|
/** The delimiter; null or undefined sniffs it from the preview. */
|
|
764
983
|
readonly delimiter?: string | null | undefined;
|
|
@@ -766,6 +985,8 @@ export interface CsvReaderOptions extends ReadOptions {
|
|
|
766
985
|
readonly comments?: readonly string[] | undefined;
|
|
767
986
|
/** RecordSyntax.skipQuoteErrors; false by default. */
|
|
768
987
|
readonly skipQuoteErrors?: boolean | undefined;
|
|
988
|
+
/** RecordSyntax.inspect; none by default. */
|
|
989
|
+
readonly inspect?: ((preview: string, line: number) => void) | undefined;
|
|
769
990
|
}
|
|
770
991
|
|
|
771
992
|
/**
|
|
@@ -774,7 +995,10 @@ export interface CsvReaderOptions extends ReadOptions {
|
|
|
774
995
|
* on. The delimiter is known after the first row (`reader.delimiter`). Rows come out as fresh
|
|
775
996
|
* arrays of cell texts exactly as written (no trimming, no typing); `reader.quoted` tells, for
|
|
776
997
|
* the row just yielded, which cells were quoted (a quoted empty cell is the empty string, an
|
|
777
|
-
* unquoted one is "not set"). Blank lines
|
|
998
|
+
* unquoted one is "not set"). Blank lines, whitespace-only lines and rows of empty unquoted cells
|
|
999
|
+
* are skipped. A space delimiter reads the whitespace dialect (RecordSyntax.collapseSpaces), and an
|
|
1000
|
+
* Excel `sep=X` first line names the delimiter when none is given.
|
|
1001
|
+
* @category Plugin helpers
|
|
778
1002
|
*/
|
|
779
1003
|
export class CsvRecordReader implements AsyncIterable<string[]> {
|
|
780
1004
|
private readonly inner: RecordReader;
|
|
@@ -794,8 +1018,17 @@ export class CsvRecordReader implements AsyncIterable<string[]> {
|
|
|
794
1018
|
quote: '"',
|
|
795
1019
|
comments: options.comments,
|
|
796
1020
|
skipQuoteErrors: options.skipQuoteErrors,
|
|
1021
|
+
collapseSpaces: true,
|
|
1022
|
+
sepDirective: true,
|
|
1023
|
+
inspect: options.inspect,
|
|
1024
|
+
},
|
|
1025
|
+
{
|
|
1026
|
+
signal: options.signal,
|
|
1027
|
+
onProgress: options.onProgress,
|
|
1028
|
+
encoding: options.encoding,
|
|
1029
|
+
allowEmpty: options.allowEmpty,
|
|
1030
|
+
nulIsBinary: true,
|
|
797
1031
|
},
|
|
798
|
-
{ signal: options.signal, onProgress: options.onProgress, encoding: options.encoding },
|
|
799
1032
|
);
|
|
800
1033
|
}
|
|
801
1034
|
|
|
@@ -807,6 +1040,14 @@ export class CsvRecordReader implements AsyncIterable<string[]> {
|
|
|
807
1040
|
return this.inner.leadingComments;
|
|
808
1041
|
}
|
|
809
1042
|
|
|
1043
|
+
/**
|
|
1044
|
+
* The 1-based line of each leading comment, parallel to `leadingComments`.
|
|
1045
|
+
* @returns the lines
|
|
1046
|
+
*/
|
|
1047
|
+
get leadingCommentLines(): readonly number[] {
|
|
1048
|
+
return this.inner.leadingCommentLines;
|
|
1049
|
+
}
|
|
1050
|
+
|
|
810
1051
|
/**
|
|
811
1052
|
* The 1-based line the most recently yielded row starts on.
|
|
812
1053
|
* @returns the line; 0 before the first row
|
|
@@ -831,6 +1072,22 @@ export class CsvRecordReader implements AsyncIterable<string[]> {
|
|
|
831
1072
|
return this.inner.quoted;
|
|
832
1073
|
}
|
|
833
1074
|
|
|
1075
|
+
/**
|
|
1076
|
+
* The line of the first quote inside an unquoted field.
|
|
1077
|
+
* @returns the line, or 0 when there was none
|
|
1078
|
+
*/
|
|
1079
|
+
get strayQuoteLine(): number {
|
|
1080
|
+
return this.inner.strayQuoteLine;
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
/**
|
|
1084
|
+
* Whether the first record is an Excel `sep=X` line the delimiter was taken from.
|
|
1085
|
+
* @returns true when it is
|
|
1086
|
+
*/
|
|
1087
|
+
get sepDirective(): boolean {
|
|
1088
|
+
return this.inner.sepDirective;
|
|
1089
|
+
}
|
|
1090
|
+
|
|
834
1091
|
/**
|
|
835
1092
|
* Iterate the rows.
|
|
836
1093
|
* @yields one row at a time as its cell texts
|
|
@@ -839,10 +1096,27 @@ export class CsvRecordReader implements AsyncIterable<string[]> {
|
|
|
839
1096
|
async *[Symbol.asyncIterator](): AsyncGenerator<string[], void, undefined> {
|
|
840
1097
|
const { inner } = this;
|
|
841
1098
|
for await (const count of inner) {
|
|
842
|
-
if (
|
|
1099
|
+
if (isBlankRecord(inner.cells, inner.quoted, count)) {
|
|
843
1100
|
continue;
|
|
844
1101
|
}
|
|
845
1102
|
yield inner.cells.slice(0, count);
|
|
846
1103
|
}
|
|
847
1104
|
}
|
|
848
1105
|
}
|
|
1106
|
+
|
|
1107
|
+
/**
|
|
1108
|
+
* Whether a record holds nothing: every cell unquoted and blank (a blank line, a whitespace-only
|
|
1109
|
+
* line, or an Excel empty row `,,,`). A quoted empty cell is a value.
|
|
1110
|
+
* @param cells - the cells
|
|
1111
|
+
* @param quoted - whether each was quoted
|
|
1112
|
+
* @param count - the cell count
|
|
1113
|
+
* @returns true when the record carries no value
|
|
1114
|
+
*/
|
|
1115
|
+
function isBlankRecord(cells: readonly string[], quoted: readonly boolean[], count: number): boolean {
|
|
1116
|
+
for (let k = 0; k < count; k++) {
|
|
1117
|
+
if (quoted[k] || cells[k].trim().length > 0) {
|
|
1118
|
+
return false;
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
return true;
|
|
1122
|
+
}
|