@graphty/graph-io 0.3.24 → 0.3.26
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-BbJI55dx.js +56 -0
- package/dist/chunks/choose-BbJI55dx.js.map +1 -0
- package/dist/chunks/constants-BCsFsMj4.js +231 -0
- package/dist/chunks/constants-BCsFsMj4.js.map +1 -0
- package/dist/chunks/{escape-B-tkk_Cl.js → escape-D0g6NNUy.js} +382 -92
- package/dist/chunks/escape-D0g6NNUy.js.map +1 -0
- package/dist/chunks/export-BTI4yQgy.js +4333 -0
- package/dist/chunks/export-BTI4yQgy.js.map +1 -0
- package/dist/chunks/exporter-BMqk5oxW.js +3465 -0
- package/dist/chunks/exporter-BMqk5oxW.js.map +1 -0
- package/dist/chunks/{importer-eUREhBGj.js → exporter-Cra2GYcN.js} +4969 -3201
- package/dist/chunks/exporter-Cra2GYcN.js.map +1 -0
- package/dist/chunks/{exporter-BGrsWamJ.js → exporter-Dxh231ZF.js} +38 -229
- package/dist/chunks/exporter-Dxh231ZF.js.map +1 -0
- package/dist/chunks/{exporter-DIeGJXAZ.js → exporter-tne475KK.js} +22 -20
- package/dist/chunks/exporter-tne475KK.js.map +1 -0
- package/dist/chunks/importer-90YWhJoF.js +2811 -0
- package/dist/chunks/importer-90YWhJoF.js.map +1 -0
- package/dist/chunks/{importer-D_e7e7LX.js → importer-Bcl1d4xH.js} +398 -145
- package/dist/chunks/importer-Bcl1d4xH.js.map +1 -0
- package/dist/chunks/{importer-BWY2FFc5.js → importer-BfIsp1f9.js} +255 -77
- package/dist/chunks/importer-BfIsp1f9.js.map +1 -0
- package/dist/chunks/{importer-zGLo8Dg_.js → importer-C_TEA1zJ.js} +484 -157
- package/dist/chunks/importer-C_TEA1zJ.js.map +1 -0
- package/dist/chunks/{importer-DET1tg6s.js → importer-DCjOvO7U.js} +1687 -310
- package/dist/chunks/importer-DCjOvO7U.js.map +1 -0
- package/dist/chunks/{importer-vi3bSdtw.js → importer-DfVu6j59.js} +509 -76
- package/dist/chunks/importer-DfVu6j59.js.map +1 -0
- package/dist/chunks/{importer-DIrbbnAf.js → importer-Dku_VAIY.js} +585 -226
- package/dist/chunks/importer-Dku_VAIY.js.map +1 -0
- package/dist/chunks/json-elements-3tnyt5uZ.js +1225 -0
- package/dist/chunks/json-elements-3tnyt5uZ.js.map +1 -0
- package/dist/chunks/ontology-export-B9Tcf37D.js +357 -0
- package/dist/chunks/ontology-export-B9Tcf37D.js.map +1 -0
- package/dist/chunks/{records-dbRkxwaq.js → records-CQXT__mY.js} +298 -114
- package/dist/chunks/records-CQXT__mY.js.map +1 -0
- package/dist/chunks/{text-Dr0Ifpag.js → text-DmcUs5_Q.js} +29 -11
- package/dist/chunks/text-DmcUs5_Q.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 +951 -261
- 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 +342 -85
- 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 +564 -157
- 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 +19 -1
- 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 +33 -17
- 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 +868 -188
- 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 +2 -0
- 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 +400 -83
- 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 +145 -32
- 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 +680 -285
- 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 +226 -79
- 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 +98 -24
- 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 +108 -26
- 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 +238 -85
- 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 +66 -34
- 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 +183 -51
- 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 +20 -21
- 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 +7 -2
- 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 +38 -4
- package/src/common/direction.ts +78 -51
- package/src/common/escape.ts +33 -17
- 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 +1034 -198
- package/src/common/line-format.ts +249 -0
- package/src/common/lists.ts +6 -1
- 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 +477 -88
- package/src/common/zip.ts +169 -7
- package/src/formats/csv/exporter.ts +201 -46
- 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 +888 -300
- package/src/formats/cx/index.ts +4 -3
- package/src/formats/cx2/exporter.ts +61 -338
- package/src/formats/cx2/importer.ts +317 -86
- 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 +139 -31
- 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 +111 -26
- 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 +320 -101
- 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 +89 -41
- package/src/formats/pajek/importer.ts +407 -93
- package/src/formats/pajek/index.ts +2 -1
- package/src/formats/pajek/syntax.ts +213 -52
- 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 +35 -27
- package/src/formats/xgmml/importer.ts +64 -86
- package/src/formats/xgmml/index.ts +1 -0
- package/src/formats/xgmml/values.ts +11 -3
- 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
package/src/common/xml.ts
CHANGED
|
@@ -8,11 +8,13 @@
|
|
|
8
8
|
* instead of re-reading the token from its `<`, and line numbers are counted from a cached next
|
|
9
9
|
* line-break position, so a document without line breaks costs the same as one with them.
|
|
10
10
|
*
|
|
11
|
-
* What it handles: the XML declaration and processing instructions (skipped), comments (skipped
|
|
11
|
+
* What it handles: the XML declaration and processing instructions (skipped), comments (skipped;
|
|
12
|
+
* a `--` inside one, which XML 1.0 forbids, is accepted: a comment carries no data and banner
|
|
13
|
+
* comments such as `<!-- ----- -->` are common in hand-written files),
|
|
12
14
|
* CDATA sections (text), a DOCTYPE with an internal subset (skipped; entity declarations are not
|
|
13
15
|
* expanded, so an unknown entity reference is a syntax error), the five predefined entities and
|
|
14
16
|
* numeric character references (decimal and hexadecimal) in text and attribute values, attribute
|
|
15
|
-
* value whitespace
|
|
17
|
+
* value whitespace normalization, end-of-line normalization (CR LF and lone CR become LF), and
|
|
16
18
|
* well-formedness: matching end tags, one root element, no text outside it, no duplicate
|
|
17
19
|
* attributes, no unterminated markup at the end of the input. Namespaces are not resolved; element
|
|
18
20
|
* and attribute names are reported as written (with their prefix).
|
|
@@ -30,16 +32,23 @@ import { type Column, type GraphSnapshot } from "@graphty/graph-format";
|
|
|
30
32
|
import { type LossNote } from "../types.js";
|
|
31
33
|
import { XML_ILLEGAL_CHAR_CODE } from "./codes.js";
|
|
32
34
|
import { isNameChar } from "./export.js";
|
|
35
|
+
import { agree, plural } from "./plural.js";
|
|
33
36
|
|
|
34
|
-
/**
|
|
37
|
+
/**
|
|
38
|
+
* The events of the tokenizer; every callback is synchronous.
|
|
39
|
+
* @category Plugin helpers
|
|
40
|
+
*/
|
|
35
41
|
export interface XmlHandler {
|
|
36
42
|
/**
|
|
37
43
|
* An element starts (a self-closing element produces start then end).
|
|
38
44
|
* @param name - the element name as written, prefix included
|
|
39
45
|
* @param attrs - the attributes, entities decoded, in document order
|
|
40
46
|
* @param line - the 1-based line of the `<`
|
|
47
|
+
* @param ns - the namespace URI the element's prefix (or the default namespace) is bound to:
|
|
48
|
+
* "" when the element is in no namespace, null when its prefix is undeclared or the name is not
|
|
49
|
+
* namespace-well-formed (a leading or trailing colon, two colons)
|
|
41
50
|
*/
|
|
42
|
-
start(name: string, attrs: ReadonlyMap<string, string>, line: number): void;
|
|
51
|
+
start(name: string, attrs: ReadonlyMap<string, string>, line: number, ns: string | null): void;
|
|
43
52
|
/**
|
|
44
53
|
* An element ends.
|
|
45
54
|
* @param name - the element name
|
|
@@ -53,9 +62,18 @@ export interface XmlHandler {
|
|
|
53
62
|
* @param line - the 1-based line where the run starts
|
|
54
63
|
*/
|
|
55
64
|
text(text: string, line: number): void;
|
|
65
|
+
/**
|
|
66
|
+
* A DOCTYPE declaration (optional; its internal subset is never expanded).
|
|
67
|
+
* @param text - the declaration as written, from `<!DOCTYPE` to its `>`
|
|
68
|
+
* @param line - the 1-based line of the `<`
|
|
69
|
+
*/
|
|
70
|
+
doctype?(text: string, line: number): void;
|
|
56
71
|
}
|
|
57
72
|
|
|
58
|
-
/**
|
|
73
|
+
/**
|
|
74
|
+
* A well-formedness or syntax error, with the line it was found on.
|
|
75
|
+
* @category Plugin helpers
|
|
76
|
+
*/
|
|
59
77
|
export class XmlSyntaxError extends Error {
|
|
60
78
|
/** The 1-based line. */
|
|
61
79
|
readonly line: number;
|
|
@@ -73,10 +91,26 @@ export class XmlSyntaxError extends Error {
|
|
|
73
91
|
}
|
|
74
92
|
|
|
75
93
|
/**
|
|
76
|
-
*
|
|
77
|
-
*
|
|
94
|
+
* The input holds no markup at all (empty, whitespace only, a byte order mark only): an importer
|
|
95
|
+
* reports it as E_EMPTY_INPUT rather than as a syntax error.
|
|
96
|
+
* @category Plugin helpers
|
|
97
|
+
*/
|
|
98
|
+
export class XmlEmptyInputError extends XmlSyntaxError {
|
|
99
|
+
/**
|
|
100
|
+
* Create the error.
|
|
101
|
+
* @param line - the 1-based line where the input ended
|
|
102
|
+
*/
|
|
103
|
+
constructor(line: number) {
|
|
104
|
+
super("the input is empty: no root element", line);
|
|
105
|
+
this.name = "XmlEmptyInputError";
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Opt-in repairs of two defects the Cytoscape XGMML writer is known to produce. Each is off unless its callback is given; GEXF and GraphML never
|
|
78
111
|
* pass them, so their documents stay strictly well-formed. The callback is told the line of each
|
|
79
112
|
* repair, so the importer can warn per occurrence.
|
|
113
|
+
* @category Plugin helpers
|
|
80
114
|
*/
|
|
81
115
|
export interface XmlRepairs {
|
|
82
116
|
/**
|
|
@@ -122,6 +156,7 @@ const OPEN_BRACKET = 91;
|
|
|
122
156
|
const CLOSE_BRACKET = 93;
|
|
123
157
|
const BANG = 33;
|
|
124
158
|
const DASH = 45;
|
|
159
|
+
const QUESTION = 63;
|
|
125
160
|
|
|
126
161
|
/** The longest entity reference held back at a chunk boundary (`` is 10 characters). */
|
|
127
162
|
const MAX_ENTITY_LENGTH = 16;
|
|
@@ -178,11 +213,22 @@ const ILLEGAL_CHAR = new RegExp(
|
|
|
178
213
|
* written even as a character reference, so a conforming parser rejects the whole document.
|
|
179
214
|
* @param text - the text
|
|
180
215
|
* @returns true when the text cannot appear in an XML 1.0 document
|
|
216
|
+
* @category Plugin helpers
|
|
181
217
|
*/
|
|
182
218
|
export function hasIllegalXmlChar(text: string): boolean {
|
|
183
219
|
return ILLEGAL_CHAR.test(text);
|
|
184
220
|
}
|
|
185
221
|
|
|
222
|
+
/**
|
|
223
|
+
* Where the first character XML 1.0 forbids is in a text.
|
|
224
|
+
* @param text - the text
|
|
225
|
+
* @returns its index, or -1
|
|
226
|
+
*/
|
|
227
|
+
function illegalCharIndex(text: string): number {
|
|
228
|
+
const match = ILLEGAL_CHAR.exec(text);
|
|
229
|
+
return match === null ? -1 : match.index;
|
|
230
|
+
}
|
|
231
|
+
|
|
186
232
|
/**
|
|
187
233
|
* The loss notes of the texts a snapshot holds that no XML 1.0 document can carry (see
|
|
188
234
|
* hasIllegalXmlChar): one E_XML_ILLEGAL_CHAR note per column (node, edge and graph string / dict /
|
|
@@ -190,6 +236,7 @@ export function hasIllegalXmlChar(text: string): boolean {
|
|
|
190
236
|
* the first such text. Shared by the GEXF and GraphML exporters' check().
|
|
191
237
|
* @param snapshot - the snapshot
|
|
192
238
|
* @returns the notes, empty when every text is writable
|
|
239
|
+
* @category Plugin helpers
|
|
193
240
|
*/
|
|
194
241
|
export function xmlIllegalTextNotes(snapshot: GraphSnapshot): LossNote[] {
|
|
195
242
|
const notes: LossNote[] = [];
|
|
@@ -204,7 +251,7 @@ export function xmlIllegalTextNotes(snapshot: GraphSnapshot): LossNote[] {
|
|
|
204
251
|
notes.push(
|
|
205
252
|
Object.freeze({
|
|
206
253
|
code: XML_ILLEGAL_CHAR_CODE,
|
|
207
|
-
message: `${ids} node id(
|
|
254
|
+
message: `${ids} node id${plural(ids)} ${agree(ids, "holds", "hold")} a character XML 1.0 cannot carry; the save fails`,
|
|
208
255
|
column: null,
|
|
209
256
|
count: ids,
|
|
210
257
|
}),
|
|
@@ -221,7 +268,7 @@ export function xmlIllegalTextNotes(snapshot: GraphSnapshot): LossNote[] {
|
|
|
221
268
|
notes.push(
|
|
222
269
|
Object.freeze({
|
|
223
270
|
code: XML_ILLEGAL_CHAR_CODE,
|
|
224
|
-
message: `${domain} column "${column.meta.name}": ${bad} value(
|
|
271
|
+
message: `${domain} column "${column.meta.name}": ${bad} value${plural(bad)} ${agree(bad, "holds", "hold")} a character XML 1.0 cannot carry; the save fails`,
|
|
225
272
|
column: column.meta.name,
|
|
226
273
|
count: bad,
|
|
227
274
|
}),
|
|
@@ -258,7 +305,10 @@ function countIllegalRows(column: Column): number {
|
|
|
258
305
|
return bad;
|
|
259
306
|
}
|
|
260
307
|
|
|
261
|
-
/**
|
|
308
|
+
/**
|
|
309
|
+
* The encoding pseudo-attribute of an XML declaration at the very start of a document (one
|
|
310
|
+
* anywhere else is a syntax error of the tokenizer).
|
|
311
|
+
*/
|
|
262
312
|
const XML_DECLARED_ENCODING = /^<\?xml\s[^>]*?\bencoding\s*=\s*["']([A-Za-z][A-Za-z0-9._-]*)["']/;
|
|
263
313
|
|
|
264
314
|
/**
|
|
@@ -266,16 +316,35 @@ const XML_DECLARED_ENCODING = /^<\?xml\s[^>]*?\bencoding\s*=\s*["']([A-Za-z][A-Z
|
|
|
266
316
|
* for the shared byte decoder (common/input.ts).
|
|
267
317
|
* @param head - the start of the document, decoded as windows-1252
|
|
268
318
|
* @returns the declared label, or null when there is no declaration or it names no encoding
|
|
319
|
+
* @category Plugin helpers
|
|
269
320
|
*/
|
|
270
321
|
export function xmlDeclaredEncoding(head: string): string | null {
|
|
271
322
|
const match = XML_DECLARED_ENCODING.exec(head);
|
|
272
323
|
return match === null ? null : match[1];
|
|
273
324
|
}
|
|
274
325
|
|
|
326
|
+
/**
|
|
327
|
+
* The head of a document as text for an XML format's sniff(): UTF-16 when a byte order mark (or
|
|
328
|
+
* BOM-less UTF-16 `<?`, XML 1.0 Appendix F) says so, else UTF-8, never failing.
|
|
329
|
+
* @param head - the first bytes
|
|
330
|
+
* @returns the text, a leading byte order mark removed
|
|
331
|
+
* @category Plugin helpers
|
|
332
|
+
*/
|
|
333
|
+
export function sniffXmlText(head: Uint8Array): string {
|
|
334
|
+
let label = "utf-8";
|
|
335
|
+
if ((head[0] === 0xff && head[1] === 0xfe) || (head[0] === 0x3c && head[1] === 0x00 && head[2] === 0x3f)) {
|
|
336
|
+
label = "utf-16le";
|
|
337
|
+
} else if ((head[0] === 0xfe && head[1] === 0xff) || (head[0] === 0x00 && head[1] === 0x3c && head[3] === 0x3f)) {
|
|
338
|
+
label = "utf-16be";
|
|
339
|
+
}
|
|
340
|
+
return new TextDecoder(label, { fatal: false }).decode(head);
|
|
341
|
+
}
|
|
342
|
+
|
|
275
343
|
/**
|
|
276
344
|
* Whether a text is an XML Name.
|
|
277
345
|
* @param text - the text
|
|
278
346
|
* @returns true for a well-formed name
|
|
347
|
+
* @category Plugin helpers
|
|
279
348
|
*/
|
|
280
349
|
export function isXmlName(text: string): boolean {
|
|
281
350
|
if (text.length === 0) {
|
|
@@ -314,6 +383,7 @@ function isXmlChar(cp: number): boolean {
|
|
|
314
383
|
* @param line - the line, for errors
|
|
315
384
|
* @param repairs - the opt-in repairs (XGMML only); none by default
|
|
316
385
|
* @returns the decoded text
|
|
386
|
+
* @category Plugin helpers
|
|
317
387
|
*/
|
|
318
388
|
export function decodeEntities(raw: string, line: number, repairs?: XmlRepairs): string {
|
|
319
389
|
let amp = raw.indexOf("&");
|
|
@@ -338,13 +408,14 @@ export function decodeEntities(raw: string, line: number, repairs?: XmlRepairs):
|
|
|
338
408
|
continue;
|
|
339
409
|
}
|
|
340
410
|
if (semi < 0) {
|
|
341
|
-
throw new XmlSyntaxError("unterminated entity reference", line);
|
|
411
|
+
throw new XmlSyntaxError("unterminated entity reference", lineAt(raw, amp, line));
|
|
342
412
|
}
|
|
343
413
|
const name = raw.slice(amp + 1, semi);
|
|
344
414
|
if (name.startsWith("#")) {
|
|
345
415
|
const hex = name.startsWith("#x") || name.startsWith("#X");
|
|
346
416
|
const digits = name.slice(hex ? 2 : 1);
|
|
347
|
-
|
|
417
|
+
// XML 1.0 puts no limit on leading zeros (A is "A")
|
|
418
|
+
const ok = hex ? /^0*[\da-fA-F]{1,6}$/.test(digits) : /^0*\d{1,7}$/.test(digits);
|
|
348
419
|
const cp = ok ? Number.parseInt(digits, hex ? 16 : 10) : -1;
|
|
349
420
|
const low = cp >= 0xd800 && cp <= 0xdbff ? lowSurrogateAfter(raw, semi + 1, repairs) : null;
|
|
350
421
|
if (low !== null) {
|
|
@@ -355,13 +426,13 @@ export function decodeEntities(raw: string, line: number, repairs?: XmlRepairs):
|
|
|
355
426
|
continue;
|
|
356
427
|
}
|
|
357
428
|
if (cp < 0 || !isXmlChar(cp)) {
|
|
358
|
-
throw new XmlSyntaxError(`invalid character reference &${name};`, line);
|
|
429
|
+
throw new XmlSyntaxError(`invalid character reference &${name};`, lineAt(raw, amp, line));
|
|
359
430
|
}
|
|
360
431
|
out += String.fromCodePoint(cp);
|
|
361
432
|
} else {
|
|
362
433
|
const value = NAMED_ENTITIES[name];
|
|
363
434
|
if (value === undefined) {
|
|
364
|
-
throw new XmlSyntaxError(`unknown entity &${name};`, line);
|
|
435
|
+
throw new XmlSyntaxError(`unknown entity &${name};`, lineAt(raw, amp, line));
|
|
365
436
|
}
|
|
366
437
|
out += value;
|
|
367
438
|
}
|
|
@@ -414,6 +485,7 @@ function lowSurrogateAfter(
|
|
|
414
485
|
* Whether a text is whitespace only.
|
|
415
486
|
* @param text - the text
|
|
416
487
|
* @returns true when every character is XML whitespace (also for an empty text)
|
|
488
|
+
* @category Plugin helpers
|
|
417
489
|
*/
|
|
418
490
|
export function isWhitespace(text: string): boolean {
|
|
419
491
|
for (let i = 0; i < text.length; i++) {
|
|
@@ -428,6 +500,7 @@ export function isWhitespace(text: string): boolean {
|
|
|
428
500
|
* The local part of a possibly prefixed XML name.
|
|
429
501
|
* @param name - the name as written
|
|
430
502
|
* @returns the text after the last colon, or the name itself
|
|
503
|
+
* @category Plugin helpers
|
|
431
504
|
*/
|
|
432
505
|
export function localName(name: string): string {
|
|
433
506
|
const colon = name.lastIndexOf(":");
|
|
@@ -440,6 +513,7 @@ export function localName(name: string): string {
|
|
|
440
513
|
* @param chunks - the text (already UTF-8 decoded, BOM removed)
|
|
441
514
|
* @param handler - the event sink
|
|
442
515
|
* @param repairs - the opt-in repairs (XGMML only); none by default
|
|
516
|
+
* @category Plugin helpers
|
|
443
517
|
*/
|
|
444
518
|
export async function tokenizeXml(
|
|
445
519
|
chunks: AsyncIterable<string>,
|
|
@@ -447,10 +521,40 @@ export async function tokenizeXml(
|
|
|
447
521
|
repairs?: XmlRepairs,
|
|
448
522
|
): Promise<void> {
|
|
449
523
|
const tokenizer = new XmlTokenizer(handler, repairs);
|
|
450
|
-
|
|
451
|
-
|
|
524
|
+
// XML 1.0 section 2.8: nothing may precede the XML declaration, not even whitespace (template
|
|
525
|
+
// output often adds a line break, and the declared encoding would then go unread)
|
|
526
|
+
let skipped = 0;
|
|
527
|
+
let lines = 1;
|
|
528
|
+
let leading = true;
|
|
529
|
+
try {
|
|
530
|
+
for await (const chunk of chunks) {
|
|
531
|
+
if (leading) {
|
|
532
|
+
const start = chunk.search(/\S/);
|
|
533
|
+
const blank = start < 0 ? chunk : chunk.slice(0, start);
|
|
534
|
+
skipped += blank.length;
|
|
535
|
+
lines += blank.split("\n").length - 1;
|
|
536
|
+
if (start >= 0) {
|
|
537
|
+
leading = false;
|
|
538
|
+
if (skipped > 0 && /^<\?xml\s/.test(chunk.slice(start, start + 6))) {
|
|
539
|
+
throw new XmlSyntaxError(
|
|
540
|
+
"the XML declaration must be at the very start of the document; whitespace precedes it",
|
|
541
|
+
lines,
|
|
542
|
+
);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
tokenizer.push(chunk);
|
|
547
|
+
}
|
|
548
|
+
tokenizer.finish();
|
|
549
|
+
} catch (err) {
|
|
550
|
+
if (err instanceof XmlSyntaxError && tokenizer.declaresXml11 && !err.message.includes("XML 1.1")) {
|
|
551
|
+
throw new XmlSyntaxError(
|
|
552
|
+
`${err.message}; the document declares XML 1.1, which graph-io does not read (it reads XML 1.0)`,
|
|
553
|
+
err.line,
|
|
554
|
+
);
|
|
555
|
+
}
|
|
556
|
+
throw err;
|
|
452
557
|
}
|
|
453
|
-
tokenizer.finish();
|
|
454
558
|
}
|
|
455
559
|
|
|
456
560
|
/** The result of parsing one start tag out of the buffer. */
|
|
@@ -461,6 +565,8 @@ interface StartTag {
|
|
|
461
565
|
readonly attrs: Map<string, string>;
|
|
462
566
|
/** Whether the tag ends with `/>`. */
|
|
463
567
|
readonly selfClosing: boolean;
|
|
568
|
+
/** Whether an attribute name starts with `xmlns` (a namespace declaration may be among them). */
|
|
569
|
+
readonly declaresNamespace: boolean;
|
|
464
570
|
/** The buffer index one past the `>`. */
|
|
465
571
|
readonly end: number;
|
|
466
572
|
}
|
|
@@ -481,7 +587,7 @@ const TERMINATORS: Readonly<Partial<Record<PendingKind, string>>> = {
|
|
|
481
587
|
* until the end is found, so a token spanning many chunks costs its length once) and the state
|
|
482
588
|
* the terminator scan is in. `tail` holds the last characters of the pieces a fixed terminator
|
|
483
589
|
* could straddle; `quote` is the open quote character (0 outside a value) of a start tag or a
|
|
484
|
-
* DOCTYPE; `depth`, `comment` and `run` track a DOCTYPE's internal subset.
|
|
590
|
+
* DOCTYPE; `depth`, `comment`, `pi` and `run` track a DOCTYPE's internal subset.
|
|
485
591
|
*/
|
|
486
592
|
interface Pending {
|
|
487
593
|
kind: PendingKind;
|
|
@@ -490,12 +596,30 @@ interface Pending {
|
|
|
490
596
|
quote: number;
|
|
491
597
|
depth: number;
|
|
492
598
|
comment: boolean;
|
|
599
|
+
pi: boolean;
|
|
493
600
|
run: number;
|
|
494
601
|
}
|
|
495
602
|
|
|
603
|
+
/** One `xmlns` / `xmlns:prefix` declaration in scope. */
|
|
604
|
+
interface NamespaceBinding {
|
|
605
|
+
/** The prefix; "" for the default namespace. */
|
|
606
|
+
readonly prefix: string;
|
|
607
|
+
/** The namespace URI; "" undeclares the default namespace. */
|
|
608
|
+
readonly uri: string;
|
|
609
|
+
/** The element depth that declared it (the stack length before the element was pushed). */
|
|
610
|
+
readonly depth: number;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
/** The namespace the `xml` prefix is bound to by definition. */
|
|
614
|
+
const XML_NAMESPACE = "http://www.w3.org/XML/1998/namespace";
|
|
615
|
+
|
|
616
|
+
/** The markup declarations a truncated `<!...` at the end of the input may have been cut from. */
|
|
617
|
+
const DECLARATION_OPENERS: readonly string[] = ["<!--", "<![CDATA[", "<!DOCTYPE"];
|
|
618
|
+
|
|
496
619
|
/**
|
|
497
620
|
* The tokenizer state: the unconsumed tail of the input, the current line, the open element
|
|
498
621
|
* stack and the text run being accumulated. `push()` chunks, then `finish()`.
|
|
622
|
+
* @category Plugin helpers
|
|
499
623
|
*/
|
|
500
624
|
export class XmlTokenizer {
|
|
501
625
|
private readonly handler: XmlHandler;
|
|
@@ -532,6 +656,27 @@ export class XmlTokenizer {
|
|
|
532
656
|
|
|
533
657
|
private rootClosed = false;
|
|
534
658
|
|
|
659
|
+
/** Whether any markup was consumed (an input with none is empty, not malformed). */
|
|
660
|
+
private markupSeen = false;
|
|
661
|
+
|
|
662
|
+
/** Whether nothing has been consumed yet: the XML declaration may only appear here. */
|
|
663
|
+
private atStart = true;
|
|
664
|
+
|
|
665
|
+
/** Whether a DOCTYPE declaration was read (one is allowed, in the prolog). */
|
|
666
|
+
private doctypeSeen = false;
|
|
667
|
+
|
|
668
|
+
/** The version of the XML declaration, or null. */
|
|
669
|
+
private version: string | null = null;
|
|
670
|
+
|
|
671
|
+
/** The last two characters of the character data since the last markup, for the `]]>` check. */
|
|
672
|
+
private textTail = "";
|
|
673
|
+
|
|
674
|
+
/** A high surrogate held back from the end of the previous chunk (its low half may start the next). */
|
|
675
|
+
private pendingHigh = "";
|
|
676
|
+
|
|
677
|
+
/** The namespace declarations in scope, innermost last. */
|
|
678
|
+
private readonly bindings: NamespaceBinding[] = [];
|
|
679
|
+
|
|
535
680
|
private readonly repairs: XmlRepairs | undefined;
|
|
536
681
|
|
|
537
682
|
/**
|
|
@@ -544,12 +689,31 @@ export class XmlTokenizer {
|
|
|
544
689
|
this.repairs = repairs;
|
|
545
690
|
}
|
|
546
691
|
|
|
692
|
+
/**
|
|
693
|
+
* Whether the document's XML declaration says version 1.1 (read by the XML 1.0 rules all the
|
|
694
|
+
* same; the syntax error of a 1.1-only construct then says why).
|
|
695
|
+
* @returns true after a `<?xml version="1.1"?>` declaration
|
|
696
|
+
*/
|
|
697
|
+
get declaresXml11(): boolean {
|
|
698
|
+
return this.version === "1.1";
|
|
699
|
+
}
|
|
700
|
+
|
|
547
701
|
/**
|
|
548
702
|
* Feed one chunk and emit every complete token in it.
|
|
549
703
|
* @param chunk - the text
|
|
550
704
|
*/
|
|
551
705
|
push(chunk: string): void {
|
|
552
706
|
let text = chunk;
|
|
707
|
+
if (this.pendingHigh.length > 0) {
|
|
708
|
+
text = this.pendingHigh + text;
|
|
709
|
+
this.pendingHigh = "";
|
|
710
|
+
}
|
|
711
|
+
const last = text.charCodeAt(text.length - 1); // NOSONAR(S7758): reads UTF-16 code units on purpose
|
|
712
|
+
if (last >= 0xd800 && last <= 0xdbff) {
|
|
713
|
+
// a text chunk may end between the two halves of an astral character
|
|
714
|
+
this.pendingHigh = text.slice(-1);
|
|
715
|
+
text = text.slice(0, -1);
|
|
716
|
+
}
|
|
553
717
|
if (this.pendingCr) {
|
|
554
718
|
text = `\r${text}`;
|
|
555
719
|
this.pendingCr = false;
|
|
@@ -567,11 +731,13 @@ export class XmlTokenizer {
|
|
|
567
731
|
/** Signal the end of the input: flush the last text run and check well-formedness. */
|
|
568
732
|
finish(): void {
|
|
569
733
|
this.final = true;
|
|
734
|
+
const held = this.pendingHigh;
|
|
735
|
+
this.pendingHigh = "";
|
|
570
736
|
if (this.pendingCr) {
|
|
571
737
|
this.pendingCr = false;
|
|
572
|
-
this.feed(
|
|
738
|
+
this.feed(`\n${held}`);
|
|
573
739
|
} else {
|
|
574
|
-
this.feed(
|
|
740
|
+
this.feed(held);
|
|
575
741
|
}
|
|
576
742
|
if (this.pending !== null || this.buffer.length > 0) {
|
|
577
743
|
throw new XmlSyntaxError("unexpected end of input inside markup", this.line);
|
|
@@ -581,14 +747,17 @@ export class XmlTokenizer {
|
|
|
581
747
|
throw new XmlSyntaxError(`unclosed element <${this.stack[this.stack.length - 1]}>`, this.line);
|
|
582
748
|
}
|
|
583
749
|
if (!this.rootSeen) {
|
|
750
|
+
if (!this.markupSeen) {
|
|
751
|
+
throw new XmlEmptyInputError(this.line);
|
|
752
|
+
}
|
|
584
753
|
throw new XmlSyntaxError("no root element", this.line);
|
|
585
754
|
}
|
|
586
755
|
}
|
|
587
756
|
|
|
588
757
|
/**
|
|
589
|
-
* Append
|
|
758
|
+
* Append normalized text: continue a pending token's terminator scan over the new text alone,
|
|
590
759
|
* and once the token is complete (or when none is pending) scan the buffer for tokens.
|
|
591
|
-
* @param text - the text, line breaks
|
|
760
|
+
* @param text - the text, line breaks normalized
|
|
592
761
|
*/
|
|
593
762
|
private feed(text: string): void {
|
|
594
763
|
const { pending } = this;
|
|
@@ -697,9 +866,19 @@ export class XmlTokenizer {
|
|
|
697
866
|
* @returns the index one past the `>`, or -1
|
|
698
867
|
*/
|
|
699
868
|
private scanDoctype(pending: Pending, text: string, from: number): number {
|
|
700
|
-
let { quote, depth, comment, run } = pending;
|
|
869
|
+
let { quote, depth, comment, pi, run } = pending;
|
|
701
870
|
for (let i = from; i < text.length; i++) {
|
|
702
871
|
const c = text.charCodeAt(i);
|
|
872
|
+
if (pi) {
|
|
873
|
+
// a processing instruction in the subset ends at `?>`; quotes inside it are text
|
|
874
|
+
if (c === GT && run === 1) {
|
|
875
|
+
pi = false;
|
|
876
|
+
run = 0;
|
|
877
|
+
} else {
|
|
878
|
+
run = c === QUESTION ? 1 : 0;
|
|
879
|
+
}
|
|
880
|
+
continue;
|
|
881
|
+
}
|
|
703
882
|
if (comment) {
|
|
704
883
|
if (c === DASH) {
|
|
705
884
|
run++;
|
|
@@ -731,6 +910,11 @@ export class XmlTokenizer {
|
|
|
731
910
|
run = 0;
|
|
732
911
|
continue;
|
|
733
912
|
}
|
|
913
|
+
if (run === 1 && c === QUESTION) {
|
|
914
|
+
pi = true;
|
|
915
|
+
run = 0;
|
|
916
|
+
continue;
|
|
917
|
+
}
|
|
734
918
|
run = 0;
|
|
735
919
|
if (c === LT) {
|
|
736
920
|
run = 1;
|
|
@@ -747,6 +931,7 @@ export class XmlTokenizer {
|
|
|
747
931
|
pending.quote = 0;
|
|
748
932
|
pending.depth = 0;
|
|
749
933
|
pending.comment = false;
|
|
934
|
+
pending.pi = false;
|
|
750
935
|
pending.run = 0;
|
|
751
936
|
return i + 1;
|
|
752
937
|
}
|
|
@@ -754,6 +939,7 @@ export class XmlTokenizer {
|
|
|
754
939
|
pending.quote = quote;
|
|
755
940
|
pending.depth = depth;
|
|
756
941
|
pending.comment = comment;
|
|
942
|
+
pending.pi = pi;
|
|
757
943
|
pending.run = run;
|
|
758
944
|
return -1;
|
|
759
945
|
}
|
|
@@ -775,7 +961,13 @@ export class XmlTokenizer {
|
|
|
775
961
|
if (!this.final) {
|
|
776
962
|
// an entity reference may be split across chunks: hold back from its "&"
|
|
777
963
|
const amp = buffer.lastIndexOf("&");
|
|
778
|
-
if (
|
|
964
|
+
if (
|
|
965
|
+
amp >= pos &&
|
|
966
|
+
!buffer.includes(";", amp) &&
|
|
967
|
+
// ponytail: a zero-padded reference longer than MAX_ENTITY_LENGTH is held back
|
|
968
|
+
// whole, so an absurdly long run of zeros is re-scanned once per chunk
|
|
969
|
+
(amp >= length - MAX_ENTITY_LENGTH || /^&#[xX]?0[\da-fA-F]*$/.test(buffer.slice(amp)))
|
|
970
|
+
) {
|
|
779
971
|
end = amp;
|
|
780
972
|
}
|
|
781
973
|
if (this.repairs?.surrogatePair !== undefined) {
|
|
@@ -822,6 +1014,7 @@ export class XmlTokenizer {
|
|
|
822
1014
|
quote: 0,
|
|
823
1015
|
depth: 0,
|
|
824
1016
|
comment: false,
|
|
1017
|
+
pi: false,
|
|
825
1018
|
run: 0,
|
|
826
1019
|
};
|
|
827
1020
|
if (piece.length < MIN_DECIDABLE_MARKUP) {
|
|
@@ -866,60 +1059,32 @@ export class XmlTokenizer {
|
|
|
866
1059
|
* @returns the index after the markup, or -1 when the buffer ends before the markup does
|
|
867
1060
|
*/
|
|
868
1061
|
private consumeMarkup(pos: number): number {
|
|
1062
|
+
const end = this.markupEnd(pos);
|
|
1063
|
+
if (end >= 0) {
|
|
1064
|
+
this.markupSeen = true;
|
|
1065
|
+
this.atStart = false;
|
|
1066
|
+
// markup ends a run of character data: a `]]>` cannot straddle it
|
|
1067
|
+
this.textTail = "";
|
|
1068
|
+
}
|
|
1069
|
+
return end;
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
/**
|
|
1073
|
+
* Read the markup starting at `pos` (a `<`) and deliver what it means.
|
|
1074
|
+
* @param pos - the index of the `<`
|
|
1075
|
+
* @returns the index after the markup, or -1 when the buffer ends before the markup does
|
|
1076
|
+
*/
|
|
1077
|
+
private markupEnd(pos: number): number {
|
|
869
1078
|
const { buffer } = this;
|
|
870
1079
|
if (buffer.startsWith("<!", pos)) {
|
|
871
|
-
|
|
872
|
-
// "<![CDATA[" is 9 characters; wait until the kind of declaration is decidable
|
|
873
|
-
return -1;
|
|
874
|
-
}
|
|
875
|
-
if (buffer.startsWith("<!--", pos)) {
|
|
876
|
-
const end = buffer.indexOf("-->", pos + 4);
|
|
877
|
-
if (end < 0) {
|
|
878
|
-
return -1;
|
|
879
|
-
}
|
|
880
|
-
this.advanceLine(pos, end + 3);
|
|
881
|
-
return end + 3;
|
|
882
|
-
}
|
|
883
|
-
if (buffer.startsWith("<![CDATA[", pos)) {
|
|
884
|
-
const end = buffer.indexOf("]]>", pos + 9);
|
|
885
|
-
if (end < 0) {
|
|
886
|
-
return -1;
|
|
887
|
-
}
|
|
888
|
-
if (this.text.length === 0) {
|
|
889
|
-
this.textLine = this.line;
|
|
890
|
-
}
|
|
891
|
-
const cdata = buffer.slice(pos + 9, end);
|
|
892
|
-
if (hasIllegalXmlChar(cdata)) {
|
|
893
|
-
throw new XmlSyntaxError("a character XML 1.0 forbids appears in a CDATA section", this.line);
|
|
894
|
-
}
|
|
895
|
-
this.text += cdata;
|
|
896
|
-
this.advanceLine(pos, end + 3);
|
|
897
|
-
return end + 3;
|
|
898
|
-
}
|
|
899
|
-
if (!buffer.startsWith("<!DOCTYPE", pos)) {
|
|
900
|
-
throw new XmlSyntaxError("unexpected markup declaration", this.line);
|
|
901
|
-
}
|
|
902
|
-
const state: Pending = {
|
|
903
|
-
kind: "doctype",
|
|
904
|
-
pieces: [],
|
|
905
|
-
tail: "",
|
|
906
|
-
quote: 0,
|
|
907
|
-
depth: 0,
|
|
908
|
-
comment: false,
|
|
909
|
-
run: 0,
|
|
910
|
-
};
|
|
911
|
-
const end = this.scanDoctype(state, buffer, pos + 9);
|
|
912
|
-
if (end < 0) {
|
|
913
|
-
return -1;
|
|
914
|
-
}
|
|
915
|
-
this.advanceLine(pos, end);
|
|
916
|
-
return end;
|
|
1080
|
+
return this.declarationEnd(pos);
|
|
917
1081
|
}
|
|
918
1082
|
if (buffer.startsWith("<?", pos)) {
|
|
919
1083
|
const end = buffer.indexOf("?>", pos + 2);
|
|
920
1084
|
if (end < 0) {
|
|
921
1085
|
return -1;
|
|
922
1086
|
}
|
|
1087
|
+
this.processingInstruction(pos, end);
|
|
923
1088
|
this.advanceLine(pos, end + 2);
|
|
924
1089
|
return end + 2;
|
|
925
1090
|
}
|
|
@@ -928,7 +1093,8 @@ export class XmlTokenizer {
|
|
|
928
1093
|
if (end < 0) {
|
|
929
1094
|
return -1;
|
|
930
1095
|
}
|
|
931
|
-
|
|
1096
|
+
// whitespace may follow the name, never precede it
|
|
1097
|
+
const name = buffer.slice(pos + 2, end).trimEnd();
|
|
932
1098
|
if (!isXmlName(name)) {
|
|
933
1099
|
throw new XmlSyntaxError(`malformed end tag </${name}>`, this.line);
|
|
934
1100
|
}
|
|
@@ -942,7 +1108,7 @@ export class XmlTokenizer {
|
|
|
942
1108
|
return -1;
|
|
943
1109
|
}
|
|
944
1110
|
this.flushText();
|
|
945
|
-
this.startElement(tag.name, tag.attrs);
|
|
1111
|
+
this.startElement(tag.name, tag.attrs, tag.declaresNamespace);
|
|
946
1112
|
if (tag.selfClosing) {
|
|
947
1113
|
this.endElement(tag.name);
|
|
948
1114
|
}
|
|
@@ -950,6 +1116,161 @@ export class XmlTokenizer {
|
|
|
950
1116
|
return tag.end;
|
|
951
1117
|
}
|
|
952
1118
|
|
|
1119
|
+
/**
|
|
1120
|
+
* Read the CDATA section at `pos` into the text.
|
|
1121
|
+
* @param pos - the index of the `<`
|
|
1122
|
+
* @returns the index after the section, or -1 when the buffer ends before the section does
|
|
1123
|
+
*/
|
|
1124
|
+
private cdataEnd(pos: number): number {
|
|
1125
|
+
const end = this.buffer.indexOf("]]>", pos + 9);
|
|
1126
|
+
if (end < 0) {
|
|
1127
|
+
return -1;
|
|
1128
|
+
}
|
|
1129
|
+
if (this.text.length === 0) {
|
|
1130
|
+
this.textLine = this.line;
|
|
1131
|
+
}
|
|
1132
|
+
const cdata = this.buffer.slice(pos + 9, end);
|
|
1133
|
+
const bad = illegalCharIndex(cdata);
|
|
1134
|
+
if (bad >= 0) {
|
|
1135
|
+
throw new XmlSyntaxError(
|
|
1136
|
+
"a character XML 1.0 forbids appears in a CDATA section",
|
|
1137
|
+
this.lineIn(pos, pos + 9 + bad),
|
|
1138
|
+
);
|
|
1139
|
+
}
|
|
1140
|
+
this.text += cdata;
|
|
1141
|
+
this.advanceLine(pos, end + 3);
|
|
1142
|
+
return end + 3;
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
/**
|
|
1146
|
+
* Read the `<!...>` markup at `pos`: a comment, a CDATA section or the DOCTYPE.
|
|
1147
|
+
* @param pos - the index of the `<`
|
|
1148
|
+
* @returns the index after the markup, or -1 when the buffer ends before the markup does
|
|
1149
|
+
*/
|
|
1150
|
+
private declarationEnd(pos: number): number {
|
|
1151
|
+
const { buffer } = this;
|
|
1152
|
+
if (buffer.length - pos < MIN_DECIDABLE_MARKUP) {
|
|
1153
|
+
// "<![CDATA[" is 9 characters; wait until the kind of declaration is decidable, and at
|
|
1154
|
+
// the end of the input read the start of one cut short as a truncation
|
|
1155
|
+
const head = buffer.slice(pos);
|
|
1156
|
+
if (!this.final || DECLARATION_OPENERS.some((opener) => opener.startsWith(head))) {
|
|
1157
|
+
return -1;
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
if (buffer.startsWith("<!--", pos)) {
|
|
1161
|
+
const end = buffer.indexOf("-->", pos + 4);
|
|
1162
|
+
if (end < 0) {
|
|
1163
|
+
return -1;
|
|
1164
|
+
}
|
|
1165
|
+
// a "--" inside, which XML 1.0 forbids, is accepted: the comment carries no data
|
|
1166
|
+
this.advanceLine(pos, end + 3);
|
|
1167
|
+
return end + 3;
|
|
1168
|
+
}
|
|
1169
|
+
if (buffer.startsWith("<![CDATA[", pos)) {
|
|
1170
|
+
return this.cdataEnd(pos);
|
|
1171
|
+
}
|
|
1172
|
+
if (!buffer.startsWith("<!DOCTYPE", pos)) {
|
|
1173
|
+
if (buffer.slice(pos + 2, pos + 9).toUpperCase() === "DOCTYPE") {
|
|
1174
|
+
throw new XmlSyntaxError(
|
|
1175
|
+
`<!${buffer.slice(pos + 2, pos + 9)}> is not XML (it must be written <!DOCTYPE>); the input looks like an HTML document`,
|
|
1176
|
+
this.line,
|
|
1177
|
+
);
|
|
1178
|
+
}
|
|
1179
|
+
throw new XmlSyntaxError("unexpected markup declaration", this.line);
|
|
1180
|
+
}
|
|
1181
|
+
if (this.rootSeen || this.doctypeSeen) {
|
|
1182
|
+
throw new XmlSyntaxError("a DOCTYPE declaration is allowed once, before the root element", this.line);
|
|
1183
|
+
}
|
|
1184
|
+
const state: Pending = {
|
|
1185
|
+
kind: "doctype",
|
|
1186
|
+
pieces: [],
|
|
1187
|
+
tail: "",
|
|
1188
|
+
quote: 0,
|
|
1189
|
+
depth: 0,
|
|
1190
|
+
comment: false,
|
|
1191
|
+
pi: false,
|
|
1192
|
+
run: 0,
|
|
1193
|
+
};
|
|
1194
|
+
const end = this.scanDoctype(state, buffer, pos + 9);
|
|
1195
|
+
if (end < 0) {
|
|
1196
|
+
return -1;
|
|
1197
|
+
}
|
|
1198
|
+
this.doctypeSeen = true;
|
|
1199
|
+
this.handler.doctype?.(buffer.slice(pos, end), this.line);
|
|
1200
|
+
this.advanceLine(pos, end);
|
|
1201
|
+
return end;
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
/**
|
|
1205
|
+
* Check a processing instruction `<?target ...?>` (skipped otherwise): its target must be a
|
|
1206
|
+
* name, and the reserved target `xml` is the XML declaration, allowed only at the very start
|
|
1207
|
+
* of the document with a 1.x version.
|
|
1208
|
+
* @param pos - the index of the `<`
|
|
1209
|
+
* @param end - the index of the `?>`
|
|
1210
|
+
*/
|
|
1211
|
+
private processingInstruction(pos: number, end: number): void {
|
|
1212
|
+
const body = this.buffer.slice(pos + 2, end);
|
|
1213
|
+
const target = /^[^\s]*/.exec(body)?.[0] ?? "";
|
|
1214
|
+
if (!isXmlName(target)) {
|
|
1215
|
+
throw new XmlSyntaxError(`a processing instruction needs a target name, not "${target}"`, this.line);
|
|
1216
|
+
}
|
|
1217
|
+
if (target.toLowerCase() !== "xml") {
|
|
1218
|
+
return;
|
|
1219
|
+
}
|
|
1220
|
+
if (target !== "xml") {
|
|
1221
|
+
throw new XmlSyntaxError(
|
|
1222
|
+
`<?${target} is not an XML declaration (which is written <?xml in lower case); the target is reserved`,
|
|
1223
|
+
this.line,
|
|
1224
|
+
);
|
|
1225
|
+
}
|
|
1226
|
+
if (!this.atStart || pos !== 0) {
|
|
1227
|
+
throw new XmlSyntaxError(
|
|
1228
|
+
"an XML declaration (<?xml ...?>) may appear only at the very start of the document; the target xml is reserved",
|
|
1229
|
+
this.line,
|
|
1230
|
+
);
|
|
1231
|
+
}
|
|
1232
|
+
const version = /\bversion\s*=\s*(["'])([^"']*)\1/.exec(body)?.[2];
|
|
1233
|
+
if (version === undefined) {
|
|
1234
|
+
throw new XmlSyntaxError("the XML declaration has no version", this.line);
|
|
1235
|
+
}
|
|
1236
|
+
if (!/^1\.\d+$/.test(version)) {
|
|
1237
|
+
throw new XmlSyntaxError(`XML version ${version} is not supported (only 1.x)`, this.line);
|
|
1238
|
+
}
|
|
1239
|
+
this.version = version;
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
/**
|
|
1243
|
+
* The line of a buffer position after `start`, the position the current line count refers to.
|
|
1244
|
+
* Used on the error path only.
|
|
1245
|
+
* @param start - a position on line `this.line`
|
|
1246
|
+
* @param at - the position
|
|
1247
|
+
* @returns its line
|
|
1248
|
+
*/
|
|
1249
|
+
private lineIn(start: number, at: number): number {
|
|
1250
|
+
return lineAt(this.buffer.slice(start, at), at - start, this.line);
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
/**
|
|
1254
|
+
* Decode the entities of a text, naming XML 1.1 when a character reference XML 1.0 forbids
|
|
1255
|
+
* appears in a document that declares version 1.1.
|
|
1256
|
+
* @param raw - the text as written
|
|
1257
|
+
* @param line - the line the text starts on
|
|
1258
|
+
* @returns the decoded text
|
|
1259
|
+
*/
|
|
1260
|
+
private decode(raw: string, line: number): string {
|
|
1261
|
+
try {
|
|
1262
|
+
return decodeEntities(raw, line, this.repairs);
|
|
1263
|
+
} catch (err) {
|
|
1264
|
+
if (err instanceof XmlSyntaxError && this.version !== null && this.version !== "1.0") {
|
|
1265
|
+
throw new XmlSyntaxError(
|
|
1266
|
+
`${err.message} (the document declares XML ${this.version}, whose character rules are not supported; it is read as XML 1.0)`,
|
|
1267
|
+
err.line,
|
|
1268
|
+
);
|
|
1269
|
+
}
|
|
1270
|
+
throw err;
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
|
|
953
1274
|
/**
|
|
954
1275
|
* Parse a start tag at `pos`.
|
|
955
1276
|
* @param pos - the index of the `<`
|
|
@@ -969,7 +1290,9 @@ export class XmlTokenizer {
|
|
|
969
1290
|
const name = buffer.slice(i, nameEnd);
|
|
970
1291
|
i = nameEnd;
|
|
971
1292
|
const attrs = new Map<string, string>();
|
|
1293
|
+
let declaresNamespace = false;
|
|
972
1294
|
for (;;) {
|
|
1295
|
+
const before = i;
|
|
973
1296
|
while (i < length && isSpace(buffer.charCodeAt(i))) {
|
|
974
1297
|
i++;
|
|
975
1298
|
}
|
|
@@ -978,23 +1301,30 @@ export class XmlTokenizer {
|
|
|
978
1301
|
}
|
|
979
1302
|
const c = buffer.charCodeAt(i);
|
|
980
1303
|
if (c === GT) {
|
|
981
|
-
return { name, attrs, selfClosing: false, end: i + 1 };
|
|
1304
|
+
return { name, attrs, selfClosing: false, declaresNamespace, end: i + 1 };
|
|
982
1305
|
}
|
|
983
1306
|
if (c === SLASH) {
|
|
984
1307
|
if (i + 1 >= length) {
|
|
985
1308
|
return null;
|
|
986
1309
|
}
|
|
987
1310
|
if (buffer.charCodeAt(i + 1) !== GT) {
|
|
988
|
-
throw new XmlSyntaxError(`unexpected "/" in <${name}>`, this.
|
|
1311
|
+
throw new XmlSyntaxError(`unexpected "/" in <${name}>`, this.lineIn(pos, i));
|
|
989
1312
|
}
|
|
990
|
-
return { name, attrs, selfClosing: true, end: i + 2 };
|
|
1313
|
+
return { name, attrs, selfClosing: true, declaresNamespace, end: i + 2 };
|
|
1314
|
+
}
|
|
1315
|
+
if (i === before) {
|
|
1316
|
+
// the name, or the previous value's closing quote, runs into the next attribute
|
|
1317
|
+
throw new XmlSyntaxError(
|
|
1318
|
+
`the attributes of <${name}> must be separated by whitespace`,
|
|
1319
|
+
this.lineIn(pos, i),
|
|
1320
|
+
);
|
|
991
1321
|
}
|
|
992
1322
|
const attrEnd = this.readName(i);
|
|
993
1323
|
if (attrEnd < 0) {
|
|
994
1324
|
return null;
|
|
995
1325
|
}
|
|
996
1326
|
if (attrEnd === i) {
|
|
997
|
-
throw new XmlSyntaxError(`malformed attribute in <${name}>`, this.
|
|
1327
|
+
throw new XmlSyntaxError(`malformed attribute in <${name}>`, this.lineIn(pos, i));
|
|
998
1328
|
}
|
|
999
1329
|
const attrName = buffer.slice(i, attrEnd);
|
|
1000
1330
|
i = attrEnd;
|
|
@@ -1005,7 +1335,7 @@ export class XmlTokenizer {
|
|
|
1005
1335
|
return null;
|
|
1006
1336
|
}
|
|
1007
1337
|
if (buffer.charCodeAt(i) !== EQUALS) {
|
|
1008
|
-
throw new XmlSyntaxError(`attribute ${attrName} of <${name}> has no value`, this.
|
|
1338
|
+
throw new XmlSyntaxError(`attribute ${attrName} of <${name}> has no value`, this.lineIn(pos, i));
|
|
1009
1339
|
}
|
|
1010
1340
|
i++;
|
|
1011
1341
|
while (i < length && isSpace(buffer.charCodeAt(i))) {
|
|
@@ -1016,23 +1346,30 @@ export class XmlTokenizer {
|
|
|
1016
1346
|
}
|
|
1017
1347
|
const quote = buffer.charCodeAt(i);
|
|
1018
1348
|
if (quote !== QUOTE && quote !== APOS) {
|
|
1019
|
-
throw new XmlSyntaxError(`attribute ${attrName} of <${name}> is not quoted`, this.
|
|
1349
|
+
throw new XmlSyntaxError(`attribute ${attrName} of <${name}> is not quoted`, this.lineIn(pos, i));
|
|
1020
1350
|
}
|
|
1021
1351
|
const close = buffer.indexOf(quote === QUOTE ? '"' : "'", i + 1);
|
|
1022
1352
|
if (close < 0) {
|
|
1023
1353
|
return null;
|
|
1024
1354
|
}
|
|
1355
|
+
const line = this.lineIn(pos, i);
|
|
1025
1356
|
if (attrs.has(attrName)) {
|
|
1026
|
-
throw new XmlSyntaxError(`duplicate attribute ${attrName} in <${name}>`,
|
|
1357
|
+
throw new XmlSyntaxError(`duplicate attribute ${attrName} in <${name}>`, line);
|
|
1027
1358
|
}
|
|
1028
1359
|
const raw = buffer.slice(i + 1, close);
|
|
1029
1360
|
if (hasIllegalXmlChar(raw)) {
|
|
1030
1361
|
throw new XmlSyntaxError(
|
|
1031
1362
|
`a character XML 1.0 forbids appears in attribute ${attrName} of <${name}>`,
|
|
1032
|
-
|
|
1363
|
+
line,
|
|
1033
1364
|
);
|
|
1034
1365
|
}
|
|
1035
|
-
|
|
1366
|
+
if (raw.includes("<")) {
|
|
1367
|
+
throw new XmlSyntaxError(`a "<" in the value of attribute ${attrName} of <${name}> (write <)`, line);
|
|
1368
|
+
}
|
|
1369
|
+
if (attrName.startsWith("xmlns")) {
|
|
1370
|
+
declaresNamespace = true;
|
|
1371
|
+
}
|
|
1372
|
+
attrs.set(attrName, this.decode(normalizeAttributeValue(raw), line));
|
|
1036
1373
|
i = close + 1;
|
|
1037
1374
|
}
|
|
1038
1375
|
}
|
|
@@ -1096,10 +1433,26 @@ export class XmlTokenizer {
|
|
|
1096
1433
|
this.textLine = this.line;
|
|
1097
1434
|
}
|
|
1098
1435
|
const raw = this.buffer.slice(start, end);
|
|
1099
|
-
|
|
1100
|
-
|
|
1436
|
+
const bad = illegalCharIndex(raw);
|
|
1437
|
+
if (bad >= 0) {
|
|
1438
|
+
throw new XmlSyntaxError(
|
|
1439
|
+
"a character XML 1.0 forbids appears in character data",
|
|
1440
|
+
lineAt(raw, bad, this.line),
|
|
1441
|
+
);
|
|
1101
1442
|
}
|
|
1102
|
-
|
|
1443
|
+
if (raw.includes(">")) {
|
|
1444
|
+
const probe = this.textTail + raw;
|
|
1445
|
+
const close = probe.indexOf("]]>");
|
|
1446
|
+
if (close >= 0) {
|
|
1447
|
+
throw new XmlSyntaxError(
|
|
1448
|
+
'"]]>" is not allowed in character data (write ]]>)',
|
|
1449
|
+
lineAt(raw, Math.max(0, close - this.textTail.length), this.line),
|
|
1450
|
+
);
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
this.textTail = raw.length >= 2 ? raw.slice(-2) : (this.textTail + raw).slice(-2);
|
|
1454
|
+
this.atStart = false;
|
|
1455
|
+
this.text += this.decode(raw, this.line);
|
|
1103
1456
|
this.advanceLine(start, end);
|
|
1104
1457
|
}
|
|
1105
1458
|
|
|
@@ -1145,16 +1498,49 @@ export class XmlTokenizer {
|
|
|
1145
1498
|
* Open an element.
|
|
1146
1499
|
* @param name - the element name
|
|
1147
1500
|
* @param attrs - its attributes
|
|
1501
|
+
* @param declaresNamespace - whether an attribute name starts with `xmlns`
|
|
1148
1502
|
*/
|
|
1149
|
-
private startElement(name: string, attrs: Map<string, string
|
|
1503
|
+
private startElement(name: string, attrs: Map<string, string>, declaresNamespace: boolean): void {
|
|
1150
1504
|
if (this.stack.length === 0) {
|
|
1151
1505
|
if (this.rootClosed) {
|
|
1152
1506
|
throw new XmlSyntaxError(`a second root element <${name}> follows the document element`, this.line);
|
|
1153
1507
|
}
|
|
1154
1508
|
this.rootSeen = true;
|
|
1155
1509
|
}
|
|
1510
|
+
if (declaresNamespace) {
|
|
1511
|
+
for (const [key, uri] of attrs) {
|
|
1512
|
+
if (key === "xmlns") {
|
|
1513
|
+
this.bindings.push({ prefix: "", uri, depth: this.stack.length });
|
|
1514
|
+
} else if (key.startsWith("xmlns:")) {
|
|
1515
|
+
this.bindings.push({ prefix: key.slice(6), uri, depth: this.stack.length });
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1156
1519
|
this.stack.push(name);
|
|
1157
|
-
this.handler.start(name, attrs, this.line);
|
|
1520
|
+
this.handler.start(name, attrs, this.line, this.namespaceOf(name));
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
/**
|
|
1524
|
+
* The namespace an element name is in (Namespaces in XML 1.0 section 6).
|
|
1525
|
+
* @param name - the element name as written
|
|
1526
|
+
* @returns the URI; "" for no namespace; null for an undeclared prefix or a name that is not
|
|
1527
|
+
* namespace-well-formed
|
|
1528
|
+
*/
|
|
1529
|
+
private namespaceOf(name: string): string | null {
|
|
1530
|
+
const colon = name.indexOf(":");
|
|
1531
|
+
if (colon >= 0 && (colon === 0 || colon === name.length - 1 || name.includes(":", colon + 1))) {
|
|
1532
|
+
return null;
|
|
1533
|
+
}
|
|
1534
|
+
const prefix = colon < 0 ? "" : name.slice(0, colon);
|
|
1535
|
+
for (let i = this.bindings.length - 1; i >= 0; i--) {
|
|
1536
|
+
if (this.bindings[i].prefix === prefix) {
|
|
1537
|
+
return this.bindings[i].uri;
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
if (prefix === "xml") {
|
|
1541
|
+
return XML_NAMESPACE;
|
|
1542
|
+
}
|
|
1543
|
+
return prefix === "" ? "" : null;
|
|
1158
1544
|
}
|
|
1159
1545
|
|
|
1160
1546
|
/**
|
|
@@ -1170,6 +1556,9 @@ export class XmlTokenizer {
|
|
|
1170
1556
|
throw new XmlSyntaxError(`end tag </${name}> does not match <${open}>`, this.line);
|
|
1171
1557
|
}
|
|
1172
1558
|
this.stack.pop();
|
|
1559
|
+
while ((this.bindings.at(-1)?.depth ?? -1) >= this.stack.length) {
|
|
1560
|
+
this.bindings.pop();
|
|
1561
|
+
}
|
|
1173
1562
|
if (this.stack.length === 0) {
|
|
1174
1563
|
this.rootClosed = true;
|
|
1175
1564
|
}
|
|
@@ -1178,10 +1567,10 @@ export class XmlTokenizer {
|
|
|
1178
1567
|
}
|
|
1179
1568
|
|
|
1180
1569
|
/**
|
|
1181
|
-
* Attribute value
|
|
1570
|
+
* Attribute value normalization (XML 1.0 section 3.3.3): a literal tab or line break becomes a
|
|
1182
1571
|
* space; a character reference to one is kept, which is why this runs before entity decoding.
|
|
1183
1572
|
* @param raw - the value between the quotes
|
|
1184
|
-
* @returns the
|
|
1573
|
+
* @returns the normalized value
|
|
1185
1574
|
*/
|
|
1186
1575
|
function normalizeAttributeValue(raw: string): string {
|
|
1187
1576
|
return raw.includes("\n") || raw.includes("\t") ? raw.replace(/[\n\t]/g, " ") : raw;
|