@graphty/graph-io 0.3.37 → 0.3.39

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.
Files changed (141) hide show
  1. package/dist/chunks/{choose-CsO24U-5.js → choose-CS3fvGT1.js} +2 -2
  2. package/dist/chunks/{choose-CsO24U-5.js.map → choose-CS3fvGT1.js.map} +1 -1
  3. package/dist/chunks/{constants-BPRiVyBu.js → constants-DqKZT6cH.js} +2 -2
  4. package/dist/chunks/{constants-BPRiVyBu.js.map → constants-DqKZT6cH.js.map} +1 -1
  5. package/dist/chunks/{escape-BRcyzD77.js → escape-BpYMQZVa.js} +2 -2
  6. package/dist/chunks/{escape-BRcyzD77.js.map → escape-BpYMQZVa.js.map} +1 -1
  7. package/dist/chunks/{export-CTUlibzr.js → export-B-aVCwyG.js} +103 -92
  8. package/dist/chunks/export-B-aVCwyG.js.map +1 -0
  9. package/dist/chunks/{exporter-flsHFoER.js → exporter-BqKs1Q12.js} +5 -5
  10. package/dist/chunks/exporter-BqKs1Q12.js.map +1 -0
  11. package/dist/chunks/{exporter-B_YFZ4ix.js → exporter-CNJZF_BN.js} +7 -7
  12. package/dist/chunks/exporter-CNJZF_BN.js.map +1 -0
  13. package/dist/chunks/{exporter-Bn4jdYfd.js → exporter-DXqCPNrI.js} +4 -4
  14. package/dist/chunks/exporter-DXqCPNrI.js.map +1 -0
  15. package/dist/chunks/{exporter-2UNIxzcu.js → exporter-SFYkDzD0.js} +6 -5
  16. package/dist/chunks/exporter-SFYkDzD0.js.map +1 -0
  17. package/dist/chunks/{importer-oIUx9u_g.js → importer--tQQsY4W.js} +40 -18
  18. package/dist/chunks/importer--tQQsY4W.js.map +1 -0
  19. package/dist/chunks/{importer-yWUPR_nU.js → importer-B7Ir269L.js} +4 -4
  20. package/dist/chunks/{importer-yWUPR_nU.js.map → importer-B7Ir269L.js.map} +1 -1
  21. package/dist/chunks/{importer-Dwqepe_B.js → importer-BfJ1jnNr.js} +10 -4
  22. package/dist/chunks/importer-BfJ1jnNr.js.map +1 -0
  23. package/dist/chunks/{importer-CEuUmyPe.js → importer-CSt-gaYE.js} +6 -6
  24. package/dist/chunks/{importer-CEuUmyPe.js.map → importer-CSt-gaYE.js.map} +1 -1
  25. package/dist/chunks/{importer-Bgb9sgZZ.js → importer-CqgYq4Ld.js} +6 -8
  26. package/dist/chunks/importer-CqgYq4Ld.js.map +1 -0
  27. package/dist/chunks/{importer-BD3mi2QH.js → importer-DvFznt5-.js} +5 -5
  28. package/dist/chunks/importer-DvFznt5-.js.map +1 -0
  29. package/dist/chunks/{importer-DqMtyYLX.js → importer-PVXkeoaS.js} +6 -8
  30. package/dist/chunks/importer-PVXkeoaS.js.map +1 -0
  31. package/dist/chunks/{json-elements-AkglPS3m.js → json-elements-CXs4z1iL.js} +3 -3
  32. package/dist/chunks/{json-elements-AkglPS3m.js.map → json-elements-CXs4z1iL.js.map} +1 -1
  33. package/dist/chunks/{ontology-export-DDoG3egY.js → ontology-export-50L9DOV4.js} +2 -2
  34. package/dist/chunks/{ontology-export-DDoG3egY.js.map → ontology-export-50L9DOV4.js.map} +1 -1
  35. package/dist/chunks/{records-ChmWZGuz.js → records-C9jd7yLD.js} +2 -2
  36. package/dist/chunks/{records-ChmWZGuz.js.map → records-C9jd7yLD.js.map} +1 -1
  37. package/dist/chunks/{text-Btw3sBe_.js → text-Bjfa1xYL.js} +2 -2
  38. package/dist/chunks/{text-Btw3sBe_.js.map → text-Bjfa1xYL.js.map} +1 -1
  39. package/dist/csv.js +4 -4
  40. package/dist/csv.js.map +1 -1
  41. package/dist/cx.js +1 -1
  42. package/dist/cx2.js +2 -2
  43. package/dist/cys.js +1 -1
  44. package/dist/dot.js +1 -1
  45. package/dist/gexf.js +2 -2
  46. package/dist/gml.js +9 -7
  47. package/dist/gml.js.map +1 -1
  48. package/dist/graph-io.js +26 -26
  49. package/dist/graphml.js +2 -2
  50. package/dist/json.js +1 -1
  51. package/dist/neo4j.js +5 -5
  52. package/dist/neo4j.js.map +1 -1
  53. package/dist/obo.js +1 -1
  54. package/dist/pajek.js +1 -1
  55. package/dist/src/common/cx-export.d.ts +5 -2
  56. package/dist/src/common/cx-export.d.ts.map +1 -1
  57. package/dist/src/common/cx-export.js +11 -2
  58. package/dist/src/common/cx-export.js.map +1 -1
  59. package/dist/src/common/direction.js +1 -1
  60. package/dist/src/common/direction.js.map +1 -1
  61. package/dist/src/common/export.d.ts.map +1 -1
  62. package/dist/src/common/export.js +136 -71
  63. package/dist/src/common/export.js.map +1 -1
  64. package/dist/src/common/input.js +1 -1
  65. package/dist/src/common/input.js.map +1 -1
  66. package/dist/src/formats/cx/exporter.js +1 -1
  67. package/dist/src/formats/cx/exporter.js.map +1 -1
  68. package/dist/src/formats/cx2/exporter.js +1 -1
  69. package/dist/src/formats/cx2/exporter.js.map +1 -1
  70. package/dist/src/formats/dot/importer.d.ts +6 -0
  71. package/dist/src/formats/dot/importer.d.ts.map +1 -1
  72. package/dist/src/formats/dot/importer.js +11 -6
  73. package/dist/src/formats/dot/importer.js.map +1 -1
  74. package/dist/src/formats/dot/names.d.ts +5 -0
  75. package/dist/src/formats/dot/names.d.ts.map +1 -1
  76. package/dist/src/formats/dot/names.js +5 -0
  77. package/dist/src/formats/dot/names.js.map +1 -1
  78. package/dist/src/formats/dot/tokenizer.js +9 -9
  79. package/dist/src/formats/dot/tokenizer.js.map +1 -1
  80. package/dist/src/formats/gml/importer.d.ts +6 -0
  81. package/dist/src/formats/gml/importer.d.ts.map +1 -1
  82. package/dist/src/formats/gml/importer.js +6 -3
  83. package/dist/src/formats/gml/importer.js.map +1 -1
  84. package/dist/src/formats/gml/syntax.js +1 -1
  85. package/dist/src/formats/gml/syntax.js.map +1 -1
  86. package/dist/src/formats/graphml/importer.d.ts.map +1 -1
  87. package/dist/src/formats/graphml/importer.js +2 -4
  88. package/dist/src/formats/graphml/importer.js.map +1 -1
  89. package/dist/src/formats/json/dialect.js +1 -1
  90. package/dist/src/formats/json/dialect.js.map +1 -1
  91. package/dist/src/formats/neo4j/exporter.d.ts.map +1 -1
  92. package/dist/src/formats/neo4j/exporter.js +1 -4
  93. package/dist/src/formats/neo4j/exporter.js.map +1 -1
  94. package/dist/src/formats/neo4j/header.js +2 -2
  95. package/dist/src/formats/neo4j/header.js.map +1 -1
  96. package/dist/src/formats/obo/importer.d.ts.map +1 -1
  97. package/dist/src/formats/obo/importer.js +2 -1
  98. package/dist/src/formats/obo/importer.js.map +1 -1
  99. package/dist/src/formats/obo/syntax.js +1 -1
  100. package/dist/src/formats/obo/syntax.js.map +1 -1
  101. package/dist/src/formats/pajek/exporter.d.ts.map +1 -1
  102. package/dist/src/formats/pajek/exporter.js +4 -5
  103. package/dist/src/formats/pajek/exporter.js.map +1 -1
  104. package/dist/src/formats/xgmml/exporter.d.ts.map +1 -1
  105. package/dist/src/formats/xgmml/exporter.js.map +1 -1
  106. package/dist/src/sniff.js +1 -1
  107. package/dist/src/sniff.js.map +1 -1
  108. package/dist/xgmml.js +2 -2
  109. package/package.json +2 -2
  110. package/src/common/cx-export.ts +11 -2
  111. package/src/common/direction.ts +1 -1
  112. package/src/common/export.ts +190 -97
  113. package/src/common/input.ts +1 -1
  114. package/src/formats/csv/importer.ts +1 -1
  115. package/src/formats/cx/exporter.ts +1 -1
  116. package/src/formats/cx2/exporter.ts +1 -1
  117. package/src/formats/dot/importer.ts +36 -5
  118. package/src/formats/dot/names.ts +6 -0
  119. package/src/formats/dot/tokenizer.ts +9 -9
  120. package/src/formats/gml/importer.ts +15 -4
  121. package/src/formats/gml/syntax.ts +1 -1
  122. package/src/formats/graphml/importer.ts +2 -4
  123. package/src/formats/json/dialect.ts +1 -1
  124. package/src/formats/neo4j/exporter.ts +1 -4
  125. package/src/formats/neo4j/header.ts +2 -2
  126. package/src/formats/obo/importer.ts +2 -1
  127. package/src/formats/obo/syntax.ts +1 -1
  128. package/src/formats/pajek/exporter.ts +6 -4
  129. package/src/formats/pajek/importer.ts +1 -1
  130. package/src/formats/xgmml/exporter.ts +4 -1
  131. package/src/sniff.ts +1 -1
  132. package/dist/chunks/export-CTUlibzr.js.map +0 -1
  133. package/dist/chunks/exporter-2UNIxzcu.js.map +0 -1
  134. package/dist/chunks/exporter-B_YFZ4ix.js.map +0 -1
  135. package/dist/chunks/exporter-Bn4jdYfd.js.map +0 -1
  136. package/dist/chunks/exporter-flsHFoER.js.map +0 -1
  137. package/dist/chunks/importer-BD3mi2QH.js.map +0 -1
  138. package/dist/chunks/importer-Bgb9sgZZ.js.map +0 -1
  139. package/dist/chunks/importer-DqMtyYLX.js.map +0 -1
  140. package/dist/chunks/importer-Dwqepe_B.js.map +0 -1
  141. package/dist/chunks/importer-oIUx9u_g.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"ontology-export-DDoG3egY.js","sources":["../../src/common/ontology.ts","../../src/common/ontology-export.ts"],"sourcesContent":["/**\n * What the OBO importer and the JSON importer's `obographs` dialect share (design section 1.0, the\n * `ontology.ts` building block): the OBO column vocabulary (names, dtypes, roles), so the `.obo` and\n * the `.json` of one ontology give the same columns, and the OBO 1.4 rule for turning the IRIs\n * OBO Graphs writes back into the identifiers the `.obo` file writes (section 4.6).\n */\n\nimport { type ColumnDecl } from \"@graphty/graph-format\";\n\n/** The dtypes the OBO vocabulary uses. */\ntype OboDtype = \"string\" | \"dict\" | \"bool\" | \"list\" | \"json\";\n\n/** One column of the OBO vocabulary. */\ninterface OboColumnSpec {\n /** The dtype. */\n readonly dtype: OboDtype;\n /** The role, when the column carries one. */\n readonly role?: \"label\" | \"kind\" | undefined;\n}\n\n/**\n * The node columns of the OBO vocabulary, keyed by column name (design section 4.2). The names are\n * the OBO tag names, so a Gene Ontology user finds `namespace`, `def` and `is_obsolete` under the\n * names the GO documentation uses.\n * @category Plugin helpers\n */\nexport const OBO_NODE_COLUMNS: Readonly<Record<string, OboColumnSpec>> = Object.freeze({\n type: { dtype: \"dict\" },\n name: { dtype: \"string\", role: \"label\" },\n namespace: { dtype: \"dict\" },\n def: { dtype: \"string\" },\n \"def.xrefs\": { dtype: \"list\" },\n comment: { dtype: \"string\" },\n synonym: { dtype: \"json\" },\n xref: { dtype: \"list\" },\n \"xref.descriptions\": { dtype: \"json\" },\n alt_id: { dtype: \"list\" },\n subset: { dtype: \"list\" },\n replaced_by: { dtype: \"list\" },\n consider: { dtype: \"list\" },\n is_obsolete: { dtype: \"bool\" },\n is_anonymous: { dtype: \"bool\" },\n builtin: { dtype: \"bool\" },\n created_by: { dtype: \"string\" },\n creation_date: { dtype: \"string\" },\n intersection_of: { dtype: \"json\" },\n union_of: { dtype: \"list\" },\n equivalent_to: { dtype: \"list\" },\n disjoint_from: { dtype: \"list\" },\n property_value: { dtype: \"json\" },\n // Typedef frames, under typedefs: \"nodes\"\n domain: { dtype: \"string\" },\n range: { dtype: \"string\" },\n inverse_of: { dtype: \"string\" },\n transitive_over: { dtype: \"list\" },\n disjoint_over: { dtype: \"list\" },\n holds_over_chain: { dtype: \"json\" },\n equivalent_to_chain: { dtype: \"json\" },\n expand_assertion_to: { dtype: \"json\" },\n expand_expression_to: { dtype: \"json\" },\n is_cyclic: { dtype: \"bool\" },\n is_reflexive: { dtype: \"bool\" },\n is_symmetric: { dtype: \"bool\" },\n is_anti_symmetric: { dtype: \"bool\" },\n is_asymmetric: { dtype: \"bool\" },\n is_transitive: { dtype: \"bool\" },\n is_functional: { dtype: \"bool\" },\n is_inverse_functional: { dtype: \"bool\" },\n is_metadata_tag: { dtype: \"bool\" },\n is_class_level: { dtype: \"bool\" },\n // OBO Graphs only\n propertyType: { dtype: \"dict\" },\n // what has no column of its own\n \"obo.qualifiers\": { dtype: \"json\" },\n \"obo.unrecognized\": { dtype: \"json\" },\n});\n\n/** The edge columns of the OBO vocabulary. */\nconst OBO_EDGE_COLUMNS: Readonly<Record<string, OboColumnSpec>> = Object.freeze({\n relation: { dtype: \"dict\", role: \"kind\" },\n qualifiers: { dtype: \"json\" },\n meta: { dtype: \"json\" },\n});\n\n/**\n * The node column that marks a node made for an undeclared reference (design section 4.2).\n * @category Plugin helpers\n */\nexport const PLACEHOLDER_COLUMN = \"graphty.placeholder\";\n\n/**\n * The declaration of an OBO vocabulary column.\n * @param domain - node or edge\n * @param name - the column name (a key of OBO_NODE_COLUMNS / OBO_EDGE_COLUMNS, or the placeholder column)\n * @returns the declaration, nullable, with origin `{ format: \"obo\", id: name }`\n * @category Plugin helpers\n */\nexport function oboColumnDecl(domain: \"node\" | \"edge\", name: string): ColumnDecl {\n if (name === PLACEHOLDER_COLUMN) {\n return { name, dtype: \"bool\", nullable: true, origin: { format: \"graphty\", id: \"placeholder\" } };\n }\n const spec = (domain === \"node\" ? OBO_NODE_COLUMNS : OBO_EDGE_COLUMNS)[name];\n const decl: ColumnDecl = { name, dtype: spec.dtype, nullable: true, origin: { format: \"obo\", id: name } };\n if (spec.dtype === \"list\") {\n decl.itemDtype = \"string\";\n }\n if (spec.role !== undefined) {\n decl.role = spec.role;\n }\n return decl;\n}\n\n/**\n * The OBO PURL base every OBO Foundry IRI starts with.\n * @category Plugin helpers\n */\nexport const OBO_PURL = \"http://purl.obolibrary.org/obo/\"; // NOSONAR(S5332): an identifier the OBO standard fixes, never fetched\n\n/** The same base under https, which newer OBO Graphs writers use. */\nconst OBO_PURL_HTTPS = \"https://purl.obolibrary.org/obo/\";\n\n/**\n * The oboInOwl namespace of the OBO-to-OWL mapping's annotation properties.\n * @category Plugin helpers\n */\nexport const OBO_IN_OWL = \"http://www.geneontology.org/formats/oboInOwl#\"; // NOSONAR(S5332): an identifier the OBO standard fixes, never fetched\n\n/**\n * An IRI as the identifier the `.obo` file writes (the OBO 1.4 mapping, section 5.9, read\n * backwards): `http://purl.obolibrary.org/obo/GO_0008150` is `GO:0008150` (the prefix is the text\n * before the first underscore), and `http://purl.obolibrary.org/obo/go#regulates` (how the OWL\n * translation writes an unprefixed OBO id: subsets, relations, synonym types) is `regulates`; the\n * same under `https://purl.obolibrary.org/obo/`.\n * Every other IRI, and an OBO PURL that fits neither form (`.../obo/T/Female`), is kept.\n * @param iri - the IRI\n * @returns the CURIE or local id, or the IRI unchanged\n * @category Plugin helpers\n */\nexport function compactOboIri(iri: string): string {\n const base = [OBO_PURL, OBO_PURL_HTTPS].find((b) => iri.startsWith(b));\n if (base === undefined) {\n return iri;\n }\n const rest = iri.slice(base.length);\n const hash = rest.indexOf(\"#\");\n if (hash > 0) {\n const local = rest.slice(hash + 1);\n return local.length > 0 && !rest.slice(0, hash).includes(\"/\") ? local : iri;\n }\n const underscore = rest.indexOf(\"_\");\n if (underscore <= 0 || rest.includes(\"/\") || underscore === rest.length - 1) {\n return iri;\n }\n return `${rest.slice(0, underscore)}:${rest.slice(underscore + 1)}`;\n}\n\n/**\n * The OBO synonym scopes.\n * @category Plugin helpers\n */\nexport const SYNONYM_SCOPES: ReadonlySet<string> = new Set([\"EXACT\", \"BROAD\", \"NARROW\", \"RELATED\"]);\n\n/**\n * The OBO synonym scope of an OBO Graphs synonym predicate (`hasExactSynonym`, with or without the\n * oboInOwl namespace).\n * @param pred - the predicate\n * @returns the scope, or null for a predicate that is not one of the four\n * @category Plugin helpers\n */\nexport function synonymScopeOf(pred: string): string | null {\n const local = pred.startsWith(OBO_IN_OWL) ? pred.slice(OBO_IN_OWL.length) : pred;\n const match = /^has(Exact|Broad|Narrow|Related)Synonym$/.exec(local);\n return match === null ? null : match[1].toUpperCase();\n}\n\n/**\n * The OBO tag a `basicPropertyValues` predicate of OBO Graphs came from (the OBO-to-OWL mapping of\n * the tags that are annotations), so the `.json` of an ontology fills the same columns as its\n * `.obo`. `shorthand` is the relation's short name; every predicate not listed is a\n * `property_value`.\n * @category Plugin helpers\n */\nexport const OBOGRAPHS_PREDICATE_TAGS: ReadonlyMap<string, string> = new Map([\n [`${OBO_IN_OWL}hasOBONamespace`, \"namespace\"],\n [`${OBO_IN_OWL}hasAlternativeId`, \"alt_id\"],\n [`${OBO_IN_OWL}created_by`, \"created_by\"],\n [`${OBO_IN_OWL}creation_date`, \"creation_date\"],\n [`${OBO_PURL}IAO_0100001`, \"replaced_by\"],\n [`${OBO_IN_OWL}consider`, \"consider\"],\n [`${OBO_IN_OWL}shorthand`, \"shorthand\"],\n]);\n","/**\n * What the two ontology exporters share (the OBO flat file and the JSON exporter's `obographs`\n * dialect): the snapshot columns that fill the OBO slots (the label as `name`, the edge kind as\n * `relation`), the placeholder nodes a re-import makes again by itself, the order a re-import gives\n * the nodes, and the text of a cell. Both read the snapshot through the column vocabulary of\n * ontology.ts, so a column means the same thing in both files.\n */\n\nimport { type Column, GraphFormatError, type GraphSnapshot } from \"@graphty/graph-format\";\n\nimport { type ExportCapabilities } from \"../types.js\";\nimport { COLUMN_AS_PROPERTY_VALUE_CODE, ROLE_ASSUMED_CODE } from \"./codes.js\";\nimport { checkCapabilities, LOSS } from \"./export.js\";\nimport { formatNumber } from \"./format.js\";\nimport { PLACEHOLDER_COLUMN } from \"./ontology.js\";\nimport { type ResolvedExportOptions } from \"./options.js\";\n\n/**\n * A note recorder.\n * @category Plugin helpers\n */\nexport type NoteFn = (code: string, message: string, column?: string | null, count?: number | null) => void;\n\n/** The generic notes the ontology formats replace with their own (see capabilityNotes()). */\nconst SUPERSEDED: ReadonlySet<string> = new Set([\n LOSS.DTYPE,\n LOSS.LIST,\n LOSS.JSON,\n LOSS.COMPONENTS,\n LOSS.GRAPH_ATTRIBUTES,\n]);\n\n/** The roles the ontology formats have a slot for, and the column names their importers give them. */\nconst SLOT_ROLES: ReadonlySet<string> = new Set([\"label\", \"kind\"]);\nconst NODE_ROLE_NAMES: Readonly<Record<string, string>> = Object.freeze({ label: \"name\" });\nconst EDGE_ROLE_NAMES: Readonly<Record<string, string>> = Object.freeze({ kind: \"relation\" });\n\n/**\n * The roles whose columns are never written as values (their loss notes come from checkCapabilities()).\n * @category Plugin helpers\n */\nexport const UNWRITTEN_ROLES: ReadonlySet<string> = new Set([\n \"directed\",\n \"pair\",\n \"mutual\",\n \"weight\",\n \"timeText\",\n \"originalId\",\n \"parent\",\n \"parents\",\n \"start\",\n \"end\",\n \"timestamp\",\n \"timestamps\",\n \"spells\",\n \"open\",\n \"spellsOpen\",\n \"position\",\n \"color\",\n \"size\",\n \"shape\",\n \"thickness\",\n]);\n\n/**\n * The OBO frame types.\n * @category Plugin helpers\n */\nexport type FrameKind = \"Term\" | \"Instance\" | \"Typedef\";\n\n/**\n * The frame types as a set, for checking a `type` cell.\n * @category Plugin helpers\n */\nexport const FRAME_KINDS: ReadonlySet<string> = new Set([\"Term\", \"Instance\", \"Typedef\"]);\n\n/**\n * The value of a set cell (a multi-component value as a plain array), or undefined when unset; a\n * declared default is not a value here (the formats have no defaults and say so).\n * @param column - the column\n * @param row - the row\n * @returns the value or undefined\n * @category Plugin helpers\n */\nexport function cellOf(column: Column, row: number): unknown {\n if (!column.isSet(row)) {\n return undefined;\n }\n const value: unknown = column.value(row);\n return ArrayBuffer.isView(value) ? Array.from(value as unknown as ArrayLike<number>) : value;\n}\n\n/**\n * The text of a scalar value: a number by its dtype's shortest round-tripping form (`-0` kept), a\n * boolean as `true` / `false`, a string as it is, anything else as its JSON text.\n * @param value - the value\n * @param dtype - the dtype it came from (the item dtype for a list item)\n * @returns the text\n * @category Plugin helpers\n */\nexport function textOf(value: unknown, dtype: string): string {\n switch (typeof value) {\n case \"string\":\n return value;\n case \"boolean\":\n return value ? \"true\" : \"false\";\n case \"number\":\n return formatNumber(\n value,\n dtype === \"f32\" || dtype === \"i32\" || dtype === \"u32\" || dtype === \"u8\" ? dtype : \"f64\",\n );\n default:\n return JSON.stringify(value) ?? \"null\";\n }\n}\n\n/**\n * Whether a column has the dtype of a vocabulary column (string and dict both fit a text column;\n * the other text dtype is reported, not refused).\n * @param want - the vocabulary dtype\n * @param column - the column\n * @returns true when the column can be written into the vocabulary slot\n * @category Plugin helpers\n */\nexport function fitsVocabulary(want: string, column: Column): boolean {\n switch (want) {\n case \"list\":\n case \"bool\":\n case \"json\":\n return column.dtype === want;\n default:\n return isText(column);\n }\n}\n\n/**\n * The values of a cell that may hold one value or a list of them.\n * @param value - the cell value, or undefined\n * @returns the values\n * @category Plugin helpers\n */\nexport function valuesOf(value: unknown): readonly unknown[] {\n if (value === undefined) {\n return [];\n }\n return Array.isArray(value) ? value : [value];\n}\n\n/** A column chosen for an OBO slot. */\ninterface Slot {\n /** The column, or null when the snapshot has none for the slot. */\n readonly column: Column | null;\n /** Whether a role-less column was chosen by its name; it reads back with the role (W_ROLE_ASSUMED). */\n readonly assumed: boolean;\n}\n\n/**\n * The column of a slot: the column with the role when it holds text, else a role-less text column\n * of the slot's name (which the importer reads back with the role).\n * @param table - the node or edge table\n * @param role - the role (`label` for `name`, `kind` for `relation`)\n * @param name - the name the importer gives the slot\n * @returns the column and whether its role is assumed\n * @category Plugin helpers\n */\nexport function slotColumn(table: GraphSnapshot[\"nodes\"], role: \"label\" | \"kind\", name: string): Slot {\n const byRole = table.byRole(role);\n if (byRole !== null) {\n return { column: isText(byRole) ? byRole : null, assumed: false };\n }\n const byName = table.get(name);\n if (byName !== null && byName.meta.role === null && isText(byName)) {\n return { column: byName, assumed: true };\n }\n return { column: null, assumed: false };\n}\n\n/**\n * Whether a column holds text (string or dict).\n * @param column - the column\n * @returns true for string and dict\n */\nfunction isText(column: Column): boolean {\n return column.dtype === \"string\" || column.dtype === \"dict\";\n}\n\n/**\n * The placeholder column (`graphty.placeholder`, a bool), or null.\n * @param snapshot - the snapshot\n * @returns the column\n */\nfunction placeholderColumn(snapshot: GraphSnapshot): Column | null {\n const column = snapshot.nodes.get(PLACEHOLDER_COLUMN);\n return column !== null && column.dtype === \"bool\" ? column : null;\n}\n\n/**\n * The nodes written without a frame (OBO) or a node record (OBO Graphs) because the re-import makes\n * them again as placeholders: a `graphty.placeholder` true cell, no other set cell, an id written\n * as it is, an edge that references it, and (in the flat file, where an edge is written on its\n * source's frame) no edge of its own.\n * @param snapshot - the snapshot\n * @param written - the logical edges written\n * @param options - `outgoingAllowed`: whether a placeholder may be an edge's source; `changed`: whether a node's id is rewritten\n * @param options.outgoingAllowed - whether a placeholder may be an edge's source\n * @param options.changed - whether a node's id is rewritten\n * @returns one flag per node, 1 for a node written without a frame\n * @category Plugin helpers\n */\nexport function framelessNodes(\n snapshot: GraphSnapshot,\n written: readonly number[],\n options: { readonly outgoingAllowed: boolean; readonly changed: (i: number) => boolean },\n): Uint8Array {\n const flags = new Uint8Array(snapshot.nodeCount);\n const placeholder = placeholderColumn(snapshot);\n if (placeholder === null) {\n return flags;\n }\n const { src, dst } = snapshot.edgeList();\n const referenced = new Uint8Array(snapshot.nodeCount);\n const sources = new Uint8Array(snapshot.nodeCount);\n for (const e of written) {\n referenced[dst[e]] = 1;\n if (options.outgoingAllowed) {\n referenced[src[e]] = 1;\n } else {\n sources[src[e]] = 1;\n }\n }\n const others = [...snapshot.nodes].filter((c) => c !== placeholder);\n for (let i = 0; i < snapshot.nodeCount; i++) {\n if (\n placeholder.isSet(i) &&\n placeholder.value(i) === true &&\n referenced[i] === 1 &&\n sources[i] === 0 &&\n !options.changed(i) &&\n others.every((c) => !c.isSet(i))\n ) {\n flags[i] = 1;\n }\n }\n return flags;\n}\n\n/**\n * How many nodes a re-import puts at another index: the written nodes keep their order and the\n * placeholders follow in the order the edges first reference them.\n * @param frameless - the framelessNodes() flags\n * @param references - the node indices in the order the written edges reference them\n * @returns the number of nodes whose index changes\n * @category Plugin helpers\n */\nexport function movedNodes(frameless: Uint8Array, references: Iterable<number>): number {\n const order: number[] = [];\n for (let i = 0; i < frameless.length; i++) {\n if (frameless[i] === 0) {\n order.push(i);\n }\n }\n const seen = new Uint8Array(frameless.length);\n for (const i of references) {\n if (frameless[i] === 1 && seen[i] === 0) {\n seen[i] = 1;\n order.push(i);\n }\n }\n let moved = 0;\n for (let k = 0; k < order.length; k++) {\n if (order[k] !== k) {\n moved++;\n }\n }\n return moved;\n}\n\n/**\n * Whether a value is a qualifier record as the importers make it: a non-empty object of names to a\n * text or a list of two or more texts (a name given once reads back as a text, never as a\n * one-item list), every name passing `nameOk`.\n * @param value - the value\n * @param nameOk - the name rule\n * @returns true when the record is written and read back unchanged\n * @category Plugin helpers\n */\nexport function isQualifierRecord(value: unknown, nameOk: (name: string) => boolean): boolean {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n return false;\n }\n const entries = Object.entries(value);\n return (\n entries.length > 0 &&\n entries.every(\n ([name, v]) =>\n nameOk(name) &&\n (typeof v === \"string\" ||\n (Array.isArray(v) && v.length >= 2 && v.every((item) => typeof item === \"string\"))),\n )\n );\n}\n\n/** What capabilityNotes() needs to know about the export. */\ninterface CapabilityNoteSpec {\n /** The format's capabilities. */\n readonly caps: ExportCapabilities;\n /** The label column written as the name, or null. */\n readonly label: Column | null;\n /** Whether the label is a role-less `name` column. */\n readonly labelAssumed: boolean;\n /** Where an edge column without a slot goes, for messages (\"a qualifier\"). */\n readonly edgeSlot: string;\n /** Records a note. */\n readonly note: NoteFn;\n}\n\n/**\n * The generic notes of checkCapabilities() as the ontology formats see them. No column of the\n * snapshot's own survives as a column (the formats keep only the OBO vocabulary), so the generic\n * dtype, list, json, strides and graph-attribute notes give way to the formats' own, which say\n * where each column goes (a vocabulary column written into its slot needs no note; any other\n * becomes property values, edge qualifiers or metadata). The name-change notes stand only for the\n * label and kind columns actually written into their slot. Added: the roles without a slot (a\n * node kind, an edge label) and a role-less `name` column taking the label role.\n * @param snapshot - the snapshot\n * @param common - the common options\n * @param spec - the export's facts\n * @returns the fatal error of a mixed graph under onMixedDirection \"error\", or null\n * @category Plugin helpers\n */\nexport function capabilityNotes(\n snapshot: GraphSnapshot,\n common: ResolvedExportOptions,\n spec: CapabilityNoteSpec,\n): GraphFormatError | null {\n const { label, note } = spec;\n let fatal: GraphFormatError | null = null;\n const kind = snapshot.edges.byRole(\"kind\");\n // a node label goes into the name slot and an edge kind into the relation slot, when written there;\n // an edge label has no slot (it becomes edge qualifiers or metadata), so it is never renamed\n const extras = {\n roles: SLOT_ROLES,\n nodeRoleNames: label === null ? {} : NODE_ROLE_NAMES,\n edgeRoleNames: kind !== null && isText(kind) ? EDGE_ROLE_NAMES : {},\n };\n for (const gen of checkCapabilities(snapshot, spec.caps, common, extras)) {\n if (SUPERSEDED.has(gen.code)) {\n continue;\n }\n if (gen.code === LOSS.MIXED_DIRECTION_ERROR && fatal === null) {\n fatal = new GraphFormatError(\"E_DIRECTED\", gen.message, { reason: \"mixed direction\" });\n }\n note(gen.code, gen.message, gen.column, gen.count);\n }\n slotlessRoleNotes(snapshot, spec);\n return fatal;\n}\n\n/**\n * Note the roles the ontology formats have no slot for (a node kind, an edge label), and a\n * role-less `name` column taking the label role.\n * @param snapshot - the snapshot\n * @param spec - the export's facts\n */\nfunction slotlessRoleNotes(snapshot: GraphSnapshot, spec: CapabilityNoteSpec): void {\n const { label, note } = spec;\n const nodeKind = snapshot.nodes.byRole(\"kind\");\n if (nodeKind !== null) {\n note(\n LOSS.ROLE,\n `node column \"${nodeKind.meta.name}\" (kind) is written as property values; the format has no node kind slot`,\n nodeKind.meta.name,\n nodeKind.length - nodeKind.nullCount,\n );\n }\n const edgeLabel = snapshot.edges.byRole(\"label\");\n if (edgeLabel !== null) {\n note(\n LOSS.ROLE,\n `edge column \"${edgeLabel.meta.name}\" (label) is written as ${spec.edgeSlot}; the format has no edge label slot`,\n edgeLabel.meta.name,\n edgeLabel.length - edgeLabel.nullCount,\n );\n }\n if (label !== null && spec.labelAssumed) {\n note(\n ROLE_ASSUMED_CODE,\n `node column \"name\" is written as each node's name and reads back as the label`,\n \"name\",\n label.length - label.nullCount,\n );\n }\n}\n\n/**\n * Record the columns that read back absent: a node or edge column without a set cell on a written\n * element (the importers declare a column when a value needs it).\n * @param snapshot - the snapshot\n * @param rows - the written nodes\n * @param frameless - the frameless flags (a placeholder column set only there reads back)\n * @param written - the written edges\n * @param note - records a note\n * @category Plugin helpers\n */\nexport function emptyColumnNotes(\n snapshot: GraphSnapshot,\n rows: readonly number[],\n frameless: Uint8Array,\n written: readonly number[],\n note: NoteFn,\n): void {\n const tables = [\n [\n \"node\",\n snapshot.nodes,\n (c: Column): boolean =>\n rows.some((i) => c.isSet(i)) ||\n (c.meta.name === PLACEHOLDER_COLUMN && frameless.some((f, i) => f === 1 && c.isSet(i))),\n ],\n [\"edge\", snapshot.edges, (c: Column): boolean => written.some((e) => c.isSet(e))],\n ] as const;\n for (const [domain, table, used] of tables) {\n for (const column of table) {\n const { role, name } = column.meta;\n if (\n (role !== null && (UNWRITTEN_ROLES.has(role) || (domain === \"edge\" && role === \"id\"))) ||\n used(column)\n ) {\n continue;\n }\n note(LOSS.EMPTY_COLUMN, `${domain} column \"${name}\" has no value to write; it reads back absent`, name, 0);\n }\n }\n}\n\n/**\n * Record the note of a node column written as property values.\n * @param note - the recorder\n * @param column - the column\n * @param rows - the written nodes\n * @param how - how the values are written, for the message\n * @category Plugin helpers\n */\nexport function notePropertyColumn(note: NoteFn, column: Column, rows: readonly number[], how: string): void {\n const count = rows.filter((i) => column.isSet(i)).length;\n if (count === 0) {\n return;\n }\n const role = column.meta.role === null ? \"\" : ` (its ${column.meta.role} role is lost)`;\n note(\n COLUMN_AS_PROPERTY_VALUE_CODE,\n `node column \"${column.meta.name}\" is written as ${how} and reads back inside the property_value column, not as its own column${role}`,\n column.meta.name,\n count,\n );\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ontology-export-50L9DOV4.js","sources":["../../src/common/ontology.ts","../../src/common/ontology-export.ts"],"sourcesContent":["/**\n * What the OBO importer and the JSON importer's `obographs` dialect share (design section 1.0, the\n * `ontology.ts` building block): the OBO column vocabulary (names, dtypes, roles), so the `.obo` and\n * the `.json` of one ontology give the same columns, and the OBO 1.4 rule for turning the IRIs\n * OBO Graphs writes back into the identifiers the `.obo` file writes (section 4.6).\n */\n\nimport { type ColumnDecl } from \"@graphty/graph-format\";\n\n/** The dtypes the OBO vocabulary uses. */\ntype OboDtype = \"string\" | \"dict\" | \"bool\" | \"list\" | \"json\";\n\n/** One column of the OBO vocabulary. */\ninterface OboColumnSpec {\n /** The dtype. */\n readonly dtype: OboDtype;\n /** The role, when the column carries one. */\n readonly role?: \"label\" | \"kind\" | undefined;\n}\n\n/**\n * The node columns of the OBO vocabulary, keyed by column name (design section 4.2). The names are\n * the OBO tag names, so a Gene Ontology user finds `namespace`, `def` and `is_obsolete` under the\n * names the GO documentation uses.\n * @category Plugin helpers\n */\nexport const OBO_NODE_COLUMNS: Readonly<Record<string, OboColumnSpec>> = Object.freeze({\n type: { dtype: \"dict\" },\n name: { dtype: \"string\", role: \"label\" },\n namespace: { dtype: \"dict\" },\n def: { dtype: \"string\" },\n \"def.xrefs\": { dtype: \"list\" },\n comment: { dtype: \"string\" },\n synonym: { dtype: \"json\" },\n xref: { dtype: \"list\" },\n \"xref.descriptions\": { dtype: \"json\" },\n alt_id: { dtype: \"list\" },\n subset: { dtype: \"list\" },\n replaced_by: { dtype: \"list\" },\n consider: { dtype: \"list\" },\n is_obsolete: { dtype: \"bool\" },\n is_anonymous: { dtype: \"bool\" },\n builtin: { dtype: \"bool\" },\n created_by: { dtype: \"string\" },\n creation_date: { dtype: \"string\" },\n intersection_of: { dtype: \"json\" },\n union_of: { dtype: \"list\" },\n equivalent_to: { dtype: \"list\" },\n disjoint_from: { dtype: \"list\" },\n property_value: { dtype: \"json\" },\n // Typedef frames, under typedefs: \"nodes\"\n domain: { dtype: \"string\" },\n range: { dtype: \"string\" },\n inverse_of: { dtype: \"string\" },\n transitive_over: { dtype: \"list\" },\n disjoint_over: { dtype: \"list\" },\n holds_over_chain: { dtype: \"json\" },\n equivalent_to_chain: { dtype: \"json\" },\n expand_assertion_to: { dtype: \"json\" },\n expand_expression_to: { dtype: \"json\" },\n is_cyclic: { dtype: \"bool\" },\n is_reflexive: { dtype: \"bool\" },\n is_symmetric: { dtype: \"bool\" },\n is_anti_symmetric: { dtype: \"bool\" },\n is_asymmetric: { dtype: \"bool\" },\n is_transitive: { dtype: \"bool\" },\n is_functional: { dtype: \"bool\" },\n is_inverse_functional: { dtype: \"bool\" },\n is_metadata_tag: { dtype: \"bool\" },\n is_class_level: { dtype: \"bool\" },\n // OBO Graphs only\n propertyType: { dtype: \"dict\" },\n // what has no column of its own\n \"obo.qualifiers\": { dtype: \"json\" },\n \"obo.unrecognized\": { dtype: \"json\" },\n});\n\n/** The edge columns of the OBO vocabulary. */\nconst OBO_EDGE_COLUMNS: Readonly<Record<string, OboColumnSpec>> = Object.freeze({\n relation: { dtype: \"dict\", role: \"kind\" },\n qualifiers: { dtype: \"json\" },\n meta: { dtype: \"json\" },\n});\n\n/**\n * The node column that marks a node made for an undeclared reference (design section 4.2).\n * @category Plugin helpers\n */\nexport const PLACEHOLDER_COLUMN = \"graphty.placeholder\";\n\n/**\n * The declaration of an OBO vocabulary column.\n * @param domain - node or edge\n * @param name - the column name (a key of OBO_NODE_COLUMNS / OBO_EDGE_COLUMNS, or the placeholder column)\n * @returns the declaration, nullable, with origin `{ format: \"obo\", id: name }`\n * @category Plugin helpers\n */\nexport function oboColumnDecl(domain: \"node\" | \"edge\", name: string): ColumnDecl {\n if (name === PLACEHOLDER_COLUMN) {\n return { name, dtype: \"bool\", nullable: true, origin: { format: \"graphty\", id: \"placeholder\" } };\n }\n const spec = (domain === \"node\" ? OBO_NODE_COLUMNS : OBO_EDGE_COLUMNS)[name];\n const decl: ColumnDecl = { name, dtype: spec.dtype, nullable: true, origin: { format: \"obo\", id: name } };\n if (spec.dtype === \"list\") {\n decl.itemDtype = \"string\";\n }\n if (spec.role !== undefined) {\n decl.role = spec.role;\n }\n return decl;\n}\n\n/**\n * The OBO PURL base every OBO Foundry IRI starts with.\n * @category Plugin helpers\n */\nexport const OBO_PURL = \"http://purl.obolibrary.org/obo/\"; // NOSONAR(S5332): an identifier the OBO standard fixes, never fetched\n\n/** The same base under https, which newer OBO Graphs writers use. */\nconst OBO_PURL_HTTPS = \"https://purl.obolibrary.org/obo/\";\n\n/**\n * The oboInOwl namespace of the OBO-to-OWL mapping's annotation properties.\n * @category Plugin helpers\n */\nexport const OBO_IN_OWL = \"http://www.geneontology.org/formats/oboInOwl#\"; // NOSONAR(S5332): an identifier the OBO standard fixes, never fetched\n\n/**\n * An IRI as the identifier the `.obo` file writes (the OBO 1.4 mapping, section 5.9, read\n * backwards): `http://purl.obolibrary.org/obo/GO_0008150` is `GO:0008150` (the prefix is the text\n * before the first underscore), and `http://purl.obolibrary.org/obo/go#regulates` (how the OWL\n * translation writes an unprefixed OBO id: subsets, relations, synonym types) is `regulates`; the\n * same under `https://purl.obolibrary.org/obo/`.\n * Every other IRI, and an OBO PURL that fits neither form (`.../obo/T/Female`), is kept.\n * @param iri - the IRI\n * @returns the CURIE or local id, or the IRI unchanged\n * @category Plugin helpers\n */\nexport function compactOboIri(iri: string): string {\n const base = [OBO_PURL, OBO_PURL_HTTPS].find((b) => iri.startsWith(b));\n if (base === undefined) {\n return iri;\n }\n const rest = iri.slice(base.length);\n const hash = rest.indexOf(\"#\");\n if (hash > 0) {\n const local = rest.slice(hash + 1);\n return local.length > 0 && !rest.slice(0, hash).includes(\"/\") ? local : iri;\n }\n const underscore = rest.indexOf(\"_\");\n if (underscore <= 0 || rest.includes(\"/\") || underscore === rest.length - 1) {\n return iri;\n }\n return `${rest.slice(0, underscore)}:${rest.slice(underscore + 1)}`;\n}\n\n/**\n * The OBO synonym scopes.\n * @category Plugin helpers\n */\nexport const SYNONYM_SCOPES: ReadonlySet<string> = new Set([\"EXACT\", \"BROAD\", \"NARROW\", \"RELATED\"]);\n\n/**\n * The OBO synonym scope of an OBO Graphs synonym predicate (`hasExactSynonym`, with or without the\n * oboInOwl namespace).\n * @param pred - the predicate\n * @returns the scope, or null for a predicate that is not one of the four\n * @category Plugin helpers\n */\nexport function synonymScopeOf(pred: string): string | null {\n const local = pred.startsWith(OBO_IN_OWL) ? pred.slice(OBO_IN_OWL.length) : pred;\n const match = /^has(Exact|Broad|Narrow|Related)Synonym$/.exec(local);\n return match === null ? null : match[1].toUpperCase();\n}\n\n/**\n * The OBO tag a `basicPropertyValues` predicate of OBO Graphs came from (the OBO-to-OWL mapping of\n * the tags that are annotations), so the `.json` of an ontology fills the same columns as its\n * `.obo`. `shorthand` is the relation's short name; every predicate not listed is a\n * `property_value`.\n * @category Plugin helpers\n */\nexport const OBOGRAPHS_PREDICATE_TAGS: ReadonlyMap<string, string> = new Map([\n [`${OBO_IN_OWL}hasOBONamespace`, \"namespace\"],\n [`${OBO_IN_OWL}hasAlternativeId`, \"alt_id\"],\n [`${OBO_IN_OWL}created_by`, \"created_by\"],\n [`${OBO_IN_OWL}creation_date`, \"creation_date\"],\n [`${OBO_PURL}IAO_0100001`, \"replaced_by\"],\n [`${OBO_IN_OWL}consider`, \"consider\"],\n [`${OBO_IN_OWL}shorthand`, \"shorthand\"],\n]);\n","/**\n * What the two ontology exporters share (the OBO flat file and the JSON exporter's `obographs`\n * dialect): the snapshot columns that fill the OBO slots (the label as `name`, the edge kind as\n * `relation`), the placeholder nodes a re-import makes again by itself, the order a re-import gives\n * the nodes, and the text of a cell. Both read the snapshot through the column vocabulary of\n * ontology.ts, so a column means the same thing in both files.\n */\n\nimport { type Column, GraphFormatError, type GraphSnapshot } from \"@graphty/graph-format\";\n\nimport { type ExportCapabilities } from \"../types.js\";\nimport { COLUMN_AS_PROPERTY_VALUE_CODE, ROLE_ASSUMED_CODE } from \"./codes.js\";\nimport { checkCapabilities, LOSS } from \"./export.js\";\nimport { formatNumber } from \"./format.js\";\nimport { PLACEHOLDER_COLUMN } from \"./ontology.js\";\nimport { type ResolvedExportOptions } from \"./options.js\";\n\n/**\n * A note recorder.\n * @category Plugin helpers\n */\nexport type NoteFn = (code: string, message: string, column?: string | null, count?: number | null) => void;\n\n/** The generic notes the ontology formats replace with their own (see capabilityNotes()). */\nconst SUPERSEDED: ReadonlySet<string> = new Set([\n LOSS.DTYPE,\n LOSS.LIST,\n LOSS.JSON,\n LOSS.COMPONENTS,\n LOSS.GRAPH_ATTRIBUTES,\n]);\n\n/** The roles the ontology formats have a slot for, and the column names their importers give them. */\nconst SLOT_ROLES: ReadonlySet<string> = new Set([\"label\", \"kind\"]);\nconst NODE_ROLE_NAMES: Readonly<Record<string, string>> = Object.freeze({ label: \"name\" });\nconst EDGE_ROLE_NAMES: Readonly<Record<string, string>> = Object.freeze({ kind: \"relation\" });\n\n/**\n * The roles whose columns are never written as values (their loss notes come from checkCapabilities()).\n * @category Plugin helpers\n */\nexport const UNWRITTEN_ROLES: ReadonlySet<string> = new Set([\n \"directed\",\n \"pair\",\n \"mutual\",\n \"weight\",\n \"timeText\",\n \"originalId\",\n \"parent\",\n \"parents\",\n \"start\",\n \"end\",\n \"timestamp\",\n \"timestamps\",\n \"spells\",\n \"open\",\n \"spellsOpen\",\n \"position\",\n \"color\",\n \"size\",\n \"shape\",\n \"thickness\",\n]);\n\n/**\n * The OBO frame types.\n * @category Plugin helpers\n */\nexport type FrameKind = \"Term\" | \"Instance\" | \"Typedef\";\n\n/**\n * The frame types as a set, for checking a `type` cell.\n * @category Plugin helpers\n */\nexport const FRAME_KINDS: ReadonlySet<string> = new Set([\"Term\", \"Instance\", \"Typedef\"]);\n\n/**\n * The value of a set cell (a multi-component value as a plain array), or undefined when unset; a\n * declared default is not a value here (the formats have no defaults and say so).\n * @param column - the column\n * @param row - the row\n * @returns the value or undefined\n * @category Plugin helpers\n */\nexport function cellOf(column: Column, row: number): unknown {\n if (!column.isSet(row)) {\n return undefined;\n }\n const value: unknown = column.value(row);\n return ArrayBuffer.isView(value) ? Array.from(value as unknown as ArrayLike<number>) : value;\n}\n\n/**\n * The text of a scalar value: a number by its dtype's shortest round-tripping form (`-0` kept), a\n * boolean as `true` / `false`, a string as it is, anything else as its JSON text.\n * @param value - the value\n * @param dtype - the dtype it came from (the item dtype for a list item)\n * @returns the text\n * @category Plugin helpers\n */\nexport function textOf(value: unknown, dtype: string): string {\n switch (typeof value) {\n case \"string\":\n return value;\n case \"boolean\":\n return value ? \"true\" : \"false\";\n case \"number\":\n return formatNumber(\n value,\n dtype === \"f32\" || dtype === \"i32\" || dtype === \"u32\" || dtype === \"u8\" ? dtype : \"f64\",\n );\n default:\n return JSON.stringify(value) ?? \"null\";\n }\n}\n\n/**\n * Whether a column has the dtype of a vocabulary column (string and dict both fit a text column;\n * the other text dtype is reported, not refused).\n * @param want - the vocabulary dtype\n * @param column - the column\n * @returns true when the column can be written into the vocabulary slot\n * @category Plugin helpers\n */\nexport function fitsVocabulary(want: string, column: Column): boolean {\n switch (want) {\n case \"list\":\n case \"bool\":\n case \"json\":\n return column.dtype === want;\n default:\n return isText(column);\n }\n}\n\n/**\n * The values of a cell that may hold one value or a list of them.\n * @param value - the cell value, or undefined\n * @returns the values\n * @category Plugin helpers\n */\nexport function valuesOf(value: unknown): readonly unknown[] {\n if (value === undefined) {\n return [];\n }\n return Array.isArray(value) ? value : [value];\n}\n\n/** A column chosen for an OBO slot. */\ninterface Slot {\n /** The column, or null when the snapshot has none for the slot. */\n readonly column: Column | null;\n /** Whether a role-less column was chosen by its name; it reads back with the role (W_ROLE_ASSUMED). */\n readonly assumed: boolean;\n}\n\n/**\n * The column of a slot: the column with the role when it holds text, else a role-less text column\n * of the slot's name (which the importer reads back with the role).\n * @param table - the node or edge table\n * @param role - the role (`label` for `name`, `kind` for `relation`)\n * @param name - the name the importer gives the slot\n * @returns the column and whether its role is assumed\n * @category Plugin helpers\n */\nexport function slotColumn(table: GraphSnapshot[\"nodes\"], role: \"label\" | \"kind\", name: string): Slot {\n const byRole = table.byRole(role);\n if (byRole !== null) {\n return { column: isText(byRole) ? byRole : null, assumed: false };\n }\n const byName = table.get(name);\n if (byName !== null && byName.meta.role === null && isText(byName)) {\n return { column: byName, assumed: true };\n }\n return { column: null, assumed: false };\n}\n\n/**\n * Whether a column holds text (string or dict).\n * @param column - the column\n * @returns true for string and dict\n */\nfunction isText(column: Column): boolean {\n return column.dtype === \"string\" || column.dtype === \"dict\";\n}\n\n/**\n * The placeholder column (`graphty.placeholder`, a bool), or null.\n * @param snapshot - the snapshot\n * @returns the column\n */\nfunction placeholderColumn(snapshot: GraphSnapshot): Column | null {\n const column = snapshot.nodes.get(PLACEHOLDER_COLUMN);\n return column !== null && column.dtype === \"bool\" ? column : null;\n}\n\n/**\n * The nodes written without a frame (OBO) or a node record (OBO Graphs) because the re-import makes\n * them again as placeholders: a `graphty.placeholder` true cell, no other set cell, an id written\n * as it is, an edge that references it, and (in the flat file, where an edge is written on its\n * source's frame) no edge of its own.\n * @param snapshot - the snapshot\n * @param written - the logical edges written\n * @param options - `outgoingAllowed`: whether a placeholder may be an edge's source; `changed`: whether a node's id is rewritten\n * @param options.outgoingAllowed - whether a placeholder may be an edge's source\n * @param options.changed - whether a node's id is rewritten\n * @returns one flag per node, 1 for a node written without a frame\n * @category Plugin helpers\n */\nexport function framelessNodes(\n snapshot: GraphSnapshot,\n written: readonly number[],\n options: { readonly outgoingAllowed: boolean; readonly changed: (i: number) => boolean },\n): Uint8Array {\n const flags = new Uint8Array(snapshot.nodeCount);\n const placeholder = placeholderColumn(snapshot);\n if (placeholder === null) {\n return flags;\n }\n const { src, dst } = snapshot.edgeList();\n const referenced = new Uint8Array(snapshot.nodeCount);\n const sources = new Uint8Array(snapshot.nodeCount);\n for (const e of written) {\n referenced[dst[e]] = 1;\n if (options.outgoingAllowed) {\n referenced[src[e]] = 1;\n } else {\n sources[src[e]] = 1;\n }\n }\n const others = [...snapshot.nodes].filter((c) => c !== placeholder);\n for (let i = 0; i < snapshot.nodeCount; i++) {\n if (\n placeholder.isSet(i) &&\n placeholder.value(i) === true &&\n referenced[i] === 1 &&\n sources[i] === 0 &&\n !options.changed(i) &&\n others.every((c) => !c.isSet(i))\n ) {\n flags[i] = 1;\n }\n }\n return flags;\n}\n\n/**\n * How many nodes a re-import puts at another index: the written nodes keep their order and the\n * placeholders follow in the order the edges first reference them.\n * @param frameless - the framelessNodes() flags\n * @param references - the node indices in the order the written edges reference them\n * @returns the number of nodes whose index changes\n * @category Plugin helpers\n */\nexport function movedNodes(frameless: Uint8Array, references: Iterable<number>): number {\n const order: number[] = [];\n for (let i = 0; i < frameless.length; i++) {\n if (frameless[i] === 0) {\n order.push(i);\n }\n }\n const seen = new Uint8Array(frameless.length);\n for (const i of references) {\n if (frameless[i] === 1 && seen[i] === 0) {\n seen[i] = 1;\n order.push(i);\n }\n }\n let moved = 0;\n for (let k = 0; k < order.length; k++) {\n if (order[k] !== k) {\n moved++;\n }\n }\n return moved;\n}\n\n/**\n * Whether a value is a qualifier record as the importers make it: a non-empty object of names to a\n * text or a list of two or more texts (a name given once reads back as a text, never as a\n * one-item list), every name passing `nameOk`.\n * @param value - the value\n * @param nameOk - the name rule\n * @returns true when the record is written and read back unchanged\n * @category Plugin helpers\n */\nexport function isQualifierRecord(value: unknown, nameOk: (name: string) => boolean): boolean {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n return false;\n }\n const entries = Object.entries(value);\n return (\n entries.length > 0 &&\n entries.every(\n ([name, v]) =>\n nameOk(name) &&\n (typeof v === \"string\" ||\n (Array.isArray(v) && v.length >= 2 && v.every((item) => typeof item === \"string\"))),\n )\n );\n}\n\n/** What capabilityNotes() needs to know about the export. */\ninterface CapabilityNoteSpec {\n /** The format's capabilities. */\n readonly caps: ExportCapabilities;\n /** The label column written as the name, or null. */\n readonly label: Column | null;\n /** Whether the label is a role-less `name` column. */\n readonly labelAssumed: boolean;\n /** Where an edge column without a slot goes, for messages (\"a qualifier\"). */\n readonly edgeSlot: string;\n /** Records a note. */\n readonly note: NoteFn;\n}\n\n/**\n * The generic notes of checkCapabilities() as the ontology formats see them. No column of the\n * snapshot's own survives as a column (the formats keep only the OBO vocabulary), so the generic\n * dtype, list, json, strides and graph-attribute notes give way to the formats' own, which say\n * where each column goes (a vocabulary column written into its slot needs no note; any other\n * becomes property values, edge qualifiers or metadata). The name-change notes stand only for the\n * label and kind columns actually written into their slot. Added: the roles without a slot (a\n * node kind, an edge label) and a role-less `name` column taking the label role.\n * @param snapshot - the snapshot\n * @param common - the common options\n * @param spec - the export's facts\n * @returns the fatal error of a mixed graph under onMixedDirection \"error\", or null\n * @category Plugin helpers\n */\nexport function capabilityNotes(\n snapshot: GraphSnapshot,\n common: ResolvedExportOptions,\n spec: CapabilityNoteSpec,\n): GraphFormatError | null {\n const { label, note } = spec;\n let fatal: GraphFormatError | null = null;\n const kind = snapshot.edges.byRole(\"kind\");\n // a node label goes into the name slot and an edge kind into the relation slot, when written there;\n // an edge label has no slot (it becomes edge qualifiers or metadata), so it is never renamed\n const extras = {\n roles: SLOT_ROLES,\n nodeRoleNames: label === null ? {} : NODE_ROLE_NAMES,\n edgeRoleNames: kind !== null && isText(kind) ? EDGE_ROLE_NAMES : {},\n };\n for (const gen of checkCapabilities(snapshot, spec.caps, common, extras)) {\n if (SUPERSEDED.has(gen.code)) {\n continue;\n }\n if (gen.code === LOSS.MIXED_DIRECTION_ERROR && fatal === null) {\n fatal = new GraphFormatError(\"E_DIRECTED\", gen.message, { reason: \"mixed direction\" });\n }\n note(gen.code, gen.message, gen.column, gen.count);\n }\n slotlessRoleNotes(snapshot, spec);\n return fatal;\n}\n\n/**\n * Note the roles the ontology formats have no slot for (a node kind, an edge label), and a\n * role-less `name` column taking the label role.\n * @param snapshot - the snapshot\n * @param spec - the export's facts\n */\nfunction slotlessRoleNotes(snapshot: GraphSnapshot, spec: CapabilityNoteSpec): void {\n const { label, note } = spec;\n const nodeKind = snapshot.nodes.byRole(\"kind\");\n if (nodeKind !== null) {\n note(\n LOSS.ROLE,\n `node column \"${nodeKind.meta.name}\" (kind) is written as property values; the format has no node kind slot`,\n nodeKind.meta.name,\n nodeKind.length - nodeKind.nullCount,\n );\n }\n const edgeLabel = snapshot.edges.byRole(\"label\");\n if (edgeLabel !== null) {\n note(\n LOSS.ROLE,\n `edge column \"${edgeLabel.meta.name}\" (label) is written as ${spec.edgeSlot}; the format has no edge label slot`,\n edgeLabel.meta.name,\n edgeLabel.length - edgeLabel.nullCount,\n );\n }\n if (label !== null && spec.labelAssumed) {\n note(\n ROLE_ASSUMED_CODE,\n `node column \"name\" is written as each node's name and reads back as the label`,\n \"name\",\n label.length - label.nullCount,\n );\n }\n}\n\n/**\n * Record the columns that read back absent: a node or edge column without a set cell on a written\n * element (the importers declare a column when a value needs it).\n * @param snapshot - the snapshot\n * @param rows - the written nodes\n * @param frameless - the frameless flags (a placeholder column set only there reads back)\n * @param written - the written edges\n * @param note - records a note\n * @category Plugin helpers\n */\nexport function emptyColumnNotes(\n snapshot: GraphSnapshot,\n rows: readonly number[],\n frameless: Uint8Array,\n written: readonly number[],\n note: NoteFn,\n): void {\n const tables = [\n [\n \"node\",\n snapshot.nodes,\n (c: Column): boolean =>\n rows.some((i) => c.isSet(i)) ||\n (c.meta.name === PLACEHOLDER_COLUMN && frameless.some((f, i) => f === 1 && c.isSet(i))),\n ],\n [\"edge\", snapshot.edges, (c: Column): boolean => written.some((e) => c.isSet(e))],\n ] as const;\n for (const [domain, table, used] of tables) {\n for (const column of table) {\n const { role, name } = column.meta;\n if (\n (role !== null && (UNWRITTEN_ROLES.has(role) || (domain === \"edge\" && role === \"id\"))) ||\n used(column)\n ) {\n continue;\n }\n note(LOSS.EMPTY_COLUMN, `${domain} column \"${name}\" has no value to write; it reads back absent`, name, 0);\n }\n }\n}\n\n/**\n * Record the note of a node column written as property values.\n * @param note - the recorder\n * @param column - the column\n * @param rows - the written nodes\n * @param how - how the values are written, for the message\n * @category Plugin helpers\n */\nexport function notePropertyColumn(note: NoteFn, column: Column, rows: readonly number[], how: string): void {\n const count = rows.filter((i) => column.isSet(i)).length;\n if (count === 0) {\n return;\n }\n const role = column.meta.role === null ? \"\" : ` (its ${column.meta.role} role is lost)`;\n note(\n COLUMN_AS_PROPERTY_VALUE_CODE,\n `node column \"${column.meta.name}\" is written as ${how} and reads back inside the property_value column, not as its own column${role}`,\n column.meta.name,\n count,\n );\n}\n"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { GraphFormatError } from "@graphty/graph-format";
2
- import { a4 as textChunks, a5 as MAX_TEXT_LENGTH, a6 as tooLarge } from "./export-CTUlibzr.js";
2
+ import { a4 as textChunks, a5 as MAX_TEXT_LENGTH, a6 as tooLarge } from "./export-B-aVCwyG.js";
3
3
  const UNCLOSED_QUOTE_CODE = "E_CSV_UNCLOSED_QUOTE";
4
4
  const BAD_QUOTE_CODE = "E_CSV_QUOTE";
5
5
  const DELIMITER_CANDIDATES = Object.freeze([",", " ", ";", "|", " "]);
@@ -799,4 +799,4 @@ export {
799
799
  splitRecords as d,
800
800
  stripLeadingComments as s
801
801
  };
802
- //# sourceMappingURL=records-ChmWZGuz.js.map
802
+ //# sourceMappingURL=records-C9jd7yLD.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"records-ChmWZGuz.js","sources":["../../src/formats/csv/records.ts"],"sourcesContent":["/**\n * The one streaming CSV record reader of the package (design sections 8.2 and 8.4: hand-written\n * tokenisers per format; CSV and Neo4j CSV are line-oriented over a byte stream): RFC 4180 records\n * with a configurable single-character delimiter and quote, a doubled quote inside a quoted field,\n * quoted fields spanning lines, LF / CRLF / lone-CR terminators, read from the common text reader\n * one chunk at a time as a character state machine. Only the open field is ever held, so a field\n * spanning many chunks costs its length once and a multi-gigabyte file is never buffered.\n *\n * `RecordReader` (used by the Neo4j importer) keeps one reusable cell array and one reusable\n * \"was quoted\" array: a quoted empty field (`\"\"`) is an empty string while an unquoted empty field\n * means \"not set\", and only the tokeniser can tell the two apart. `CsvRecordReader` (the CSV\n * importer) wraps it with delimiter sniffing over a bounded preview and yields a fresh cell array\n * per row. A malformed quoted field is fatal for both: an unterminated quote swallows the rest of\n * the file, and text after a closing quote makes every later cell boundary unreliable.\n */\n\nimport { GraphFormatError } from \"@graphty/graph-format\";\n\nimport { MAX_TEXT_LENGTH, type ReadOptions, textChunks, tooLarge } from \"../../common/input.js\";\nimport { type ImportReportBuilder } from \"../../common/report.js\";\nimport { type ImportInput } from \"../../types.js\";\n\n/**\n * A quoted field is never closed; the import aborts (everything after it would be one cell).\n * @category Issue and loss codes\n */\nexport const UNCLOSED_QUOTE_CODE = \"E_CSV_UNCLOSED_QUOTE\";\n\n/**\n * A closing quote is followed by text other than a delimiter or a line break; the import aborts.\n * @category Issue and loss codes\n */\nexport const BAD_QUOTE_CODE = \"E_CSV_QUOTE\";\n\n/**\n * The delimiters tried, in priority order, when none is given.\n * @category Plugin helpers\n */\nexport const DELIMITER_CANDIDATES: readonly string[] = Object.freeze([\",\", \"\\t\", \";\", \"|\", \" \"]);\n\n/** Rows the delimiter sniff looks at. */\nconst PREVIEW_ROWS = 10;\n\n/** Characters buffered for the sniff when the input has fewer than PREVIEW_ROWS line breaks. */\nconst PREVIEW_CHARS = 64 * 1024;\n\nconst LF = 10;\nconst CR = 13;\nconst TAB = 9;\nconst SPACE = 32;\n\n/** An Excel delimiter directive: a first line `sep=;` names the delimiter and is not a record. */\nconst SEP_DIRECTIVE = /^sep=([^\\r\\n])(?:\\r\\n|\\n|\\r|$)/i;\n\n/** Tokeniser state: at the start of a field. */\nconst START = 0;\n/** Tokeniser state: inside an unquoted field. */\nconst UNQUOTED = 1;\n/** Tokeniser state: inside a quoted field. */\nconst QUOTED = 2;\n/** Tokeniser state: just after a quote inside a quoted field (a second quote is a literal, anything else ends the field). */\nconst CLOSING = 3;\n/** Tokeniser state: after a closed quoted field, before the delimiter (text here is malformed). */\nconst AFTER_QUOTED = 4;\n/** Inside a leading comment line (skipped up to its line break). */\nconst COMMENT = 5;\n\ntype State = typeof START | typeof UNQUOTED | typeof QUOTED | typeof CLOSING | typeof AFTER_QUOTED | typeof COMMENT;\n\n/**\n * The delimiter and quote of a reader.\n * @category Plugin helpers\n */\nexport interface RecordSyntax {\n /** The field delimiter, one character; null sniffs it from the first rows. */\n readonly delimiter: string | null;\n /** The quote character, one character. */\n readonly quote: string;\n /** The delimiters tried when `delimiter` is null; DELIMITER_CANDIDATES by default. */\n readonly candidates?: readonly string[] | undefined;\n /**\n * Characters that open a comment line while no record has been read yet (SNAP `#`, KONECT\n * `%`): such leading lines are skipped, kept in `leadingComments` and left out of the delimiter\n * sniff. A later line starting with one of them is an ordinary record. Default: none.\n */\n readonly comments?: readonly string[] | undefined;\n /**\n * Whether the delimiter sniff skips a candidate under which a closing quote is followed by\n * other text (see sniffDelimiter). False by default: that text then aborts the read.\n */\n readonly skipQuoteErrors?: boolean | undefined;\n /**\n * Whether a space delimiter means the whitespace dialect of SNAP / KONECT edge lists: any run of\n * spaces and tabs separates two fields, and whitespace at the start and end of a line is\n * ignored. False by default (every space is a delimiter).\n */\n readonly collapseSpaces?: boolean | undefined;\n /** Whether an Excel `sep=X` first line names the delimiter for the sniff. False by default. */\n readonly sepDirective?: boolean | undefined;\n /**\n * Called with the sniff preview (leading comment lines removed) and the line it starts on,\n * before any record is scanned, so a caller can refuse input that is not delimited text at all.\n * Only called when the delimiter is sniffed.\n */\n readonly inspect?: ((preview: string, line: number) => void) | undefined;\n}\n\n/**\n * Check a delimiter or quote option: exactly one character that is not a line break, and the two\n * must differ.\n * @param syntax - the delimiter (or null for sniffing) and quote\n * @returns the syntax unchanged; E_UNSUPPORTED when invalid\n * @category Plugin helpers\n */\nexport function checkRecordSyntax(syntax: RecordSyntax): RecordSyntax {\n for (const [option, value] of [\n [\"delimiter\", syntax.delimiter],\n [\"quote\", syntax.quote],\n ] as const) {\n if (value === null) {\n continue;\n }\n if (value.length !== 1 || value === \"\\n\" || value === \"\\r\") {\n throw new GraphFormatError(\n \"E_UNSUPPORTED\",\n `option ${option}: ${JSON.stringify(value)} is not a single non-line-break character`,\n { option, found: value },\n );\n }\n }\n if (syntax.delimiter !== null && syntax.delimiter === syntax.quote) {\n throw new GraphFormatError(\"E_UNSUPPORTED\", \"options delimiter and quote must differ\", {\n option: \"delimiter\",\n found: syntax.delimiter,\n });\n }\n return syntax;\n}\n\n/**\n * Sniff the line terminator of a text: a lone `\\r` only when the text has `\\r` and no `\\n` at all\n * (classic Mac files); `\\n` otherwise.\n * @param text - the preview text\n * @returns \"\\n\" or \"\\r\"\n * @category Plugin helpers\n */\nexport function sniffNewline(text: string): \"\\n\" | \"\\r\" {\n return !text.includes(\"\\n\") && text.includes(\"\\r\") ? \"\\r\" : \"\\n\";\n}\n\n/**\n * Split a text into records synchronously (the first `maxRows` of them), honoring quotes; for\n * the delimiter sniff and the registry's head sniff, where the input is a bounded preview.\n * @param text - the text\n * @param delimiter - the delimiter\n * @param quote - the quote character\n * @param maxRows - the most rows to return\n * @param strictQuotes - return null when a closing quote is followed by text other than the\n * delimiter or a line break (the import would abort under this delimiter)\n * @param collapse - the whitespace dialect (RecordSyntax.collapseSpaces) for a space delimiter\n * @returns the rows as cell arrays (blank lines skipped), or null (see strictQuotes)\n * @category Plugin helpers\n */\nexport function splitRecords(\n text: string,\n delimiter: string,\n quote: string,\n maxRows: number,\n strictQuotes = false,\n collapse = false,\n): string[][] | null {\n const delimiterCode = delimiter.codePointAt(0);\n const splitter = new RecordSplitter(text, quote, delimiterCode, collapse && delimiterCode === SPACE, strictQuotes);\n let i = 0;\n while (i < text.length && splitter.rows.length < maxRows) {\n i = splitter.step(i);\n if (i < 0) {\n return null;\n }\n }\n if (splitter.rows.length < maxRows) {\n splitter.finish();\n }\n return splitter.rows;\n}\n\n/** The state machine of splitRecords(): one character at a time, quotes honored. */\nclass RecordSplitter {\n /** The rows read so far. */\n readonly rows: string[][] = [];\n\n private cells: string[] = [];\n\n private state: State = START;\n\n /** Where the unread part of the current field starts. */\n private segment = 0;\n\n private field = \"\";\n\n private readonly quoteCode: number | undefined;\n\n /**\n * Create a splitter.\n * @param text - the text\n * @param quote - the quote character\n * @param delimiterCode - the delimiter's code\n * @param whitespace - whether runs of spaces and tabs are one separator\n * @param strictQuotes - whether text after a closing quote gives up\n */\n constructor(\n private readonly text: string,\n private readonly quote: string,\n private readonly delimiterCode: number | undefined,\n private readonly whitespace: boolean,\n private readonly strictQuotes: boolean,\n ) {\n this.quoteCode = quote.codePointAt(0);\n }\n\n /**\n * Read one character.\n * @param i - its index\n * @returns the index of the next character to read, or -1 to give up (strictQuotes)\n */\n step(i: number): number {\n const c = this.text.codePointAt(i);\n if (this.state === QUOTED) {\n if (c === this.quoteCode) {\n this.field += this.text.slice(this.segment, i);\n this.state = CLOSING;\n }\n return i + 1;\n }\n const isDelimiter = c === this.delimiterCode || (this.whitespace && c === TAB);\n if (this.state === CLOSING) {\n const next = this.afterQuote(i, c, isDelimiter || c === LF || c === CR);\n if (next !== null) {\n return next;\n }\n }\n if (isDelimiter || c === LF || c === CR) {\n return this.separator(i, isDelimiter);\n }\n if (this.state === START) {\n const quoted = c === this.quoteCode;\n this.state = quoted ? QUOTED : UNQUOTED;\n this.segment = quoted ? i + 1 : i;\n }\n return i + 1;\n }\n\n /**\n * Read the character after a quote that may close a quoted field: a second quote is a quote\n * character in the field; anything else closes it.\n * @param i - its index\n * @param c - its code\n * @param separates - whether it is a delimiter or a line break\n * @returns the index of the next character to read, -1 to give up (strictQuotes), or null to\n * read this character as the one after the field\n */\n private afterQuote(i: number, c: number | undefined, separates: boolean): number | null {\n if (c === this.quoteCode) {\n this.field += this.quote;\n this.segment = i + 1;\n this.state = QUOTED;\n return i + 1;\n }\n if (this.strictQuotes && !separates) {\n return -1;\n }\n this.state = AFTER_QUOTED;\n this.segment = i;\n return null;\n }\n\n /**\n * Read a delimiter or a line break: end the field, and at a line break the row.\n * @param i - its index\n * @param isDelimiter - whether it is a delimiter\n * @returns the index of the next character to read\n */\n private separator(i: number, isDelimiter: boolean): number {\n if (this.state === UNQUOTED || this.state === AFTER_QUOTED) {\n this.field += this.text.slice(this.segment, i);\n }\n let next = i + 1;\n if (isDelimiter && this.whitespace && this.state === START) {\n // a run of whitespace, or indentation: one separator\n } else if (isDelimiter) {\n this.cells.push(this.field);\n this.field = \"\";\n this.state = START;\n } else {\n this.endRow();\n this.state = START;\n if (this.text.codePointAt(i) === CR && this.text.codePointAt(i + 1) === LF) {\n next = i + 2;\n }\n }\n this.segment = next;\n return next;\n }\n\n /** End the row at a line break; a blank line is no row. */\n private endRow(): void {\n if (this.state === START && this.cells.length === 0) {\n return;\n }\n if (!(this.whitespace && this.state === START)) {\n this.cells.push(this.field);\n }\n this.rows.push(this.cells);\n this.cells = [];\n this.field = \"\";\n }\n\n /** End the text: the last row, when it has no line break after it. */\n finish(): void {\n if (this.state === START && this.cells.length === 0) {\n return;\n }\n if (this.state === UNQUOTED || this.state === AFTER_QUOTED || this.state === QUOTED) {\n this.field += this.text.slice(this.segment);\n }\n if (!(this.whitespace && this.state === START)) {\n this.cells.push(this.field);\n }\n this.rows.push(this.cells);\n }\n}\n\n/**\n * The preview text without its leading comment lines (those starting with one of the comment\n * characters), so the sniff sees the records only.\n * @param text - the preview text\n * @param comments - the comment characters\n * @returns the text from the first non-comment line on\n * @category Plugin helpers\n */\nexport function stripLeadingComments(text: string, comments: readonly string[]): string {\n if (comments.length === 0) {\n return text;\n }\n let at = 0;\n while (at < text.length) {\n if (text[at] === \"\\n\" || text[at] === \"\\r\") {\n // a blank line among the leading comments (the scanner skips it too)\n at++;\n continue;\n }\n if (!comments.includes(text[at])) {\n break;\n }\n const lf = text.indexOf(\"\\n\", at);\n const cr = text.indexOf(\"\\r\", at);\n const end = Math.min(lf < 0 ? Infinity : lf, cr < 0 ? Infinity : cr);\n if (end === Infinity) {\n return \"\";\n }\n at = end;\n }\n return at === 0 ? text : text.slice(at);\n}\n\n/**\n * Sniff the delimiter of a text: every candidate is tried over the first rows and the one whose\n * field count is most consistent across rows wins (a tie goes to the earlier candidate, so a\n * space inside comma-separated cells never beats the comma); a\n * candidate that yields fewer than two fields per row on average is never chosen. `null` when no\n * candidate qualifies (a single-column file).\n * @param text - the preview text\n * @param newline - the sniffed line terminator (unused by the splitter, which reads every kind;\n * kept for callers that sniffed it)\n * @param candidates - the delimiters to try, in priority order\n * @param quote - the quote character\n * @param skipQuoteErrors - also never choose a candidate under which a closing quote is followed\n * by other text. Only for inputs whose field counts say nothing (an adjacency table's rows vary in\n * width): elsewhere a stray character after a quote would make a worse candidate win silently\n * instead of aborting the read\n * @param collapse - read a space candidate in the whitespace dialect (RecordSyntax.collapseSpaces)\n * @returns the delimiter, or null\n * @category Plugin helpers\n */\nexport function sniffDelimiter(\n text: string,\n newline: \"\\n\" | \"\\r\" = \"\\n\",\n candidates: readonly string[] = DELIMITER_CANDIDATES,\n quote = '\"',\n skipQuoteErrors = false,\n collapse = false,\n): string | null {\n let best: string | null = null;\n let bestDelta = Infinity;\n const terminated = text.endsWith(\"\\n\") || text.endsWith(\"\\r\");\n for (const delimiter of candidates) {\n if (delimiter === quote || delimiter === newline) {\n continue;\n }\n const split = splitRecords(text, delimiter, quote, PREVIEW_ROWS, skipQuoteErrors, collapse);\n if (split === null) {\n // a quoted cell this delimiter does not close: it is not the file's delimiter\n continue;\n }\n let rows = split.filter((row) => !isBlankRow(row));\n if (rows.length > 1 && !terminated) {\n // the preview is a prefix of the file: its last row may be cut short\n rows = rows.slice(0, -1);\n }\n if (rows.length === 0) {\n continue;\n }\n let total = 0;\n let delta = 0;\n for (let i = 0; i < rows.length; i++) {\n const count = rows[i].length;\n total += count;\n if (i > 0) {\n delta += Math.abs(count - rows[i - 1].length);\n }\n }\n const average = total / rows.length;\n if (average > 1.99 && delta < bestDelta) {\n best = delimiter;\n bestDelta = delta;\n }\n }\n return best;\n}\n\n/**\n * Whether a parsed row is a blank line: one cell holding only whitespace.\n * @param row - the row\n * @returns true for a blank line\n */\nfunction isBlankRow(row: readonly string[]): boolean {\n return row.length === 1 && row[0].trim().length === 0;\n}\n\n/**\n * Reads the records of one input. Iterate with `for await (const count of reader)`: each\n * iteration fills `reader.cells[0..count)` and `reader.quoted[0..count)` and sets `reader.line`\n * to the 1-based line the record started on. Blank lines (an empty unquoted record of one field)\n * are skipped. The arrays are reused between records. When the syntax gives no delimiter, the\n * first rows (PREVIEW_ROWS, or PREVIEW_CHARS characters) are buffered and the delimiter sniffed\n * from them before the first record is yielded.\n * @category Plugin helpers\n */\nexport class RecordReader implements AsyncIterable<number> {\n /** The cells of the record most recently yielded; only the first `count` entries are valid. */\n readonly cells: string[] = [];\n\n /** Whether each cell of the record most recently yielded was quoted. */\n readonly quoted: boolean[] = [];\n\n /** The leading comment lines (without their line breaks), in order; empty without `syntax.comments`. */\n readonly leadingComments: string[] = [];\n\n /** The 1-based line of each leading comment, parallel to `leadingComments`. */\n readonly leadingCommentLines: number[] = [];\n\n /** The line of the first quote inside an unquoted field (kept as text, RFC 4180 forbids it); 0 for none. */\n strayQuoteLine = 0;\n\n /** Whether the sniff took the delimiter from an Excel `sep=X` first line (yielded as the first record). */\n sepDirective = false;\n\n private readonly input: ImportInput;\n\n readonly report: ImportReportBuilder;\n\n private readonly readOptions: ReadOptions;\n\n /** The delimiter (null while unsniffed), quote and comment characters. */\n readonly syntax: RecordSyntax;\n\n private delimiterText: string | null;\n\n private recordLine = 0;\n\n /**\n * Create a reader; nothing is read until iteration starts.\n * @param input - the input (or an async iterable of already decoded text chunks)\n * @param report - the report decode and quoting errors are recorded in\n * @param syntax - the delimiter (null to sniff) and quote (already checked)\n * @param readOptions - cancellation and progress\n */\n constructor(input: ImportInput, report: ImportReportBuilder, syntax: RecordSyntax, readOptions: ReadOptions) {\n this.input = input;\n this.report = report;\n this.readOptions = readOptions;\n this.syntax = syntax;\n this.delimiterText = syntax.delimiter;\n }\n\n /**\n * The 1-based line the most recently yielded record started on.\n * @returns the line number; 0 before the first record\n */\n get line(): number {\n return this.recordLine;\n }\n\n /**\n * The delimiter in use.\n * @returns the given or sniffed delimiter; null before the sniff ran\n */\n get delimiter(): string | null {\n return this.delimiterText;\n }\n\n /**\n * Iterate the records.\n * @yields the number of cells of each record\n * @returns nothing\n */\n async *[Symbol.asyncIterator](): AsyncGenerator<number, void, undefined> {\n const scanner = new RecordScanner(this);\n for await (const chunk of this.chunks()) {\n // the first chunk arrives after the sniff (or with the given delimiter)\n scanner.setDelimiter((this.delimiterText ?? \",\").charCodeAt(0));\n let i = 0;\n while (i < chunk.length) {\n i = scanner.scan(chunk, i);\n if (scanner.ready) {\n scanner.ready = false;\n this.recordLine = scanner.recordLine;\n yield scanner.count;\n scanner.count = 0;\n }\n }\n scanner.endChunk(chunk);\n }\n if (scanner.finish()) {\n this.recordLine = scanner.recordLine;\n yield scanner.count;\n }\n }\n\n /**\n * Record a text-after-quote error (fatal).\n * @param line - the line the field started on\n * @param field - the 1-based position of the field in its record\n * @returns never\n */\n badQuote(line: number, field: number): never {\n return this.report.fail(\n BAD_QUOTE_CODE,\n `text after the closing quote of quoted field ${field} (a quote inside a quoted field is written as two quotes)`,\n { line },\n );\n }\n\n /**\n * Record an unclosed-quote error (fatal).\n * @param line - the line the field started on\n * @returns never\n */\n unclosedQuote(line: number): never {\n return this.report.fail(UNCLOSED_QUOTE_CODE, `a quoted field starting on line ${line} is never closed`, {\n line,\n });\n }\n\n /**\n * The decoded text chunks of the input; when the delimiter is to be sniffed, the first rows\n * are buffered (bounded by PREVIEW_ROWS line breaks or PREVIEW_CHARS characters), the sniff\n * runs, and the buffered text is yielded as one chunk before the rest streams through.\n * @yields the text chunks\n * @returns nothing\n */\n private async *chunks(): AsyncGenerator<string, void, undefined> {\n const source = textChunks(this.input, this.report, this.readOptions);\n if (this.delimiterText !== null) {\n yield* source;\n return;\n }\n const pieces: string[] = [];\n let length = 0;\n let breaks = 0;\n let lastWasCr = false;\n let sniffed = false;\n // the leading comment lines are left out of the sniff, so they do not count toward its rows\n const commentCodes = (this.syntax.comments ?? []).map((c) => c.codePointAt(0));\n let leading = commentCodes.length > 0;\n let lineStart = true;\n let inComment = false;\n for await (const chunk of source) {\n if (sniffed) {\n yield chunk;\n continue;\n }\n pieces.push(chunk);\n length += chunk.length;\n for (let i = 0; i < chunk.length && breaks < PREVIEW_ROWS; i++) {\n const c = chunk.charCodeAt(i);\n if (c === LF || c === CR) {\n if (!(c === LF && lastWasCr) && !inComment) {\n breaks++;\n }\n inComment = false;\n lineStart = true;\n } else {\n if (lineStart && leading) {\n inComment = commentCodes.includes(c);\n leading = inComment;\n }\n lineStart = false;\n }\n lastWasCr = c === CR;\n }\n if (breaks < PREVIEW_ROWS && length < PREVIEW_CHARS) {\n continue;\n }\n const text = pieces.join(\"\");\n pieces.length = 0;\n this.sniff(text);\n sniffed = true;\n yield text;\n }\n if (!sniffed) {\n const text = pieces.join(\"\");\n if (text.length > 0) {\n this.sniff(text);\n yield text;\n }\n }\n }\n\n /**\n * Sniff the delimiter from the preview text and record it.\n * @param text - the preview\n */\n private sniff(text: string): void {\n // the sniff is a heuristic over the first rows: a single chunk holding a huge quoted cell\n // is capped so the candidate scans stay bounded\n const preview = text.slice(0, PREVIEW_CHARS);\n const body = stripLeadingComments(preview, this.syntax.comments ?? []);\n if (this.syntax.inspect !== undefined) {\n const skipped = preview.slice(0, preview.length - body.length).match(/\\r\\n|\\r|\\n/g)?.length ?? 0;\n this.syntax.inspect(body, skipped + 1);\n }\n const directive = this.syntax.sepDirective === true ? SEP_DIRECTIVE.exec(text) : null;\n if (directive !== null && directive[1] !== this.syntax.quote) {\n this.delimiterText = directive[1];\n this.sepDirective = true;\n return;\n }\n const candidates = this.syntax.candidates ?? DELIMITER_CANDIDATES;\n this.delimiterText =\n sniffDelimiter(\n body,\n sniffNewline(body),\n candidates,\n this.syntax.quote,\n this.syntax.skipQuoteErrors,\n this.syntax.collapseSpaces,\n ) ?? candidates[0];\n }\n}\n\n/**\n * The record state machine of RecordReader, resumable at every record end so the reader can\n * yield a record before the next one overwrites the shared cell arrays. Every state lives on the\n * instance; `scan()` runs the per-character loop over one chunk from a position and returns where\n * it stopped (after the character that completed a record, with `ready` set, or the chunk end).\n */\nclass RecordScanner {\n /** Whether a record just completed. */\n ready = false;\n\n /** The cell count of the record in progress (the completed one while `ready`). */\n count = 0;\n\n /** The line the completed record started on (valid while `ready`, and after finish()). */\n recordLine = 1;\n\n /** The line the record in progress started on. */\n private startLine = 1;\n\n private readonly reader: RecordReader;\n\n private readonly quoteCode: number;\n\n private readonly quoteText: string;\n\n private readonly commentCodes: readonly number[];\n\n private delimiterCode = -1;\n\n /** The whitespace dialect: the delimiter is a space and RecordSyntax.collapseSpaces is set. */\n private collapse = false;\n\n private state: State = START;\n\n private field = \"\";\n\n private fieldQuoted = false;\n\n private line = 1;\n\n private lastWasCr = false;\n\n /** Whether no record has started yet (comment lines are recognized until one does). */\n private leading: boolean;\n\n private comment = \"\";\n\n /** The start of the unconsumed run of the current chunk that belongs to the field or comment. */\n private segment = 0;\n\n /**\n * Create a scanner over a reader's cell arrays.\n * @param reader - the reader\n */\n constructor(reader: RecordReader) {\n this.reader = reader;\n this.quoteCode = reader.syntax.quote.charCodeAt(0);\n this.quoteText = reader.syntax.quote;\n this.commentCodes = (reader.syntax.comments ?? []).map((c) => c.charCodeAt(0));\n this.leading = this.commentCodes.length > 0;\n }\n\n /**\n * Set the delimiter once it is known (before the first chunk is scanned).\n * @param code - the delimiter's character code\n */\n setDelimiter(code: number): void {\n if (this.delimiterCode < 0) {\n this.delimiterCode = code;\n this.collapse = code === SPACE && this.reader.syntax.collapseSpaces === true;\n }\n }\n\n /**\n * Scan one chunk from a position until a record completes or the chunk ends.\n * @param chunk - the chunk\n * @param from - where to start\n * @returns the position after the last character consumed\n */\n scan(chunk: string, from: number): number {\n const { quoteCode, delimiterCode } = this;\n const n = chunk.length;\n let i = from;\n this.segment = from;\n while (i < n) {\n const c = chunk.charCodeAt(i);\n if (this.state === COMMENT) {\n if (c === LF || c === CR) {\n this.comment = this.grow(this.comment, chunk.slice(this.segment, i));\n this.reader.leadingComments.push(this.comment);\n this.reader.leadingCommentLines.push(this.line);\n this.comment = \"\";\n this.state = START;\n this.endLine(c);\n this.segment = i + 1;\n }\n i++;\n continue;\n }\n if (this.state === QUOTED) {\n // jump to the next quote; the run in between is field text whose line breaks are counted\n const q = chunk.indexOf(this.quoteText, i);\n const end = q < 0 ? n : q;\n if (end > i) {\n this.countLineBreaks(chunk, i, end);\n }\n if (q < 0) {\n i = n;\n break;\n }\n this.field = this.grow(this.field, chunk.slice(this.segment, q));\n this.state = CLOSING;\n this.lastWasCr = false;\n i = q + 1;\n continue;\n }\n const isDelimiter = c === delimiterCode || (this.collapse && c === TAB);\n if (this.state === CLOSING) {\n if (c === quoteCode) {\n this.field = this.grow(this.field, this.quoteText);\n this.segment = i + 1;\n this.state = QUOTED;\n this.lastWasCr = false;\n i++;\n continue;\n }\n if (!isDelimiter && c !== LF && c !== CR) {\n this.reader.badQuote(this.startLine, this.count + 1);\n }\n this.state = AFTER_QUOTED;\n this.segment = i;\n // fall through to the delimiter / line-end handling below without consuming c\n }\n if (isDelimiter || c === LF || c === CR) {\n if (c === LF && this.lastWasCr) {\n // the second half of a CRLF already ended the record\n this.lastWasCr = false;\n this.segment = i + 1;\n i++;\n continue;\n }\n if (this.state === UNQUOTED) {\n this.field = this.grow(this.field, chunk.slice(this.segment, i));\n }\n if (isDelimiter && this.collapse && this.state === START) {\n // a run of whitespace, or indentation: one separator\n this.lastWasCr = false;\n this.segment = i + 1;\n i++;\n continue;\n }\n if (isDelimiter) {\n this.push();\n this.state = START;\n this.lastWasCr = false;\n this.segment = i + 1;\n i++;\n continue;\n }\n // a line ending right after a whitespace run in the whitespace dialect ends no field\n if (!(this.state === START && (this.count === 0 || this.collapse))) {\n this.push();\n }\n if (this.count > 0) {\n this.ready = true;\n this.recordLine = this.startLine;\n }\n this.state = START;\n this.endLine(c);\n this.segment = i + 1;\n i++;\n if (this.ready) {\n return i;\n }\n continue;\n }\n this.lastWasCr = false;\n if (this.state === START) {\n if (this.leading && this.count === 0 && this.commentCodes.includes(c)) {\n this.state = COMMENT;\n this.segment = i;\n i++;\n continue;\n }\n this.leading = false;\n if (c === quoteCode) {\n this.state = QUOTED;\n this.fieldQuoted = true;\n this.segment = i + 1;\n } else {\n this.state = UNQUOTED;\n this.segment = i;\n }\n } else if (c === quoteCode && this.state === UNQUOTED && this.reader.strayQuoteLine === 0) {\n this.reader.strayQuoteLine = this.startLine;\n }\n i++;\n }\n return i;\n }\n\n /**\n * Keep the tail of a chunk that belongs to an open field or comment.\n * @param chunk - the chunk just scanned to its end\n */\n endChunk(chunk: string): void {\n const n = chunk.length;\n if (this.segment >= n) {\n return;\n }\n if (this.state === COMMENT) {\n this.comment = this.grow(this.comment, chunk.slice(this.segment, n));\n } else if (this.state === QUOTED || this.state === UNQUOTED) {\n this.field = this.grow(this.field, chunk.slice(this.segment, n));\n }\n this.segment = n;\n }\n\n /**\n * The end of the input: an open comment is kept, an open quote is fatal, an open record\n * (no trailing line break) is completed.\n * @returns true when a last record is ready\n */\n finish(): boolean {\n const { state } = this;\n if (state === COMMENT) {\n this.reader.leadingComments.push(this.comment);\n this.reader.leadingCommentLines.push(this.line);\n return false;\n }\n if (state === QUOTED) {\n this.reader.unclosedQuote(this.startLine);\n }\n if (state !== START || this.count > 0) {\n if (!(state === START && this.collapse)) {\n this.push();\n }\n this.recordLine = this.startLine;\n return true;\n }\n return false;\n }\n\n /**\n * Count the line breaks of a run of quoted text (a CRLF counts once, like the record loop).\n * @param chunk - the chunk\n * @param from - the first index of the run\n * @param to - the index after the run\n */\n private countLineBreaks(chunk: string, from: number, to: number): void {\n // indexOf, not a charCodeAt loop: a quoted cell may be hundreds of MiB, and a per-character\n // loop over it is the whole cost of reading it (and many times that under coverage)\n const run = chunk.slice(from, to);\n let { line } = this;\n // the index of the last CR seen; -1 is a CR just before the run, -2 none\n let lastCr = this.lastWasCr ? -1 : -2;\n let lf = run.indexOf(\"\\n\");\n let cr = run.indexOf(\"\\r\");\n while (lf >= 0 || cr >= 0) {\n if (lf < 0 || (cr >= 0 && cr < lf)) {\n line++;\n lastCr = cr;\n cr = run.indexOf(\"\\r\", cr + 1);\n } else {\n if (lf !== lastCr + 1) {\n line++;\n }\n lf = run.indexOf(\"\\n\", lf + 1);\n }\n }\n this.line = line;\n this.lastWasCr = lastCr === run.length - 1;\n }\n\n /**\n * Append to the field or comment in progress, failing with E_TOO_LARGE instead of a RangeError\n * when the result would be longer than one JavaScript string can hold.\n * @param text - the text so far\n * @param piece - the text to append\n * @returns the joined text\n */\n private grow(text: string, piece: string): string {\n if (text.length + piece.length > MAX_TEXT_LENGTH) {\n tooLarge(\n this.reader.report,\n `a cell is longer than ${MAX_TEXT_LENGTH} characters, the most one JavaScript string holds`,\n this.startLine,\n );\n }\n return text + piece;\n }\n\n /** Store the field in progress as the next cell. */\n private push(): void {\n const { reader, count } = this;\n reader.cells[count] = this.field;\n reader.quoted[count] = this.fieldQuoted;\n this.count = count + 1;\n this.field = \"\";\n this.fieldQuoted = false;\n }\n\n /**\n * Count a line break that ends a record or a comment line.\n * @param c - the LF or CR\n */\n private endLine(c: number): void {\n if (!(c === LF && this.lastWasCr)) {\n this.line++;\n }\n this.startLine = this.line;\n this.lastWasCr = c === CR;\n }\n}\n\n/**\n * What the CSV importer's reader needs besides the input.\n * @category Plugin helpers\n */\nexport interface CsvReaderOptions extends ReadOptions {\n /** The delimiter; null or undefined sniffs it from the preview. */\n readonly delimiter?: string | null | undefined;\n /** The characters opening a leading comment line (RecordSyntax.comments); none by default. */\n readonly comments?: readonly string[] | undefined;\n /** RecordSyntax.skipQuoteErrors; false by default. */\n readonly skipQuoteErrors?: boolean | undefined;\n /** RecordSyntax.inspect; none by default. */\n readonly inspect?: ((preview: string, line: number) => void) | undefined;\n}\n\n/**\n * Records of a CSV input, one string array per row, streamed: iterate with\n * `for await (const row of reader)` and read `reader.line` for the 1-based line the row starts\n * on. The delimiter is known after the first row (`reader.delimiter`). Rows come out as fresh\n * arrays of cell texts exactly as written (no trimming, no typing); `reader.quoted` tells, for\n * the row just yielded, which cells were quoted (a quoted empty cell is the empty string, an\n * unquoted one is \"not set\"). Blank lines, whitespace-only lines and rows of empty unquoted cells\n * are skipped. A space delimiter reads the whitespace dialect (RecordSyntax.collapseSpaces), and an\n * Excel `sep=X` first line names the delimiter when none is given.\n * @category Plugin helpers\n */\nexport class CsvRecordReader implements AsyncIterable<string[]> {\n private readonly inner: RecordReader;\n\n /**\n * Create a reader; nothing is read until iteration starts.\n * @param input - the input\n * @param report - the report parse errors are recorded in\n * @param options - the delimiter, cancellation and progress\n */\n constructor(input: ImportInput, report: ImportReportBuilder, options: CsvReaderOptions = {}) {\n this.inner = new RecordReader(\n input,\n report,\n {\n delimiter: options.delimiter ?? null,\n quote: '\"',\n comments: options.comments,\n skipQuoteErrors: options.skipQuoteErrors,\n collapseSpaces: true,\n sepDirective: true,\n inspect: options.inspect,\n },\n {\n signal: options.signal,\n onProgress: options.onProgress,\n encoding: options.encoding,\n allowEmpty: options.allowEmpty,\n nulIsBinary: true,\n },\n );\n }\n\n /**\n * The leading comment lines the reader skipped (SNAP `#` lines, the KONECT `%` header), in order.\n * @returns the lines without their line breaks\n */\n get leadingComments(): readonly string[] {\n return this.inner.leadingComments;\n }\n\n /**\n * The 1-based line of each leading comment, parallel to `leadingComments`.\n * @returns the lines\n */\n get leadingCommentLines(): readonly number[] {\n return this.inner.leadingCommentLines;\n }\n\n /**\n * The 1-based line the most recently yielded row starts on.\n * @returns the line; 0 before the first row\n */\n get line(): number {\n return this.inner.line;\n }\n\n /**\n * The delimiter in use.\n * @returns the given or sniffed delimiter; null before the sniff ran\n */\n get delimiter(): string | null {\n return this.inner.delimiter;\n }\n\n /**\n * Whether each cell of the row most recently yielded was quoted.\n * @returns the flags (valid for the first `row.length` entries)\n */\n get quoted(): readonly boolean[] {\n return this.inner.quoted;\n }\n\n /**\n * The line of the first quote inside an unquoted field.\n * @returns the line, or 0 when there was none\n */\n get strayQuoteLine(): number {\n return this.inner.strayQuoteLine;\n }\n\n /**\n * Whether the first record is an Excel `sep=X` line the delimiter was taken from.\n * @returns true when it is\n */\n get sepDirective(): boolean {\n return this.inner.sepDirective;\n }\n\n /**\n * Iterate the rows.\n * @yields one row at a time as its cell texts\n * @returns nothing\n */\n async *[Symbol.asyncIterator](): AsyncGenerator<string[], void, undefined> {\n const { inner } = this;\n for await (const count of inner) {\n if (isBlankRecord(inner.cells, inner.quoted, count)) {\n continue;\n }\n yield inner.cells.slice(0, count);\n }\n }\n}\n\n/**\n * Whether a record holds nothing: every cell unquoted and blank (a blank line, a whitespace-only\n * line, or an Excel empty row `,,,`). A quoted empty cell is a value.\n * @param cells - the cells\n * @param quoted - whether each was quoted\n * @param count - the cell count\n * @returns true when the record carries no value\n */\nfunction isBlankRecord(cells: readonly string[], quoted: readonly boolean[], count: number): boolean {\n for (let k = 0; k < count; k++) {\n if (quoted[k] || cells[k].trim().length > 0) {\n return false;\n }\n }\n return true;\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"records-C9jd7yLD.js","sources":["../../src/formats/csv/records.ts"],"sourcesContent":["/**\n * The one streaming CSV record reader of the package (design sections 8.2 and 8.4: hand-written\n * tokenisers per format; CSV and Neo4j CSV are line-oriented over a byte stream): RFC 4180 records\n * with a configurable single-character delimiter and quote, a doubled quote inside a quoted field,\n * quoted fields spanning lines, LF / CRLF / lone-CR terminators, read from the common text reader\n * one chunk at a time as a character state machine. Only the open field is ever held, so a field\n * spanning many chunks costs its length once and a multi-gigabyte file is never buffered.\n *\n * `RecordReader` (used by the Neo4j importer) keeps one reusable cell array and one reusable\n * \"was quoted\" array: a quoted empty field (`\"\"`) is an empty string while an unquoted empty field\n * means \"not set\", and only the tokeniser can tell the two apart. `CsvRecordReader` (the CSV\n * importer) wraps it with delimiter sniffing over a bounded preview and yields a fresh cell array\n * per row. A malformed quoted field is fatal for both: an unterminated quote swallows the rest of\n * the file, and text after a closing quote makes every later cell boundary unreliable.\n */\n\nimport { GraphFormatError } from \"@graphty/graph-format\";\n\nimport { MAX_TEXT_LENGTH, type ReadOptions, textChunks, tooLarge } from \"../../common/input.js\";\nimport { type ImportReportBuilder } from \"../../common/report.js\";\nimport { type ImportInput } from \"../../types.js\";\n\n/**\n * A quoted field is never closed; the import aborts (everything after it would be one cell).\n * @category Issue and loss codes\n */\nexport const UNCLOSED_QUOTE_CODE = \"E_CSV_UNCLOSED_QUOTE\";\n\n/**\n * A closing quote is followed by text other than a delimiter or a line break; the import aborts.\n * @category Issue and loss codes\n */\nexport const BAD_QUOTE_CODE = \"E_CSV_QUOTE\";\n\n/**\n * The delimiters tried, in priority order, when none is given.\n * @category Plugin helpers\n */\nexport const DELIMITER_CANDIDATES: readonly string[] = Object.freeze([\",\", \"\\t\", \";\", \"|\", \" \"]);\n\n/** Rows the delimiter sniff looks at. */\nconst PREVIEW_ROWS = 10;\n\n/** Characters buffered for the sniff when the input has fewer than PREVIEW_ROWS line breaks. */\nconst PREVIEW_CHARS = 64 * 1024;\n\nconst LF = 10;\nconst CR = 13;\nconst TAB = 9;\nconst SPACE = 32;\n\n/** An Excel delimiter directive: a first line `sep=;` names the delimiter and is not a record. */\nconst SEP_DIRECTIVE = /^sep=([^\\r\\n])(?:\\r\\n|\\n|\\r|$)/i;\n\n/** Tokeniser state: at the start of a field. */\nconst START = 0;\n/** Tokeniser state: inside an unquoted field. */\nconst UNQUOTED = 1;\n/** Tokeniser state: inside a quoted field. */\nconst QUOTED = 2;\n/** Tokeniser state: just after a quote inside a quoted field (a second quote is a literal, anything else ends the field). */\nconst CLOSING = 3;\n/** Tokeniser state: after a closed quoted field, before the delimiter (text here is malformed). */\nconst AFTER_QUOTED = 4;\n/** Inside a leading comment line (skipped up to its line break). */\nconst COMMENT = 5;\n\ntype State = typeof START | typeof UNQUOTED | typeof QUOTED | typeof CLOSING | typeof AFTER_QUOTED | typeof COMMENT;\n\n/**\n * The delimiter and quote of a reader.\n * @category Plugin helpers\n */\nexport interface RecordSyntax {\n /** The field delimiter, one character; null sniffs it from the first rows. */\n readonly delimiter: string | null;\n /** The quote character, one character. */\n readonly quote: string;\n /** The delimiters tried when `delimiter` is null; DELIMITER_CANDIDATES by default. */\n readonly candidates?: readonly string[] | undefined;\n /**\n * Characters that open a comment line while no record has been read yet (SNAP `#`, KONECT\n * `%`): such leading lines are skipped, kept in `leadingComments` and left out of the delimiter\n * sniff. A later line starting with one of them is an ordinary record. Default: none.\n */\n readonly comments?: readonly string[] | undefined;\n /**\n * Whether the delimiter sniff skips a candidate under which a closing quote is followed by\n * other text (see sniffDelimiter). False by default: that text then aborts the read.\n */\n readonly skipQuoteErrors?: boolean | undefined;\n /**\n * Whether a space delimiter means the whitespace dialect of SNAP / KONECT edge lists: any run of\n * spaces and tabs separates two fields, and whitespace at the start and end of a line is\n * ignored. False by default (every space is a delimiter).\n */\n readonly collapseSpaces?: boolean | undefined;\n /** Whether an Excel `sep=X` first line names the delimiter for the sniff. False by default. */\n readonly sepDirective?: boolean | undefined;\n /**\n * Called with the sniff preview (leading comment lines removed) and the line it starts on,\n * before any record is scanned, so a caller can refuse input that is not delimited text at all.\n * Only called when the delimiter is sniffed.\n */\n readonly inspect?: ((preview: string, line: number) => void) | undefined;\n}\n\n/**\n * Check a delimiter or quote option: exactly one character that is not a line break, and the two\n * must differ.\n * @param syntax - the delimiter (or null for sniffing) and quote\n * @returns the syntax unchanged; E_UNSUPPORTED when invalid\n * @category Plugin helpers\n */\nexport function checkRecordSyntax(syntax: RecordSyntax): RecordSyntax {\n for (const [option, value] of [\n [\"delimiter\", syntax.delimiter],\n [\"quote\", syntax.quote],\n ] as const) {\n if (value === null) {\n continue;\n }\n if (value.length !== 1 || value === \"\\n\" || value === \"\\r\") {\n throw new GraphFormatError(\n \"E_UNSUPPORTED\",\n `option ${option}: ${JSON.stringify(value)} is not a single non-line-break character`,\n { option, found: value },\n );\n }\n }\n if (syntax.delimiter !== null && syntax.delimiter === syntax.quote) {\n throw new GraphFormatError(\"E_UNSUPPORTED\", \"options delimiter and quote must differ\", {\n option: \"delimiter\",\n found: syntax.delimiter,\n });\n }\n return syntax;\n}\n\n/**\n * Sniff the line terminator of a text: a lone `\\r` only when the text has `\\r` and no `\\n` at all\n * (classic Mac files); `\\n` otherwise.\n * @param text - the preview text\n * @returns \"\\n\" or \"\\r\"\n * @category Plugin helpers\n */\nexport function sniffNewline(text: string): \"\\n\" | \"\\r\" {\n return !text.includes(\"\\n\") && text.includes(\"\\r\") ? \"\\r\" : \"\\n\";\n}\n\n/**\n * Split a text into records synchronously (the first `maxRows` of them), honoring quotes; for\n * the delimiter sniff and the registry's head sniff, where the input is a bounded preview.\n * @param text - the text\n * @param delimiter - the delimiter\n * @param quote - the quote character\n * @param maxRows - the most rows to return\n * @param strictQuotes - return null when a closing quote is followed by text other than the\n * delimiter or a line break (the import would abort under this delimiter)\n * @param collapse - the whitespace dialect (RecordSyntax.collapseSpaces) for a space delimiter\n * @returns the rows as cell arrays (blank lines skipped), or null (see strictQuotes)\n * @category Plugin helpers\n */\nexport function splitRecords(\n text: string,\n delimiter: string,\n quote: string,\n maxRows: number,\n strictQuotes = false,\n collapse = false,\n): string[][] | null {\n const delimiterCode = delimiter.codePointAt(0);\n const splitter = new RecordSplitter(text, quote, delimiterCode, collapse && delimiterCode === SPACE, strictQuotes);\n let i = 0;\n while (i < text.length && splitter.rows.length < maxRows) {\n i = splitter.step(i);\n if (i < 0) {\n return null;\n }\n }\n if (splitter.rows.length < maxRows) {\n splitter.finish();\n }\n return splitter.rows;\n}\n\n/** The state machine of splitRecords(): one character at a time, quotes honored. */\nclass RecordSplitter {\n /** The rows read so far. */\n readonly rows: string[][] = [];\n\n private cells: string[] = [];\n\n private state: State = START;\n\n /** Where the unread part of the current field starts. */\n private segment = 0;\n\n private field = \"\";\n\n private readonly quoteCode: number | undefined;\n\n /**\n * Create a splitter.\n * @param text - the text\n * @param quote - the quote character\n * @param delimiterCode - the delimiter's code\n * @param whitespace - whether runs of spaces and tabs are one separator\n * @param strictQuotes - whether text after a closing quote gives up\n */\n constructor(\n private readonly text: string,\n private readonly quote: string,\n private readonly delimiterCode: number | undefined,\n private readonly whitespace: boolean,\n private readonly strictQuotes: boolean,\n ) {\n this.quoteCode = quote.codePointAt(0);\n }\n\n /**\n * Read one character.\n * @param i - its index\n * @returns the index of the next character to read, or -1 to give up (strictQuotes)\n */\n step(i: number): number {\n const c = this.text.codePointAt(i);\n if (this.state === QUOTED) {\n if (c === this.quoteCode) {\n this.field += this.text.slice(this.segment, i);\n this.state = CLOSING;\n }\n return i + 1;\n }\n const isDelimiter = c === this.delimiterCode || (this.whitespace && c === TAB);\n if (this.state === CLOSING) {\n const next = this.afterQuote(i, c, isDelimiter || c === LF || c === CR);\n if (next !== null) {\n return next;\n }\n }\n if (isDelimiter || c === LF || c === CR) {\n return this.separator(i, isDelimiter);\n }\n if (this.state === START) {\n const quoted = c === this.quoteCode;\n this.state = quoted ? QUOTED : UNQUOTED;\n this.segment = quoted ? i + 1 : i;\n }\n return i + 1;\n }\n\n /**\n * Read the character after a quote that may close a quoted field: a second quote is a quote\n * character in the field; anything else closes it.\n * @param i - its index\n * @param c - its code\n * @param separates - whether it is a delimiter or a line break\n * @returns the index of the next character to read, -1 to give up (strictQuotes), or null to\n * read this character as the one after the field\n */\n private afterQuote(i: number, c: number | undefined, separates: boolean): number | null {\n if (c === this.quoteCode) {\n this.field += this.quote;\n this.segment = i + 1;\n this.state = QUOTED;\n return i + 1;\n }\n if (this.strictQuotes && !separates) {\n return -1;\n }\n this.state = AFTER_QUOTED;\n this.segment = i;\n return null;\n }\n\n /**\n * Read a delimiter or a line break: end the field, and at a line break the row.\n * @param i - its index\n * @param isDelimiter - whether it is a delimiter\n * @returns the index of the next character to read\n */\n private separator(i: number, isDelimiter: boolean): number {\n if (this.state === UNQUOTED || this.state === AFTER_QUOTED) {\n this.field += this.text.slice(this.segment, i);\n }\n let next = i + 1;\n if (isDelimiter && this.whitespace && this.state === START) {\n // a run of whitespace, or indentation: one separator\n } else if (isDelimiter) {\n this.cells.push(this.field);\n this.field = \"\";\n this.state = START;\n } else {\n this.endRow();\n this.state = START;\n if (this.text.codePointAt(i) === CR && this.text.codePointAt(i + 1) === LF) {\n next = i + 2;\n }\n }\n this.segment = next;\n return next;\n }\n\n /** End the row at a line break; a blank line is no row. */\n private endRow(): void {\n if (this.state === START && this.cells.length === 0) {\n return;\n }\n if (!(this.whitespace && this.state === START)) {\n this.cells.push(this.field);\n }\n this.rows.push(this.cells);\n this.cells = [];\n this.field = \"\";\n }\n\n /** End the text: the last row, when it has no line break after it. */\n finish(): void {\n if (this.state === START && this.cells.length === 0) {\n return;\n }\n if (this.state === UNQUOTED || this.state === AFTER_QUOTED || this.state === QUOTED) {\n this.field += this.text.slice(this.segment);\n }\n if (!(this.whitespace && this.state === START)) {\n this.cells.push(this.field);\n }\n this.rows.push(this.cells);\n }\n}\n\n/**\n * The preview text without its leading comment lines (those starting with one of the comment\n * characters), so the sniff sees the records only.\n * @param text - the preview text\n * @param comments - the comment characters\n * @returns the text from the first non-comment line on\n * @category Plugin helpers\n */\nexport function stripLeadingComments(text: string, comments: readonly string[]): string {\n if (comments.length === 0) {\n return text;\n }\n let at = 0;\n while (at < text.length) {\n if (text[at] === \"\\n\" || text[at] === \"\\r\") {\n // a blank line among the leading comments (the scanner skips it too)\n at++;\n continue;\n }\n if (!comments.includes(text[at])) {\n break;\n }\n const lf = text.indexOf(\"\\n\", at);\n const cr = text.indexOf(\"\\r\", at);\n const end = Math.min(lf < 0 ? Infinity : lf, cr < 0 ? Infinity : cr);\n if (end === Infinity) {\n return \"\";\n }\n at = end;\n }\n return at === 0 ? text : text.slice(at);\n}\n\n/**\n * Sniff the delimiter of a text: every candidate is tried over the first rows and the one whose\n * field count is most consistent across rows wins (a tie goes to the earlier candidate, so a\n * space inside comma-separated cells never beats the comma); a\n * candidate that yields fewer than two fields per row on average is never chosen. `null` when no\n * candidate qualifies (a single-column file).\n * @param text - the preview text\n * @param newline - the sniffed line terminator (unused by the splitter, which reads every kind;\n * kept for callers that sniffed it)\n * @param candidates - the delimiters to try, in priority order\n * @param quote - the quote character\n * @param skipQuoteErrors - also never choose a candidate under which a closing quote is followed\n * by other text. Only for inputs whose field counts say nothing (an adjacency table's rows vary in\n * width): elsewhere a stray character after a quote would make a worse candidate win silently\n * instead of aborting the read\n * @param collapse - read a space candidate in the whitespace dialect (RecordSyntax.collapseSpaces)\n * @returns the delimiter, or null\n * @category Plugin helpers\n */\nexport function sniffDelimiter(\n text: string,\n newline: \"\\n\" | \"\\r\" = \"\\n\",\n candidates: readonly string[] = DELIMITER_CANDIDATES,\n quote = '\"',\n skipQuoteErrors = false,\n collapse = false,\n): string | null {\n let best: string | null = null;\n let bestDelta = Infinity;\n const terminated = text.endsWith(\"\\n\") || text.endsWith(\"\\r\");\n for (const delimiter of candidates) {\n if (delimiter === quote || delimiter === newline) {\n continue;\n }\n const split = splitRecords(text, delimiter, quote, PREVIEW_ROWS, skipQuoteErrors, collapse);\n if (split === null) {\n // a quoted cell this delimiter does not close: it is not the file's delimiter\n continue;\n }\n let rows = split.filter((row) => !isBlankRow(row));\n if (rows.length > 1 && !terminated) {\n // the preview is a prefix of the file: its last row may be cut short\n rows = rows.slice(0, -1);\n }\n if (rows.length === 0) {\n continue;\n }\n let total = 0;\n let delta = 0;\n for (let i = 0; i < rows.length; i++) {\n const count = rows[i].length;\n total += count;\n if (i > 0) {\n delta += Math.abs(count - rows[i - 1].length);\n }\n }\n const average = total / rows.length;\n if (average > 1.99 && delta < bestDelta) {\n best = delimiter;\n bestDelta = delta;\n }\n }\n return best;\n}\n\n/**\n * Whether a parsed row is a blank line: one cell holding only whitespace.\n * @param row - the row\n * @returns true for a blank line\n */\nfunction isBlankRow(row: readonly string[]): boolean {\n return row.length === 1 && row[0].trim().length === 0;\n}\n\n/**\n * Reads the records of one input. Iterate with `for await (const count of reader)`: each\n * iteration fills `reader.cells[0..count)` and `reader.quoted[0..count)` and sets `reader.line`\n * to the 1-based line the record started on. Blank lines (an empty unquoted record of one field)\n * are skipped. The arrays are reused between records. When the syntax gives no delimiter, the\n * first rows (PREVIEW_ROWS, or PREVIEW_CHARS characters) are buffered and the delimiter sniffed\n * from them before the first record is yielded.\n * @category Plugin helpers\n */\nexport class RecordReader implements AsyncIterable<number> {\n /** The cells of the record most recently yielded; only the first `count` entries are valid. */\n readonly cells: string[] = [];\n\n /** Whether each cell of the record most recently yielded was quoted. */\n readonly quoted: boolean[] = [];\n\n /** The leading comment lines (without their line breaks), in order; empty without `syntax.comments`. */\n readonly leadingComments: string[] = [];\n\n /** The 1-based line of each leading comment, parallel to `leadingComments`. */\n readonly leadingCommentLines: number[] = [];\n\n /** The line of the first quote inside an unquoted field (kept as text, RFC 4180 forbids it); 0 for none. */\n strayQuoteLine = 0;\n\n /** Whether the sniff took the delimiter from an Excel `sep=X` first line (yielded as the first record). */\n sepDirective = false;\n\n private readonly input: ImportInput;\n\n readonly report: ImportReportBuilder;\n\n private readonly readOptions: ReadOptions;\n\n /** The delimiter (null while unsniffed), quote and comment characters. */\n readonly syntax: RecordSyntax;\n\n private delimiterText: string | null;\n\n private recordLine = 0;\n\n /**\n * Create a reader; nothing is read until iteration starts.\n * @param input - the input (or an async iterable of already decoded text chunks)\n * @param report - the report decode and quoting errors are recorded in\n * @param syntax - the delimiter (null to sniff) and quote (already checked)\n * @param readOptions - cancellation and progress\n */\n constructor(input: ImportInput, report: ImportReportBuilder, syntax: RecordSyntax, readOptions: ReadOptions) {\n this.input = input;\n this.report = report;\n this.readOptions = readOptions;\n this.syntax = syntax;\n this.delimiterText = syntax.delimiter;\n }\n\n /**\n * The 1-based line the most recently yielded record started on.\n * @returns the line number; 0 before the first record\n */\n get line(): number {\n return this.recordLine;\n }\n\n /**\n * The delimiter in use.\n * @returns the given or sniffed delimiter; null before the sniff ran\n */\n get delimiter(): string | null {\n return this.delimiterText;\n }\n\n /**\n * Iterate the records.\n * @yields the number of cells of each record\n * @returns nothing\n */\n async *[Symbol.asyncIterator](): AsyncGenerator<number, void, undefined> {\n const scanner = new RecordScanner(this);\n for await (const chunk of this.chunks()) {\n // the first chunk arrives after the sniff (or with the given delimiter)\n scanner.setDelimiter((this.delimiterText ?? \",\").charCodeAt(0));\n let i = 0;\n while (i < chunk.length) {\n i = scanner.scan(chunk, i);\n if (scanner.ready) {\n scanner.ready = false;\n this.recordLine = scanner.recordLine;\n yield scanner.count;\n scanner.count = 0;\n }\n }\n scanner.endChunk(chunk);\n }\n if (scanner.finish()) {\n this.recordLine = scanner.recordLine;\n yield scanner.count;\n }\n }\n\n /**\n * Record a text-after-quote error (fatal).\n * @param line - the line the field started on\n * @param field - the 1-based position of the field in its record\n * @returns never\n */\n badQuote(line: number, field: number): never {\n return this.report.fail(\n BAD_QUOTE_CODE,\n `text after the closing quote of quoted field ${field} (a quote inside a quoted field is written as two quotes)`,\n { line },\n );\n }\n\n /**\n * Record an unclosed-quote error (fatal).\n * @param line - the line the field started on\n * @returns never\n */\n unclosedQuote(line: number): never {\n return this.report.fail(UNCLOSED_QUOTE_CODE, `a quoted field starting on line ${line} is never closed`, {\n line,\n });\n }\n\n /**\n * The decoded text chunks of the input; when the delimiter is to be sniffed, the first rows\n * are buffered (bounded by PREVIEW_ROWS line breaks or PREVIEW_CHARS characters), the sniff\n * runs, and the buffered text is yielded as one chunk before the rest streams through.\n * @yields the text chunks\n * @returns nothing\n */\n private async *chunks(): AsyncGenerator<string, void, undefined> {\n const source = textChunks(this.input, this.report, this.readOptions);\n if (this.delimiterText !== null) {\n yield* source;\n return;\n }\n const pieces: string[] = [];\n let length = 0;\n let breaks = 0;\n let lastWasCr = false;\n let sniffed = false;\n // the leading comment lines are left out of the sniff, so they do not count toward its rows\n const commentCodes = (this.syntax.comments ?? []).map((c) => c.codePointAt(0));\n let leading = commentCodes.length > 0;\n let lineStart = true;\n let inComment = false;\n for await (const chunk of source) {\n if (sniffed) {\n yield chunk;\n continue;\n }\n pieces.push(chunk);\n length += chunk.length;\n for (let i = 0; i < chunk.length && breaks < PREVIEW_ROWS; i++) {\n const c = chunk.charCodeAt(i);\n if (c === LF || c === CR) {\n if (!(c === LF && lastWasCr) && !inComment) {\n breaks++;\n }\n inComment = false;\n lineStart = true;\n } else {\n if (lineStart && leading) {\n inComment = commentCodes.includes(c);\n leading = inComment;\n }\n lineStart = false;\n }\n lastWasCr = c === CR;\n }\n if (breaks < PREVIEW_ROWS && length < PREVIEW_CHARS) {\n continue;\n }\n const text = pieces.join(\"\");\n pieces.length = 0;\n this.sniff(text);\n sniffed = true;\n yield text;\n }\n if (!sniffed) {\n const text = pieces.join(\"\");\n if (text.length > 0) {\n this.sniff(text);\n yield text;\n }\n }\n }\n\n /**\n * Sniff the delimiter from the preview text and record it.\n * @param text - the preview\n */\n private sniff(text: string): void {\n // the sniff is a heuristic over the first rows: a single chunk holding a huge quoted cell\n // is capped so the candidate scans stay bounded\n const preview = text.slice(0, PREVIEW_CHARS);\n const body = stripLeadingComments(preview, this.syntax.comments ?? []);\n if (this.syntax.inspect !== undefined) {\n const skipped = preview.slice(0, preview.length - body.length).match(/\\r\\n|\\r|\\n/g)?.length ?? 0;\n this.syntax.inspect(body, skipped + 1);\n }\n const directive = this.syntax.sepDirective === true ? SEP_DIRECTIVE.exec(text) : null;\n if (directive !== null && directive[1] !== this.syntax.quote) {\n this.delimiterText = directive[1];\n this.sepDirective = true;\n return;\n }\n const candidates = this.syntax.candidates ?? DELIMITER_CANDIDATES;\n this.delimiterText =\n sniffDelimiter(\n body,\n sniffNewline(body),\n candidates,\n this.syntax.quote,\n this.syntax.skipQuoteErrors,\n this.syntax.collapseSpaces,\n ) ?? candidates[0];\n }\n}\n\n/**\n * The record state machine of RecordReader, resumable at every record end so the reader can\n * yield a record before the next one overwrites the shared cell arrays. Every state lives on the\n * instance; `scan()` runs the per-character loop over one chunk from a position and returns where\n * it stopped (after the character that completed a record, with `ready` set, or the chunk end).\n */\nclass RecordScanner {\n /** Whether a record just completed. */\n ready = false;\n\n /** The cell count of the record in progress (the completed one while `ready`). */\n count = 0;\n\n /** The line the completed record started on (valid while `ready`, and after finish()). */\n recordLine = 1;\n\n /** The line the record in progress started on. */\n private startLine = 1;\n\n private readonly reader: RecordReader;\n\n private readonly quoteCode: number;\n\n private readonly quoteText: string;\n\n private readonly commentCodes: readonly number[];\n\n private delimiterCode = -1;\n\n /** The whitespace dialect: the delimiter is a space and RecordSyntax.collapseSpaces is set. */\n private collapse = false;\n\n private state: State = START;\n\n private field = \"\";\n\n private fieldQuoted = false;\n\n private line = 1;\n\n private lastWasCr = false;\n\n /** Whether no record has started yet (comment lines are recognized until one does). */\n private leading: boolean;\n\n private comment = \"\";\n\n /** The start of the unconsumed run of the current chunk that belongs to the field or comment. */\n private segment = 0;\n\n /**\n * Create a scanner over a reader's cell arrays.\n * @param reader - the reader\n */\n constructor(reader: RecordReader) {\n this.reader = reader;\n this.quoteCode = reader.syntax.quote.charCodeAt(0);\n this.quoteText = reader.syntax.quote;\n this.commentCodes = (reader.syntax.comments ?? []).map((c) => c.charCodeAt(0));\n this.leading = this.commentCodes.length > 0;\n }\n\n /**\n * Set the delimiter once it is known (before the first chunk is scanned).\n * @param code - the delimiter's character code\n */\n setDelimiter(code: number): void {\n if (this.delimiterCode < 0) {\n this.delimiterCode = code;\n this.collapse = code === SPACE && this.reader.syntax.collapseSpaces === true;\n }\n }\n\n /**\n * Scan one chunk from a position until a record completes or the chunk ends.\n * @param chunk - the chunk\n * @param from - where to start\n * @returns the position after the last character consumed\n */\n scan(chunk: string, from: number): number {\n const { quoteCode, delimiterCode } = this;\n const n = chunk.length;\n let i = from;\n this.segment = from;\n while (i < n) {\n const c = chunk.charCodeAt(i);\n if (this.state === COMMENT) {\n if (c === LF || c === CR) {\n this.comment = this.grow(this.comment, chunk.slice(this.segment, i));\n this.reader.leadingComments.push(this.comment);\n this.reader.leadingCommentLines.push(this.line);\n this.comment = \"\";\n this.state = START;\n this.endLine(c);\n this.segment = i + 1;\n }\n i++;\n continue;\n }\n if (this.state === QUOTED) {\n // jump to the next quote; the run in between is field text whose line breaks are counted\n const q = chunk.indexOf(this.quoteText, i);\n const end = q < 0 ? n : q;\n if (end > i) {\n this.countLineBreaks(chunk, i, end);\n }\n if (q < 0) {\n i = n;\n break;\n }\n this.field = this.grow(this.field, chunk.slice(this.segment, q));\n this.state = CLOSING;\n this.lastWasCr = false;\n i = q + 1;\n continue;\n }\n const isDelimiter = c === delimiterCode || (this.collapse && c === TAB);\n if (this.state === CLOSING) {\n if (c === quoteCode) {\n this.field = this.grow(this.field, this.quoteText);\n this.segment = i + 1;\n this.state = QUOTED;\n this.lastWasCr = false;\n i++;\n continue;\n }\n if (!isDelimiter && c !== LF && c !== CR) {\n this.reader.badQuote(this.startLine, this.count + 1);\n }\n this.state = AFTER_QUOTED;\n this.segment = i;\n // fall through to the delimiter / line-end handling below without consuming c\n }\n if (isDelimiter || c === LF || c === CR) {\n if (c === LF && this.lastWasCr) {\n // the second half of a CRLF already ended the record\n this.lastWasCr = false;\n this.segment = i + 1;\n i++;\n continue;\n }\n if (this.state === UNQUOTED) {\n this.field = this.grow(this.field, chunk.slice(this.segment, i));\n }\n if (isDelimiter && this.collapse && this.state === START) {\n // a run of whitespace, or indentation: one separator\n this.lastWasCr = false;\n this.segment = i + 1;\n i++;\n continue;\n }\n if (isDelimiter) {\n this.push();\n this.state = START;\n this.lastWasCr = false;\n this.segment = i + 1;\n i++;\n continue;\n }\n // a line ending right after a whitespace run in the whitespace dialect ends no field\n if (!(this.state === START && (this.count === 0 || this.collapse))) {\n this.push();\n }\n if (this.count > 0) {\n this.ready = true;\n this.recordLine = this.startLine;\n }\n this.state = START;\n this.endLine(c);\n this.segment = i + 1;\n i++;\n if (this.ready) {\n return i;\n }\n continue;\n }\n this.lastWasCr = false;\n if (this.state === START) {\n if (this.leading && this.count === 0 && this.commentCodes.includes(c)) {\n this.state = COMMENT;\n this.segment = i;\n i++;\n continue;\n }\n this.leading = false;\n if (c === quoteCode) {\n this.state = QUOTED;\n this.fieldQuoted = true;\n this.segment = i + 1;\n } else {\n this.state = UNQUOTED;\n this.segment = i;\n }\n } else if (c === quoteCode && this.state === UNQUOTED && this.reader.strayQuoteLine === 0) {\n this.reader.strayQuoteLine = this.startLine;\n }\n i++;\n }\n return i;\n }\n\n /**\n * Keep the tail of a chunk that belongs to an open field or comment.\n * @param chunk - the chunk just scanned to its end\n */\n endChunk(chunk: string): void {\n const n = chunk.length;\n if (this.segment >= n) {\n return;\n }\n if (this.state === COMMENT) {\n this.comment = this.grow(this.comment, chunk.slice(this.segment, n));\n } else if (this.state === QUOTED || this.state === UNQUOTED) {\n this.field = this.grow(this.field, chunk.slice(this.segment, n));\n }\n this.segment = n;\n }\n\n /**\n * The end of the input: an open comment is kept, an open quote is fatal, an open record\n * (no trailing line break) is completed.\n * @returns true when a last record is ready\n */\n finish(): boolean {\n const { state } = this;\n if (state === COMMENT) {\n this.reader.leadingComments.push(this.comment);\n this.reader.leadingCommentLines.push(this.line);\n return false;\n }\n if (state === QUOTED) {\n this.reader.unclosedQuote(this.startLine);\n }\n if (state !== START || this.count > 0) {\n if (!(state === START && this.collapse)) {\n this.push();\n }\n this.recordLine = this.startLine;\n return true;\n }\n return false;\n }\n\n /**\n * Count the line breaks of a run of quoted text (a CRLF counts once, like the record loop).\n * @param chunk - the chunk\n * @param from - the first index of the run\n * @param to - the index after the run\n */\n private countLineBreaks(chunk: string, from: number, to: number): void {\n // indexOf, not a charCodeAt loop: a quoted cell may be hundreds of MiB, and a per-character\n // loop over it is the whole cost of reading it (and many times that under coverage)\n const run = chunk.slice(from, to);\n let { line } = this;\n // the index of the last CR seen; -1 is a CR just before the run, -2 none\n let lastCr = this.lastWasCr ? -1 : -2;\n let lf = run.indexOf(\"\\n\");\n let cr = run.indexOf(\"\\r\");\n while (lf >= 0 || cr >= 0) {\n if (lf < 0 || (cr >= 0 && cr < lf)) {\n line++;\n lastCr = cr;\n cr = run.indexOf(\"\\r\", cr + 1);\n } else {\n if (lf !== lastCr + 1) {\n line++;\n }\n lf = run.indexOf(\"\\n\", lf + 1);\n }\n }\n this.line = line;\n this.lastWasCr = lastCr === run.length - 1;\n }\n\n /**\n * Append to the field or comment in progress, failing with E_TOO_LARGE instead of a RangeError\n * when the result would be longer than one JavaScript string can hold.\n * @param text - the text so far\n * @param piece - the text to append\n * @returns the joined text\n */\n private grow(text: string, piece: string): string {\n if (text.length + piece.length > MAX_TEXT_LENGTH) {\n tooLarge(\n this.reader.report,\n `a cell is longer than ${MAX_TEXT_LENGTH} characters, the most one JavaScript string holds`,\n this.startLine,\n );\n }\n return text + piece;\n }\n\n /** Store the field in progress as the next cell. */\n private push(): void {\n const { reader, count } = this;\n reader.cells[count] = this.field;\n reader.quoted[count] = this.fieldQuoted;\n this.count = count + 1;\n this.field = \"\";\n this.fieldQuoted = false;\n }\n\n /**\n * Count a line break that ends a record or a comment line.\n * @param c - the LF or CR\n */\n private endLine(c: number): void {\n if (!(c === LF && this.lastWasCr)) {\n this.line++;\n }\n this.startLine = this.line;\n this.lastWasCr = c === CR;\n }\n}\n\n/**\n * What the CSV importer's reader needs besides the input.\n * @category Plugin helpers\n */\nexport interface CsvReaderOptions extends ReadOptions {\n /** The delimiter; null or undefined sniffs it from the preview. */\n readonly delimiter?: string | null | undefined;\n /** The characters opening a leading comment line (RecordSyntax.comments); none by default. */\n readonly comments?: readonly string[] | undefined;\n /** RecordSyntax.skipQuoteErrors; false by default. */\n readonly skipQuoteErrors?: boolean | undefined;\n /** RecordSyntax.inspect; none by default. */\n readonly inspect?: ((preview: string, line: number) => void) | undefined;\n}\n\n/**\n * Records of a CSV input, one string array per row, streamed: iterate with\n * `for await (const row of reader)` and read `reader.line` for the 1-based line the row starts\n * on. The delimiter is known after the first row (`reader.delimiter`). Rows come out as fresh\n * arrays of cell texts exactly as written (no trimming, no typing); `reader.quoted` tells, for\n * the row just yielded, which cells were quoted (a quoted empty cell is the empty string, an\n * unquoted one is \"not set\"). Blank lines, whitespace-only lines and rows of empty unquoted cells\n * are skipped. A space delimiter reads the whitespace dialect (RecordSyntax.collapseSpaces), and an\n * Excel `sep=X` first line names the delimiter when none is given.\n * @category Plugin helpers\n */\nexport class CsvRecordReader implements AsyncIterable<string[]> {\n private readonly inner: RecordReader;\n\n /**\n * Create a reader; nothing is read until iteration starts.\n * @param input - the input\n * @param report - the report parse errors are recorded in\n * @param options - the delimiter, cancellation and progress\n */\n constructor(input: ImportInput, report: ImportReportBuilder, options: CsvReaderOptions = {}) {\n this.inner = new RecordReader(\n input,\n report,\n {\n delimiter: options.delimiter ?? null,\n quote: '\"',\n comments: options.comments,\n skipQuoteErrors: options.skipQuoteErrors,\n collapseSpaces: true,\n sepDirective: true,\n inspect: options.inspect,\n },\n {\n signal: options.signal,\n onProgress: options.onProgress,\n encoding: options.encoding,\n allowEmpty: options.allowEmpty,\n nulIsBinary: true,\n },\n );\n }\n\n /**\n * The leading comment lines the reader skipped (SNAP `#` lines, the KONECT `%` header), in order.\n * @returns the lines without their line breaks\n */\n get leadingComments(): readonly string[] {\n return this.inner.leadingComments;\n }\n\n /**\n * The 1-based line of each leading comment, parallel to `leadingComments`.\n * @returns the lines\n */\n get leadingCommentLines(): readonly number[] {\n return this.inner.leadingCommentLines;\n }\n\n /**\n * The 1-based line the most recently yielded row starts on.\n * @returns the line; 0 before the first row\n */\n get line(): number {\n return this.inner.line;\n }\n\n /**\n * The delimiter in use.\n * @returns the given or sniffed delimiter; null before the sniff ran\n */\n get delimiter(): string | null {\n return this.inner.delimiter;\n }\n\n /**\n * Whether each cell of the row most recently yielded was quoted.\n * @returns the flags (valid for the first `row.length` entries)\n */\n get quoted(): readonly boolean[] {\n return this.inner.quoted;\n }\n\n /**\n * The line of the first quote inside an unquoted field.\n * @returns the line, or 0 when there was none\n */\n get strayQuoteLine(): number {\n return this.inner.strayQuoteLine;\n }\n\n /**\n * Whether the first record is an Excel `sep=X` line the delimiter was taken from.\n * @returns true when it is\n */\n get sepDirective(): boolean {\n return this.inner.sepDirective;\n }\n\n /**\n * Iterate the rows.\n * @yields one row at a time as its cell texts\n * @returns nothing\n */\n async *[Symbol.asyncIterator](): AsyncGenerator<string[], void, undefined> {\n const { inner } = this;\n for await (const count of inner) {\n if (isBlankRecord(inner.cells, inner.quoted, count)) {\n continue;\n }\n yield inner.cells.slice(0, count);\n }\n }\n}\n\n/**\n * Whether a record holds nothing: every cell unquoted and blank (a blank line, a whitespace-only\n * line, or an Excel empty row `,,,`). A quoted empty cell is a value.\n * @param cells - the cells\n * @param quoted - whether each was quoted\n * @param count - the cell count\n * @returns true when the record carries no value\n */\nfunction isBlankRecord(cells: readonly string[], quoted: readonly boolean[], count: number): boolean {\n for (let k = 0; k < count; k++) {\n if (quoted[k] || cells[k].trim().length > 0) {\n return false;\n }\n }\n return true;\n}\n"],"names":[],"mappings":";;AA0BO,MAAM,sBAAsB;AAM5B,MAAM,iBAAiB;AAMvB,MAAM,uBAA0C,OAAO,OAAO,CAAC,KAAK,KAAM,KAAK,KAAK,GAAG,CAAC;AAG/F,MAAM,eAAe;AAGrB,MAAM,gBAAgB,KAAK;AAE3B,MAAM,KAAK;AACX,MAAM,KAAK;AACX,MAAM,MAAM;AACZ,MAAM,QAAQ;AAGd,MAAM,gBAAgB;AAGtB,MAAM,QAAQ;AAEd,MAAM,WAAW;AAEjB,MAAM,SAAS;AAEf,MAAM,UAAU;AAEhB,MAAM,eAAe;AAErB,MAAM,UAAU;AAiDT,SAAS,kBAAkB,QAAoC;AAClE,aAAW,CAAC,QAAQ,KAAK,KAAK;AAAA,IAC1B,CAAC,aAAa,OAAO,SAAS;AAAA,IAC9B,CAAC,SAAS,OAAO,KAAK;AAAA,EAAA,GACd;AACR,QAAI,UAAU,MAAM;AAChB;AAAA,IACJ;AACA,QAAI,MAAM,WAAW,KAAK,UAAU,QAAQ,UAAU,MAAM;AACxD,YAAM,IAAI;AAAA,QACN;AAAA,QACA,UAAU,MAAM,KAAK,KAAK,UAAU,KAAK,CAAC;AAAA,QAC1C,EAAE,QAAQ,OAAO,MAAA;AAAA,MAAM;AAAA,IAE/B;AAAA,EACJ;AACA,MAAI,OAAO,cAAc,QAAQ,OAAO,cAAc,OAAO,OAAO;AAChE,UAAM,IAAI,iBAAiB,iBAAiB,2CAA2C;AAAA,MACnF,QAAQ;AAAA,MACR,OAAO,OAAO;AAAA,IAAA,CACjB;AAAA,EACL;AACA,SAAO;AACX;AASO,SAAS,aAAa,MAA2B;AACpD,SAAO,CAAC,KAAK,SAAS,IAAI,KAAK,KAAK,SAAS,IAAI,IAAI,OAAO;AAChE;AAeO,SAAS,aACZ,MACA,WACA,OACA,SACA,eAAe,OACf,WAAW,OACM;AACjB,QAAM,gBAAgB,UAAU,YAAY,CAAC;AAC7C,QAAM,WAAW,IAAI,eAAe,MAAM,OAAO,eAAe,YAAY,kBAAkB,OAAO,YAAY;AACjH,MAAI,IAAI;AACR,SAAO,IAAI,KAAK,UAAU,SAAS,KAAK,SAAS,SAAS;AACtD,QAAI,SAAS,KAAK,CAAC;AACnB,QAAI,IAAI,GAAG;AACP,aAAO;AAAA,IACX;AAAA,EACJ;AACA,MAAI,SAAS,KAAK,SAAS,SAAS;AAChC,aAAS,OAAA;AAAA,EACb;AACA,SAAO,SAAS;AACpB;AAGA,MAAM,eAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAuBjB,YACqB,MACA,OACA,eACA,YACA,cACnB;AALmB,SAAA,OAAA;AACA,SAAA,QAAA;AACA,SAAA,gBAAA;AACA,SAAA,aAAA;AACA,SAAA,eAAA;AA1BrB,SAAS,OAAmB,CAAA;AAE5B,SAAQ,QAAkB,CAAA;AAE1B,SAAQ,QAAe;AAGvB,SAAQ,UAAU;AAElB,SAAQ,QAAQ;AAmBZ,SAAK,YAAY,MAAM,YAAY,CAAC;AAAA,EACxC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,KAAK,GAAmB;AACpB,UAAM,IAAI,KAAK,KAAK,YAAY,CAAC;AACjC,QAAI,KAAK,UAAU,QAAQ;AACvB,UAAI,MAAM,KAAK,WAAW;AACtB,aAAK,SAAS,KAAK,KAAK,MAAM,KAAK,SAAS,CAAC;AAC7C,aAAK,QAAQ;AAAA,MACjB;AACA,aAAO,IAAI;AAAA,IACf;AACA,UAAM,cAAc,MAAM,KAAK,iBAAkB,KAAK,cAAc,MAAM;AAC1E,QAAI,KAAK,UAAU,SAAS;AACxB,YAAM,OAAO,KAAK,WAAW,GAAG,GAAG,eAAe,MAAM,MAAM,MAAM,EAAE;AACtE,UAAI,SAAS,MAAM;AACf,eAAO;AAAA,MACX;AAAA,IACJ;AACA,QAAI,eAAe,MAAM,MAAM,MAAM,IAAI;AACrC,aAAO,KAAK,UAAU,GAAG,WAAW;AAAA,IACxC;AACA,QAAI,KAAK,UAAU,OAAO;AACtB,YAAM,SAAS,MAAM,KAAK;AAC1B,WAAK,QAAQ,SAAS,SAAS;AAC/B,WAAK,UAAU,SAAS,IAAI,IAAI;AAAA,IACpC;AACA,WAAO,IAAI;AAAA,EACf;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWQ,WAAW,GAAW,GAAuB,WAAmC;AACpF,QAAI,MAAM,KAAK,WAAW;AACtB,WAAK,SAAS,KAAK;AACnB,WAAK,UAAU,IAAI;AACnB,WAAK,QAAQ;AACb,aAAO,IAAI;AAAA,IACf;AACA,QAAI,KAAK,gBAAgB,CAAC,WAAW;AACjC,aAAO;AAAA,IACX;AACA,SAAK,QAAQ;AACb,SAAK,UAAU;AACf,WAAO;AAAA,EACX;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,UAAU,GAAW,aAA8B;AACvD,QAAI,KAAK,UAAU,YAAY,KAAK,UAAU,cAAc;AACxD,WAAK,SAAS,KAAK,KAAK,MAAM,KAAK,SAAS,CAAC;AAAA,IACjD;AACA,QAAI,OAAO,IAAI;AACf,QAAI,eAAe,KAAK,cAAc,KAAK,UAAU,MAAO;AAAA,aAEjD,aAAa;AACpB,WAAK,MAAM,KAAK,KAAK,KAAK;AAC1B,WAAK,QAAQ;AACb,WAAK,QAAQ;AAAA,IACjB,OAAO;AACH,WAAK,OAAA;AACL,WAAK,QAAQ;AACb,UAAI,KAAK,KAAK,YAAY,CAAC,MAAM,MAAM,KAAK,KAAK,YAAY,IAAI,CAAC,MAAM,IAAI;AACxE,eAAO,IAAI;AAAA,MACf;AAAA,IACJ;AACA,SAAK,UAAU;AACf,WAAO;AAAA,EACX;AAAA;AAAA,EAGQ,SAAe;AACnB,QAAI,KAAK,UAAU,SAAS,KAAK,MAAM,WAAW,GAAG;AACjD;AAAA,IACJ;AACA,QAAI,EAAE,KAAK,cAAc,KAAK,UAAU,QAAQ;AAC5C,WAAK,MAAM,KAAK,KAAK,KAAK;AAAA,IAC9B;AACA,SAAK,KAAK,KAAK,KAAK,KAAK;AACzB,SAAK,QAAQ,CAAA;AACb,SAAK,QAAQ;AAAA,EACjB;AAAA;AAAA,EAGA,SAAe;AACX,QAAI,KAAK,UAAU,SAAS,KAAK,MAAM,WAAW,GAAG;AACjD;AAAA,IACJ;AACA,QAAI,KAAK,UAAU,YAAY,KAAK,UAAU,gBAAgB,KAAK,UAAU,QAAQ;AACjF,WAAK,SAAS,KAAK,KAAK,MAAM,KAAK,OAAO;AAAA,IAC9C;AACA,QAAI,EAAE,KAAK,cAAc,KAAK,UAAU,QAAQ;AAC5C,WAAK,MAAM,KAAK,KAAK,KAAK;AAAA,IAC9B;AACA,SAAK,KAAK,KAAK,KAAK,KAAK;AAAA,EAC7B;AACJ;AAUO,SAAS,qBAAqB,MAAc,UAAqC;AACpF,MAAI,SAAS,WAAW,GAAG;AACvB,WAAO;AAAA,EACX;AACA,MAAI,KAAK;AACT,SAAO,KAAK,KAAK,QAAQ;AACrB,QAAI,KAAK,EAAE,MAAM,QAAQ,KAAK,EAAE,MAAM,MAAM;AAExC;AACA;AAAA,IACJ;AACA,QAAI,CAAC,SAAS,SAAS,KAAK,EAAE,CAAC,GAAG;AAC9B;AAAA,IACJ;AACA,UAAM,KAAK,KAAK,QAAQ,MAAM,EAAE;AAChC,UAAM,KAAK,KAAK,QAAQ,MAAM,EAAE;AAChC,UAAM,MAAM,KAAK,IAAI,KAAK,IAAI,WAAW,IAAI,KAAK,IAAI,WAAW,EAAE;AACnE,QAAI,QAAQ,UAAU;AAClB,aAAO;AAAA,IACX;AACA,SAAK;AAAA,EACT;AACA,SAAO,OAAO,IAAI,OAAO,KAAK,MAAM,EAAE;AAC1C;AAqBO,SAAS,eACZ,MACA,UAAuB,MACvB,aAAgC,sBAChC,QAAQ,KACR,kBAAkB,OAClB,WAAW,OACE;AACb,MAAI,OAAsB;AAC1B,MAAI,YAAY;AAChB,QAAM,aAAa,KAAK,SAAS,IAAI,KAAK,KAAK,SAAS,IAAI;AAC5D,aAAW,aAAa,YAAY;AAChC,QAAI,cAAc,SAAS,cAAc,SAAS;AAC9C;AAAA,IACJ;AACA,UAAM,QAAQ,aAAa,MAAM,WAAW,OAAO,cAAc,iBAAiB,QAAQ;AAC1F,QAAI,UAAU,MAAM;AAEhB;AAAA,IACJ;AACA,QAAI,OAAO,MAAM,OAAO,CAAC,QAAQ,CAAC,WAAW,GAAG,CAAC;AACjD,QAAI,KAAK,SAAS,KAAK,CAAC,YAAY;AAEhC,aAAO,KAAK,MAAM,GAAG,EAAE;AAAA,IAC3B;AACA,QAAI,KAAK,WAAW,GAAG;AACnB;AAAA,IACJ;AACA,QAAI,QAAQ;AACZ,QAAI,QAAQ;AACZ,aAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AAClC,YAAM,QAAQ,KAAK,CAAC,EAAE;AACtB,eAAS;AACT,UAAI,IAAI,GAAG;AACP,iBAAS,KAAK,IAAI,QAAQ,KAAK,IAAI,CAAC,EAAE,MAAM;AAAA,MAChD;AAAA,IACJ;AACA,UAAM,UAAU,QAAQ,KAAK;AAC7B,QAAI,UAAU,QAAQ,QAAQ,WAAW;AACrC,aAAO;AACP,kBAAY;AAAA,IAChB;AAAA,EACJ;AACA,SAAO;AACX;AAOA,SAAS,WAAW,KAAiC;AACjD,SAAO,IAAI,WAAW,KAAK,IAAI,CAAC,EAAE,OAAO,WAAW;AACxD;AAWO,MAAM,aAA8C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAuCvD,YAAY,OAAoB,QAA6B,QAAsB,aAA0B;AArC7G,SAAS,QAAkB,CAAA;AAG3B,SAAS,SAAoB,CAAA;AAG7B,SAAS,kBAA4B,CAAA;AAGrC,SAAS,sBAAgC,CAAA;AAGzC,SAAA,iBAAiB;AAGjB,SAAA,eAAe;AAaf,SAAQ,aAAa;AAUjB,SAAK,QAAQ;AACb,SAAK,SAAS;AACd,SAAK,cAAc;AACnB,SAAK,SAAS;AACd,SAAK,gBAAgB,OAAO;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,IAAI,OAAe;AACf,WAAO,KAAK;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,IAAI,YAA2B;AAC3B,WAAO,KAAK;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,QAAQ,OAAO,aAAa,IAA6C;AACrE,UAAM,UAAU,IAAI,cAAc,IAAI;AACtC,qBAAiB,SAAS,KAAK,UAAU;AAErC,cAAQ,cAAc,KAAK,iBAAiB,KAAK,WAAW,CAAC,CAAC;AAC9D,UAAI,IAAI;AACR,aAAO,IAAI,MAAM,QAAQ;AACrB,YAAI,QAAQ,KAAK,OAAO,CAAC;AACzB,YAAI,QAAQ,OAAO;AACf,kBAAQ,QAAQ;AAChB,eAAK,aAAa,QAAQ;AAC1B,gBAAM,QAAQ;AACd,kBAAQ,QAAQ;AAAA,QACpB;AAAA,MACJ;AACA,cAAQ,SAAS,KAAK;AAAA,IAC1B;AACA,QAAI,QAAQ,UAAU;AAClB,WAAK,aAAa,QAAQ;AAC1B,YAAM,QAAQ;AAAA,IAClB;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,SAAS,MAAc,OAAsB;AACzC,WAAO,KAAK,OAAO;AAAA,MACf;AAAA,MACA,gDAAgD,KAAK;AAAA,MACrD,EAAE,KAAA;AAAA,IAAK;AAAA,EAEf;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,cAAc,MAAqB;AAC/B,WAAO,KAAK,OAAO,KAAK,qBAAqB,mCAAmC,IAAI,oBAAoB;AAAA,MACpG;AAAA,IAAA,CACH;AAAA,EACL;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,OAAe,SAAkD;AAC7D,UAAM,SAAS,WAAW,KAAK,OAAO,KAAK,QAAQ,KAAK,WAAW;AACnE,QAAI,KAAK,kBAAkB,MAAM;AAC7B,aAAO;AACP;AAAA,IACJ;AACA,UAAM,SAAmB,CAAA;AACzB,QAAI,SAAS;AACb,QAAI,SAAS;AACb,QAAI,YAAY;AAChB,QAAI,UAAU;AAEd,UAAM,gBAAgB,KAAK,OAAO,YAAY,CAAA,GAAI,IAAI,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;AAC7E,QAAI,UAAU,aAAa,SAAS;AACpC,QAAI,YAAY;AAChB,QAAI,YAAY;AAChB,qBAAiB,SAAS,QAAQ;AAC9B,UAAI,SAAS;AACT,cAAM;AACN;AAAA,MACJ;AACA,aAAO,KAAK,KAAK;AACjB,gBAAU,MAAM;AAChB,eAAS,IAAI,GAAG,IAAI,MAAM,UAAU,SAAS,cAAc,KAAK;AAC5D,cAAM,IAAI,MAAM,WAAW,CAAC;AAC5B,YAAI,MAAM,MAAM,MAAM,IAAI;AACtB,cAAI,EAAE,MAAM,MAAM,cAAc,CAAC,WAAW;AACxC;AAAA,UACJ;AACA,sBAAY;AACZ,sBAAY;AAAA,QAChB,OAAO;AACH,cAAI,aAAa,SAAS;AACtB,wBAAY,aAAa,SAAS,CAAC;AACnC,sBAAU;AAAA,UACd;AACA,sBAAY;AAAA,QAChB;AACA,oBAAY,MAAM;AAAA,MACtB;AACA,UAAI,SAAS,gBAAgB,SAAS,eAAe;AACjD;AAAA,MACJ;AACA,YAAM,OAAO,OAAO,KAAK,EAAE;AAC3B,aAAO,SAAS;AAChB,WAAK,MAAM,IAAI;AACf,gBAAU;AACV,YAAM;AAAA,IACV;AACA,QAAI,CAAC,SAAS;AACV,YAAM,OAAO,OAAO,KAAK,EAAE;AAC3B,UAAI,KAAK,SAAS,GAAG;AACjB,aAAK,MAAM,IAAI;AACf,cAAM;AAAA,MACV;AAAA,IACJ;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,MAAM,MAAoB;AAG9B,UAAM,UAAU,KAAK,MAAM,GAAG,aAAa;AAC3C,UAAM,OAAO,qBAAqB,SAAS,KAAK,OAAO,YAAY,EAAE;AACrE,QAAI,KAAK,OAAO,YAAY,QAAW;AACnC,YAAM,UAAU,QAAQ,MAAM,GAAG,QAAQ,SAAS,KAAK,MAAM,EAAE,MAAM,aAAa,GAAG,UAAU;AAC/F,WAAK,OAAO,QAAQ,MAAM,UAAU,CAAC;AAAA,IACzC;AACA,UAAM,YAAY,KAAK,OAAO,iBAAiB,OAAO,cAAc,KAAK,IAAI,IAAI;AACjF,QAAI,cAAc,QAAQ,UAAU,CAAC,MAAM,KAAK,OAAO,OAAO;AAC1D,WAAK,gBAAgB,UAAU,CAAC;AAChC,WAAK,eAAe;AACpB;AAAA,IACJ;AACA,UAAM,aAAa,KAAK,OAAO,cAAc;AAC7C,SAAK,gBACD;AAAA,MACI;AAAA,MACA,aAAa,IAAI;AAAA,MACjB;AAAA,MACA,KAAK,OAAO;AAAA,MACZ,KAAK,OAAO;AAAA,MACZ,KAAK,OAAO;AAAA,IAAA,KACX,WAAW,CAAC;AAAA,EACzB;AACJ;AAQA,MAAM,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA,EAgDhB,YAAY,QAAsB;AA9ClC,SAAA,QAAQ;AAGR,SAAA,QAAQ;AAGR,SAAA,aAAa;AAGb,SAAQ,YAAY;AAUpB,SAAQ,gBAAgB;AAGxB,SAAQ,WAAW;AAEnB,SAAQ,QAAe;AAEvB,SAAQ,QAAQ;AAEhB,SAAQ,cAAc;AAEtB,SAAQ,OAAO;AAEf,SAAQ,YAAY;AAKpB,SAAQ,UAAU;AAGlB,SAAQ,UAAU;AAOd,SAAK,SAAS;AACd,SAAK,YAAY,OAAO,OAAO,MAAM,WAAW,CAAC;AACjD,SAAK,YAAY,OAAO,OAAO;AAC/B,SAAK,gBAAgB,OAAO,OAAO,YAAY,CAAA,GAAI,IAAI,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;AAC7E,SAAK,UAAU,KAAK,aAAa,SAAS;AAAA,EAC9C;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,aAAa,MAAoB;AAC7B,QAAI,KAAK,gBAAgB,GAAG;AACxB,WAAK,gBAAgB;AACrB,WAAK,WAAW,SAAS,SAAS,KAAK,OAAO,OAAO,mBAAmB;AAAA,IAC5E;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,KAAK,OAAe,MAAsB;AACtC,UAAM,EAAE,WAAW,cAAA,IAAkB;AACrC,UAAM,IAAI,MAAM;AAChB,QAAI,IAAI;AACR,SAAK,UAAU;AACf,WAAO,IAAI,GAAG;AACV,YAAM,IAAI,MAAM,WAAW,CAAC;AAC5B,UAAI,KAAK,UAAU,SAAS;AACxB,YAAI,MAAM,MAAM,MAAM,IAAI;AACtB,eAAK,UAAU,KAAK,KAAK,KAAK,SAAS,MAAM,MAAM,KAAK,SAAS,CAAC,CAAC;AACnE,eAAK,OAAO,gBAAgB,KAAK,KAAK,OAAO;AAC7C,eAAK,OAAO,oBAAoB,KAAK,KAAK,IAAI;AAC9C,eAAK,UAAU;AACf,eAAK,QAAQ;AACb,eAAK,QAAQ,CAAC;AACd,eAAK,UAAU,IAAI;AAAA,QACvB;AACA;AACA;AAAA,MACJ;AACA,UAAI,KAAK,UAAU,QAAQ;AAEvB,cAAM,IAAI,MAAM,QAAQ,KAAK,WAAW,CAAC;AACzC,cAAM,MAAM,IAAI,IAAI,IAAI;AACxB,YAAI,MAAM,GAAG;AACT,eAAK,gBAAgB,OAAO,GAAG,GAAG;AAAA,QACtC;AACA,YAAI,IAAI,GAAG;AACP,cAAI;AACJ;AAAA,QACJ;AACA,aAAK,QAAQ,KAAK,KAAK,KAAK,OAAO,MAAM,MAAM,KAAK,SAAS,CAAC,CAAC;AAC/D,aAAK,QAAQ;AACb,aAAK,YAAY;AACjB,YAAI,IAAI;AACR;AAAA,MACJ;AACA,YAAM,cAAc,MAAM,iBAAkB,KAAK,YAAY,MAAM;AACnE,UAAI,KAAK,UAAU,SAAS;AACxB,YAAI,MAAM,WAAW;AACjB,eAAK,QAAQ,KAAK,KAAK,KAAK,OAAO,KAAK,SAAS;AACjD,eAAK,UAAU,IAAI;AACnB,eAAK,QAAQ;AACb,eAAK,YAAY;AACjB;AACA;AAAA,QACJ;AACA,YAAI,CAAC,eAAe,MAAM,MAAM,MAAM,IAAI;AACtC,eAAK,OAAO,SAAS,KAAK,WAAW,KAAK,QAAQ,CAAC;AAAA,QACvD;AACA,aAAK,QAAQ;AACb,aAAK,UAAU;AAAA,MAEnB;AACA,UAAI,eAAe,MAAM,MAAM,MAAM,IAAI;AACrC,YAAI,MAAM,MAAM,KAAK,WAAW;AAE5B,eAAK,YAAY;AACjB,eAAK,UAAU,IAAI;AACnB;AACA;AAAA,QACJ;AACA,YAAI,KAAK,UAAU,UAAU;AACzB,eAAK,QAAQ,KAAK,KAAK,KAAK,OAAO,MAAM,MAAM,KAAK,SAAS,CAAC,CAAC;AAAA,QACnE;AACA,YAAI,eAAe,KAAK,YAAY,KAAK,UAAU,OAAO;AAEtD,eAAK,YAAY;AACjB,eAAK,UAAU,IAAI;AACnB;AACA;AAAA,QACJ;AACA,YAAI,aAAa;AACb,eAAK,KAAA;AACL,eAAK,QAAQ;AACb,eAAK,YAAY;AACjB,eAAK,UAAU,IAAI;AACnB;AACA;AAAA,QACJ;AAEA,YAAI,EAAE,KAAK,UAAU,UAAU,KAAK,UAAU,KAAK,KAAK,YAAY;AAChE,eAAK,KAAA;AAAA,QACT;AACA,YAAI,KAAK,QAAQ,GAAG;AAChB,eAAK,QAAQ;AACb,eAAK,aAAa,KAAK;AAAA,QAC3B;AACA,aAAK,QAAQ;AACb,aAAK,QAAQ,CAAC;AACd,aAAK,UAAU,IAAI;AACnB;AACA,YAAI,KAAK,OAAO;AACZ,iBAAO;AAAA,QACX;AACA;AAAA,MACJ;AACA,WAAK,YAAY;AACjB,UAAI,KAAK,UAAU,OAAO;AACtB,YAAI,KAAK,WAAW,KAAK,UAAU,KAAK,KAAK,aAAa,SAAS,CAAC,GAAG;AACnE,eAAK,QAAQ;AACb,eAAK,UAAU;AACf;AACA;AAAA,QACJ;AACA,aAAK,UAAU;AACf,YAAI,MAAM,WAAW;AACjB,eAAK,QAAQ;AACb,eAAK,cAAc;AACnB,eAAK,UAAU,IAAI;AAAA,QACvB,OAAO;AACH,eAAK,QAAQ;AACb,eAAK,UAAU;AAAA,QACnB;AAAA,MACJ,WAAW,MAAM,aAAa,KAAK,UAAU,YAAY,KAAK,OAAO,mBAAmB,GAAG;AACvF,aAAK,OAAO,iBAAiB,KAAK;AAAA,MACtC;AACA;AAAA,IACJ;AACA,WAAO;AAAA,EACX;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,SAAS,OAAqB;AAC1B,UAAM,IAAI,MAAM;AAChB,QAAI,KAAK,WAAW,GAAG;AACnB;AAAA,IACJ;AACA,QAAI,KAAK,UAAU,SAAS;AACxB,WAAK,UAAU,KAAK,KAAK,KAAK,SAAS,MAAM,MAAM,KAAK,SAAS,CAAC,CAAC;AAAA,IACvE,WAAW,KAAK,UAAU,UAAU,KAAK,UAAU,UAAU;AACzD,WAAK,QAAQ,KAAK,KAAK,KAAK,OAAO,MAAM,MAAM,KAAK,SAAS,CAAC,CAAC;AAAA,IACnE;AACA,SAAK,UAAU;AAAA,EACnB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,SAAkB;AACd,UAAM,EAAE,UAAU;AAClB,QAAI,UAAU,SAAS;AACnB,WAAK,OAAO,gBAAgB,KAAK,KAAK,OAAO;AAC7C,WAAK,OAAO,oBAAoB,KAAK,KAAK,IAAI;AAC9C,aAAO;AAAA,IACX;AACA,QAAI,UAAU,QAAQ;AAClB,WAAK,OAAO,cAAc,KAAK,SAAS;AAAA,IAC5C;AACA,QAAI,UAAU,SAAS,KAAK,QAAQ,GAAG;AACnC,UAAI,EAAE,UAAU,SAAS,KAAK,WAAW;AACrC,aAAK,KAAA;AAAA,MACT;AACA,WAAK,aAAa,KAAK;AACvB,aAAO;AAAA,IACX;AACA,WAAO;AAAA,EACX;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,gBAAgB,OAAe,MAAc,IAAkB;AAGnE,UAAM,MAAM,MAAM,MAAM,MAAM,EAAE;AAChC,QAAI,EAAE,SAAS;AAEf,QAAI,SAAS,KAAK,YAAY,KAAK;AACnC,QAAI,KAAK,IAAI,QAAQ,IAAI;AACzB,QAAI,KAAK,IAAI,QAAQ,IAAI;AACzB,WAAO,MAAM,KAAK,MAAM,GAAG;AACvB,UAAI,KAAK,KAAM,MAAM,KAAK,KAAK,IAAK;AAChC;AACA,iBAAS;AACT,aAAK,IAAI,QAAQ,MAAM,KAAK,CAAC;AAAA,MACjC,OAAO;AACH,YAAI,OAAO,SAAS,GAAG;AACnB;AAAA,QACJ;AACA,aAAK,IAAI,QAAQ,MAAM,KAAK,CAAC;AAAA,MACjC;AAAA,IACJ;AACA,SAAK,OAAO;AACZ,SAAK,YAAY,WAAW,IAAI,SAAS;AAAA,EAC7C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,KAAK,MAAc,OAAuB;AAC9C,QAAI,KAAK,SAAS,MAAM,SAAS,iBAAiB;AAC9C;AAAA,QACI,KAAK,OAAO;AAAA,QACZ,yBAAyB,eAAe;AAAA,QACxC,KAAK;AAAA,MAAA;AAAA,IAEb;AACA,WAAO,OAAO;AAAA,EAClB;AAAA;AAAA,EAGQ,OAAa;AACjB,UAAM,EAAE,QAAQ,MAAA,IAAU;AAC1B,WAAO,MAAM,KAAK,IAAI,KAAK;AAC3B,WAAO,OAAO,KAAK,IAAI,KAAK;AAC5B,SAAK,QAAQ,QAAQ;AACrB,SAAK,QAAQ;AACb,SAAK,cAAc;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,QAAQ,GAAiB;AAC7B,QAAI,EAAE,MAAM,MAAM,KAAK,YAAY;AAC/B,WAAK;AAAA,IACT;AACA,SAAK,YAAY,KAAK;AACtB,SAAK,YAAY,MAAM;AAAA,EAC3B;AACJ;AA4BO,MAAM,gBAAmD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAS5D,YAAY,OAAoB,QAA6B,UAA4B,CAAA,GAAI;AACzF,SAAK,QAAQ,IAAI;AAAA,MACb;AAAA,MACA;AAAA,MACA;AAAA,QACI,WAAW,QAAQ,aAAa;AAAA,QAChC,OAAO;AAAA,QACP,UAAU,QAAQ;AAAA,QAClB,iBAAiB,QAAQ;AAAA,QACzB,gBAAgB;AAAA,QAChB,cAAc;AAAA,QACd,SAAS,QAAQ;AAAA,MAAA;AAAA,MAErB;AAAA,QACI,QAAQ,QAAQ;AAAA,QAChB,YAAY,QAAQ;AAAA,QACpB,UAAU,QAAQ;AAAA,QAClB,YAAY,QAAQ;AAAA,QACpB,aAAa;AAAA,MAAA;AAAA,IACjB;AAAA,EAER;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,IAAI,kBAAqC;AACrC,WAAO,KAAK,MAAM;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,IAAI,sBAAyC;AACzC,WAAO,KAAK,MAAM;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,IAAI,OAAe;AACf,WAAO,KAAK,MAAM;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,IAAI,YAA2B;AAC3B,WAAO,KAAK,MAAM;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,IAAI,SAA6B;AAC7B,WAAO,KAAK,MAAM;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,IAAI,iBAAyB;AACzB,WAAO,KAAK,MAAM;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,IAAI,eAAwB;AACxB,WAAO,KAAK,MAAM;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,QAAQ,OAAO,aAAa,IAA+C;AACvE,UAAM,EAAE,UAAU;AAClB,qBAAiB,SAAS,OAAO;AAC7B,UAAI,cAAc,MAAM,OAAO,MAAM,QAAQ,KAAK,GAAG;AACjD;AAAA,MACJ;AACA,YAAM,MAAM,MAAM,MAAM,GAAG,KAAK;AAAA,IACpC;AAAA,EACJ;AACJ;AAUA,SAAS,cAAc,OAA0B,QAA4B,OAAwB;AACjG,WAAS,IAAI,GAAG,IAAI,OAAO,KAAK;AAC5B,QAAI,OAAO,CAAC,KAAK,MAAM,CAAC,EAAE,KAAA,EAAO,SAAS,GAAG;AACzC,aAAO;AAAA,IACX;AAAA,EACJ;AACA,SAAO;AACX;"}
@@ -1,5 +1,5 @@
1
1
  import { INVALID_INDEX, GraphFormatError } from "@graphty/graph-format";
2
- import { P as PRECISION_CODE } from "./export-CTUlibzr.js";
2
+ import { P as PRECISION_CODE } from "./export-B-aVCwyG.js";
3
3
  const I32_TEXT = /^-?(0|[1-9]\d*)$/;
4
4
  const F64_TEXT = /^[+-]?((0|[1-9]\d*)(\.\d*)?|\.\d+)([eE][+-]?\d+)?$/;
5
5
  const I32_MIN = -2147483648;
@@ -204,4 +204,4 @@ export {
204
204
  parseTextCell as p,
205
205
  trimTrailingZeros as t
206
206
  };
207
- //# sourceMappingURL=text-Btw3sBe_.js.map
207
+ //# sourceMappingURL=text-Bjfa1xYL.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"text-Btw3sBe_.js","sources":["../../src/common/text.ts"],"sourcesContent":["/**\n * The fixed lexical grammar of design section 5.1 for untyped text sources (CSV cells, GML\n * values, DOT strings, Pajek tokens), reproduced from the core's reference implementation so the\n * two agree (invariant I15). Text importers parse each cell with parseTextCell() and push the JS\n * value; the sink's per-column inference then widens `bool -> i32 -> f64 -> string` and never per\n * cell, so a column that saw `1` and then `\"01\"` becomes string for all rows.\n *\n * - `bool` is exactly `true` / `false` (case-sensitive);\n * - `i32` is `/^-?(0|[1-9][0-9]*)$/` within `[-2^31, 2^31)`; `0` / `1` are i32, never bool;\n * - `f64` is a decimal or exponent literal accepted by `Number()` that is not empty, not\n * whitespace, not `Infinity` / `NaN`, not a hex / octal / binary form, has no leading zeros in\n * its integer part, and whose value is finite (an integer literal outside i32 range is f64);\n * - everything else is `string`.\n */\n\nimport { type ColumnHandle, GraphFormatError, type GraphSink, INVALID_INDEX } from \"@graphty/graph-format\";\n\nimport { PRECISION_CODE } from \"./codes.js\";\nimport { type ImportReportBuilder } from \"./report.js\";\n\nconst I32_TEXT = /^-?(0|[1-9]\\d*)$/;\nconst F64_TEXT = /^[+-]?((0|[1-9]\\d*)(\\.\\d*)?|\\.\\d+)([eE][+-]?\\d+)?$/;\nconst I32_MIN = -2147483648;\nconst I32_MAX = 2147483647;\n\n/**\n * The dtype a text cell parses as under the fixed text grammar.\n * @public\n * @category Plugin helpers\n */\nexport type TextDtype = \"bool\" | \"i32\" | \"f64\" | \"string\";\n\n/**\n * A digit string without its trailing zeros (`\"1200\"` becomes `\"12\"`).\n * @param digits - the digits\n * @returns the digits up to the last one that is not 0\n */\nexport function trimTrailingZeros(digits: string): string {\n let end = digits.length;\n while (end > 0 && digits[end - 1] === \"0\") {\n end--;\n }\n return digits.slice(0, end);\n}\n\n/**\n * Classify one text cell by the fixed grammar.\n * @param text - the cell text, exactly as read (no trimming)\n * @returns bool, i32, f64 or string\n * @category Plugin helpers\n */\nexport function inferTextDtype(text: string): TextDtype {\n if (text === \"true\" || text === \"false\") {\n return \"bool\";\n }\n if (I32_TEXT.test(text)) {\n const n = Number(text);\n if (n >= I32_MIN && n <= I32_MAX) {\n return \"i32\";\n }\n return Number.isFinite(n) ? \"f64\" : \"string\";\n }\n if (F64_TEXT.test(text) && Number.isFinite(Number(text))) {\n return \"f64\";\n }\n return \"string\";\n}\n\n/**\n * Parse one text cell into the JS value its grammar class implies: a boolean for bool, a number for\n * i32 / f64, the text itself for string. The sink infers the column dtype from the value.\n * @param text - the cell text, exactly as read\n * @returns the value\n * @category Plugin helpers\n */\nexport function parseTextCell(text: string): boolean | number | string {\n switch (inferTextDtype(text)) {\n case \"bool\":\n return text === \"true\";\n case \"i32\":\n case \"f64\":\n return Number(text);\n case \"string\":\n return text;\n default:\n return text;\n }\n}\n\n/**\n * Whether a text cell is a number under the f64 grammar (an i32 or f64 literal).\n * @param text - the cell text\n * @returns true when parseTextCell(text) returns a number\n * @category Plugin helpers\n */\nexport function isNumericText(text: string): boolean {\n const dtype = inferTextDtype(text);\n return dtype === \"i32\" || dtype === \"f64\";\n}\n\n/**\n * Whether a numeric cell text is the canonical spelling of its value (`String(value) === text`),\n * so that the sink's own re-formatting of the value would reproduce it.\n * @param text - the cell text\n * @param value - the parsed value\n * @returns true when the lexical form survives a widening to string\n */\nfunction isCanonicalNumberText(text: string, value: number): boolean {\n return String(value) === text;\n}\n\n/**\n * The widening rank of a text dtype in the design section 5.1 order.\n * @param dtype - the text dtype\n * @returns 0 for bool, 1 for i32, 2 for f64, 3 for string\n */\nfunction textDtypeRank(dtype: TextDtype): number {\n switch (dtype) {\n case \"bool\":\n return 0;\n case \"i32\":\n return 1;\n case \"f64\":\n return 2;\n case \"string\":\n return 3;\n default: {\n const name: string = dtype;\n throw new GraphFormatError(\"E_UNSUPPORTED\", `unknown text dtype ${name}`, { dtype: name });\n }\n }\n}\n\n/**\n * Writes the cells of one attribute of a text format whose values carry no type (CSV, DOT, Pajek):\n * you hand it each cell's text and it stores a number, a boolean or text, choosing one type for the\n * whole attribute. A column of `2.0`, `3.5` stays a number column with decimals even though `2.0`\n * alone looks like an integer, and once one cell turns out to be text, earlier cells such as `1e5`\n * are kept as written instead of as `100000`. Create one writer per attribute name and per kind\n * (\"node\" or \"edge\"), and call `write(index, text)` with the node or edge index the sink gave you:\n *\n * ```ts\n * const color = new TextCellWriter(\"color\", \"node\", sink, report);\n * color.write(sink.addNode(\"a\"), \"red\");\n * ```\n *\n * A sink that cannot change a column's type after the fact (one of your own without\n * `widenNodeColumn` / `widenEdgeColumn`) keeps the type the first values implied, with one W_WIDENING_UNSUPPORTED warning\n * per attribute.\n * @public\n * @category Writing a format\n */\nexport class TextCellWriter {\n /** The column name in the sink. */\n readonly name: string;\n\n private readonly domain: \"node\" | \"edge\";\n\n private readonly sink: GraphSink;\n\n private readonly report: ImportReportBuilder;\n\n private handle: ColumnHandle = INVALID_INDEX as ColumnHandle;\n\n /** The widest text dtype seen (the column's dtype under the 5.1 grammar). */\n private textDtype: TextDtype | null = null;\n\n /** The widest dtype the pushed values imply (what the sink inferred on its own). */\n private valueDtype: TextDtype | null = null;\n\n private readonly keptRows: number[] = [];\n\n private readonly keptTexts: string[] = [];\n\n /**\n * Create a writer. The attribute is added to the sink on the first write.\n * @param name - the attribute name\n * @param domain - \"node\" for a node attribute, \"edge\" for an edge attribute\n * @param sink - the sink the importer fills\n * @param report - the import report, for warnings about the values\n */\n constructor(name: string, domain: \"node\" | \"edge\", sink: GraphSink, report: ImportReportBuilder) {\n this.name = name;\n this.domain = domain;\n this.sink = sink;\n this.report = report;\n }\n\n /**\n * The sink's handle of the attribute's column, for reading it back.\n * @returns the handle, or INVALID_INDEX before the first write\n */\n get column(): ColumnHandle {\n return this.handle;\n }\n\n /**\n * Write one cell.\n * @param row - the node or edge index, as the sink's addNode() or addEdge() returned it\n * @param text - the cell text, exactly as read from the file\n */\n write(row: number, text: string): void {\n const kind = inferTextDtype(text);\n const value = parseTextCell(text);\n if (\n typeof value === \"number\" &&\n !Number.isSafeInteger(value) &&\n I32_TEXT.test(text) &&\n BigInt(text) !== BigInt(value)\n ) {\n // an integer beyond 2^53 is stored as the nearest double; say so once per column\n this.report.warnOnce(\n \"precision\",\n PRECISION_CODE,\n `${this.domain} column \"${this.name}\": integers beyond 2^53 are stored as the nearest double (first: ${text})`,\n { element: this.name },\n `${PRECISION_CODE}:${this.domain}:${this.name}`,\n );\n }\n const wasString = this.textDtype === \"string\";\n const textDtype =\n this.textDtype === null || textDtypeRank(kind) > textDtypeRank(this.textDtype) ? kind : this.textDtype;\n // a string column keeps every cell's lexical form; a numeric text is never re-spelled. The\n // sink may refuse the value (a declared column of another dtype): the state advances only\n // once the cell is written, so a refused cell leaves the writer as it was.\n this.set(row, textDtype === \"string\" ? text : value);\n this.textDtype = textDtype;\n const valueKind = kindOfValue(value);\n if (this.valueDtype === null || textDtypeRank(valueKind) > textDtypeRank(this.valueDtype)) {\n this.valueDtype = valueKind;\n }\n if (this.textDtype === \"string\") {\n if (!wasString && this.keptRows.length > 0) {\n // the sink just widened the column to string from the values; restore the texts\n // whose lexical form the values did not carry\n for (let i = 0; i < this.keptRows.length; i++) {\n this.set(this.keptRows[i], this.keptTexts[i]);\n }\n this.keptRows.length = 0;\n this.keptTexts.length = 0;\n }\n return;\n }\n if (typeof value === \"number\" && !isCanonicalNumberText(text, value)) {\n this.keptRows.push(row);\n this.keptTexts.push(text);\n }\n if (this.textDtype === \"f64\" && this.valueDtype !== \"f64\") {\n this.widen(\"f64\");\n }\n }\n\n /**\n * Write a value (the parsed scalar, or a text into a widened column).\n * @param row - the row\n * @param value - the value\n */\n private set(row: number, value: unknown): void {\n if (this.handle === INVALID_INDEX) {\n if (this.domain === \"node\") {\n this.sink.setNodeValue(this.name, row, value);\n this.handle = this.sink.nodeColumn(this.name);\n } else {\n this.sink.setEdgeValue(this.name, row, value);\n this.handle = this.sink.edgeColumn(this.name);\n }\n return;\n }\n if (this.domain === \"node\") {\n this.sink.setNodeValue(this.handle, row, value);\n } else {\n this.sink.setEdgeValue(this.handle, row, value);\n }\n }\n\n /**\n * Widen the column to the dtype the text grammar implies, through the sink's optional call.\n * @param dtype - the dtype\n */\n private widen(dtype: \"f64\"): void {\n const { sink } = this;\n const supported =\n this.domain === \"node\" ? sink.widenNodeColumn !== undefined : sink.widenEdgeColumn !== undefined;\n if (!supported) {\n this.report.warnOnce(\n \"coercion\",\n WIDENING_UNSUPPORTED_CODE,\n `column \"${this.name}\" holds ${dtype} text but the sink cannot widen an inferred column; it keeps the value-inferred dtype`,\n { element: this.name },\n );\n this.valueDtype = dtype;\n return;\n }\n try {\n if (this.domain === \"node\") {\n sink.widenNodeColumn?.(this.handle, dtype);\n } else {\n sink.widenEdgeColumn?.(this.handle, dtype);\n }\n this.valueDtype = dtype;\n } catch (err) {\n if (!(err instanceof GraphFormatError) || err.code !== \"E_COLUMN_TYPE\") {\n throw err;\n }\n // a caller's declared column of the name: its dtype stands\n this.valueDtype = dtype;\n }\n }\n}\n\n/**\n * Your graph builder cannot change an attribute's type after its first value, so a text column keeps the type of its\n * first values.\n * @category Issue and loss codes\n */\nexport const WIDENING_UNSUPPORTED_CODE = \"W_WIDENING_UNSUPPORTED\";\n\n/**\n * The text dtype a parsed cell value implies on its own (what the sink's inference sees). It must\n * be graph-format's inference rule exactly: -0 is an integer there, so a column holding only\n * `-0.0` is inferred i32 and must be widened to f64, or the sign is lost.\n * @param value - the parsed value\n * @returns the dtype\n */\nfunction kindOfValue(value: boolean | number | string): TextDtype {\n if (typeof value === \"boolean\") {\n return \"bool\";\n }\n if (typeof value === \"string\") {\n return \"string\";\n }\n return Number.isInteger(value) && value >= -2147483648 && value <= 2147483647 ? \"i32\" : \"f64\";\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"text-Bjfa1xYL.js","sources":["../../src/common/text.ts"],"sourcesContent":["/**\n * The fixed lexical grammar of design section 5.1 for untyped text sources (CSV cells, GML\n * values, DOT strings, Pajek tokens), reproduced from the core's reference implementation so the\n * two agree (invariant I15). Text importers parse each cell with parseTextCell() and push the JS\n * value; the sink's per-column inference then widens `bool -> i32 -> f64 -> string` and never per\n * cell, so a column that saw `1` and then `\"01\"` becomes string for all rows.\n *\n * - `bool` is exactly `true` / `false` (case-sensitive);\n * - `i32` is `/^-?(0|[1-9][0-9]*)$/` within `[-2^31, 2^31)`; `0` / `1` are i32, never bool;\n * - `f64` is a decimal or exponent literal accepted by `Number()` that is not empty, not\n * whitespace, not `Infinity` / `NaN`, not a hex / octal / binary form, has no leading zeros in\n * its integer part, and whose value is finite (an integer literal outside i32 range is f64);\n * - everything else is `string`.\n */\n\nimport { type ColumnHandle, GraphFormatError, type GraphSink, INVALID_INDEX } from \"@graphty/graph-format\";\n\nimport { PRECISION_CODE } from \"./codes.js\";\nimport { type ImportReportBuilder } from \"./report.js\";\n\nconst I32_TEXT = /^-?(0|[1-9]\\d*)$/;\nconst F64_TEXT = /^[+-]?((0|[1-9]\\d*)(\\.\\d*)?|\\.\\d+)([eE][+-]?\\d+)?$/;\nconst I32_MIN = -2147483648;\nconst I32_MAX = 2147483647;\n\n/**\n * The dtype a text cell parses as under the fixed text grammar.\n * @public\n * @category Plugin helpers\n */\nexport type TextDtype = \"bool\" | \"i32\" | \"f64\" | \"string\";\n\n/**\n * A digit string without its trailing zeros (`\"1200\"` becomes `\"12\"`).\n * @param digits - the digits\n * @returns the digits up to the last one that is not 0\n */\nexport function trimTrailingZeros(digits: string): string {\n let end = digits.length;\n while (end > 0 && digits[end - 1] === \"0\") {\n end--;\n }\n return digits.slice(0, end);\n}\n\n/**\n * Classify one text cell by the fixed grammar.\n * @param text - the cell text, exactly as read (no trimming)\n * @returns bool, i32, f64 or string\n * @category Plugin helpers\n */\nexport function inferTextDtype(text: string): TextDtype {\n if (text === \"true\" || text === \"false\") {\n return \"bool\";\n }\n if (I32_TEXT.test(text)) {\n const n = Number(text);\n if (n >= I32_MIN && n <= I32_MAX) {\n return \"i32\";\n }\n return Number.isFinite(n) ? \"f64\" : \"string\";\n }\n if (F64_TEXT.test(text) && Number.isFinite(Number(text))) {\n return \"f64\";\n }\n return \"string\";\n}\n\n/**\n * Parse one text cell into the JS value its grammar class implies: a boolean for bool, a number for\n * i32 / f64, the text itself for string. The sink infers the column dtype from the value.\n * @param text - the cell text, exactly as read\n * @returns the value\n * @category Plugin helpers\n */\nexport function parseTextCell(text: string): boolean | number | string {\n switch (inferTextDtype(text)) {\n case \"bool\":\n return text === \"true\";\n case \"i32\":\n case \"f64\":\n return Number(text);\n case \"string\":\n return text;\n default:\n return text;\n }\n}\n\n/**\n * Whether a text cell is a number under the f64 grammar (an i32 or f64 literal).\n * @param text - the cell text\n * @returns true when parseTextCell(text) returns a number\n * @category Plugin helpers\n */\nexport function isNumericText(text: string): boolean {\n const dtype = inferTextDtype(text);\n return dtype === \"i32\" || dtype === \"f64\";\n}\n\n/**\n * Whether a numeric cell text is the canonical spelling of its value (`String(value) === text`),\n * so that the sink's own re-formatting of the value would reproduce it.\n * @param text - the cell text\n * @param value - the parsed value\n * @returns true when the lexical form survives a widening to string\n */\nfunction isCanonicalNumberText(text: string, value: number): boolean {\n return String(value) === text;\n}\n\n/**\n * The widening rank of a text dtype in the design section 5.1 order.\n * @param dtype - the text dtype\n * @returns 0 for bool, 1 for i32, 2 for f64, 3 for string\n */\nfunction textDtypeRank(dtype: TextDtype): number {\n switch (dtype) {\n case \"bool\":\n return 0;\n case \"i32\":\n return 1;\n case \"f64\":\n return 2;\n case \"string\":\n return 3;\n default: {\n const name: string = dtype;\n throw new GraphFormatError(\"E_UNSUPPORTED\", `unknown text dtype ${name}`, { dtype: name });\n }\n }\n}\n\n/**\n * Writes the cells of one attribute of a text format whose values carry no type (CSV, DOT, Pajek):\n * you hand it each cell's text and it stores a number, a boolean or text, choosing one type for the\n * whole attribute. A column of `2.0`, `3.5` stays a number column with decimals even though `2.0`\n * alone looks like an integer, and once one cell turns out to be text, earlier cells such as `1e5`\n * are kept as written instead of as `100000`. Create one writer per attribute name and per kind\n * (\"node\" or \"edge\"), and call `write(index, text)` with the node or edge index the sink gave you:\n *\n * ```ts\n * const color = new TextCellWriter(\"color\", \"node\", sink, report);\n * color.write(sink.addNode(\"a\"), \"red\");\n * ```\n *\n * A sink that cannot change a column's type after the fact (one of your own without\n * `widenNodeColumn` / `widenEdgeColumn`) keeps the type the first values implied, with one W_WIDENING_UNSUPPORTED warning\n * per attribute.\n * @public\n * @category Writing a format\n */\nexport class TextCellWriter {\n /** The column name in the sink. */\n readonly name: string;\n\n private readonly domain: \"node\" | \"edge\";\n\n private readonly sink: GraphSink;\n\n private readonly report: ImportReportBuilder;\n\n private handle: ColumnHandle = INVALID_INDEX as ColumnHandle;\n\n /** The widest text dtype seen (the column's dtype under the 5.1 grammar). */\n private textDtype: TextDtype | null = null;\n\n /** The widest dtype the pushed values imply (what the sink inferred on its own). */\n private valueDtype: TextDtype | null = null;\n\n private readonly keptRows: number[] = [];\n\n private readonly keptTexts: string[] = [];\n\n /**\n * Create a writer. The attribute is added to the sink on the first write.\n * @param name - the attribute name\n * @param domain - \"node\" for a node attribute, \"edge\" for an edge attribute\n * @param sink - the sink the importer fills\n * @param report - the import report, for warnings about the values\n */\n constructor(name: string, domain: \"node\" | \"edge\", sink: GraphSink, report: ImportReportBuilder) {\n this.name = name;\n this.domain = domain;\n this.sink = sink;\n this.report = report;\n }\n\n /**\n * The sink's handle of the attribute's column, for reading it back.\n * @returns the handle, or INVALID_INDEX before the first write\n */\n get column(): ColumnHandle {\n return this.handle;\n }\n\n /**\n * Write one cell.\n * @param row - the node or edge index, as the sink's addNode() or addEdge() returned it\n * @param text - the cell text, exactly as read from the file\n */\n write(row: number, text: string): void {\n const kind = inferTextDtype(text);\n const value = parseTextCell(text);\n if (\n typeof value === \"number\" &&\n !Number.isSafeInteger(value) &&\n I32_TEXT.test(text) &&\n BigInt(text) !== BigInt(value)\n ) {\n // an integer beyond 2^53 is stored as the nearest double; say so once per column\n this.report.warnOnce(\n \"precision\",\n PRECISION_CODE,\n `${this.domain} column \"${this.name}\": integers beyond 2^53 are stored as the nearest double (first: ${text})`,\n { element: this.name },\n `${PRECISION_CODE}:${this.domain}:${this.name}`,\n );\n }\n const wasString = this.textDtype === \"string\";\n const textDtype =\n this.textDtype === null || textDtypeRank(kind) > textDtypeRank(this.textDtype) ? kind : this.textDtype;\n // a string column keeps every cell's lexical form; a numeric text is never re-spelled. The\n // sink may refuse the value (a declared column of another dtype): the state advances only\n // once the cell is written, so a refused cell leaves the writer as it was.\n this.set(row, textDtype === \"string\" ? text : value);\n this.textDtype = textDtype;\n const valueKind = kindOfValue(value);\n if (this.valueDtype === null || textDtypeRank(valueKind) > textDtypeRank(this.valueDtype)) {\n this.valueDtype = valueKind;\n }\n if (this.textDtype === \"string\") {\n if (!wasString && this.keptRows.length > 0) {\n // the sink just widened the column to string from the values; restore the texts\n // whose lexical form the values did not carry\n for (let i = 0; i < this.keptRows.length; i++) {\n this.set(this.keptRows[i], this.keptTexts[i]);\n }\n this.keptRows.length = 0;\n this.keptTexts.length = 0;\n }\n return;\n }\n if (typeof value === \"number\" && !isCanonicalNumberText(text, value)) {\n this.keptRows.push(row);\n this.keptTexts.push(text);\n }\n if (this.textDtype === \"f64\" && this.valueDtype !== \"f64\") {\n this.widen(\"f64\");\n }\n }\n\n /**\n * Write a value (the parsed scalar, or a text into a widened column).\n * @param row - the row\n * @param value - the value\n */\n private set(row: number, value: unknown): void {\n if (this.handle === INVALID_INDEX) {\n if (this.domain === \"node\") {\n this.sink.setNodeValue(this.name, row, value);\n this.handle = this.sink.nodeColumn(this.name);\n } else {\n this.sink.setEdgeValue(this.name, row, value);\n this.handle = this.sink.edgeColumn(this.name);\n }\n return;\n }\n if (this.domain === \"node\") {\n this.sink.setNodeValue(this.handle, row, value);\n } else {\n this.sink.setEdgeValue(this.handle, row, value);\n }\n }\n\n /**\n * Widen the column to the dtype the text grammar implies, through the sink's optional call.\n * @param dtype - the dtype\n */\n private widen(dtype: \"f64\"): void {\n const { sink } = this;\n const supported =\n this.domain === \"node\" ? sink.widenNodeColumn !== undefined : sink.widenEdgeColumn !== undefined;\n if (!supported) {\n this.report.warnOnce(\n \"coercion\",\n WIDENING_UNSUPPORTED_CODE,\n `column \"${this.name}\" holds ${dtype} text but the sink cannot widen an inferred column; it keeps the value-inferred dtype`,\n { element: this.name },\n );\n this.valueDtype = dtype;\n return;\n }\n try {\n if (this.domain === \"node\") {\n sink.widenNodeColumn?.(this.handle, dtype);\n } else {\n sink.widenEdgeColumn?.(this.handle, dtype);\n }\n this.valueDtype = dtype;\n } catch (err) {\n if (!(err instanceof GraphFormatError) || err.code !== \"E_COLUMN_TYPE\") {\n throw err;\n }\n // a caller's declared column of the name: its dtype stands\n this.valueDtype = dtype;\n }\n }\n}\n\n/**\n * Your graph builder cannot change an attribute's type after its first value, so a text column keeps the type of its\n * first values.\n * @category Issue and loss codes\n */\nexport const WIDENING_UNSUPPORTED_CODE = \"W_WIDENING_UNSUPPORTED\";\n\n/**\n * The text dtype a parsed cell value implies on its own (what the sink's inference sees). It must\n * be graph-format's inference rule exactly: -0 is an integer there, so a column holding only\n * `-0.0` is inferred i32 and must be widened to f64, or the sign is lost.\n * @param value - the parsed value\n * @returns the dtype\n */\nfunction kindOfValue(value: boolean | number | string): TextDtype {\n if (typeof value === \"boolean\") {\n return \"bool\";\n }\n if (typeof value === \"string\") {\n return \"string\";\n }\n return Number.isInteger(value) && value >= -2147483648 && value <= 2147483647 ? \"i32\" : \"f64\";\n}\n"],"names":[],"mappings":"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"}
package/dist/csv.js CHANGED
@@ -1,8 +1,8 @@
1
- import { b as plural, bU as DICT_SAMPLE_ROWS, ac as DictHeuristic, R as ROLE_TAKEN_CODE$1, I as ID_MERGED_CODE$1, m as DUPLICATE_EDGE_ID_CODE$1, n as DUPLICATE_NODE_CODE$1, t as MISSING_ID_CODE$1, s as MISSING_ENDPOINT_CODE$1, E as EMPTY_INPUT_CODE$1, al as resolveImportOptions, a8 as ImportReportBuilder, ao as reportSinkOptions, an as reportUnusedOptions, aq as DirectionResolver, ar as IdCoercer, am as throwIfAborted, O as OPTION_IGNORED_CODE, aK as parseWeightText, a0 as uniqueColumnName, ay as RENAMED_CODE, a1 as declareResolved, c as capabilities, L as LOSS, a as checkCapabilities, r as resolveExportOptions, p as pairFolding, bD as countMixedEdges, $ as agree, e as explicitWeights, b_ as otherFormatDialect, bS as joinListText, aG as formatInteger, aF as formatDecimal, aH as canonicalId, M as MIXED_DIRECTION_CODE, D as DIRECTION_FORCED_CODE, d as DIRECTION_REFUSED_CODE, S as SINK_OPTION_CODE, C as COLUMN_RENAMED_CODE, w as UNKNOWN_ENCODING_CODE, x as ENCODING_FALLBACK_CODE, y as INVALID_ENCODING_CODE, z as INVALID_UTF8_CODE, P as PRECISION_CODE, F as ELEMENT_ISSUE, G as INPUT_ISSUE } from "./chunks/export-CTUlibzr.js";
2
- import { i as inferTextDtype, T as TextCellWriter, p as parseTextCell, W as WIDENING_UNSUPPORTED_CODE } from "./chunks/text-Btw3sBe_.js";
1
+ import { b as plural, bU as DICT_SAMPLE_ROWS, ac as DictHeuristic, R as ROLE_TAKEN_CODE$1, I as ID_MERGED_CODE$1, m as DUPLICATE_EDGE_ID_CODE$1, n as DUPLICATE_NODE_CODE$1, t as MISSING_ID_CODE$1, s as MISSING_ENDPOINT_CODE$1, E as EMPTY_INPUT_CODE$1, al as resolveImportOptions, a8 as ImportReportBuilder, ao as reportSinkOptions, an as reportUnusedOptions, aq as DirectionResolver, ar as IdCoercer, am as throwIfAborted, O as OPTION_IGNORED_CODE, aK as parseWeightText, a0 as uniqueColumnName, ay as RENAMED_CODE, a1 as declareResolved, c as capabilities, L as LOSS, a as checkCapabilities, r as resolveExportOptions, p as pairFolding, bD as countMixedEdges, $ as agree, e as explicitWeights, b_ as otherFormatDialect, bS as joinListText, aG as formatInteger, aF as formatDecimal, aH as canonicalId, M as MIXED_DIRECTION_CODE, D as DIRECTION_FORCED_CODE, d as DIRECTION_REFUSED_CODE, S as SINK_OPTION_CODE, C as COLUMN_RENAMED_CODE, w as UNKNOWN_ENCODING_CODE, x as ENCODING_FALLBACK_CODE, y as INVALID_ENCODING_CODE, z as INVALID_UTF8_CODE, P as PRECISION_CODE, F as ELEMENT_ISSUE, G as INPUT_ISSUE } from "./chunks/export-B-aVCwyG.js";
2
+ import { i as inferTextDtype, T as TextCellWriter, p as parseTextCell, W as WIDENING_UNSUPPORTED_CODE } from "./chunks/text-Bjfa1xYL.js";
3
3
  import { GraphFormatError, INVALID_INDEX } from "@graphty/graph-format";
4
- import { s as stripLeadingComments, a as sniffNewline, b as sniffDelimiter, D as DELIMITER_CANDIDATES, c as checkRecordSyntax, C as CsvRecordReader, d as splitRecords, B as BAD_QUOTE_CODE, U as UNCLOSED_QUOTE_CODE } from "./chunks/records-ChmWZGuz.js";
5
- import { o as quoteCsvCell } from "./chunks/escape-BRcyzD77.js";
4
+ import { s as stripLeadingComments, a as sniffNewline, b as sniffDelimiter, D as DELIMITER_CANDIDATES, c as checkRecordSyntax, C as CsvRecordReader, d as splitRecords, B as BAD_QUOTE_CODE, U as UNCLOSED_QUOTE_CODE } from "./chunks/records-C9jd7yLD.js";
5
+ import { o as quoteCsvCell } from "./chunks/escape-BpYMQZVa.js";
6
6
  import { j as joinText, e as encodeChunks } from "./chunks/writer-DxjgHwSe.js";
7
7
  const SOURCE_NAMES = Object.freeze([
8
8
  "source",