From ad0501cc40914b98bf7b4a3ea94d402bf8767541 Mon Sep 17 00:00:00 2001 From: Benjamin Young Date: Tue, 6 May 2025 15:43:06 -0400 Subject: [PATCH 01/54] Store current Playground examples as static files. --- .eleventy.js | 2 +- examples/playground/activity.jsonld | 14 ++++++++++ examples/playground/event.jsonld | 12 ++++++++ examples/playground/library-frame.jsonld | 11 ++++++++ examples/playground/library.jsonld | 24 ++++++++++++++++ examples/playground/person.jsonld | 8 ++++++ examples/playground/place.jsonld | 27 ++++++++++++++++++ examples/playground/product.jsonld | 27 ++++++++++++++++++ examples/playground/recipe.jsonld | 35 ++++++++++++++++++++++++ 9 files changed, 159 insertions(+), 1 deletion(-) create mode 100644 examples/playground/activity.jsonld create mode 100644 examples/playground/event.jsonld create mode 100644 examples/playground/library-frame.jsonld create mode 100644 examples/playground/library.jsonld create mode 100644 examples/playground/person.jsonld create mode 100644 examples/playground/place.jsonld create mode 100644 examples/playground/product.jsonld create mode 100644 examples/playground/recipe.jsonld diff --git a/.eleventy.js b/.eleventy.js index f7c15d28f..bc9bda0e3 100644 --- a/.eleventy.js +++ b/.eleventy.js @@ -20,7 +20,7 @@ export default async function(eleventyConfig) { eleventyConfig.addPassthroughCopy('benchmarks/**/*.{jsonld,nq,md}'); eleventyConfig.addPassthroughCopy('contexts/**/*.{htaccess,html,jsonld}'); eleventyConfig.addPassthroughCopy('contexts/{event,person,place,recipe,remote-context}'); - eleventyConfig.addPassthroughCopy('examples/**/*.{html,ttl,txt,json}'); + eleventyConfig.addPassthroughCopy('examples/**/*.{html,ttl,txt,json,jsonld}'); eleventyConfig.addPassthroughCopy('favicon.ico'); eleventyConfig.addPassthroughCopy('fonts'); eleventyConfig.addPassthroughCopy('functions/**/*.js'); diff --git a/examples/playground/activity.jsonld b/examples/playground/activity.jsonld new file mode 100644 index 000000000..533cc8aef --- /dev/null +++ b/examples/playground/activity.jsonld @@ -0,0 +1,14 @@ +{ + "@context": "https://www.w3.org/ns/activitystreams", + "@type": "Create", + "actor": { + "@type": "Person", + "@id": "acct:sally@example.org", + "name": "Sally" + }, + "object": { + "@type": "Note", + "content": "This is a simple note" + }, + "published": "2015-01-25T12:34:56Z" +} diff --git a/examples/playground/event.jsonld b/examples/playground/event.jsonld new file mode 100644 index 000000000..1a1e6ef87 --- /dev/null +++ b/examples/playground/event.jsonld @@ -0,0 +1,12 @@ +{ + "@context": { + "ical": "http://www.w3.org/2002/12/cal/ical#", + "xsd": "http://www.w3.org/2001/XMLSchema#", + "ical:dtstart": { + "@type": "xsd:dateTime" + } + }, + "ical:summary": "Lady Gaga Concert", + "ical:location": "New Orleans Arena, New Orleans, Louisiana, USA", + "ical:dtstart": "2011-04-09T20:00:00Z" +} diff --git a/examples/playground/library-frame.jsonld b/examples/playground/library-frame.jsonld new file mode 100644 index 000000000..10d3f9063 --- /dev/null +++ b/examples/playground/library-frame.jsonld @@ -0,0 +1,11 @@ +{ + "@context": { + "dc11": "http://purl.org/dc/elements/1.1/", + "ex": "http://example.org/vocab#" + }, + "@type": "ex:Library", + "ex:contains": { + "@type": "ex:Book", + "ex:contains": {"@type": "ex:Chapter"} + } +} diff --git a/examples/playground/library.jsonld b/examples/playground/library.jsonld new file mode 100644 index 000000000..8288de408 --- /dev/null +++ b/examples/playground/library.jsonld @@ -0,0 +1,24 @@ +{ + "@context": { + "dc11": "http://purl.org/dc/elements/1.1/", + "ex": "http://example.org/vocab#", + "xsd": "http://www.w3.org/2001/XMLSchema#", + "ex:contains": {"@type": "@id"} + }, + "@graph": [{ + "@id": "http://example.org/library", + "@type": "ex:Library", + "ex:contains": "http://example.org/library/the-republic" + }, { + "@id": "http://example.org/library/the-republic", + "@type": "ex:Book", + "dc11:creator": "Plato", + "dc11:title": "The Republic", + "ex:contains": "http://example.org/library/the-republic#introduction" + }, { + "@id": "http://example.org/library/the-republic#introduction", + "@type": "ex:Chapter", + "dc11:description": "An introductory chapter on The Republic.", + "dc11:title": "The Introduction" + }] +} diff --git a/examples/playground/person.jsonld b/examples/playground/person.jsonld new file mode 100644 index 000000000..26159e375 --- /dev/null +++ b/examples/playground/person.jsonld @@ -0,0 +1,8 @@ +{ + "@context": "http://schema.org/", + "@type": "Person", + "name": "Jane Doe", + "jobTitle": "Professor", + "telephone": "(425) 123-4567", + "url": "http://www.janedoe.com" +} diff --git a/examples/playground/place.jsonld b/examples/playground/place.jsonld new file mode 100644 index 000000000..130e30e1b --- /dev/null +++ b/examples/playground/place.jsonld @@ -0,0 +1,27 @@ +{ + "@context": { + "name": "http://schema.org/name", + "description": "http://schema.org/description", + "image": { + "@id": "http://schema.org/image", + "@type": "@id" + }, + "geo": "http://schema.org/geo", + "latitude": { + "@id": "http://schema.org/latitude", + "@type": "xsd:float" + }, + "longitude": { + "@id": "http://schema.org/longitude", + "@type": "xsd:float" + }, + "xsd": "http://www.w3.org/2001/XMLSchema#" + }, + "name": "The Empire State Building", + "description": "The Empire State Building is a 102-story landmark in New York City.", + "image": "http://www.civil.usherbrooke.ca/cours/gci215a/empire-state-building.jpg", + "geo": { + "latitude": "40.75", + "longitude": "73.98" + } +} diff --git a/examples/playground/product.jsonld b/examples/playground/product.jsonld new file mode 100644 index 000000000..202214f65 --- /dev/null +++ b/examples/playground/product.jsonld @@ -0,0 +1,27 @@ +{ + "@context": { + "gr": "http://purl.org/goodrelations/v1#", + "pto": "http://www.productontology.org/id/", + "foaf": "http://xmlns.com/foaf/0.1/", + "xsd": "http://www.w3.org/2001/XMLSchema#", + "foaf:page": {"@type": "@id"}, + "gr:acceptedPaymentMethods": {"@type": "@id"}, + "gr:hasBusinessFunction": {"@type": "@id"}, + "gr:hasCurrencyValue": {"@type": "xsd:float"} + }, + "@id": "http://example.org/cars/for-sale#tesla", + "@type": "gr:Offering", + "gr:name": "Used Tesla Roadster", + "gr:description": "Need to sell fast and furiously", + "gr:hasBusinessFunction": "gr:Sell", + "gr:acceptedPaymentMethods": "gr:Cash", + "gr:hasPriceSpecification": { + "gr:hasCurrencyValue": "85000", + "gr:hasCurrency": "USD" + }, + "gr:includes": { + "@type": ["gr:Individual", "pto:Vehicle"], + "gr:name": "Tesla Roadster", + "foaf:page": "http://www.teslamotors.com/roadster" + } +} diff --git a/examples/playground/recipe.jsonld b/examples/playground/recipe.jsonld new file mode 100644 index 000000000..22a6ccf99 --- /dev/null +++ b/examples/playground/recipe.jsonld @@ -0,0 +1,35 @@ +{ + "@context": { + "name": "http://rdf.data-vocabulary.org/#name", + "ingredient": "http://rdf.data-vocabulary.org/#ingredients", + "yield": "http://rdf.data-vocabulary.org/#yield", + "instructions": "http://rdf.data-vocabulary.org/#instructions", + "step": { + "@id": "http://rdf.data-vocabulary.org/#step", + "@type": "xsd:integer" + }, + "description": "http://rdf.data-vocabulary.org/#description", + "xsd": "http://www.w3.org/2001/XMLSchema#" + }, + "name": "Mojito", + "ingredient": ["12 fresh mint leaves", "1/2 lime, juiced with pulp", + "1 tablespoons white sugar", "1 cup ice cubes", + "2 fluid ounces white rum", "1/2 cup club soda"], + "yield": "1 cocktail", + "instructions" : [{ + "step": 1, + "description": "Crush lime juice, mint and sugar together in glass." + }, { + "step": 2, + "description": "Fill glass to top with ice cubes." + }, { + "step": 3, + "description": "Pour white rum over ice." + }, { + "step": 4, + "description": "Fill the rest of glass with club soda, stir." + }, { + "step": 5, + "description": "Garnish with a lime wedge." + }] +} From fd7957859910fe98c631853e4f72caeef7119e7b Mon Sep 17 00:00:00 2001 From: Benjamin Young Date: Tue, 6 May 2025 15:50:59 -0400 Subject: [PATCH 02/54] Use HTTPS where we can in examples. Many of the global IRIs should not change as the defined term IRI is an `http://` IRI and the community has not (yet?) defined new term IRIs with the `https://` prefix. They are identifiers...not necessarily locators, after all. --- examples/playground/library.jsonld | 10 +++++----- examples/playground/person.jsonld | 2 +- examples/playground/place.jsonld | 12 ++++++------ examples/playground/product.jsonld | 4 ++-- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/examples/playground/library.jsonld b/examples/playground/library.jsonld index 8288de408..b20c1d34f 100644 --- a/examples/playground/library.jsonld +++ b/examples/playground/library.jsonld @@ -6,17 +6,17 @@ "ex:contains": {"@type": "@id"} }, "@graph": [{ - "@id": "http://example.org/library", + "@id": "https://example.org/library", "@type": "ex:Library", - "ex:contains": "http://example.org/library/the-republic" + "ex:contains": "https://example.org/library/the-republic" }, { - "@id": "http://example.org/library/the-republic", + "@id": "https://example.org/library/the-republic", "@type": "ex:Book", "dc11:creator": "Plato", "dc11:title": "The Republic", - "ex:contains": "http://example.org/library/the-republic#introduction" + "ex:contains": "https://example.org/library/the-republic#introduction" }, { - "@id": "http://example.org/library/the-republic#introduction", + "@id": "https://example.org/library/the-republic#introduction", "@type": "ex:Chapter", "dc11:description": "An introductory chapter on The Republic.", "dc11:title": "The Introduction" diff --git a/examples/playground/person.jsonld b/examples/playground/person.jsonld index 26159e375..7bc4f461e 100644 --- a/examples/playground/person.jsonld +++ b/examples/playground/person.jsonld @@ -1,5 +1,5 @@ { - "@context": "http://schema.org/", + "@context": "https://schema.org/", "@type": "Person", "name": "Jane Doe", "jobTitle": "Professor", diff --git a/examples/playground/place.jsonld b/examples/playground/place.jsonld index 130e30e1b..2b44fa21a 100644 --- a/examples/playground/place.jsonld +++ b/examples/playground/place.jsonld @@ -1,18 +1,18 @@ { "@context": { - "name": "http://schema.org/name", - "description": "http://schema.org/description", + "name": "https://schema.org/name", + "description": "https://schema.org/description", "image": { - "@id": "http://schema.org/image", + "@id": "https://schema.org/image", "@type": "@id" }, - "geo": "http://schema.org/geo", + "geo": "https://schema.org/geo", "latitude": { - "@id": "http://schema.org/latitude", + "@id": "https://schema.org/latitude", "@type": "xsd:float" }, "longitude": { - "@id": "http://schema.org/longitude", + "@id": "https://schema.org/longitude", "@type": "xsd:float" }, "xsd": "http://www.w3.org/2001/XMLSchema#" diff --git a/examples/playground/product.jsonld b/examples/playground/product.jsonld index 202214f65..30b29d028 100644 --- a/examples/playground/product.jsonld +++ b/examples/playground/product.jsonld @@ -9,7 +9,7 @@ "gr:hasBusinessFunction": {"@type": "@id"}, "gr:hasCurrencyValue": {"@type": "xsd:float"} }, - "@id": "http://example.org/cars/for-sale#tesla", + "@id": "https://example.org/cars/for-sale#tesla", "@type": "gr:Offering", "gr:name": "Used Tesla Roadster", "gr:description": "Need to sell fast and furiously", @@ -22,6 +22,6 @@ "gr:includes": { "@type": ["gr:Individual", "pto:Vehicle"], "gr:name": "Tesla Roadster", - "foaf:page": "http://www.teslamotors.com/roadster" + "foaf:page": "https://www.teslamotors.com/roadster" } } From cc0ce33b2b94554ec2dfe58d8625a1af810f60a8 Mon Sep 17 00:00:00 2001 From: Benjamin Young Date: Tue, 6 May 2025 15:51:54 -0400 Subject: [PATCH 03/54] Use a .example domain for the Person. Not a good idea to link to someone elses .com domain. --- examples/playground/person.jsonld | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/playground/person.jsonld b/examples/playground/person.jsonld index 7bc4f461e..5d044084b 100644 --- a/examples/playground/person.jsonld +++ b/examples/playground/person.jsonld @@ -4,5 +4,5 @@ "name": "Jane Doe", "jobTitle": "Professor", "telephone": "(425) 123-4567", - "url": "http://www.janedoe.com" + "url": "https://www.janedoe.example/" } From 95c951d835eab2a663d337c3cdafcab14a58cfea Mon Sep 17 00:00:00 2001 From: Benjamin Young Date: Tue, 6 May 2025 15:52:25 -0400 Subject: [PATCH 04/54] Update image URL for the Empire State Building. --- examples/playground/place.jsonld | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/playground/place.jsonld b/examples/playground/place.jsonld index 2b44fa21a..f0ad4f480 100644 --- a/examples/playground/place.jsonld +++ b/examples/playground/place.jsonld @@ -19,7 +19,7 @@ }, "name": "The Empire State Building", "description": "The Empire State Building is a 102-story landmark in New York City.", - "image": "http://www.civil.usherbrooke.ca/cours/gci215a/empire-state-building.jpg", + "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/9/91/View_of_Empire_State_Building_from_Rockefeller_Center_New_York_City_dllu_Cropped.jpg/250px-View_of_Empire_State_Building_from_Rockefeller_Center_New_York_City_dllu_Cropped.jpg", "geo": { "latitude": "40.75", "longitude": "73.98" From 0010d52c6b9b67469e2e53ccdf5439f4ddf4c55d Mon Sep 17 00:00:00 2001 From: Benjamin Young Date: Wed, 7 May 2025 09:19:33 -0400 Subject: [PATCH 05/54] Initial Bootstrap 5 wrapping template. Really not a fan of the Bootstrap 5 UI kit. It is very low contrast (bad for accessibility) and changes most of the old Bootstrap classes (making upgrade non-trivial). Consequently, any other CSS framework would be nearly as complex to upgrade to...so exploring other options is back on the table. --- _layouts/bs5.liquid | 92 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 _layouts/bs5.liquid diff --git a/_layouts/bs5.liquid b/_layouts/bs5.liquid new file mode 100644 index 000000000..d17890e7e --- /dev/null +++ b/_layouts/bs5.liquid @@ -0,0 +1,92 @@ + + + + {% if title %}{{ title }} - {% endif %}{{ site.title }} + + + + + + + + + + + + + + + + + + + {{ content }} + + + + + From ea21587a9cbab3da11533837be29e7c2abac8bcd Mon Sep 17 00:00:00 2001 From: Benjamin Young Date: Wed, 7 May 2025 10:32:09 -0400 Subject: [PATCH 06/54] Add Fomantic-UI based layout. Fomantic is far more legible and learnable than Bootstrap (any version) and has an active community and plenty of primitives for updating this site. --- _layouts/fomantic.liquid | 111 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 _layouts/fomantic.liquid diff --git a/_layouts/fomantic.liquid b/_layouts/fomantic.liquid new file mode 100644 index 000000000..605f06891 --- /dev/null +++ b/_layouts/fomantic.liquid @@ -0,0 +1,111 @@ + + + + {% if title %}{{ title }} - {% endif %}{{ site.title }} + + + + + + + + + + + + + + + + + + + + +
+ {{ content }} +
+ + + + + + + From 734245f0774242b84873cc1508099d8cd63f5481 Mon Sep 17 00:00:00 2001 From: Benjamin Young Date: Wed, 7 May 2025 16:11:35 -0400 Subject: [PATCH 07/54] Add initial Playground Next code. So far the editor only displays JSON (with linting) and loads examples. Much more to come. --- README.md | 20 + package.json | 10 + playground/next/editor.bundle.js | 26629 +++++++++++++++++++++++++++++ playground/next/editor.mjs | 35 + playground/next/index.html | 104 + rollup.config.mjs | 9 + 6 files changed, 26807 insertions(+) create mode 100644 playground/next/editor.bundle.js create mode 100644 playground/next/editor.mjs create mode 100644 playground/next/index.html create mode 100644 rollup.config.mjs diff --git a/README.md b/README.md index 848826013..6c9588f1b 100644 --- a/README.md +++ b/README.md @@ -89,6 +89,23 @@ the [Wrangler][] server to emulate the [Cloudflare Pages Functions][] code: npm run dev ``` +Editor Development +------------------ + +The new Playground is build using [CodeMirror][] 6 and is integrated into the +surrounding UI using [petite-vue][]. + +* `playground/next/index.html` has the HTML and petite-vue markup-level code +* `playground/next/editor.mjs` contains the JS for setting up CodeMirror and + attaching it to the DOM via petite-vue + +The `npm run build:editor` command uses [Rollup][] to build the final JS bundle +at `playground/next/editor.bundle.js` which contains the browser ready JS code. + +Only the first two should be edited. However, `editor.bundle.js` should be built +locally and committed along with the rest of the site to avoid unnecessary build +time/cost/waste at deployment time. + Website Analytics ----------------- @@ -110,3 +127,6 @@ The default code can be overridden by setting `ga` to a specific number (ex: [Wrangler]: https://developers.cloudflare.com/workers/wrangler/ [list of users]: https://github.com/json-ld/json-ld.org/wiki/Users-of-JSON-LD [playground]: https://json-ld.org/playground/ +[CodeMirror]: https://codemirror.net/ +[petite-vue]: https://github.com/vuejs/petite-vue +[Rollup]: https://rollupjs.org/ diff --git a/package.json b/package.json index a1e31927a..a6f743ec4 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "type": "module", "scripts": { "build": "eleventy", + "build:editor": "rollup -c && cp playground/next/editor.bundle.js _site/playground/next/editor.bundle.js", "pages": "wrangler pages dev _site/ --compatibility-date=2025-04-02", "watch": "eleventy --watch", "dev": "wrangler pages dev _site --compatibility-date=2024-04-27 --live-reload --port 8788", @@ -12,6 +13,15 @@ }, "devDependencies": { "@11ty/eleventy": "^3.0.0", + "@codemirror/commands": "^6.8.1", + "@codemirror/lang-javascript": "^6.2.3", + "@codemirror/lang-json": "^6.0.1", + "@codemirror/lint": "^6.8.5", + "@codemirror/view": "^6.36.7", + "@rollup/plugin-node-resolve": "^16.0.1", + "codemirror": "^6.0.1", + "petite-vue": "^0.4.1", + "rollup": "^4.40.1", "wrangler": "^4.6.0" } } diff --git a/playground/next/editor.bundle.js b/playground/next/editor.bundle.js new file mode 100644 index 000000000..8dacfc36f --- /dev/null +++ b/playground/next/editor.bundle.js @@ -0,0 +1,26629 @@ +(function () { + 'use strict'; + + // These are filled with ranges (rangeFrom[i] up to but not including + // rangeTo[i]) of code points that count as extending characters. + let rangeFrom = [], rangeTo = [] + + ;(() => { + // Compressed representation of the Grapheme_Cluster_Break=Extend + // information from + // http://www.unicode.org/Public/16.0.0/ucd/auxiliary/GraphemeBreakProperty.txt. + // Each pair of elements represents a range, as an offet from the + // previous range and a length. Numbers are in base-36, with the empty + // string being a shorthand for 1. + let numbers = "lc,34,7n,7,7b,19,,,,2,,2,,,20,b,1c,l,g,,2t,7,2,6,2,2,,4,z,,u,r,2j,b,1m,9,9,,o,4,,9,,3,,5,17,3,3b,f,,w,1j,,,,4,8,4,,3,7,a,2,t,,1m,,,,2,4,8,,9,,a,2,q,,2,2,1l,,4,2,4,2,2,3,3,,u,2,3,,b,2,1l,,4,5,,2,4,,k,2,m,6,,,1m,,,2,,4,8,,7,3,a,2,u,,1n,,,,c,,9,,14,,3,,1l,3,5,3,,4,7,2,b,2,t,,1m,,2,,2,,3,,5,2,7,2,b,2,s,2,1l,2,,,2,4,8,,9,,a,2,t,,20,,4,,2,3,,,8,,29,,2,7,c,8,2q,,2,9,b,6,22,2,r,,,,,,1j,e,,5,,2,5,b,,10,9,,2u,4,,6,,2,2,2,p,2,4,3,g,4,d,,2,2,6,,f,,jj,3,qa,3,t,3,t,2,u,2,1s,2,,7,8,,2,b,9,,19,3,3b,2,y,,3a,3,4,2,9,,6,3,63,2,2,,1m,,,7,,,,,2,8,6,a,2,,1c,h,1r,4,1c,7,,,5,,14,9,c,2,w,4,2,2,,3,1k,,,2,3,,,3,1m,8,2,2,48,3,,d,,7,4,,6,,3,2,5i,1m,,5,ek,,5f,x,2da,3,3x,,2o,w,fe,6,2x,2,n9w,4,,a,w,2,28,2,7k,,3,,4,,p,2,5,,47,2,q,i,d,,12,8,p,b,1a,3,1c,,2,4,2,2,13,,1v,6,2,2,2,2,c,,8,,1b,,1f,,,3,2,2,5,2,,,16,2,8,,6m,,2,,4,,fn4,,kh,g,g,g,a6,2,gt,,6a,,45,5,1ae,3,,2,5,4,14,3,4,,4l,2,fx,4,ar,2,49,b,4w,,1i,f,1k,3,1d,4,2,2,1x,3,10,5,,8,1q,,c,2,1g,9,a,4,2,,2n,3,2,,,2,6,,4g,,3,8,l,2,1l,2,,,,,m,,e,7,3,5,5f,8,2,3,,,n,,29,,2,6,,,2,,,2,,2,6j,,2,4,6,2,,2,r,2,2d,8,2,,,2,2y,,,,2,6,,,2t,3,2,4,,5,77,9,,2,6t,,a,2,,,4,,40,4,2,2,4,,w,a,14,6,2,4,8,,9,6,2,3,1a,d,,2,ba,7,,6,,,2a,m,2,7,,2,,2,3e,6,3,,,2,,7,,,20,2,3,,,,9n,2,f0b,5,1n,7,t4,,1r,4,29,,f5k,2,43q,,,3,4,5,8,8,2,7,u,4,44,3,1iz,1j,4,1e,8,,e,,m,5,,f,11s,7,,h,2,7,,2,,5,79,7,c5,4,15s,7,31,7,240,5,gx7k,2o,3k,6o".split(",").map(s => s ? parseInt(s, 36) : 1); + for (let i = 0, n = 0; i < numbers.length; i++) + (i % 2 ? rangeTo : rangeFrom).push(n = n + numbers[i]); + })(); + + function isExtendingChar(code) { + if (code < 768) return false + for (let from = 0, to = rangeFrom.length;;) { + let mid = (from + to) >> 1; + if (code < rangeFrom[mid]) to = mid; + else if (code >= rangeTo[mid]) from = mid + 1; + else return true + if (from == to) return false + } + } + + function isRegionalIndicator(code) { + return code >= 0x1F1E6 && code <= 0x1F1FF + } + + const ZWJ = 0x200d; + + function findClusterBreak$1(str, pos, forward = true, includeExtending = true) { + return (forward ? nextClusterBreak : prevClusterBreak)(str, pos, includeExtending) + } + + function nextClusterBreak(str, pos, includeExtending) { + if (pos == str.length) return pos + // If pos is in the middle of a surrogate pair, move to its start + if (pos && surrogateLow$1(str.charCodeAt(pos)) && surrogateHigh$1(str.charCodeAt(pos - 1))) pos--; + let prev = codePointAt$1(str, pos); + pos += codePointSize$1(prev); + while (pos < str.length) { + let next = codePointAt$1(str, pos); + if (prev == ZWJ || next == ZWJ || includeExtending && isExtendingChar(next)) { + pos += codePointSize$1(next); + prev = next; + } else if (isRegionalIndicator(next)) { + let countBefore = 0, i = pos - 2; + while (i >= 0 && isRegionalIndicator(codePointAt$1(str, i))) { countBefore++; i -= 2; } + if (countBefore % 2 == 0) break + else pos += 2; + } else { + break + } + } + return pos + } + + function prevClusterBreak(str, pos, includeExtending) { + while (pos > 0) { + let found = nextClusterBreak(str, pos - 2, includeExtending); + if (found < pos) return found + pos--; + } + return 0 + } + + function codePointAt$1(str, pos) { + let code0 = str.charCodeAt(pos); + if (!surrogateHigh$1(code0) || pos + 1 == str.length) return code0 + let code1 = str.charCodeAt(pos + 1); + if (!surrogateLow$1(code1)) return code0 + return ((code0 - 0xd800) << 10) + (code1 - 0xdc00) + 0x10000 + } + + function surrogateLow$1(ch) { return ch >= 0xDC00 && ch < 0xE000 } + function surrogateHigh$1(ch) { return ch >= 0xD800 && ch < 0xDC00 } + function codePointSize$1(code) { return code < 0x10000 ? 1 : 2 } + + /** + The data structure for documents. @nonabstract + */ + let Text$1 = class Text { + /** + Get the line description around the given position. + */ + lineAt(pos) { + if (pos < 0 || pos > this.length) + throw new RangeError(`Invalid position ${pos} in document of length ${this.length}`); + return this.lineInner(pos, false, 1, 0); + } + /** + Get the description for the given (1-based) line number. + */ + line(n) { + if (n < 1 || n > this.lines) + throw new RangeError(`Invalid line number ${n} in ${this.lines}-line document`); + return this.lineInner(n, true, 1, 0); + } + /** + Replace a range of the text with the given content. + */ + replace(from, to, text) { + [from, to] = clip(this, from, to); + let parts = []; + this.decompose(0, from, parts, 2 /* Open.To */); + if (text.length) + text.decompose(0, text.length, parts, 1 /* Open.From */ | 2 /* Open.To */); + this.decompose(to, this.length, parts, 1 /* Open.From */); + return TextNode.from(parts, this.length - (to - from) + text.length); + } + /** + Append another document to this one. + */ + append(other) { + return this.replace(this.length, this.length, other); + } + /** + Retrieve the text between the given points. + */ + slice(from, to = this.length) { + [from, to] = clip(this, from, to); + let parts = []; + this.decompose(from, to, parts, 0); + return TextNode.from(parts, to - from); + } + /** + Test whether this text is equal to another instance. + */ + eq(other) { + if (other == this) + return true; + if (other.length != this.length || other.lines != this.lines) + return false; + let start = this.scanIdentical(other, 1), end = this.length - this.scanIdentical(other, -1); + let a = new RawTextCursor(this), b = new RawTextCursor(other); + for (let skip = start, pos = start;;) { + a.next(skip); + b.next(skip); + skip = 0; + if (a.lineBreak != b.lineBreak || a.done != b.done || a.value != b.value) + return false; + pos += a.value.length; + if (a.done || pos >= end) + return true; + } + } + /** + Iterate over the text. When `dir` is `-1`, iteration happens + from end to start. This will return lines and the breaks between + them as separate strings. + */ + iter(dir = 1) { return new RawTextCursor(this, dir); } + /** + Iterate over a range of the text. When `from` > `to`, the + iterator will run in reverse. + */ + iterRange(from, to = this.length) { return new PartialTextCursor(this, from, to); } + /** + Return a cursor that iterates over the given range of lines, + _without_ returning the line breaks between, and yielding empty + strings for empty lines. + + When `from` and `to` are given, they should be 1-based line numbers. + */ + iterLines(from, to) { + let inner; + if (from == null) { + inner = this.iter(); + } + else { + if (to == null) + to = this.lines + 1; + let start = this.line(from).from; + inner = this.iterRange(start, Math.max(start, to == this.lines + 1 ? this.length : to <= 1 ? 0 : this.line(to - 1).to)); + } + return new LineCursor(inner); + } + /** + Return the document as a string, using newline characters to + separate lines. + */ + toString() { return this.sliceString(0); } + /** + Convert the document to an array of lines (which can be + deserialized again via [`Text.of`](https://codemirror.net/6/docs/ref/#state.Text^of)). + */ + toJSON() { + let lines = []; + this.flatten(lines); + return lines; + } + /** + @internal + */ + constructor() { } + /** + Create a `Text` instance for the given array of lines. + */ + static of(text) { + if (text.length == 0) + throw new RangeError("A document must have at least one line"); + if (text.length == 1 && !text[0]) + return Text.empty; + return text.length <= 32 /* Tree.Branch */ ? new TextLeaf(text) : TextNode.from(TextLeaf.split(text, [])); + } + }; + // Leaves store an array of line strings. There are always line breaks + // between these strings. Leaves are limited in size and have to be + // contained in TextNode instances for bigger documents. + class TextLeaf extends Text$1 { + constructor(text, length = textLength(text)) { + super(); + this.text = text; + this.length = length; + } + get lines() { return this.text.length; } + get children() { return null; } + lineInner(target, isLine, line, offset) { + for (let i = 0;; i++) { + let string = this.text[i], end = offset + string.length; + if ((isLine ? line : end) >= target) + return new Line(offset, end, line, string); + offset = end + 1; + line++; + } + } + decompose(from, to, target, open) { + let text = from <= 0 && to >= this.length ? this + : new TextLeaf(sliceText(this.text, from, to), Math.min(to, this.length) - Math.max(0, from)); + if (open & 1 /* Open.From */) { + let prev = target.pop(); + let joined = appendText(text.text, prev.text.slice(), 0, text.length); + if (joined.length <= 32 /* Tree.Branch */) { + target.push(new TextLeaf(joined, prev.length + text.length)); + } + else { + let mid = joined.length >> 1; + target.push(new TextLeaf(joined.slice(0, mid)), new TextLeaf(joined.slice(mid))); + } + } + else { + target.push(text); + } + } + replace(from, to, text) { + if (!(text instanceof TextLeaf)) + return super.replace(from, to, text); + [from, to] = clip(this, from, to); + let lines = appendText(this.text, appendText(text.text, sliceText(this.text, 0, from)), to); + let newLen = this.length + text.length - (to - from); + if (lines.length <= 32 /* Tree.Branch */) + return new TextLeaf(lines, newLen); + return TextNode.from(TextLeaf.split(lines, []), newLen); + } + sliceString(from, to = this.length, lineSep = "\n") { + [from, to] = clip(this, from, to); + let result = ""; + for (let pos = 0, i = 0; pos <= to && i < this.text.length; i++) { + let line = this.text[i], end = pos + line.length; + if (pos > from && i) + result += lineSep; + if (from < end && to > pos) + result += line.slice(Math.max(0, from - pos), to - pos); + pos = end + 1; + } + return result; + } + flatten(target) { + for (let line of this.text) + target.push(line); + } + scanIdentical() { return 0; } + static split(text, target) { + let part = [], len = -1; + for (let line of text) { + part.push(line); + len += line.length + 1; + if (part.length == 32 /* Tree.Branch */) { + target.push(new TextLeaf(part, len)); + part = []; + len = -1; + } + } + if (len > -1) + target.push(new TextLeaf(part, len)); + return target; + } + } + // Nodes provide the tree structure of the `Text` type. They store a + // number of other nodes or leaves, taking care to balance themselves + // on changes. There are implied line breaks _between_ the children of + // a node (but not before the first or after the last child). + class TextNode extends Text$1 { + constructor(children, length) { + super(); + this.children = children; + this.length = length; + this.lines = 0; + for (let child of children) + this.lines += child.lines; + } + lineInner(target, isLine, line, offset) { + for (let i = 0;; i++) { + let child = this.children[i], end = offset + child.length, endLine = line + child.lines - 1; + if ((isLine ? endLine : end) >= target) + return child.lineInner(target, isLine, line, offset); + offset = end + 1; + line = endLine + 1; + } + } + decompose(from, to, target, open) { + for (let i = 0, pos = 0; pos <= to && i < this.children.length; i++) { + let child = this.children[i], end = pos + child.length; + if (from <= end && to >= pos) { + let childOpen = open & ((pos <= from ? 1 /* Open.From */ : 0) | (end >= to ? 2 /* Open.To */ : 0)); + if (pos >= from && end <= to && !childOpen) + target.push(child); + else + child.decompose(from - pos, to - pos, target, childOpen); + } + pos = end + 1; + } + } + replace(from, to, text) { + [from, to] = clip(this, from, to); + if (text.lines < this.lines) + for (let i = 0, pos = 0; i < this.children.length; i++) { + let child = this.children[i], end = pos + child.length; + // Fast path: if the change only affects one child and the + // child's size remains in the acceptable range, only update + // that child + if (from >= pos && to <= end) { + let updated = child.replace(from - pos, to - pos, text); + let totalLines = this.lines - child.lines + updated.lines; + if (updated.lines < (totalLines >> (5 /* Tree.BranchShift */ - 1)) && + updated.lines > (totalLines >> (5 /* Tree.BranchShift */ + 1))) { + let copy = this.children.slice(); + copy[i] = updated; + return new TextNode(copy, this.length - (to - from) + text.length); + } + return super.replace(pos, end, updated); + } + pos = end + 1; + } + return super.replace(from, to, text); + } + sliceString(from, to = this.length, lineSep = "\n") { + [from, to] = clip(this, from, to); + let result = ""; + for (let i = 0, pos = 0; i < this.children.length && pos <= to; i++) { + let child = this.children[i], end = pos + child.length; + if (pos > from && i) + result += lineSep; + if (from < end && to > pos) + result += child.sliceString(from - pos, to - pos, lineSep); + pos = end + 1; + } + return result; + } + flatten(target) { + for (let child of this.children) + child.flatten(target); + } + scanIdentical(other, dir) { + if (!(other instanceof TextNode)) + return 0; + let length = 0; + let [iA, iB, eA, eB] = dir > 0 ? [0, 0, this.children.length, other.children.length] + : [this.children.length - 1, other.children.length - 1, -1, -1]; + for (;; iA += dir, iB += dir) { + if (iA == eA || iB == eB) + return length; + let chA = this.children[iA], chB = other.children[iB]; + if (chA != chB) + return length + chA.scanIdentical(chB, dir); + length += chA.length + 1; + } + } + static from(children, length = children.reduce((l, ch) => l + ch.length + 1, -1)) { + let lines = 0; + for (let ch of children) + lines += ch.lines; + if (lines < 32 /* Tree.Branch */) { + let flat = []; + for (let ch of children) + ch.flatten(flat); + return new TextLeaf(flat, length); + } + let chunk = Math.max(32 /* Tree.Branch */, lines >> 5 /* Tree.BranchShift */), maxChunk = chunk << 1, minChunk = chunk >> 1; + let chunked = [], currentLines = 0, currentLen = -1, currentChunk = []; + function add(child) { + let last; + if (child.lines > maxChunk && child instanceof TextNode) { + for (let node of child.children) + add(node); + } + else if (child.lines > minChunk && (currentLines > minChunk || !currentLines)) { + flush(); + chunked.push(child); + } + else if (child instanceof TextLeaf && currentLines && + (last = currentChunk[currentChunk.length - 1]) instanceof TextLeaf && + child.lines + last.lines <= 32 /* Tree.Branch */) { + currentLines += child.lines; + currentLen += child.length + 1; + currentChunk[currentChunk.length - 1] = new TextLeaf(last.text.concat(child.text), last.length + 1 + child.length); + } + else { + if (currentLines + child.lines > chunk) + flush(); + currentLines += child.lines; + currentLen += child.length + 1; + currentChunk.push(child); + } + } + function flush() { + if (currentLines == 0) + return; + chunked.push(currentChunk.length == 1 ? currentChunk[0] : TextNode.from(currentChunk, currentLen)); + currentLen = -1; + currentLines = currentChunk.length = 0; + } + for (let child of children) + add(child); + flush(); + return chunked.length == 1 ? chunked[0] : new TextNode(chunked, length); + } + } + Text$1.empty = /*@__PURE__*/new TextLeaf([""], 0); + function textLength(text) { + let length = -1; + for (let line of text) + length += line.length + 1; + return length; + } + function appendText(text, target, from = 0, to = 1e9) { + for (let pos = 0, i = 0, first = true; i < text.length && pos <= to; i++) { + let line = text[i], end = pos + line.length; + if (end >= from) { + if (end > to) + line = line.slice(0, to - pos); + if (pos < from) + line = line.slice(from - pos); + if (first) { + target[target.length - 1] += line; + first = false; + } + else + target.push(line); + } + pos = end + 1; + } + return target; + } + function sliceText(text, from, to) { + return appendText(text, [""], from, to); + } + class RawTextCursor { + constructor(text, dir = 1) { + this.dir = dir; + this.done = false; + this.lineBreak = false; + this.value = ""; + this.nodes = [text]; + this.offsets = [dir > 0 ? 1 : (text instanceof TextLeaf ? text.text.length : text.children.length) << 1]; + } + nextInner(skip, dir) { + this.done = this.lineBreak = false; + for (;;) { + let last = this.nodes.length - 1; + let top = this.nodes[last], offsetValue = this.offsets[last], offset = offsetValue >> 1; + let size = top instanceof TextLeaf ? top.text.length : top.children.length; + if (offset == (dir > 0 ? size : 0)) { + if (last == 0) { + this.done = true; + this.value = ""; + return this; + } + if (dir > 0) + this.offsets[last - 1]++; + this.nodes.pop(); + this.offsets.pop(); + } + else if ((offsetValue & 1) == (dir > 0 ? 0 : 1)) { + this.offsets[last] += dir; + if (skip == 0) { + this.lineBreak = true; + this.value = "\n"; + return this; + } + skip--; + } + else if (top instanceof TextLeaf) { + // Move to the next string + let next = top.text[offset + (dir < 0 ? -1 : 0)]; + this.offsets[last] += dir; + if (next.length > Math.max(0, skip)) { + this.value = skip == 0 ? next : dir > 0 ? next.slice(skip) : next.slice(0, next.length - skip); + return this; + } + skip -= next.length; + } + else { + let next = top.children[offset + (dir < 0 ? -1 : 0)]; + if (skip > next.length) { + skip -= next.length; + this.offsets[last] += dir; + } + else { + if (dir < 0) + this.offsets[last]--; + this.nodes.push(next); + this.offsets.push(dir > 0 ? 1 : (next instanceof TextLeaf ? next.text.length : next.children.length) << 1); + } + } + } + } + next(skip = 0) { + if (skip < 0) { + this.nextInner(-skip, (-this.dir)); + skip = this.value.length; + } + return this.nextInner(skip, this.dir); + } + } + class PartialTextCursor { + constructor(text, start, end) { + this.value = ""; + this.done = false; + this.cursor = new RawTextCursor(text, start > end ? -1 : 1); + this.pos = start > end ? text.length : 0; + this.from = Math.min(start, end); + this.to = Math.max(start, end); + } + nextInner(skip, dir) { + if (dir < 0 ? this.pos <= this.from : this.pos >= this.to) { + this.value = ""; + this.done = true; + return this; + } + skip += Math.max(0, dir < 0 ? this.pos - this.to : this.from - this.pos); + let limit = dir < 0 ? this.pos - this.from : this.to - this.pos; + if (skip > limit) + skip = limit; + limit -= skip; + let { value } = this.cursor.next(skip); + this.pos += (value.length + skip) * dir; + this.value = value.length <= limit ? value : dir < 0 ? value.slice(value.length - limit) : value.slice(0, limit); + this.done = !this.value; + return this; + } + next(skip = 0) { + if (skip < 0) + skip = Math.max(skip, this.from - this.pos); + else if (skip > 0) + skip = Math.min(skip, this.to - this.pos); + return this.nextInner(skip, this.cursor.dir); + } + get lineBreak() { return this.cursor.lineBreak && this.value != ""; } + } + class LineCursor { + constructor(inner) { + this.inner = inner; + this.afterBreak = true; + this.value = ""; + this.done = false; + } + next(skip = 0) { + let { done, lineBreak, value } = this.inner.next(skip); + if (done && this.afterBreak) { + this.value = ""; + this.afterBreak = false; + } + else if (done) { + this.done = true; + this.value = ""; + } + else if (lineBreak) { + if (this.afterBreak) { + this.value = ""; + } + else { + this.afterBreak = true; + this.next(); + } + } + else { + this.value = value; + this.afterBreak = false; + } + return this; + } + get lineBreak() { return false; } + } + if (typeof Symbol != "undefined") { + Text$1.prototype[Symbol.iterator] = function () { return this.iter(); }; + RawTextCursor.prototype[Symbol.iterator] = PartialTextCursor.prototype[Symbol.iterator] = + LineCursor.prototype[Symbol.iterator] = function () { return this; }; + } + /** + This type describes a line in the document. It is created + on-demand when lines are [queried](https://codemirror.net/6/docs/ref/#state.Text.lineAt). + */ + class Line { + /** + @internal + */ + constructor( + /** + The position of the start of the line. + */ + from, + /** + The position at the end of the line (_before_ the line break, + or at the end of document for the last line). + */ + to, + /** + This line's line number (1-based). + */ + number, + /** + The line's content. + */ + text) { + this.from = from; + this.to = to; + this.number = number; + this.text = text; + } + /** + The length of the line (not including any line break after it). + */ + get length() { return this.to - this.from; } + } + function clip(text, from, to) { + from = Math.max(0, Math.min(text.length, from)); + return [from, Math.max(from, Math.min(text.length, to))]; + } + + /** + Returns a next grapheme cluster break _after_ (not equal to) + `pos`, if `forward` is true, or before otherwise. Returns `pos` + itself if no further cluster break is available in the string. + Moves across surrogate pairs, extending characters (when + `includeExtending` is true), characters joined with zero-width + joiners, and flag emoji. + */ + function findClusterBreak(str, pos, forward = true, includeExtending = true) { + return findClusterBreak$1(str, pos, forward, includeExtending); + } + function surrogateLow(ch) { return ch >= 0xDC00 && ch < 0xE000; } + function surrogateHigh(ch) { return ch >= 0xD800 && ch < 0xDC00; } + /** + Find the code point at the given position in a string (like the + [`codePointAt`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/codePointAt) + string method). + */ + function codePointAt(str, pos) { + let code0 = str.charCodeAt(pos); + if (!surrogateHigh(code0) || pos + 1 == str.length) + return code0; + let code1 = str.charCodeAt(pos + 1); + if (!surrogateLow(code1)) + return code0; + return ((code0 - 0xd800) << 10) + (code1 - 0xdc00) + 0x10000; + } + /** + Given a Unicode codepoint, return the JavaScript string that + respresents it (like + [`String.fromCodePoint`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/fromCodePoint)). + */ + function fromCodePoint(code) { + if (code <= 0xffff) + return String.fromCharCode(code); + code -= 0x10000; + return String.fromCharCode((code >> 10) + 0xd800, (code & 1023) + 0xdc00); + } + /** + The amount of positions a character takes up in a JavaScript string. + */ + function codePointSize(code) { return code < 0x10000 ? 1 : 2; } + + const DefaultSplit = /\r\n?|\n/; + /** + Distinguishes different ways in which positions can be mapped. + */ + var MapMode = /*@__PURE__*/(function (MapMode) { + /** + Map a position to a valid new position, even when its context + was deleted. + */ + MapMode[MapMode["Simple"] = 0] = "Simple"; + /** + Return null if deletion happens across the position. + */ + MapMode[MapMode["TrackDel"] = 1] = "TrackDel"; + /** + Return null if the character _before_ the position is deleted. + */ + MapMode[MapMode["TrackBefore"] = 2] = "TrackBefore"; + /** + Return null if the character _after_ the position is deleted. + */ + MapMode[MapMode["TrackAfter"] = 3] = "TrackAfter"; + return MapMode})(MapMode || (MapMode = {})); + /** + A change description is a variant of [change set](https://codemirror.net/6/docs/ref/#state.ChangeSet) + that doesn't store the inserted text. As such, it can't be + applied, but is cheaper to store and manipulate. + */ + class ChangeDesc { + // Sections are encoded as pairs of integers. The first is the + // length in the current document, and the second is -1 for + // unaffected sections, and the length of the replacement content + // otherwise. So an insertion would be (0, n>0), a deletion (n>0, + // 0), and a replacement two positive numbers. + /** + @internal + */ + constructor( + /** + @internal + */ + sections) { + this.sections = sections; + } + /** + The length of the document before the change. + */ + get length() { + let result = 0; + for (let i = 0; i < this.sections.length; i += 2) + result += this.sections[i]; + return result; + } + /** + The length of the document after the change. + */ + get newLength() { + let result = 0; + for (let i = 0; i < this.sections.length; i += 2) { + let ins = this.sections[i + 1]; + result += ins < 0 ? this.sections[i] : ins; + } + return result; + } + /** + False when there are actual changes in this set. + */ + get empty() { return this.sections.length == 0 || this.sections.length == 2 && this.sections[1] < 0; } + /** + Iterate over the unchanged parts left by these changes. `posA` + provides the position of the range in the old document, `posB` + the new position in the changed document. + */ + iterGaps(f) { + for (let i = 0, posA = 0, posB = 0; i < this.sections.length;) { + let len = this.sections[i++], ins = this.sections[i++]; + if (ins < 0) { + f(posA, posB, len); + posB += len; + } + else { + posB += ins; + } + posA += len; + } + } + /** + Iterate over the ranges changed by these changes. (See + [`ChangeSet.iterChanges`](https://codemirror.net/6/docs/ref/#state.ChangeSet.iterChanges) for a + variant that also provides you with the inserted text.) + `fromA`/`toA` provides the extent of the change in the starting + document, `fromB`/`toB` the extent of the replacement in the + changed document. + + When `individual` is true, adjacent changes (which are kept + separate for [position mapping](https://codemirror.net/6/docs/ref/#state.ChangeDesc.mapPos)) are + reported separately. + */ + iterChangedRanges(f, individual = false) { + iterChanges(this, f, individual); + } + /** + Get a description of the inverted form of these changes. + */ + get invertedDesc() { + let sections = []; + for (let i = 0; i < this.sections.length;) { + let len = this.sections[i++], ins = this.sections[i++]; + if (ins < 0) + sections.push(len, ins); + else + sections.push(ins, len); + } + return new ChangeDesc(sections); + } + /** + Compute the combined effect of applying another set of changes + after this one. The length of the document after this set should + match the length before `other`. + */ + composeDesc(other) { return this.empty ? other : other.empty ? this : composeSets(this, other); } + /** + Map this description, which should start with the same document + as `other`, over another set of changes, so that it can be + applied after it. When `before` is true, map as if the changes + in `this` happened before the ones in `other`. + */ + mapDesc(other, before = false) { return other.empty ? this : mapSet(this, other, before); } + mapPos(pos, assoc = -1, mode = MapMode.Simple) { + let posA = 0, posB = 0; + for (let i = 0; i < this.sections.length;) { + let len = this.sections[i++], ins = this.sections[i++], endA = posA + len; + if (ins < 0) { + if (endA > pos) + return posB + (pos - posA); + posB += len; + } + else { + if (mode != MapMode.Simple && endA >= pos && + (mode == MapMode.TrackDel && posA < pos && endA > pos || + mode == MapMode.TrackBefore && posA < pos || + mode == MapMode.TrackAfter && endA > pos)) + return null; + if (endA > pos || endA == pos && assoc < 0 && !len) + return pos == posA || assoc < 0 ? posB : posB + ins; + posB += ins; + } + posA = endA; + } + if (pos > posA) + throw new RangeError(`Position ${pos} is out of range for changeset of length ${posA}`); + return posB; + } + /** + Check whether these changes touch a given range. When one of the + changes entirely covers the range, the string `"cover"` is + returned. + */ + touchesRange(from, to = from) { + for (let i = 0, pos = 0; i < this.sections.length && pos <= to;) { + let len = this.sections[i++], ins = this.sections[i++], end = pos + len; + if (ins >= 0 && pos <= to && end >= from) + return pos < from && end > to ? "cover" : true; + pos = end; + } + return false; + } + /** + @internal + */ + toString() { + let result = ""; + for (let i = 0; i < this.sections.length;) { + let len = this.sections[i++], ins = this.sections[i++]; + result += (result ? " " : "") + len + (ins >= 0 ? ":" + ins : ""); + } + return result; + } + /** + Serialize this change desc to a JSON-representable value. + */ + toJSON() { return this.sections; } + /** + Create a change desc from its JSON representation (as produced + by [`toJSON`](https://codemirror.net/6/docs/ref/#state.ChangeDesc.toJSON). + */ + static fromJSON(json) { + if (!Array.isArray(json) || json.length % 2 || json.some(a => typeof a != "number")) + throw new RangeError("Invalid JSON representation of ChangeDesc"); + return new ChangeDesc(json); + } + /** + @internal + */ + static create(sections) { return new ChangeDesc(sections); } + } + /** + A change set represents a group of modifications to a document. It + stores the document length, and can only be applied to documents + with exactly that length. + */ + class ChangeSet extends ChangeDesc { + constructor(sections, + /** + @internal + */ + inserted) { + super(sections); + this.inserted = inserted; + } + /** + Apply the changes to a document, returning the modified + document. + */ + apply(doc) { + if (this.length != doc.length) + throw new RangeError("Applying change set to a document with the wrong length"); + iterChanges(this, (fromA, toA, fromB, _toB, text) => doc = doc.replace(fromB, fromB + (toA - fromA), text), false); + return doc; + } + mapDesc(other, before = false) { return mapSet(this, other, before, true); } + /** + Given the document as it existed _before_ the changes, return a + change set that represents the inverse of this set, which could + be used to go from the document created by the changes back to + the document as it existed before the changes. + */ + invert(doc) { + let sections = this.sections.slice(), inserted = []; + for (let i = 0, pos = 0; i < sections.length; i += 2) { + let len = sections[i], ins = sections[i + 1]; + if (ins >= 0) { + sections[i] = ins; + sections[i + 1] = len; + let index = i >> 1; + while (inserted.length < index) + inserted.push(Text$1.empty); + inserted.push(len ? doc.slice(pos, pos + len) : Text$1.empty); + } + pos += len; + } + return new ChangeSet(sections, inserted); + } + /** + Combine two subsequent change sets into a single set. `other` + must start in the document produced by `this`. If `this` goes + `docA` → `docB` and `other` represents `docB` → `docC`, the + returned value will represent the change `docA` → `docC`. + */ + compose(other) { return this.empty ? other : other.empty ? this : composeSets(this, other, true); } + /** + Given another change set starting in the same document, maps this + change set over the other, producing a new change set that can be + applied to the document produced by applying `other`. When + `before` is `true`, order changes as if `this` comes before + `other`, otherwise (the default) treat `other` as coming first. + + Given two changes `A` and `B`, `A.compose(B.map(A))` and + `B.compose(A.map(B, true))` will produce the same document. This + provides a basic form of [operational + transformation](https://en.wikipedia.org/wiki/Operational_transformation), + and can be used for collaborative editing. + */ + map(other, before = false) { return other.empty ? this : mapSet(this, other, before, true); } + /** + Iterate over the changed ranges in the document, calling `f` for + each, with the range in the original document (`fromA`-`toA`) + and the range that replaces it in the new document + (`fromB`-`toB`). + + When `individual` is true, adjacent changes are reported + separately. + */ + iterChanges(f, individual = false) { + iterChanges(this, f, individual); + } + /** + Get a [change description](https://codemirror.net/6/docs/ref/#state.ChangeDesc) for this change + set. + */ + get desc() { return ChangeDesc.create(this.sections); } + /** + @internal + */ + filter(ranges) { + let resultSections = [], resultInserted = [], filteredSections = []; + let iter = new SectionIter(this); + done: for (let i = 0, pos = 0;;) { + let next = i == ranges.length ? 1e9 : ranges[i++]; + while (pos < next || pos == next && iter.len == 0) { + if (iter.done) + break done; + let len = Math.min(iter.len, next - pos); + addSection(filteredSections, len, -1); + let ins = iter.ins == -1 ? -1 : iter.off == 0 ? iter.ins : 0; + addSection(resultSections, len, ins); + if (ins > 0) + addInsert(resultInserted, resultSections, iter.text); + iter.forward(len); + pos += len; + } + let end = ranges[i++]; + while (pos < end) { + if (iter.done) + break done; + let len = Math.min(iter.len, end - pos); + addSection(resultSections, len, -1); + addSection(filteredSections, len, iter.ins == -1 ? -1 : iter.off == 0 ? iter.ins : 0); + iter.forward(len); + pos += len; + } + } + return { changes: new ChangeSet(resultSections, resultInserted), + filtered: ChangeDesc.create(filteredSections) }; + } + /** + Serialize this change set to a JSON-representable value. + */ + toJSON() { + let parts = []; + for (let i = 0; i < this.sections.length; i += 2) { + let len = this.sections[i], ins = this.sections[i + 1]; + if (ins < 0) + parts.push(len); + else if (ins == 0) + parts.push([len]); + else + parts.push([len].concat(this.inserted[i >> 1].toJSON())); + } + return parts; + } + /** + Create a change set for the given changes, for a document of the + given length, using `lineSep` as line separator. + */ + static of(changes, length, lineSep) { + let sections = [], inserted = [], pos = 0; + let total = null; + function flush(force = false) { + if (!force && !sections.length) + return; + if (pos < length) + addSection(sections, length - pos, -1); + let set = new ChangeSet(sections, inserted); + total = total ? total.compose(set.map(total)) : set; + sections = []; + inserted = []; + pos = 0; + } + function process(spec) { + if (Array.isArray(spec)) { + for (let sub of spec) + process(sub); + } + else if (spec instanceof ChangeSet) { + if (spec.length != length) + throw new RangeError(`Mismatched change set length (got ${spec.length}, expected ${length})`); + flush(); + total = total ? total.compose(spec.map(total)) : spec; + } + else { + let { from, to = from, insert } = spec; + if (from > to || from < 0 || to > length) + throw new RangeError(`Invalid change range ${from} to ${to} (in doc of length ${length})`); + let insText = !insert ? Text$1.empty : typeof insert == "string" ? Text$1.of(insert.split(lineSep || DefaultSplit)) : insert; + let insLen = insText.length; + if (from == to && insLen == 0) + return; + if (from < pos) + flush(); + if (from > pos) + addSection(sections, from - pos, -1); + addSection(sections, to - from, insLen); + addInsert(inserted, sections, insText); + pos = to; + } + } + process(changes); + flush(!total); + return total; + } + /** + Create an empty changeset of the given length. + */ + static empty(length) { + return new ChangeSet(length ? [length, -1] : [], []); + } + /** + Create a changeset from its JSON representation (as produced by + [`toJSON`](https://codemirror.net/6/docs/ref/#state.ChangeSet.toJSON). + */ + static fromJSON(json) { + if (!Array.isArray(json)) + throw new RangeError("Invalid JSON representation of ChangeSet"); + let sections = [], inserted = []; + for (let i = 0; i < json.length; i++) { + let part = json[i]; + if (typeof part == "number") { + sections.push(part, -1); + } + else if (!Array.isArray(part) || typeof part[0] != "number" || part.some((e, i) => i && typeof e != "string")) { + throw new RangeError("Invalid JSON representation of ChangeSet"); + } + else if (part.length == 1) { + sections.push(part[0], 0); + } + else { + while (inserted.length < i) + inserted.push(Text$1.empty); + inserted[i] = Text$1.of(part.slice(1)); + sections.push(part[0], inserted[i].length); + } + } + return new ChangeSet(sections, inserted); + } + /** + @internal + */ + static createSet(sections, inserted) { + return new ChangeSet(sections, inserted); + } + } + function addSection(sections, len, ins, forceJoin = false) { + if (len == 0 && ins <= 0) + return; + let last = sections.length - 2; + if (last >= 0 && ins <= 0 && ins == sections[last + 1]) + sections[last] += len; + else if (last >= 0 && len == 0 && sections[last] == 0) + sections[last + 1] += ins; + else if (forceJoin) { + sections[last] += len; + sections[last + 1] += ins; + } + else + sections.push(len, ins); + } + function addInsert(values, sections, value) { + if (value.length == 0) + return; + let index = (sections.length - 2) >> 1; + if (index < values.length) { + values[values.length - 1] = values[values.length - 1].append(value); + } + else { + while (values.length < index) + values.push(Text$1.empty); + values.push(value); + } + } + function iterChanges(desc, f, individual) { + let inserted = desc.inserted; + for (let posA = 0, posB = 0, i = 0; i < desc.sections.length;) { + let len = desc.sections[i++], ins = desc.sections[i++]; + if (ins < 0) { + posA += len; + posB += len; + } + else { + let endA = posA, endB = posB, text = Text$1.empty; + for (;;) { + endA += len; + endB += ins; + if (ins && inserted) + text = text.append(inserted[(i - 2) >> 1]); + if (individual || i == desc.sections.length || desc.sections[i + 1] < 0) + break; + len = desc.sections[i++]; + ins = desc.sections[i++]; + } + f(posA, endA, posB, endB, text); + posA = endA; + posB = endB; + } + } + } + function mapSet(setA, setB, before, mkSet = false) { + // Produce a copy of setA that applies to the document after setB + // has been applied (assuming both start at the same document). + let sections = [], insert = mkSet ? [] : null; + let a = new SectionIter(setA), b = new SectionIter(setB); + // Iterate over both sets in parallel. inserted tracks, for changes + // in A that have to be processed piece-by-piece, whether their + // content has been inserted already, and refers to the section + // index. + for (let inserted = -1;;) { + if (a.done && b.len || b.done && a.len) { + throw new Error("Mismatched change set lengths"); + } + else if (a.ins == -1 && b.ins == -1) { + // Move across ranges skipped by both sets. + let len = Math.min(a.len, b.len); + addSection(sections, len, -1); + a.forward(len); + b.forward(len); + } + else if (b.ins >= 0 && (a.ins < 0 || inserted == a.i || a.off == 0 && (b.len < a.len || b.len == a.len && !before))) { + // If there's a change in B that comes before the next change in + // A (ordered by start pos, then len, then before flag), skip + // that (and process any changes in A it covers). + let len = b.len; + addSection(sections, b.ins, -1); + while (len) { + let piece = Math.min(a.len, len); + if (a.ins >= 0 && inserted < a.i && a.len <= piece) { + addSection(sections, 0, a.ins); + if (insert) + addInsert(insert, sections, a.text); + inserted = a.i; + } + a.forward(piece); + len -= piece; + } + b.next(); + } + else if (a.ins >= 0) { + // Process the part of a change in A up to the start of the next + // non-deletion change in B (if overlapping). + let len = 0, left = a.len; + while (left) { + if (b.ins == -1) { + let piece = Math.min(left, b.len); + len += piece; + left -= piece; + b.forward(piece); + } + else if (b.ins == 0 && b.len < left) { + left -= b.len; + b.next(); + } + else { + break; + } + } + addSection(sections, len, inserted < a.i ? a.ins : 0); + if (insert && inserted < a.i) + addInsert(insert, sections, a.text); + inserted = a.i; + a.forward(a.len - left); + } + else if (a.done && b.done) { + return insert ? ChangeSet.createSet(sections, insert) : ChangeDesc.create(sections); + } + else { + throw new Error("Mismatched change set lengths"); + } + } + } + function composeSets(setA, setB, mkSet = false) { + let sections = []; + let insert = mkSet ? [] : null; + let a = new SectionIter(setA), b = new SectionIter(setB); + for (let open = false;;) { + if (a.done && b.done) { + return insert ? ChangeSet.createSet(sections, insert) : ChangeDesc.create(sections); + } + else if (a.ins == 0) { // Deletion in A + addSection(sections, a.len, 0, open); + a.next(); + } + else if (b.len == 0 && !b.done) { // Insertion in B + addSection(sections, 0, b.ins, open); + if (insert) + addInsert(insert, sections, b.text); + b.next(); + } + else if (a.done || b.done) { + throw new Error("Mismatched change set lengths"); + } + else { + let len = Math.min(a.len2, b.len), sectionLen = sections.length; + if (a.ins == -1) { + let insB = b.ins == -1 ? -1 : b.off ? 0 : b.ins; + addSection(sections, len, insB, open); + if (insert && insB) + addInsert(insert, sections, b.text); + } + else if (b.ins == -1) { + addSection(sections, a.off ? 0 : a.len, len, open); + if (insert) + addInsert(insert, sections, a.textBit(len)); + } + else { + addSection(sections, a.off ? 0 : a.len, b.off ? 0 : b.ins, open); + if (insert && !b.off) + addInsert(insert, sections, b.text); + } + open = (a.ins > len || b.ins >= 0 && b.len > len) && (open || sections.length > sectionLen); + a.forward2(len); + b.forward(len); + } + } + } + class SectionIter { + constructor(set) { + this.set = set; + this.i = 0; + this.next(); + } + next() { + let { sections } = this.set; + if (this.i < sections.length) { + this.len = sections[this.i++]; + this.ins = sections[this.i++]; + } + else { + this.len = 0; + this.ins = -2; + } + this.off = 0; + } + get done() { return this.ins == -2; } + get len2() { return this.ins < 0 ? this.len : this.ins; } + get text() { + let { inserted } = this.set, index = (this.i - 2) >> 1; + return index >= inserted.length ? Text$1.empty : inserted[index]; + } + textBit(len) { + let { inserted } = this.set, index = (this.i - 2) >> 1; + return index >= inserted.length && !len ? Text$1.empty + : inserted[index].slice(this.off, len == null ? undefined : this.off + len); + } + forward(len) { + if (len == this.len) + this.next(); + else { + this.len -= len; + this.off += len; + } + } + forward2(len) { + if (this.ins == -1) + this.forward(len); + else if (len == this.ins) + this.next(); + else { + this.ins -= len; + this.off += len; + } + } + } + + /** + A single selection range. When + [`allowMultipleSelections`](https://codemirror.net/6/docs/ref/#state.EditorState^allowMultipleSelections) + is enabled, a [selection](https://codemirror.net/6/docs/ref/#state.EditorSelection) may hold + multiple ranges. By default, selections hold exactly one range. + */ + class SelectionRange { + constructor( + /** + The lower boundary of the range. + */ + from, + /** + The upper boundary of the range. + */ + to, flags) { + this.from = from; + this.to = to; + this.flags = flags; + } + /** + The anchor of the range—the side that doesn't move when you + extend it. + */ + get anchor() { return this.flags & 32 /* RangeFlag.Inverted */ ? this.to : this.from; } + /** + The head of the range, which is moved when the range is + [extended](https://codemirror.net/6/docs/ref/#state.SelectionRange.extend). + */ + get head() { return this.flags & 32 /* RangeFlag.Inverted */ ? this.from : this.to; } + /** + True when `anchor` and `head` are at the same position. + */ + get empty() { return this.from == this.to; } + /** + If this is a cursor that is explicitly associated with the + character on one of its sides, this returns the side. -1 means + the character before its position, 1 the character after, and 0 + means no association. + */ + get assoc() { return this.flags & 8 /* RangeFlag.AssocBefore */ ? -1 : this.flags & 16 /* RangeFlag.AssocAfter */ ? 1 : 0; } + /** + The bidirectional text level associated with this cursor, if + any. + */ + get bidiLevel() { + let level = this.flags & 7 /* RangeFlag.BidiLevelMask */; + return level == 7 ? null : level; + } + /** + The goal column (stored vertical offset) associated with a + cursor. This is used to preserve the vertical position when + [moving](https://codemirror.net/6/docs/ref/#view.EditorView.moveVertically) across + lines of different length. + */ + get goalColumn() { + let value = this.flags >> 6 /* RangeFlag.GoalColumnOffset */; + return value == 16777215 /* RangeFlag.NoGoalColumn */ ? undefined : value; + } + /** + Map this range through a change, producing a valid range in the + updated document. + */ + map(change, assoc = -1) { + let from, to; + if (this.empty) { + from = to = change.mapPos(this.from, assoc); + } + else { + from = change.mapPos(this.from, 1); + to = change.mapPos(this.to, -1); + } + return from == this.from && to == this.to ? this : new SelectionRange(from, to, this.flags); + } + /** + Extend this range to cover at least `from` to `to`. + */ + extend(from, to = from) { + if (from <= this.anchor && to >= this.anchor) + return EditorSelection.range(from, to); + let head = Math.abs(from - this.anchor) > Math.abs(to - this.anchor) ? from : to; + return EditorSelection.range(this.anchor, head); + } + /** + Compare this range to another range. + */ + eq(other, includeAssoc = false) { + return this.anchor == other.anchor && this.head == other.head && + (!includeAssoc || !this.empty || this.assoc == other.assoc); + } + /** + Return a JSON-serializable object representing the range. + */ + toJSON() { return { anchor: this.anchor, head: this.head }; } + /** + Convert a JSON representation of a range to a `SelectionRange` + instance. + */ + static fromJSON(json) { + if (!json || typeof json.anchor != "number" || typeof json.head != "number") + throw new RangeError("Invalid JSON representation for SelectionRange"); + return EditorSelection.range(json.anchor, json.head); + } + /** + @internal + */ + static create(from, to, flags) { + return new SelectionRange(from, to, flags); + } + } + /** + An editor selection holds one or more selection ranges. + */ + class EditorSelection { + constructor( + /** + The ranges in the selection, sorted by position. Ranges cannot + overlap (but they may touch, if they aren't empty). + */ + ranges, + /** + The index of the _main_ range in the selection (which is + usually the range that was added last). + */ + mainIndex) { + this.ranges = ranges; + this.mainIndex = mainIndex; + } + /** + Map a selection through a change. Used to adjust the selection + position for changes. + */ + map(change, assoc = -1) { + if (change.empty) + return this; + return EditorSelection.create(this.ranges.map(r => r.map(change, assoc)), this.mainIndex); + } + /** + Compare this selection to another selection. By default, ranges + are compared only by position. When `includeAssoc` is true, + cursor ranges must also have the same + [`assoc`](https://codemirror.net/6/docs/ref/#state.SelectionRange.assoc) value. + */ + eq(other, includeAssoc = false) { + if (this.ranges.length != other.ranges.length || + this.mainIndex != other.mainIndex) + return false; + for (let i = 0; i < this.ranges.length; i++) + if (!this.ranges[i].eq(other.ranges[i], includeAssoc)) + return false; + return true; + } + /** + Get the primary selection range. Usually, you should make sure + your code applies to _all_ ranges, by using methods like + [`changeByRange`](https://codemirror.net/6/docs/ref/#state.EditorState.changeByRange). + */ + get main() { return this.ranges[this.mainIndex]; } + /** + Make sure the selection only has one range. Returns a selection + holding only the main range from this selection. + */ + asSingle() { + return this.ranges.length == 1 ? this : new EditorSelection([this.main], 0); + } + /** + Extend this selection with an extra range. + */ + addRange(range, main = true) { + return EditorSelection.create([range].concat(this.ranges), main ? 0 : this.mainIndex + 1); + } + /** + Replace a given range with another range, and then normalize the + selection to merge and sort ranges if necessary. + */ + replaceRange(range, which = this.mainIndex) { + let ranges = this.ranges.slice(); + ranges[which] = range; + return EditorSelection.create(ranges, this.mainIndex); + } + /** + Convert this selection to an object that can be serialized to + JSON. + */ + toJSON() { + return { ranges: this.ranges.map(r => r.toJSON()), main: this.mainIndex }; + } + /** + Create a selection from a JSON representation. + */ + static fromJSON(json) { + if (!json || !Array.isArray(json.ranges) || typeof json.main != "number" || json.main >= json.ranges.length) + throw new RangeError("Invalid JSON representation for EditorSelection"); + return new EditorSelection(json.ranges.map((r) => SelectionRange.fromJSON(r)), json.main); + } + /** + Create a selection holding a single range. + */ + static single(anchor, head = anchor) { + return new EditorSelection([EditorSelection.range(anchor, head)], 0); + } + /** + Sort and merge the given set of ranges, creating a valid + selection. + */ + static create(ranges, mainIndex = 0) { + if (ranges.length == 0) + throw new RangeError("A selection needs at least one range"); + for (let pos = 0, i = 0; i < ranges.length; i++) { + let range = ranges[i]; + if (range.empty ? range.from <= pos : range.from < pos) + return EditorSelection.normalized(ranges.slice(), mainIndex); + pos = range.to; + } + return new EditorSelection(ranges, mainIndex); + } + /** + Create a cursor selection range at the given position. You can + safely ignore the optional arguments in most situations. + */ + static cursor(pos, assoc = 0, bidiLevel, goalColumn) { + return SelectionRange.create(pos, pos, (assoc == 0 ? 0 : assoc < 0 ? 8 /* RangeFlag.AssocBefore */ : 16 /* RangeFlag.AssocAfter */) | + (bidiLevel == null ? 7 : Math.min(6, bidiLevel)) | + ((goalColumn !== null && goalColumn !== void 0 ? goalColumn : 16777215 /* RangeFlag.NoGoalColumn */) << 6 /* RangeFlag.GoalColumnOffset */)); + } + /** + Create a selection range. + */ + static range(anchor, head, goalColumn, bidiLevel) { + let flags = ((goalColumn !== null && goalColumn !== void 0 ? goalColumn : 16777215 /* RangeFlag.NoGoalColumn */) << 6 /* RangeFlag.GoalColumnOffset */) | + (bidiLevel == null ? 7 : Math.min(6, bidiLevel)); + return head < anchor ? SelectionRange.create(head, anchor, 32 /* RangeFlag.Inverted */ | 16 /* RangeFlag.AssocAfter */ | flags) + : SelectionRange.create(anchor, head, (head > anchor ? 8 /* RangeFlag.AssocBefore */ : 0) | flags); + } + /** + @internal + */ + static normalized(ranges, mainIndex = 0) { + let main = ranges[mainIndex]; + ranges.sort((a, b) => a.from - b.from); + mainIndex = ranges.indexOf(main); + for (let i = 1; i < ranges.length; i++) { + let range = ranges[i], prev = ranges[i - 1]; + if (range.empty ? range.from <= prev.to : range.from < prev.to) { + let from = prev.from, to = Math.max(range.to, prev.to); + if (i <= mainIndex) + mainIndex--; + ranges.splice(--i, 2, range.anchor > range.head ? EditorSelection.range(to, from) : EditorSelection.range(from, to)); + } + } + return new EditorSelection(ranges, mainIndex); + } + } + function checkSelection(selection, docLength) { + for (let range of selection.ranges) + if (range.to > docLength) + throw new RangeError("Selection points outside of document"); + } + + let nextID = 0; + /** + A facet is a labeled value that is associated with an editor + state. It takes inputs from any number of extensions, and combines + those into a single output value. + + Examples of uses of facets are the [tab + size](https://codemirror.net/6/docs/ref/#state.EditorState^tabSize), [editor + attributes](https://codemirror.net/6/docs/ref/#view.EditorView^editorAttributes), and [update + listeners](https://codemirror.net/6/docs/ref/#view.EditorView^updateListener). + + Note that `Facet` instances can be used anywhere where + [`FacetReader`](https://codemirror.net/6/docs/ref/#state.FacetReader) is expected. + */ + class Facet { + constructor( + /** + @internal + */ + combine, + /** + @internal + */ + compareInput, + /** + @internal + */ + compare, isStatic, enables) { + this.combine = combine; + this.compareInput = compareInput; + this.compare = compare; + this.isStatic = isStatic; + /** + @internal + */ + this.id = nextID++; + this.default = combine([]); + this.extensions = typeof enables == "function" ? enables(this) : enables; + } + /** + Returns a facet reader for this facet, which can be used to + [read](https://codemirror.net/6/docs/ref/#state.EditorState.facet) it but not to define values for it. + */ + get reader() { return this; } + /** + Define a new facet. + */ + static define(config = {}) { + return new Facet(config.combine || ((a) => a), config.compareInput || ((a, b) => a === b), config.compare || (!config.combine ? sameArray$1 : (a, b) => a === b), !!config.static, config.enables); + } + /** + Returns an extension that adds the given value to this facet. + */ + of(value) { + return new FacetProvider([], this, 0 /* Provider.Static */, value); + } + /** + Create an extension that computes a value for the facet from a + state. You must take care to declare the parts of the state that + this value depends on, since your function is only called again + for a new state when one of those parts changed. + + In cases where your value depends only on a single field, you'll + want to use the [`from`](https://codemirror.net/6/docs/ref/#state.Facet.from) method instead. + */ + compute(deps, get) { + if (this.isStatic) + throw new Error("Can't compute a static facet"); + return new FacetProvider(deps, this, 1 /* Provider.Single */, get); + } + /** + Create an extension that computes zero or more values for this + facet from a state. + */ + computeN(deps, get) { + if (this.isStatic) + throw new Error("Can't compute a static facet"); + return new FacetProvider(deps, this, 2 /* Provider.Multi */, get); + } + from(field, get) { + if (!get) + get = x => x; + return this.compute([field], state => get(state.field(field))); + } + } + function sameArray$1(a, b) { + return a == b || a.length == b.length && a.every((e, i) => e === b[i]); + } + class FacetProvider { + constructor(dependencies, facet, type, value) { + this.dependencies = dependencies; + this.facet = facet; + this.type = type; + this.value = value; + this.id = nextID++; + } + dynamicSlot(addresses) { + var _a; + let getter = this.value; + let compare = this.facet.compareInput; + let id = this.id, idx = addresses[id] >> 1, multi = this.type == 2 /* Provider.Multi */; + let depDoc = false, depSel = false, depAddrs = []; + for (let dep of this.dependencies) { + if (dep == "doc") + depDoc = true; + else if (dep == "selection") + depSel = true; + else if ((((_a = addresses[dep.id]) !== null && _a !== void 0 ? _a : 1) & 1) == 0) + depAddrs.push(addresses[dep.id]); + } + return { + create(state) { + state.values[idx] = getter(state); + return 1 /* SlotStatus.Changed */; + }, + update(state, tr) { + if ((depDoc && tr.docChanged) || (depSel && (tr.docChanged || tr.selection)) || ensureAll(state, depAddrs)) { + let newVal = getter(state); + if (multi ? !compareArray(newVal, state.values[idx], compare) : !compare(newVal, state.values[idx])) { + state.values[idx] = newVal; + return 1 /* SlotStatus.Changed */; + } + } + return 0; + }, + reconfigure: (state, oldState) => { + let newVal, oldAddr = oldState.config.address[id]; + if (oldAddr != null) { + let oldVal = getAddr(oldState, oldAddr); + if (this.dependencies.every(dep => { + return dep instanceof Facet ? oldState.facet(dep) === state.facet(dep) : + dep instanceof StateField ? oldState.field(dep, false) == state.field(dep, false) : true; + }) || (multi ? compareArray(newVal = getter(state), oldVal, compare) : compare(newVal = getter(state), oldVal))) { + state.values[idx] = oldVal; + return 0; + } + } + else { + newVal = getter(state); + } + state.values[idx] = newVal; + return 1 /* SlotStatus.Changed */; + } + }; + } + } + function compareArray(a, b, compare) { + if (a.length != b.length) + return false; + for (let i = 0; i < a.length; i++) + if (!compare(a[i], b[i])) + return false; + return true; + } + function ensureAll(state, addrs) { + let changed = false; + for (let addr of addrs) + if (ensureAddr(state, addr) & 1 /* SlotStatus.Changed */) + changed = true; + return changed; + } + function dynamicFacetSlot(addresses, facet, providers) { + let providerAddrs = providers.map(p => addresses[p.id]); + let providerTypes = providers.map(p => p.type); + let dynamic = providerAddrs.filter(p => !(p & 1)); + let idx = addresses[facet.id] >> 1; + function get(state) { + let values = []; + for (let i = 0; i < providerAddrs.length; i++) { + let value = getAddr(state, providerAddrs[i]); + if (providerTypes[i] == 2 /* Provider.Multi */) + for (let val of value) + values.push(val); + else + values.push(value); + } + return facet.combine(values); + } + return { + create(state) { + for (let addr of providerAddrs) + ensureAddr(state, addr); + state.values[idx] = get(state); + return 1 /* SlotStatus.Changed */; + }, + update(state, tr) { + if (!ensureAll(state, dynamic)) + return 0; + let value = get(state); + if (facet.compare(value, state.values[idx])) + return 0; + state.values[idx] = value; + return 1 /* SlotStatus.Changed */; + }, + reconfigure(state, oldState) { + let depChanged = ensureAll(state, providerAddrs); + let oldProviders = oldState.config.facets[facet.id], oldValue = oldState.facet(facet); + if (oldProviders && !depChanged && sameArray$1(providers, oldProviders)) { + state.values[idx] = oldValue; + return 0; + } + let value = get(state); + if (facet.compare(value, oldValue)) { + state.values[idx] = oldValue; + return 0; + } + state.values[idx] = value; + return 1 /* SlotStatus.Changed */; + } + }; + } + const initField = /*@__PURE__*/Facet.define({ static: true }); + /** + Fields can store additional information in an editor state, and + keep it in sync with the rest of the state. + */ + class StateField { + constructor( + /** + @internal + */ + id, createF, updateF, compareF, + /** + @internal + */ + spec) { + this.id = id; + this.createF = createF; + this.updateF = updateF; + this.compareF = compareF; + this.spec = spec; + /** + @internal + */ + this.provides = undefined; + } + /** + Define a state field. + */ + static define(config) { + let field = new StateField(nextID++, config.create, config.update, config.compare || ((a, b) => a === b), config); + if (config.provide) + field.provides = config.provide(field); + return field; + } + create(state) { + let init = state.facet(initField).find(i => i.field == this); + return ((init === null || init === void 0 ? void 0 : init.create) || this.createF)(state); + } + /** + @internal + */ + slot(addresses) { + let idx = addresses[this.id] >> 1; + return { + create: (state) => { + state.values[idx] = this.create(state); + return 1 /* SlotStatus.Changed */; + }, + update: (state, tr) => { + let oldVal = state.values[idx]; + let value = this.updateF(oldVal, tr); + if (this.compareF(oldVal, value)) + return 0; + state.values[idx] = value; + return 1 /* SlotStatus.Changed */; + }, + reconfigure: (state, oldState) => { + let init = state.facet(initField), oldInit = oldState.facet(initField), reInit; + if ((reInit = init.find(i => i.field == this)) && reInit != oldInit.find(i => i.field == this)) { + state.values[idx] = reInit.create(state); + return 1 /* SlotStatus.Changed */; + } + if (oldState.config.address[this.id] != null) { + state.values[idx] = oldState.field(this); + return 0; + } + state.values[idx] = this.create(state); + return 1 /* SlotStatus.Changed */; + } + }; + } + /** + Returns an extension that enables this field and overrides the + way it is initialized. Can be useful when you need to provide a + non-default starting value for the field. + */ + init(create) { + return [this, initField.of({ field: this, create })]; + } + /** + State field instances can be used as + [`Extension`](https://codemirror.net/6/docs/ref/#state.Extension) values to enable the field in a + given state. + */ + get extension() { return this; } + } + const Prec_ = { lowest: 4, low: 3, default: 2, high: 1, highest: 0 }; + function prec(value) { + return (ext) => new PrecExtension(ext, value); + } + /** + By default extensions are registered in the order they are found + in the flattened form of nested array that was provided. + Individual extension values can be assigned a precedence to + override this. Extensions that do not have a precedence set get + the precedence of the nearest parent with a precedence, or + [`default`](https://codemirror.net/6/docs/ref/#state.Prec.default) if there is no such parent. The + final ordering of extensions is determined by first sorting by + precedence and then by order within each precedence. + */ + const Prec = { + /** + The highest precedence level, for extensions that should end up + near the start of the precedence ordering. + */ + highest: /*@__PURE__*/prec(Prec_.highest), + /** + A higher-than-default precedence, for extensions that should + come before those with default precedence. + */ + high: /*@__PURE__*/prec(Prec_.high), + /** + The default precedence, which is also used for extensions + without an explicit precedence. + */ + default: /*@__PURE__*/prec(Prec_.default), + /** + A lower-than-default precedence. + */ + low: /*@__PURE__*/prec(Prec_.low), + /** + The lowest precedence level. Meant for things that should end up + near the end of the extension order. + */ + lowest: /*@__PURE__*/prec(Prec_.lowest) + }; + class PrecExtension { + constructor(inner, prec) { + this.inner = inner; + this.prec = prec; + } + } + /** + Extension compartments can be used to make a configuration + dynamic. By [wrapping](https://codemirror.net/6/docs/ref/#state.Compartment.of) part of your + configuration in a compartment, you can later + [replace](https://codemirror.net/6/docs/ref/#state.Compartment.reconfigure) that part through a + transaction. + */ + class Compartment { + /** + Create an instance of this compartment to add to your [state + configuration](https://codemirror.net/6/docs/ref/#state.EditorStateConfig.extensions). + */ + of(ext) { return new CompartmentInstance(this, ext); } + /** + Create an [effect](https://codemirror.net/6/docs/ref/#state.TransactionSpec.effects) that + reconfigures this compartment. + */ + reconfigure(content) { + return Compartment.reconfigure.of({ compartment: this, extension: content }); + } + /** + Get the current content of the compartment in the state, or + `undefined` if it isn't present. + */ + get(state) { + return state.config.compartments.get(this); + } + } + class CompartmentInstance { + constructor(compartment, inner) { + this.compartment = compartment; + this.inner = inner; + } + } + class Configuration { + constructor(base, compartments, dynamicSlots, address, staticValues, facets) { + this.base = base; + this.compartments = compartments; + this.dynamicSlots = dynamicSlots; + this.address = address; + this.staticValues = staticValues; + this.facets = facets; + this.statusTemplate = []; + while (this.statusTemplate.length < dynamicSlots.length) + this.statusTemplate.push(0 /* SlotStatus.Unresolved */); + } + staticFacet(facet) { + let addr = this.address[facet.id]; + return addr == null ? facet.default : this.staticValues[addr >> 1]; + } + static resolve(base, compartments, oldState) { + let fields = []; + let facets = Object.create(null); + let newCompartments = new Map(); + for (let ext of flatten(base, compartments, newCompartments)) { + if (ext instanceof StateField) + fields.push(ext); + else + (facets[ext.facet.id] || (facets[ext.facet.id] = [])).push(ext); + } + let address = Object.create(null); + let staticValues = []; + let dynamicSlots = []; + for (let field of fields) { + address[field.id] = dynamicSlots.length << 1; + dynamicSlots.push(a => field.slot(a)); + } + let oldFacets = oldState === null || oldState === void 0 ? void 0 : oldState.config.facets; + for (let id in facets) { + let providers = facets[id], facet = providers[0].facet; + let oldProviders = oldFacets && oldFacets[id] || []; + if (providers.every(p => p.type == 0 /* Provider.Static */)) { + address[facet.id] = (staticValues.length << 1) | 1; + if (sameArray$1(oldProviders, providers)) { + staticValues.push(oldState.facet(facet)); + } + else { + let value = facet.combine(providers.map(p => p.value)); + staticValues.push(oldState && facet.compare(value, oldState.facet(facet)) ? oldState.facet(facet) : value); + } + } + else { + for (let p of providers) { + if (p.type == 0 /* Provider.Static */) { + address[p.id] = (staticValues.length << 1) | 1; + staticValues.push(p.value); + } + else { + address[p.id] = dynamicSlots.length << 1; + dynamicSlots.push(a => p.dynamicSlot(a)); + } + } + address[facet.id] = dynamicSlots.length << 1; + dynamicSlots.push(a => dynamicFacetSlot(a, facet, providers)); + } + } + let dynamic = dynamicSlots.map(f => f(address)); + return new Configuration(base, newCompartments, dynamic, address, staticValues, facets); + } + } + function flatten(extension, compartments, newCompartments) { + let result = [[], [], [], [], []]; + let seen = new Map(); + function inner(ext, prec) { + let known = seen.get(ext); + if (known != null) { + if (known <= prec) + return; + let found = result[known].indexOf(ext); + if (found > -1) + result[known].splice(found, 1); + if (ext instanceof CompartmentInstance) + newCompartments.delete(ext.compartment); + } + seen.set(ext, prec); + if (Array.isArray(ext)) { + for (let e of ext) + inner(e, prec); + } + else if (ext instanceof CompartmentInstance) { + if (newCompartments.has(ext.compartment)) + throw new RangeError(`Duplicate use of compartment in extensions`); + let content = compartments.get(ext.compartment) || ext.inner; + newCompartments.set(ext.compartment, content); + inner(content, prec); + } + else if (ext instanceof PrecExtension) { + inner(ext.inner, ext.prec); + } + else if (ext instanceof StateField) { + result[prec].push(ext); + if (ext.provides) + inner(ext.provides, prec); + } + else if (ext instanceof FacetProvider) { + result[prec].push(ext); + if (ext.facet.extensions) + inner(ext.facet.extensions, Prec_.default); + } + else { + let content = ext.extension; + if (!content) + throw new Error(`Unrecognized extension value in extension set (${ext}). This sometimes happens because multiple instances of @codemirror/state are loaded, breaking instanceof checks.`); + inner(content, prec); + } + } + inner(extension, Prec_.default); + return result.reduce((a, b) => a.concat(b)); + } + function ensureAddr(state, addr) { + if (addr & 1) + return 2 /* SlotStatus.Computed */; + let idx = addr >> 1; + let status = state.status[idx]; + if (status == 4 /* SlotStatus.Computing */) + throw new Error("Cyclic dependency between fields and/or facets"); + if (status & 2 /* SlotStatus.Computed */) + return status; + state.status[idx] = 4 /* SlotStatus.Computing */; + let changed = state.computeSlot(state, state.config.dynamicSlots[idx]); + return state.status[idx] = 2 /* SlotStatus.Computed */ | changed; + } + function getAddr(state, addr) { + return addr & 1 ? state.config.staticValues[addr >> 1] : state.values[addr >> 1]; + } + + const languageData = /*@__PURE__*/Facet.define(); + const allowMultipleSelections = /*@__PURE__*/Facet.define({ + combine: values => values.some(v => v), + static: true + }); + const lineSeparator = /*@__PURE__*/Facet.define({ + combine: values => values.length ? values[0] : undefined, + static: true + }); + const changeFilter = /*@__PURE__*/Facet.define(); + const transactionFilter = /*@__PURE__*/Facet.define(); + const transactionExtender = /*@__PURE__*/Facet.define(); + const readOnly = /*@__PURE__*/Facet.define({ + combine: values => values.length ? values[0] : false + }); + + /** + Annotations are tagged values that are used to add metadata to + transactions in an extensible way. They should be used to model + things that effect the entire transaction (such as its [time + stamp](https://codemirror.net/6/docs/ref/#state.Transaction^time) or information about its + [origin](https://codemirror.net/6/docs/ref/#state.Transaction^userEvent)). For effects that happen + _alongside_ the other changes made by the transaction, [state + effects](https://codemirror.net/6/docs/ref/#state.StateEffect) are more appropriate. + */ + class Annotation { + /** + @internal + */ + constructor( + /** + The annotation type. + */ + type, + /** + The value of this annotation. + */ + value) { + this.type = type; + this.value = value; + } + /** + Define a new type of annotation. + */ + static define() { return new AnnotationType(); } + } + /** + Marker that identifies a type of [annotation](https://codemirror.net/6/docs/ref/#state.Annotation). + */ + class AnnotationType { + /** + Create an instance of this annotation. + */ + of(value) { return new Annotation(this, value); } + } + /** + Representation of a type of state effect. Defined with + [`StateEffect.define`](https://codemirror.net/6/docs/ref/#state.StateEffect^define). + */ + class StateEffectType { + /** + @internal + */ + constructor( + // The `any` types in these function types are there to work + // around TypeScript issue #37631, where the type guard on + // `StateEffect.is` mysteriously stops working when these properly + // have type `Value`. + /** + @internal + */ + map) { + this.map = map; + } + /** + Create a [state effect](https://codemirror.net/6/docs/ref/#state.StateEffect) instance of this + type. + */ + of(value) { return new StateEffect(this, value); } + } + /** + State effects can be used to represent additional effects + associated with a [transaction](https://codemirror.net/6/docs/ref/#state.Transaction.effects). They + are often useful to model changes to custom [state + fields](https://codemirror.net/6/docs/ref/#state.StateField), when those changes aren't implicit in + document or selection changes. + */ + class StateEffect { + /** + @internal + */ + constructor( + /** + @internal + */ + type, + /** + The value of this effect. + */ + value) { + this.type = type; + this.value = value; + } + /** + Map this effect through a position mapping. Will return + `undefined` when that ends up deleting the effect. + */ + map(mapping) { + let mapped = this.type.map(this.value, mapping); + return mapped === undefined ? undefined : mapped == this.value ? this : new StateEffect(this.type, mapped); + } + /** + Tells you whether this effect object is of a given + [type](https://codemirror.net/6/docs/ref/#state.StateEffectType). + */ + is(type) { return this.type == type; } + /** + Define a new effect type. The type parameter indicates the type + of values that his effect holds. It should be a type that + doesn't include `undefined`, since that is used in + [mapping](https://codemirror.net/6/docs/ref/#state.StateEffect.map) to indicate that an effect is + removed. + */ + static define(spec = {}) { + return new StateEffectType(spec.map || (v => v)); + } + /** + Map an array of effects through a change set. + */ + static mapEffects(effects, mapping) { + if (!effects.length) + return effects; + let result = []; + for (let effect of effects) { + let mapped = effect.map(mapping); + if (mapped) + result.push(mapped); + } + return result; + } + } + /** + This effect can be used to reconfigure the root extensions of + the editor. Doing this will discard any extensions + [appended](https://codemirror.net/6/docs/ref/#state.StateEffect^appendConfig), but does not reset + the content of [reconfigured](https://codemirror.net/6/docs/ref/#state.Compartment.reconfigure) + compartments. + */ + StateEffect.reconfigure = /*@__PURE__*/StateEffect.define(); + /** + Append extensions to the top-level configuration of the editor. + */ + StateEffect.appendConfig = /*@__PURE__*/StateEffect.define(); + /** + Changes to the editor state are grouped into transactions. + Typically, a user action creates a single transaction, which may + contain any number of document changes, may change the selection, + or have other effects. Create a transaction by calling + [`EditorState.update`](https://codemirror.net/6/docs/ref/#state.EditorState.update), or immediately + dispatch one by calling + [`EditorView.dispatch`](https://codemirror.net/6/docs/ref/#view.EditorView.dispatch). + */ + class Transaction { + constructor( + /** + The state from which the transaction starts. + */ + startState, + /** + The document changes made by this transaction. + */ + changes, + /** + The selection set by this transaction, or undefined if it + doesn't explicitly set a selection. + */ + selection, + /** + The effects added to the transaction. + */ + effects, + /** + @internal + */ + annotations, + /** + Whether the selection should be scrolled into view after this + transaction is dispatched. + */ + scrollIntoView) { + this.startState = startState; + this.changes = changes; + this.selection = selection; + this.effects = effects; + this.annotations = annotations; + this.scrollIntoView = scrollIntoView; + /** + @internal + */ + this._doc = null; + /** + @internal + */ + this._state = null; + if (selection) + checkSelection(selection, changes.newLength); + if (!annotations.some((a) => a.type == Transaction.time)) + this.annotations = annotations.concat(Transaction.time.of(Date.now())); + } + /** + @internal + */ + static create(startState, changes, selection, effects, annotations, scrollIntoView) { + return new Transaction(startState, changes, selection, effects, annotations, scrollIntoView); + } + /** + The new document produced by the transaction. Contrary to + [`.state`](https://codemirror.net/6/docs/ref/#state.Transaction.state)`.doc`, accessing this won't + force the entire new state to be computed right away, so it is + recommended that [transaction + filters](https://codemirror.net/6/docs/ref/#state.EditorState^transactionFilter) use this getter + when they need to look at the new document. + */ + get newDoc() { + return this._doc || (this._doc = this.changes.apply(this.startState.doc)); + } + /** + The new selection produced by the transaction. If + [`this.selection`](https://codemirror.net/6/docs/ref/#state.Transaction.selection) is undefined, + this will [map](https://codemirror.net/6/docs/ref/#state.EditorSelection.map) the start state's + current selection through the changes made by the transaction. + */ + get newSelection() { + return this.selection || this.startState.selection.map(this.changes); + } + /** + The new state created by the transaction. Computed on demand + (but retained for subsequent access), so it is recommended not to + access it in [transaction + filters](https://codemirror.net/6/docs/ref/#state.EditorState^transactionFilter) when possible. + */ + get state() { + if (!this._state) + this.startState.applyTransaction(this); + return this._state; + } + /** + Get the value of the given annotation type, if any. + */ + annotation(type) { + for (let ann of this.annotations) + if (ann.type == type) + return ann.value; + return undefined; + } + /** + Indicates whether the transaction changed the document. + */ + get docChanged() { return !this.changes.empty; } + /** + Indicates whether this transaction reconfigures the state + (through a [configuration compartment](https://codemirror.net/6/docs/ref/#state.Compartment) or + with a top-level configuration + [effect](https://codemirror.net/6/docs/ref/#state.StateEffect^reconfigure). + */ + get reconfigured() { return this.startState.config != this.state.config; } + /** + Returns true if the transaction has a [user + event](https://codemirror.net/6/docs/ref/#state.Transaction^userEvent) annotation that is equal to + or more specific than `event`. For example, if the transaction + has `"select.pointer"` as user event, `"select"` and + `"select.pointer"` will match it. + */ + isUserEvent(event) { + let e = this.annotation(Transaction.userEvent); + return !!(e && (e == event || e.length > event.length && e.slice(0, event.length) == event && e[event.length] == ".")); + } + } + /** + Annotation used to store transaction timestamps. Automatically + added to every transaction, holding `Date.now()`. + */ + Transaction.time = /*@__PURE__*/Annotation.define(); + /** + Annotation used to associate a transaction with a user interface + event. Holds a string identifying the event, using a + dot-separated format to support attaching more specific + information. The events used by the core libraries are: + + - `"input"` when content is entered + - `"input.type"` for typed input + - `"input.type.compose"` for composition + - `"input.paste"` for pasted input + - `"input.drop"` when adding content with drag-and-drop + - `"input.complete"` when autocompleting + - `"delete"` when the user deletes content + - `"delete.selection"` when deleting the selection + - `"delete.forward"` when deleting forward from the selection + - `"delete.backward"` when deleting backward from the selection + - `"delete.cut"` when cutting to the clipboard + - `"move"` when content is moved + - `"move.drop"` when content is moved within the editor through drag-and-drop + - `"select"` when explicitly changing the selection + - `"select.pointer"` when selecting with a mouse or other pointing device + - `"undo"` and `"redo"` for history actions + + Use [`isUserEvent`](https://codemirror.net/6/docs/ref/#state.Transaction.isUserEvent) to check + whether the annotation matches a given event. + */ + Transaction.userEvent = /*@__PURE__*/Annotation.define(); + /** + Annotation indicating whether a transaction should be added to + the undo history or not. + */ + Transaction.addToHistory = /*@__PURE__*/Annotation.define(); + /** + Annotation indicating (when present and true) that a transaction + represents a change made by some other actor, not the user. This + is used, for example, to tag other people's changes in + collaborative editing. + */ + Transaction.remote = /*@__PURE__*/Annotation.define(); + function joinRanges(a, b) { + let result = []; + for (let iA = 0, iB = 0;;) { + let from, to; + if (iA < a.length && (iB == b.length || b[iB] >= a[iA])) { + from = a[iA++]; + to = a[iA++]; + } + else if (iB < b.length) { + from = b[iB++]; + to = b[iB++]; + } + else + return result; + if (!result.length || result[result.length - 1] < from) + result.push(from, to); + else if (result[result.length - 1] < to) + result[result.length - 1] = to; + } + } + function mergeTransaction(a, b, sequential) { + var _a; + let mapForA, mapForB, changes; + if (sequential) { + mapForA = b.changes; + mapForB = ChangeSet.empty(b.changes.length); + changes = a.changes.compose(b.changes); + } + else { + mapForA = b.changes.map(a.changes); + mapForB = a.changes.mapDesc(b.changes, true); + changes = a.changes.compose(mapForA); + } + return { + changes, + selection: b.selection ? b.selection.map(mapForB) : (_a = a.selection) === null || _a === void 0 ? void 0 : _a.map(mapForA), + effects: StateEffect.mapEffects(a.effects, mapForA).concat(StateEffect.mapEffects(b.effects, mapForB)), + annotations: a.annotations.length ? a.annotations.concat(b.annotations) : b.annotations, + scrollIntoView: a.scrollIntoView || b.scrollIntoView + }; + } + function resolveTransactionInner(state, spec, docSize) { + let sel = spec.selection, annotations = asArray$1(spec.annotations); + if (spec.userEvent) + annotations = annotations.concat(Transaction.userEvent.of(spec.userEvent)); + return { + changes: spec.changes instanceof ChangeSet ? spec.changes + : ChangeSet.of(spec.changes || [], docSize, state.facet(lineSeparator)), + selection: sel && (sel instanceof EditorSelection ? sel : EditorSelection.single(sel.anchor, sel.head)), + effects: asArray$1(spec.effects), + annotations, + scrollIntoView: !!spec.scrollIntoView + }; + } + function resolveTransaction(state, specs, filter) { + let s = resolveTransactionInner(state, specs.length ? specs[0] : {}, state.doc.length); + if (specs.length && specs[0].filter === false) + filter = false; + for (let i = 1; i < specs.length; i++) { + if (specs[i].filter === false) + filter = false; + let seq = !!specs[i].sequential; + s = mergeTransaction(s, resolveTransactionInner(state, specs[i], seq ? s.changes.newLength : state.doc.length), seq); + } + let tr = Transaction.create(state, s.changes, s.selection, s.effects, s.annotations, s.scrollIntoView); + return extendTransaction(filter ? filterTransaction(tr) : tr); + } + // Finish a transaction by applying filters if necessary. + function filterTransaction(tr) { + let state = tr.startState; + // Change filters + let result = true; + for (let filter of state.facet(changeFilter)) { + let value = filter(tr); + if (value === false) { + result = false; + break; + } + if (Array.isArray(value)) + result = result === true ? value : joinRanges(result, value); + } + if (result !== true) { + let changes, back; + if (result === false) { + back = tr.changes.invertedDesc; + changes = ChangeSet.empty(state.doc.length); + } + else { + let filtered = tr.changes.filter(result); + changes = filtered.changes; + back = filtered.filtered.mapDesc(filtered.changes).invertedDesc; + } + tr = Transaction.create(state, changes, tr.selection && tr.selection.map(back), StateEffect.mapEffects(tr.effects, back), tr.annotations, tr.scrollIntoView); + } + // Transaction filters + let filters = state.facet(transactionFilter); + for (let i = filters.length - 1; i >= 0; i--) { + let filtered = filters[i](tr); + if (filtered instanceof Transaction) + tr = filtered; + else if (Array.isArray(filtered) && filtered.length == 1 && filtered[0] instanceof Transaction) + tr = filtered[0]; + else + tr = resolveTransaction(state, asArray$1(filtered), false); + } + return tr; + } + function extendTransaction(tr) { + let state = tr.startState, extenders = state.facet(transactionExtender), spec = tr; + for (let i = extenders.length - 1; i >= 0; i--) { + let extension = extenders[i](tr); + if (extension && Object.keys(extension).length) + spec = mergeTransaction(spec, resolveTransactionInner(state, extension, tr.changes.newLength), true); + } + return spec == tr ? tr : Transaction.create(state, tr.changes, tr.selection, spec.effects, spec.annotations, spec.scrollIntoView); + } + const none$2 = []; + function asArray$1(value) { + return value == null ? none$2 : Array.isArray(value) ? value : [value]; + } + + /** + The categories produced by a [character + categorizer](https://codemirror.net/6/docs/ref/#state.EditorState.charCategorizer). These are used + do things like selecting by word. + */ + var CharCategory = /*@__PURE__*/(function (CharCategory) { + /** + Word characters. + */ + CharCategory[CharCategory["Word"] = 0] = "Word"; + /** + Whitespace. + */ + CharCategory[CharCategory["Space"] = 1] = "Space"; + /** + Anything else. + */ + CharCategory[CharCategory["Other"] = 2] = "Other"; + return CharCategory})(CharCategory || (CharCategory = {})); + const nonASCIISingleCaseWordChar = /[\u00df\u0587\u0590-\u05f4\u0600-\u06ff\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/; + let wordChar; + try { + wordChar = /*@__PURE__*/new RegExp("[\\p{Alphabetic}\\p{Number}_]", "u"); + } + catch (_) { } + function hasWordChar(str) { + if (wordChar) + return wordChar.test(str); + for (let i = 0; i < str.length; i++) { + let ch = str[i]; + if (/\w/.test(ch) || ch > "\x80" && (ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(ch))) + return true; + } + return false; + } + function makeCategorizer(wordChars) { + return (char) => { + if (!/\S/.test(char)) + return CharCategory.Space; + if (hasWordChar(char)) + return CharCategory.Word; + for (let i = 0; i < wordChars.length; i++) + if (char.indexOf(wordChars[i]) > -1) + return CharCategory.Word; + return CharCategory.Other; + }; + } + + /** + The editor state class is a persistent (immutable) data structure. + To update a state, you [create](https://codemirror.net/6/docs/ref/#state.EditorState.update) a + [transaction](https://codemirror.net/6/docs/ref/#state.Transaction), which produces a _new_ state + instance, without modifying the original object. + + As such, _never_ mutate properties of a state directly. That'll + just break things. + */ + class EditorState { + constructor( + /** + @internal + */ + config, + /** + The current document. + */ + doc, + /** + The current selection. + */ + selection, + /** + @internal + */ + values, computeSlot, tr) { + this.config = config; + this.doc = doc; + this.selection = selection; + this.values = values; + this.status = config.statusTemplate.slice(); + this.computeSlot = computeSlot; + // Fill in the computed state immediately, so that further queries + // for it made during the update return this state + if (tr) + tr._state = this; + for (let i = 0; i < this.config.dynamicSlots.length; i++) + ensureAddr(this, i << 1); + this.computeSlot = null; + } + field(field, require = true) { + let addr = this.config.address[field.id]; + if (addr == null) { + if (require) + throw new RangeError("Field is not present in this state"); + return undefined; + } + ensureAddr(this, addr); + return getAddr(this, addr); + } + /** + Create a [transaction](https://codemirror.net/6/docs/ref/#state.Transaction) that updates this + state. Any number of [transaction specs](https://codemirror.net/6/docs/ref/#state.TransactionSpec) + can be passed. Unless + [`sequential`](https://codemirror.net/6/docs/ref/#state.TransactionSpec.sequential) is set, the + [changes](https://codemirror.net/6/docs/ref/#state.TransactionSpec.changes) (if any) of each spec + are assumed to start in the _current_ document (not the document + produced by previous specs), and its + [selection](https://codemirror.net/6/docs/ref/#state.TransactionSpec.selection) and + [effects](https://codemirror.net/6/docs/ref/#state.TransactionSpec.effects) are assumed to refer + to the document created by its _own_ changes. The resulting + transaction contains the combined effect of all the different + specs. For [selection](https://codemirror.net/6/docs/ref/#state.TransactionSpec.selection), later + specs take precedence over earlier ones. + */ + update(...specs) { + return resolveTransaction(this, specs, true); + } + /** + @internal + */ + applyTransaction(tr) { + let conf = this.config, { base, compartments } = conf; + for (let effect of tr.effects) { + if (effect.is(Compartment.reconfigure)) { + if (conf) { + compartments = new Map; + conf.compartments.forEach((val, key) => compartments.set(key, val)); + conf = null; + } + compartments.set(effect.value.compartment, effect.value.extension); + } + else if (effect.is(StateEffect.reconfigure)) { + conf = null; + base = effect.value; + } + else if (effect.is(StateEffect.appendConfig)) { + conf = null; + base = asArray$1(base).concat(effect.value); + } + } + let startValues; + if (!conf) { + conf = Configuration.resolve(base, compartments, this); + let intermediateState = new EditorState(conf, this.doc, this.selection, conf.dynamicSlots.map(() => null), (state, slot) => slot.reconfigure(state, this), null); + startValues = intermediateState.values; + } + else { + startValues = tr.startState.values.slice(); + } + let selection = tr.startState.facet(allowMultipleSelections) ? tr.newSelection : tr.newSelection.asSingle(); + new EditorState(conf, tr.newDoc, selection, startValues, (state, slot) => slot.update(state, tr), tr); + } + /** + Create a [transaction spec](https://codemirror.net/6/docs/ref/#state.TransactionSpec) that + replaces every selection range with the given content. + */ + replaceSelection(text) { + if (typeof text == "string") + text = this.toText(text); + return this.changeByRange(range => ({ changes: { from: range.from, to: range.to, insert: text }, + range: EditorSelection.cursor(range.from + text.length) })); + } + /** + Create a set of changes and a new selection by running the given + function for each range in the active selection. The function + can return an optional set of changes (in the coordinate space + of the start document), plus an updated range (in the coordinate + space of the document produced by the call's own changes). This + method will merge all the changes and ranges into a single + changeset and selection, and return it as a [transaction + spec](https://codemirror.net/6/docs/ref/#state.TransactionSpec), which can be passed to + [`update`](https://codemirror.net/6/docs/ref/#state.EditorState.update). + */ + changeByRange(f) { + let sel = this.selection; + let result1 = f(sel.ranges[0]); + let changes = this.changes(result1.changes), ranges = [result1.range]; + let effects = asArray$1(result1.effects); + for (let i = 1; i < sel.ranges.length; i++) { + let result = f(sel.ranges[i]); + let newChanges = this.changes(result.changes), newMapped = newChanges.map(changes); + for (let j = 0; j < i; j++) + ranges[j] = ranges[j].map(newMapped); + let mapBy = changes.mapDesc(newChanges, true); + ranges.push(result.range.map(mapBy)); + changes = changes.compose(newMapped); + effects = StateEffect.mapEffects(effects, newMapped).concat(StateEffect.mapEffects(asArray$1(result.effects), mapBy)); + } + return { + changes, + selection: EditorSelection.create(ranges, sel.mainIndex), + effects + }; + } + /** + Create a [change set](https://codemirror.net/6/docs/ref/#state.ChangeSet) from the given change + description, taking the state's document length and line + separator into account. + */ + changes(spec = []) { + if (spec instanceof ChangeSet) + return spec; + return ChangeSet.of(spec, this.doc.length, this.facet(EditorState.lineSeparator)); + } + /** + Using the state's [line + separator](https://codemirror.net/6/docs/ref/#state.EditorState^lineSeparator), create a + [`Text`](https://codemirror.net/6/docs/ref/#state.Text) instance from the given string. + */ + toText(string) { + return Text$1.of(string.split(this.facet(EditorState.lineSeparator) || DefaultSplit)); + } + /** + Return the given range of the document as a string. + */ + sliceDoc(from = 0, to = this.doc.length) { + return this.doc.sliceString(from, to, this.lineBreak); + } + /** + Get the value of a state [facet](https://codemirror.net/6/docs/ref/#state.Facet). + */ + facet(facet) { + let addr = this.config.address[facet.id]; + if (addr == null) + return facet.default; + ensureAddr(this, addr); + return getAddr(this, addr); + } + /** + Convert this state to a JSON-serializable object. When custom + fields should be serialized, you can pass them in as an object + mapping property names (in the resulting object, which should + not use `doc` or `selection`) to fields. + */ + toJSON(fields) { + let result = { + doc: this.sliceDoc(), + selection: this.selection.toJSON() + }; + if (fields) + for (let prop in fields) { + let value = fields[prop]; + if (value instanceof StateField && this.config.address[value.id] != null) + result[prop] = value.spec.toJSON(this.field(fields[prop]), this); + } + return result; + } + /** + Deserialize a state from its JSON representation. When custom + fields should be deserialized, pass the same object you passed + to [`toJSON`](https://codemirror.net/6/docs/ref/#state.EditorState.toJSON) when serializing as + third argument. + */ + static fromJSON(json, config = {}, fields) { + if (!json || typeof json.doc != "string") + throw new RangeError("Invalid JSON representation for EditorState"); + let fieldInit = []; + if (fields) + for (let prop in fields) { + if (Object.prototype.hasOwnProperty.call(json, prop)) { + let field = fields[prop], value = json[prop]; + fieldInit.push(field.init(state => field.spec.fromJSON(value, state))); + } + } + return EditorState.create({ + doc: json.doc, + selection: EditorSelection.fromJSON(json.selection), + extensions: config.extensions ? fieldInit.concat([config.extensions]) : fieldInit + }); + } + /** + Create a new state. You'll usually only need this when + initializing an editor—updated states are created by applying + transactions. + */ + static create(config = {}) { + let configuration = Configuration.resolve(config.extensions || [], new Map); + let doc = config.doc instanceof Text$1 ? config.doc + : Text$1.of((config.doc || "").split(configuration.staticFacet(EditorState.lineSeparator) || DefaultSplit)); + let selection = !config.selection ? EditorSelection.single(0) + : config.selection instanceof EditorSelection ? config.selection + : EditorSelection.single(config.selection.anchor, config.selection.head); + checkSelection(selection, doc.length); + if (!configuration.staticFacet(allowMultipleSelections)) + selection = selection.asSingle(); + return new EditorState(configuration, doc, selection, configuration.dynamicSlots.map(() => null), (state, slot) => slot.create(state), null); + } + /** + The size (in columns) of a tab in the document, determined by + the [`tabSize`](https://codemirror.net/6/docs/ref/#state.EditorState^tabSize) facet. + */ + get tabSize() { return this.facet(EditorState.tabSize); } + /** + Get the proper [line-break](https://codemirror.net/6/docs/ref/#state.EditorState^lineSeparator) + string for this state. + */ + get lineBreak() { return this.facet(EditorState.lineSeparator) || "\n"; } + /** + Returns true when the editor is + [configured](https://codemirror.net/6/docs/ref/#state.EditorState^readOnly) to be read-only. + */ + get readOnly() { return this.facet(readOnly); } + /** + Look up a translation for the given phrase (via the + [`phrases`](https://codemirror.net/6/docs/ref/#state.EditorState^phrases) facet), or return the + original string if no translation is found. + + If additional arguments are passed, they will be inserted in + place of markers like `$1` (for the first value) and `$2`, etc. + A single `$` is equivalent to `$1`, and `$$` will produce a + literal dollar sign. + */ + phrase(phrase, ...insert) { + for (let map of this.facet(EditorState.phrases)) + if (Object.prototype.hasOwnProperty.call(map, phrase)) { + phrase = map[phrase]; + break; + } + if (insert.length) + phrase = phrase.replace(/\$(\$|\d*)/g, (m, i) => { + if (i == "$") + return "$"; + let n = +(i || 1); + return !n || n > insert.length ? m : insert[n - 1]; + }); + return phrase; + } + /** + Find the values for a given language data field, provided by the + the [`languageData`](https://codemirror.net/6/docs/ref/#state.EditorState^languageData) facet. + + Examples of language data fields are... + + - [`"commentTokens"`](https://codemirror.net/6/docs/ref/#commands.CommentTokens) for specifying + comment syntax. + - [`"autocomplete"`](https://codemirror.net/6/docs/ref/#autocomplete.autocompletion^config.override) + for providing language-specific completion sources. + - [`"wordChars"`](https://codemirror.net/6/docs/ref/#state.EditorState.charCategorizer) for adding + characters that should be considered part of words in this + language. + - [`"closeBrackets"`](https://codemirror.net/6/docs/ref/#autocomplete.CloseBracketConfig) controls + bracket closing behavior. + */ + languageDataAt(name, pos, side = -1) { + let values = []; + for (let provider of this.facet(languageData)) { + for (let result of provider(this, pos, side)) { + if (Object.prototype.hasOwnProperty.call(result, name)) + values.push(result[name]); + } + } + return values; + } + /** + Return a function that can categorize strings (expected to + represent a single [grapheme cluster](https://codemirror.net/6/docs/ref/#state.findClusterBreak)) + into one of: + + - Word (contains an alphanumeric character or a character + explicitly listed in the local language's `"wordChars"` + language data, which should be a string) + - Space (contains only whitespace) + - Other (anything else) + */ + charCategorizer(at) { + return makeCategorizer(this.languageDataAt("wordChars", at).join("")); + } + /** + Find the word at the given position, meaning the range + containing all [word](https://codemirror.net/6/docs/ref/#state.CharCategory.Word) characters + around it. If no word characters are adjacent to the position, + this returns null. + */ + wordAt(pos) { + let { text, from, length } = this.doc.lineAt(pos); + let cat = this.charCategorizer(pos); + let start = pos - from, end = pos - from; + while (start > 0) { + let prev = findClusterBreak(text, start, false); + if (cat(text.slice(prev, start)) != CharCategory.Word) + break; + start = prev; + } + while (end < length) { + let next = findClusterBreak(text, end); + if (cat(text.slice(end, next)) != CharCategory.Word) + break; + end = next; + } + return start == end ? null : EditorSelection.range(start + from, end + from); + } + } + /** + A facet that, when enabled, causes the editor to allow multiple + ranges to be selected. Be careful though, because by default the + editor relies on the native DOM selection, which cannot handle + multiple selections. An extension like + [`drawSelection`](https://codemirror.net/6/docs/ref/#view.drawSelection) can be used to make + secondary selections visible to the user. + */ + EditorState.allowMultipleSelections = allowMultipleSelections; + /** + Configures the tab size to use in this state. The first + (highest-precedence) value of the facet is used. If no value is + given, this defaults to 4. + */ + EditorState.tabSize = /*@__PURE__*/Facet.define({ + combine: values => values.length ? values[0] : 4 + }); + /** + The line separator to use. By default, any of `"\n"`, `"\r\n"` + and `"\r"` is treated as a separator when splitting lines, and + lines are joined with `"\n"`. + + When you configure a value here, only that precise separator + will be used, allowing you to round-trip documents through the + editor without normalizing line separators. + */ + EditorState.lineSeparator = lineSeparator; + /** + This facet controls the value of the + [`readOnly`](https://codemirror.net/6/docs/ref/#state.EditorState.readOnly) getter, which is + consulted by commands and extensions that implement editing + functionality to determine whether they should apply. It + defaults to false, but when its highest-precedence value is + `true`, such functionality disables itself. + + Not to be confused with + [`EditorView.editable`](https://codemirror.net/6/docs/ref/#view.EditorView^editable), which + controls whether the editor's DOM is set to be editable (and + thus focusable). + */ + EditorState.readOnly = readOnly; + /** + Registers translation phrases. The + [`phrase`](https://codemirror.net/6/docs/ref/#state.EditorState.phrase) method will look through + all objects registered with this facet to find translations for + its argument. + */ + EditorState.phrases = /*@__PURE__*/Facet.define({ + compare(a, b) { + let kA = Object.keys(a), kB = Object.keys(b); + return kA.length == kB.length && kA.every(k => a[k] == b[k]); + } + }); + /** + A facet used to register [language + data](https://codemirror.net/6/docs/ref/#state.EditorState.languageDataAt) providers. + */ + EditorState.languageData = languageData; + /** + Facet used to register change filters, which are called for each + transaction (unless explicitly + [disabled](https://codemirror.net/6/docs/ref/#state.TransactionSpec.filter)), and can suppress + part of the transaction's changes. + + Such a function can return `true` to indicate that it doesn't + want to do anything, `false` to completely stop the changes in + the transaction, or a set of ranges in which changes should be + suppressed. Such ranges are represented as an array of numbers, + with each pair of two numbers indicating the start and end of a + range. So for example `[10, 20, 100, 110]` suppresses changes + between 10 and 20, and between 100 and 110. + */ + EditorState.changeFilter = changeFilter; + /** + Facet used to register a hook that gets a chance to update or + replace transaction specs before they are applied. This will + only be applied for transactions that don't have + [`filter`](https://codemirror.net/6/docs/ref/#state.TransactionSpec.filter) set to `false`. You + can either return a single transaction spec (possibly the input + transaction), or an array of specs (which will be combined in + the same way as the arguments to + [`EditorState.update`](https://codemirror.net/6/docs/ref/#state.EditorState.update)). + + When possible, it is recommended to avoid accessing + [`Transaction.state`](https://codemirror.net/6/docs/ref/#state.Transaction.state) in a filter, + since it will force creation of a state that will then be + discarded again, if the transaction is actually filtered. + + (This functionality should be used with care. Indiscriminately + modifying transaction is likely to break something or degrade + the user experience.) + */ + EditorState.transactionFilter = transactionFilter; + /** + This is a more limited form of + [`transactionFilter`](https://codemirror.net/6/docs/ref/#state.EditorState^transactionFilter), + which can only add + [annotations](https://codemirror.net/6/docs/ref/#state.TransactionSpec.annotations) and + [effects](https://codemirror.net/6/docs/ref/#state.TransactionSpec.effects). _But_, this type + of filter runs even if the transaction has disabled regular + [filtering](https://codemirror.net/6/docs/ref/#state.TransactionSpec.filter), making it suitable + for effects that don't need to touch the changes or selection, + but do want to process every transaction. + + Extenders run _after_ filters, when both are present. + */ + EditorState.transactionExtender = transactionExtender; + Compartment.reconfigure = /*@__PURE__*/StateEffect.define(); + + /** + Utility function for combining behaviors to fill in a config + object from an array of provided configs. `defaults` should hold + default values for all optional fields in `Config`. + + The function will, by default, error + when a field gets two values that aren't `===`-equal, but you can + provide combine functions per field to do something else. + */ + function combineConfig(configs, defaults, // Should hold only the optional properties of Config, but I haven't managed to express that + combine = {}) { + let result = {}; + for (let config of configs) + for (let key of Object.keys(config)) { + let value = config[key], current = result[key]; + if (current === undefined) + result[key] = value; + else if (current === value || value === undefined) ; // No conflict + else if (Object.hasOwnProperty.call(combine, key)) + result[key] = combine[key](current, value); + else + throw new Error("Config merge conflict for field " + key); + } + for (let key in defaults) + if (result[key] === undefined) + result[key] = defaults[key]; + return result; + } + + /** + Each range is associated with a value, which must inherit from + this class. + */ + class RangeValue { + /** + Compare this value with another value. Used when comparing + rangesets. The default implementation compares by identity. + Unless you are only creating a fixed number of unique instances + of your value type, it is a good idea to implement this + properly. + */ + eq(other) { return this == other; } + /** + Create a [range](https://codemirror.net/6/docs/ref/#state.Range) with this value. + */ + range(from, to = from) { return Range$1.create(from, to, this); } + } + RangeValue.prototype.startSide = RangeValue.prototype.endSide = 0; + RangeValue.prototype.point = false; + RangeValue.prototype.mapMode = MapMode.TrackDel; + /** + A range associates a value with a range of positions. + */ + let Range$1 = class Range { + constructor( + /** + The range's start position. + */ + from, + /** + Its end position. + */ + to, + /** + The value associated with this range. + */ + value) { + this.from = from; + this.to = to; + this.value = value; + } + /** + @internal + */ + static create(from, to, value) { + return new Range(from, to, value); + } + }; + function cmpRange(a, b) { + return a.from - b.from || a.value.startSide - b.value.startSide; + } + class Chunk { + constructor(from, to, value, + // Chunks are marked with the largest point that occurs + // in them (or -1 for no points), so that scans that are + // only interested in points (such as the + // heightmap-related logic) can skip range-only chunks. + maxPoint) { + this.from = from; + this.to = to; + this.value = value; + this.maxPoint = maxPoint; + } + get length() { return this.to[this.to.length - 1]; } + // Find the index of the given position and side. Use the ranges' + // `from` pos when `end == false`, `to` when `end == true`. + findIndex(pos, side, end, startAt = 0) { + let arr = end ? this.to : this.from; + for (let lo = startAt, hi = arr.length;;) { + if (lo == hi) + return lo; + let mid = (lo + hi) >> 1; + let diff = arr[mid] - pos || (end ? this.value[mid].endSide : this.value[mid].startSide) - side; + if (mid == lo) + return diff >= 0 ? lo : hi; + if (diff >= 0) + hi = mid; + else + lo = mid + 1; + } + } + between(offset, from, to, f) { + for (let i = this.findIndex(from, -1e9 /* C.Far */, true), e = this.findIndex(to, 1000000000 /* C.Far */, false, i); i < e; i++) + if (f(this.from[i] + offset, this.to[i] + offset, this.value[i]) === false) + return false; + } + map(offset, changes) { + let value = [], from = [], to = [], newPos = -1, maxPoint = -1; + for (let i = 0; i < this.value.length; i++) { + let val = this.value[i], curFrom = this.from[i] + offset, curTo = this.to[i] + offset, newFrom, newTo; + if (curFrom == curTo) { + let mapped = changes.mapPos(curFrom, val.startSide, val.mapMode); + if (mapped == null) + continue; + newFrom = newTo = mapped; + if (val.startSide != val.endSide) { + newTo = changes.mapPos(curFrom, val.endSide); + if (newTo < newFrom) + continue; + } + } + else { + newFrom = changes.mapPos(curFrom, val.startSide); + newTo = changes.mapPos(curTo, val.endSide); + if (newFrom > newTo || newFrom == newTo && val.startSide > 0 && val.endSide <= 0) + continue; + } + if ((newTo - newFrom || val.endSide - val.startSide) < 0) + continue; + if (newPos < 0) + newPos = newFrom; + if (val.point) + maxPoint = Math.max(maxPoint, newTo - newFrom); + value.push(val); + from.push(newFrom - newPos); + to.push(newTo - newPos); + } + return { mapped: value.length ? new Chunk(from, to, value, maxPoint) : null, pos: newPos }; + } + } + /** + A range set stores a collection of [ranges](https://codemirror.net/6/docs/ref/#state.Range) in a + way that makes them efficient to [map](https://codemirror.net/6/docs/ref/#state.RangeSet.map) and + [update](https://codemirror.net/6/docs/ref/#state.RangeSet.update). This is an immutable data + structure. + */ + class RangeSet { + constructor( + /** + @internal + */ + chunkPos, + /** + @internal + */ + chunk, + /** + @internal + */ + nextLayer, + /** + @internal + */ + maxPoint) { + this.chunkPos = chunkPos; + this.chunk = chunk; + this.nextLayer = nextLayer; + this.maxPoint = maxPoint; + } + /** + @internal + */ + static create(chunkPos, chunk, nextLayer, maxPoint) { + return new RangeSet(chunkPos, chunk, nextLayer, maxPoint); + } + /** + @internal + */ + get length() { + let last = this.chunk.length - 1; + return last < 0 ? 0 : Math.max(this.chunkEnd(last), this.nextLayer.length); + } + /** + The number of ranges in the set. + */ + get size() { + if (this.isEmpty) + return 0; + let size = this.nextLayer.size; + for (let chunk of this.chunk) + size += chunk.value.length; + return size; + } + /** + @internal + */ + chunkEnd(index) { + return this.chunkPos[index] + this.chunk[index].length; + } + /** + Update the range set, optionally adding new ranges or filtering + out existing ones. + + (Note: The type parameter is just there as a kludge to work + around TypeScript variance issues that prevented `RangeSet` + from being a subtype of `RangeSet` when `X` is a subtype of + `Y`.) + */ + update(updateSpec) { + let { add = [], sort = false, filterFrom = 0, filterTo = this.length } = updateSpec; + let filter = updateSpec.filter; + if (add.length == 0 && !filter) + return this; + if (sort) + add = add.slice().sort(cmpRange); + if (this.isEmpty) + return add.length ? RangeSet.of(add) : this; + let cur = new LayerCursor(this, null, -1).goto(0), i = 0, spill = []; + let builder = new RangeSetBuilder(); + while (cur.value || i < add.length) { + if (i < add.length && (cur.from - add[i].from || cur.startSide - add[i].value.startSide) >= 0) { + let range = add[i++]; + if (!builder.addInner(range.from, range.to, range.value)) + spill.push(range); + } + else if (cur.rangeIndex == 1 && cur.chunkIndex < this.chunk.length && + (i == add.length || this.chunkEnd(cur.chunkIndex) < add[i].from) && + (!filter || filterFrom > this.chunkEnd(cur.chunkIndex) || filterTo < this.chunkPos[cur.chunkIndex]) && + builder.addChunk(this.chunkPos[cur.chunkIndex], this.chunk[cur.chunkIndex])) { + cur.nextChunk(); + } + else { + if (!filter || filterFrom > cur.to || filterTo < cur.from || filter(cur.from, cur.to, cur.value)) { + if (!builder.addInner(cur.from, cur.to, cur.value)) + spill.push(Range$1.create(cur.from, cur.to, cur.value)); + } + cur.next(); + } + } + return builder.finishInner(this.nextLayer.isEmpty && !spill.length ? RangeSet.empty + : this.nextLayer.update({ add: spill, filter, filterFrom, filterTo })); + } + /** + Map this range set through a set of changes, return the new set. + */ + map(changes) { + if (changes.empty || this.isEmpty) + return this; + let chunks = [], chunkPos = [], maxPoint = -1; + for (let i = 0; i < this.chunk.length; i++) { + let start = this.chunkPos[i], chunk = this.chunk[i]; + let touch = changes.touchesRange(start, start + chunk.length); + if (touch === false) { + maxPoint = Math.max(maxPoint, chunk.maxPoint); + chunks.push(chunk); + chunkPos.push(changes.mapPos(start)); + } + else if (touch === true) { + let { mapped, pos } = chunk.map(start, changes); + if (mapped) { + maxPoint = Math.max(maxPoint, mapped.maxPoint); + chunks.push(mapped); + chunkPos.push(pos); + } + } + } + let next = this.nextLayer.map(changes); + return chunks.length == 0 ? next : new RangeSet(chunkPos, chunks, next || RangeSet.empty, maxPoint); + } + /** + Iterate over the ranges that touch the region `from` to `to`, + calling `f` for each. There is no guarantee that the ranges will + be reported in any specific order. When the callback returns + `false`, iteration stops. + */ + between(from, to, f) { + if (this.isEmpty) + return; + for (let i = 0; i < this.chunk.length; i++) { + let start = this.chunkPos[i], chunk = this.chunk[i]; + if (to >= start && from <= start + chunk.length && + chunk.between(start, from - start, to - start, f) === false) + return; + } + this.nextLayer.between(from, to, f); + } + /** + Iterate over the ranges in this set, in order, including all + ranges that end at or after `from`. + */ + iter(from = 0) { + return HeapCursor.from([this]).goto(from); + } + /** + @internal + */ + get isEmpty() { return this.nextLayer == this; } + /** + Iterate over the ranges in a collection of sets, in order, + starting from `from`. + */ + static iter(sets, from = 0) { + return HeapCursor.from(sets).goto(from); + } + /** + Iterate over two groups of sets, calling methods on `comparator` + to notify it of possible differences. + */ + static compare(oldSets, newSets, + /** + This indicates how the underlying data changed between these + ranges, and is needed to synchronize the iteration. + */ + textDiff, comparator, + /** + Can be used to ignore all non-point ranges, and points below + the given size. When -1, all ranges are compared. + */ + minPointSize = -1) { + let a = oldSets.filter(set => set.maxPoint > 0 || !set.isEmpty && set.maxPoint >= minPointSize); + let b = newSets.filter(set => set.maxPoint > 0 || !set.isEmpty && set.maxPoint >= minPointSize); + let sharedChunks = findSharedChunks(a, b, textDiff); + let sideA = new SpanCursor(a, sharedChunks, minPointSize); + let sideB = new SpanCursor(b, sharedChunks, minPointSize); + textDiff.iterGaps((fromA, fromB, length) => compare(sideA, fromA, sideB, fromB, length, comparator)); + if (textDiff.empty && textDiff.length == 0) + compare(sideA, 0, sideB, 0, 0, comparator); + } + /** + Compare the contents of two groups of range sets, returning true + if they are equivalent in the given range. + */ + static eq(oldSets, newSets, from = 0, to) { + if (to == null) + to = 1000000000 /* C.Far */ - 1; + let a = oldSets.filter(set => !set.isEmpty && newSets.indexOf(set) < 0); + let b = newSets.filter(set => !set.isEmpty && oldSets.indexOf(set) < 0); + if (a.length != b.length) + return false; + if (!a.length) + return true; + let sharedChunks = findSharedChunks(a, b); + let sideA = new SpanCursor(a, sharedChunks, 0).goto(from), sideB = new SpanCursor(b, sharedChunks, 0).goto(from); + for (;;) { + if (sideA.to != sideB.to || + !sameValues(sideA.active, sideB.active) || + sideA.point && (!sideB.point || !sideA.point.eq(sideB.point))) + return false; + if (sideA.to > to) + return true; + sideA.next(); + sideB.next(); + } + } + /** + Iterate over a group of range sets at the same time, notifying + the iterator about the ranges covering every given piece of + content. Returns the open count (see + [`SpanIterator.span`](https://codemirror.net/6/docs/ref/#state.SpanIterator.span)) at the end + of the iteration. + */ + static spans(sets, from, to, iterator, + /** + When given and greater than -1, only points of at least this + size are taken into account. + */ + minPointSize = -1) { + let cursor = new SpanCursor(sets, null, minPointSize).goto(from), pos = from; + let openRanges = cursor.openStart; + for (;;) { + let curTo = Math.min(cursor.to, to); + if (cursor.point) { + let active = cursor.activeForPoint(cursor.to); + let openCount = cursor.pointFrom < from ? active.length + 1 + : cursor.point.startSide < 0 ? active.length + : Math.min(active.length, openRanges); + iterator.point(pos, curTo, cursor.point, active, openCount, cursor.pointRank); + openRanges = Math.min(cursor.openEnd(curTo), active.length); + } + else if (curTo > pos) { + iterator.span(pos, curTo, cursor.active, openRanges); + openRanges = cursor.openEnd(curTo); + } + if (cursor.to > to) + return openRanges + (cursor.point && cursor.to > to ? 1 : 0); + pos = cursor.to; + cursor.next(); + } + } + /** + Create a range set for the given range or array of ranges. By + default, this expects the ranges to be _sorted_ (by start + position and, if two start at the same position, + `value.startSide`). You can pass `true` as second argument to + cause the method to sort them. + */ + static of(ranges, sort = false) { + let build = new RangeSetBuilder(); + for (let range of ranges instanceof Range$1 ? [ranges] : sort ? lazySort(ranges) : ranges) + build.add(range.from, range.to, range.value); + return build.finish(); + } + /** + Join an array of range sets into a single set. + */ + static join(sets) { + if (!sets.length) + return RangeSet.empty; + let result = sets[sets.length - 1]; + for (let i = sets.length - 2; i >= 0; i--) { + for (let layer = sets[i]; layer != RangeSet.empty; layer = layer.nextLayer) + result = new RangeSet(layer.chunkPos, layer.chunk, result, Math.max(layer.maxPoint, result.maxPoint)); + } + return result; + } + } + /** + The empty set of ranges. + */ + RangeSet.empty = /*@__PURE__*/new RangeSet([], [], null, -1); + function lazySort(ranges) { + if (ranges.length > 1) + for (let prev = ranges[0], i = 1; i < ranges.length; i++) { + let cur = ranges[i]; + if (cmpRange(prev, cur) > 0) + return ranges.slice().sort(cmpRange); + prev = cur; + } + return ranges; + } + RangeSet.empty.nextLayer = RangeSet.empty; + /** + A range set builder is a data structure that helps build up a + [range set](https://codemirror.net/6/docs/ref/#state.RangeSet) directly, without first allocating + an array of [`Range`](https://codemirror.net/6/docs/ref/#state.Range) objects. + */ + class RangeSetBuilder { + finishChunk(newArrays) { + this.chunks.push(new Chunk(this.from, this.to, this.value, this.maxPoint)); + this.chunkPos.push(this.chunkStart); + this.chunkStart = -1; + this.setMaxPoint = Math.max(this.setMaxPoint, this.maxPoint); + this.maxPoint = -1; + if (newArrays) { + this.from = []; + this.to = []; + this.value = []; + } + } + /** + Create an empty builder. + */ + constructor() { + this.chunks = []; + this.chunkPos = []; + this.chunkStart = -1; + this.last = null; + this.lastFrom = -1e9 /* C.Far */; + this.lastTo = -1e9 /* C.Far */; + this.from = []; + this.to = []; + this.value = []; + this.maxPoint = -1; + this.setMaxPoint = -1; + this.nextLayer = null; + } + /** + Add a range. Ranges should be added in sorted (by `from` and + `value.startSide`) order. + */ + add(from, to, value) { + if (!this.addInner(from, to, value)) + (this.nextLayer || (this.nextLayer = new RangeSetBuilder)).add(from, to, value); + } + /** + @internal + */ + addInner(from, to, value) { + let diff = from - this.lastTo || value.startSide - this.last.endSide; + if (diff <= 0 && (from - this.lastFrom || value.startSide - this.last.startSide) < 0) + throw new Error("Ranges must be added sorted by `from` position and `startSide`"); + if (diff < 0) + return false; + if (this.from.length == 250 /* C.ChunkSize */) + this.finishChunk(true); + if (this.chunkStart < 0) + this.chunkStart = from; + this.from.push(from - this.chunkStart); + this.to.push(to - this.chunkStart); + this.last = value; + this.lastFrom = from; + this.lastTo = to; + this.value.push(value); + if (value.point) + this.maxPoint = Math.max(this.maxPoint, to - from); + return true; + } + /** + @internal + */ + addChunk(from, chunk) { + if ((from - this.lastTo || chunk.value[0].startSide - this.last.endSide) < 0) + return false; + if (this.from.length) + this.finishChunk(true); + this.setMaxPoint = Math.max(this.setMaxPoint, chunk.maxPoint); + this.chunks.push(chunk); + this.chunkPos.push(from); + let last = chunk.value.length - 1; + this.last = chunk.value[last]; + this.lastFrom = chunk.from[last] + from; + this.lastTo = chunk.to[last] + from; + return true; + } + /** + Finish the range set. Returns the new set. The builder can't be + used anymore after this has been called. + */ + finish() { return this.finishInner(RangeSet.empty); } + /** + @internal + */ + finishInner(next) { + if (this.from.length) + this.finishChunk(false); + if (this.chunks.length == 0) + return next; + let result = RangeSet.create(this.chunkPos, this.chunks, this.nextLayer ? this.nextLayer.finishInner(next) : next, this.setMaxPoint); + this.from = null; // Make sure further `add` calls produce errors + return result; + } + } + function findSharedChunks(a, b, textDiff) { + let inA = new Map(); + for (let set of a) + for (let i = 0; i < set.chunk.length; i++) + if (set.chunk[i].maxPoint <= 0) + inA.set(set.chunk[i], set.chunkPos[i]); + let shared = new Set(); + for (let set of b) + for (let i = 0; i < set.chunk.length; i++) { + let known = inA.get(set.chunk[i]); + if (known != null && (textDiff ? textDiff.mapPos(known) : known) == set.chunkPos[i] && + !(textDiff === null || textDiff === void 0 ? void 0 : textDiff.touchesRange(known, known + set.chunk[i].length))) + shared.add(set.chunk[i]); + } + return shared; + } + class LayerCursor { + constructor(layer, skip, minPoint, rank = 0) { + this.layer = layer; + this.skip = skip; + this.minPoint = minPoint; + this.rank = rank; + } + get startSide() { return this.value ? this.value.startSide : 0; } + get endSide() { return this.value ? this.value.endSide : 0; } + goto(pos, side = -1e9 /* C.Far */) { + this.chunkIndex = this.rangeIndex = 0; + this.gotoInner(pos, side, false); + return this; + } + gotoInner(pos, side, forward) { + while (this.chunkIndex < this.layer.chunk.length) { + let next = this.layer.chunk[this.chunkIndex]; + if (!(this.skip && this.skip.has(next) || + this.layer.chunkEnd(this.chunkIndex) < pos || + next.maxPoint < this.minPoint)) + break; + this.chunkIndex++; + forward = false; + } + if (this.chunkIndex < this.layer.chunk.length) { + let rangeIndex = this.layer.chunk[this.chunkIndex].findIndex(pos - this.layer.chunkPos[this.chunkIndex], side, true); + if (!forward || this.rangeIndex < rangeIndex) + this.setRangeIndex(rangeIndex); + } + this.next(); + } + forward(pos, side) { + if ((this.to - pos || this.endSide - side) < 0) + this.gotoInner(pos, side, true); + } + next() { + for (;;) { + if (this.chunkIndex == this.layer.chunk.length) { + this.from = this.to = 1000000000 /* C.Far */; + this.value = null; + break; + } + else { + let chunkPos = this.layer.chunkPos[this.chunkIndex], chunk = this.layer.chunk[this.chunkIndex]; + let from = chunkPos + chunk.from[this.rangeIndex]; + this.from = from; + this.to = chunkPos + chunk.to[this.rangeIndex]; + this.value = chunk.value[this.rangeIndex]; + this.setRangeIndex(this.rangeIndex + 1); + if (this.minPoint < 0 || this.value.point && this.to - this.from >= this.minPoint) + break; + } + } + } + setRangeIndex(index) { + if (index == this.layer.chunk[this.chunkIndex].value.length) { + this.chunkIndex++; + if (this.skip) { + while (this.chunkIndex < this.layer.chunk.length && this.skip.has(this.layer.chunk[this.chunkIndex])) + this.chunkIndex++; + } + this.rangeIndex = 0; + } + else { + this.rangeIndex = index; + } + } + nextChunk() { + this.chunkIndex++; + this.rangeIndex = 0; + this.next(); + } + compare(other) { + return this.from - other.from || this.startSide - other.startSide || this.rank - other.rank || + this.to - other.to || this.endSide - other.endSide; + } + } + class HeapCursor { + constructor(heap) { + this.heap = heap; + } + static from(sets, skip = null, minPoint = -1) { + let heap = []; + for (let i = 0; i < sets.length; i++) { + for (let cur = sets[i]; !cur.isEmpty; cur = cur.nextLayer) { + if (cur.maxPoint >= minPoint) + heap.push(new LayerCursor(cur, skip, minPoint, i)); + } + } + return heap.length == 1 ? heap[0] : new HeapCursor(heap); + } + get startSide() { return this.value ? this.value.startSide : 0; } + goto(pos, side = -1e9 /* C.Far */) { + for (let cur of this.heap) + cur.goto(pos, side); + for (let i = this.heap.length >> 1; i >= 0; i--) + heapBubble(this.heap, i); + this.next(); + return this; + } + forward(pos, side) { + for (let cur of this.heap) + cur.forward(pos, side); + for (let i = this.heap.length >> 1; i >= 0; i--) + heapBubble(this.heap, i); + if ((this.to - pos || this.value.endSide - side) < 0) + this.next(); + } + next() { + if (this.heap.length == 0) { + this.from = this.to = 1000000000 /* C.Far */; + this.value = null; + this.rank = -1; + } + else { + let top = this.heap[0]; + this.from = top.from; + this.to = top.to; + this.value = top.value; + this.rank = top.rank; + if (top.value) + top.next(); + heapBubble(this.heap, 0); + } + } + } + function heapBubble(heap, index) { + for (let cur = heap[index];;) { + let childIndex = (index << 1) + 1; + if (childIndex >= heap.length) + break; + let child = heap[childIndex]; + if (childIndex + 1 < heap.length && child.compare(heap[childIndex + 1]) >= 0) { + child = heap[childIndex + 1]; + childIndex++; + } + if (cur.compare(child) < 0) + break; + heap[childIndex] = cur; + heap[index] = child; + index = childIndex; + } + } + class SpanCursor { + constructor(sets, skip, minPoint) { + this.minPoint = minPoint; + this.active = []; + this.activeTo = []; + this.activeRank = []; + this.minActive = -1; + // A currently active point range, if any + this.point = null; + this.pointFrom = 0; + this.pointRank = 0; + this.to = -1e9 /* C.Far */; + this.endSide = 0; + // The amount of open active ranges at the start of the iterator. + // Not including points. + this.openStart = -1; + this.cursor = HeapCursor.from(sets, skip, minPoint); + } + goto(pos, side = -1e9 /* C.Far */) { + this.cursor.goto(pos, side); + this.active.length = this.activeTo.length = this.activeRank.length = 0; + this.minActive = -1; + this.to = pos; + this.endSide = side; + this.openStart = -1; + this.next(); + return this; + } + forward(pos, side) { + while (this.minActive > -1 && (this.activeTo[this.minActive] - pos || this.active[this.minActive].endSide - side) < 0) + this.removeActive(this.minActive); + this.cursor.forward(pos, side); + } + removeActive(index) { + remove(this.active, index); + remove(this.activeTo, index); + remove(this.activeRank, index); + this.minActive = findMinIndex(this.active, this.activeTo); + } + addActive(trackOpen) { + let i = 0, { value, to, rank } = this.cursor; + // Organize active marks by rank first, then by size + while (i < this.activeRank.length && (rank - this.activeRank[i] || to - this.activeTo[i]) > 0) + i++; + insert(this.active, i, value); + insert(this.activeTo, i, to); + insert(this.activeRank, i, rank); + if (trackOpen) + insert(trackOpen, i, this.cursor.from); + this.minActive = findMinIndex(this.active, this.activeTo); + } + // After calling this, if `this.point` != null, the next range is a + // point. Otherwise, it's a regular range, covered by `this.active`. + next() { + let from = this.to, wasPoint = this.point; + this.point = null; + let trackOpen = this.openStart < 0 ? [] : null; + for (;;) { + let a = this.minActive; + if (a > -1 && (this.activeTo[a] - this.cursor.from || this.active[a].endSide - this.cursor.startSide) < 0) { + if (this.activeTo[a] > from) { + this.to = this.activeTo[a]; + this.endSide = this.active[a].endSide; + break; + } + this.removeActive(a); + if (trackOpen) + remove(trackOpen, a); + } + else if (!this.cursor.value) { + this.to = this.endSide = 1000000000 /* C.Far */; + break; + } + else if (this.cursor.from > from) { + this.to = this.cursor.from; + this.endSide = this.cursor.startSide; + break; + } + else { + let nextVal = this.cursor.value; + if (!nextVal.point) { // Opening a range + this.addActive(trackOpen); + this.cursor.next(); + } + else if (wasPoint && this.cursor.to == this.to && this.cursor.from < this.cursor.to) { + // Ignore any non-empty points that end precisely at the end of the prev point + this.cursor.next(); + } + else { // New point + this.point = nextVal; + this.pointFrom = this.cursor.from; + this.pointRank = this.cursor.rank; + this.to = this.cursor.to; + this.endSide = nextVal.endSide; + this.cursor.next(); + this.forward(this.to, this.endSide); + break; + } + } + } + if (trackOpen) { + this.openStart = 0; + for (let i = trackOpen.length - 1; i >= 0 && trackOpen[i] < from; i--) + this.openStart++; + } + } + activeForPoint(to) { + if (!this.active.length) + return this.active; + let active = []; + for (let i = this.active.length - 1; i >= 0; i--) { + if (this.activeRank[i] < this.pointRank) + break; + if (this.activeTo[i] > to || this.activeTo[i] == to && this.active[i].endSide >= this.point.endSide) + active.push(this.active[i]); + } + return active.reverse(); + } + openEnd(to) { + let open = 0; + for (let i = this.activeTo.length - 1; i >= 0 && this.activeTo[i] > to; i--) + open++; + return open; + } + } + function compare(a, startA, b, startB, length, comparator) { + a.goto(startA); + b.goto(startB); + let endB = startB + length; + let pos = startB, dPos = startB - startA; + for (;;) { + let dEnd = (a.to + dPos) - b.to, diff = dEnd || a.endSide - b.endSide; + let end = diff < 0 ? a.to + dPos : b.to, clipEnd = Math.min(end, endB); + if (a.point || b.point) { + if (!(a.point && b.point && (a.point == b.point || a.point.eq(b.point)) && + sameValues(a.activeForPoint(a.to), b.activeForPoint(b.to)))) + comparator.comparePoint(pos, clipEnd, a.point, b.point); + } + else { + if (clipEnd > pos && !sameValues(a.active, b.active)) + comparator.compareRange(pos, clipEnd, a.active, b.active); + } + if (end > endB) + break; + if ((dEnd || a.openEnd != b.openEnd) && comparator.boundChange) + comparator.boundChange(end); + pos = end; + if (diff <= 0) + a.next(); + if (diff >= 0) + b.next(); + } + } + function sameValues(a, b) { + if (a.length != b.length) + return false; + for (let i = 0; i < a.length; i++) + if (a[i] != b[i] && !a[i].eq(b[i])) + return false; + return true; + } + function remove(array, index) { + for (let i = index, e = array.length - 1; i < e; i++) + array[i] = array[i + 1]; + array.pop(); + } + function insert(array, index, value) { + for (let i = array.length - 1; i >= index; i--) + array[i + 1] = array[i]; + array[index] = value; + } + function findMinIndex(value, array) { + let found = -1, foundPos = 1000000000 /* C.Far */; + for (let i = 0; i < array.length; i++) + if ((array[i] - foundPos || value[i].endSide - value[found].endSide) < 0) { + found = i; + foundPos = array[i]; + } + return found; + } + + /** + Count the column position at the given offset into the string, + taking extending characters and tab size into account. + */ + function countColumn(string, tabSize, to = string.length) { + let n = 0; + for (let i = 0; i < to && i < string.length;) { + if (string.charCodeAt(i) == 9) { + n += tabSize - (n % tabSize); + i++; + } + else { + n++; + i = findClusterBreak(string, i); + } + } + return n; + } + /** + Find the offset that corresponds to the given column position in a + string, taking extending characters and tab size into account. By + default, the string length is returned when it is too short to + reach the column. Pass `strict` true to make it return -1 in that + situation. + */ + function findColumn(string, col, tabSize, strict) { + for (let i = 0, n = 0;;) { + if (n >= col) + return i; + if (i == string.length) + break; + n += string.charCodeAt(i) == 9 ? tabSize - (n % tabSize) : 1; + i = findClusterBreak(string, i); + } + return strict === true ? -1 : string.length; + } + + const C$1 = "\u037c"; + const COUNT = typeof Symbol == "undefined" ? "__" + C$1 : Symbol.for(C$1); + const SET = typeof Symbol == "undefined" ? "__styleSet" + Math.floor(Math.random() * 1e8) : Symbol("styleSet"); + const top = typeof globalThis != "undefined" ? globalThis : typeof window != "undefined" ? window : {}; + + // :: - Style modules encapsulate a set of CSS rules defined from + // JavaScript. Their definitions are only available in a given DOM + // root after it has been _mounted_ there with `StyleModule.mount`. + // + // Style modules should be created once and stored somewhere, as + // opposed to re-creating them every time you need them. The amount of + // CSS rules generated for a given DOM root is bounded by the amount + // of style modules that were used. So to avoid leaking rules, don't + // create these dynamically, but treat them as one-time allocations. + class StyleModule { + // :: (Object From f114a784fbc27038630706c951a57687901335fc Mon Sep 17 00:00:00 2001 From: Benjamin Young Date: Mon, 19 May 2025 14:20:35 -0400 Subject: [PATCH 32/54] Add N-Quads tab. --- package.json | 3 + playground/next/editor.bundle.js | 668 ++++++++++++++++++++++++++++++- playground/next/editor.mjs | 29 +- playground/next/index.html | 1 + 4 files changed, 687 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index 31fb5567e..8266afb25 100644 --- a/package.json +++ b/package.json @@ -25,5 +25,8 @@ "petite-vue": "^0.4.1", "rollup": "^4.40.1", "wrangler": "^4.6.0" + }, + "dependencies": { + "@codemirror/legacy-modes": "^6.5.1" } } diff --git a/playground/next/editor.bundle.js b/playground/next/editor.bundle.js index 222058588..94d3ef111 100644 --- a/playground/next/editor.bundle.js +++ b/playground/next/editor.bundle.js @@ -17897,7 +17897,7 @@ Object.defineProperty(EditorState.prototype, "tree", { get() { return syntaxTree(this); } }); this.parser = parser; this.extension = [ - language.of(this), + language$1.of(this), EditorState.languageData.of((state, pos, side) => { let top = topNodeAt(state, pos, side), data = top.type.prop(languageDataProp); if (!data) @@ -17927,7 +17927,7 @@ in this language, when those exist. */ findRegions(state) { - let lang = state.facet(language); + let lang = state.facet(language$1); if ((lang === null || lang === void 0 ? void 0 : lang.data) == this.data) return [{ from: 0, to: state.doc.length }]; if (!lang || !lang.allowsNesting) @@ -17975,7 +17975,7 @@ */ Language.setState = /*@__PURE__*/StateEffect.define(); function topNodeAt(state, pos, side) { - let topLang = state.facet(language), tree = syntaxTree(state).topNode; + let topLang = state.facet(language$1), tree = syntaxTree(state).topNode; if (!topLang || topLang.allowsNesting) { for (let node = tree; node; node = node.enter(pos, side, IterMode.ExcludeBuffers)) if (node.type.isTop) @@ -18321,7 +18321,7 @@ } static init(state) { let vpTo = Math.min(3000 /* Work.InitViewport */, state.doc.length); - let parseState = ParseContext.create(state.facet(language).parser, state, { from: 0, to: vpTo }); + let parseState = ParseContext.create(state.facet(language$1).parser, state, { from: 0, to: vpTo }); if (!parseState.work(20 /* Work.Apply */, vpTo)) parseState.takeTree(); return new LanguageState(parseState); @@ -18333,7 +18333,7 @@ for (let e of tr.effects) if (e.is(Language.setState)) return e.value; - if (tr.startState.facet(language) != tr.state.facet(language)) + if (tr.startState.facet(language$1) != tr.state.facet(language$1)) return LanguageState.init(tr.state); return value.apply(tr); } @@ -18433,7 +18433,7 @@ manually wrap your languages in this). Can be used to access the current language on a state. */ - const language = /*@__PURE__*/Facet.define({ + const language$1 = /*@__PURE__*/Facet.define({ combine(languages) { return languages.length ? languages[0] : null; }, enables: language => [ Language.state, @@ -19182,7 +19182,7 @@ } update(update) { if (update.docChanged || update.viewportChanged || - update.startState.facet(language) != update.state.facet(language) || + update.startState.facet(language$1) != update.state.facet(language$1) || update.startState.field(foldState, false) != update.state.field(foldState, false) || syntaxTree(update.startState) != syntaxTree(update.state) || fullConfig.foldingChanged(update)) @@ -19566,8 +19566,479 @@ } return iter.done ? { start: startToken, matched: false } : null; } + + // Counts the column offset in a string, taking tabs into account. + // Used mostly to find indentation. + function countCol(string, end, tabSize, startIndex = 0, startValue = 0) { + if (end == null) { + end = string.search(/[^\s\u00a0]/); + if (end == -1) + end = string.length; + } + let n = startValue; + for (let i = startIndex; i < end; i++) { + if (string.charCodeAt(i) == 9) + n += tabSize - (n % tabSize); + else + n++; + } + return n; + } + /** + Encapsulates a single line of input. Given to stream syntax code, + which uses it to tokenize the content. + */ + class StringStream { + /** + Create a stream. + */ + constructor( + /** + The line. + */ + string, tabSize, + /** + The current indent unit size. + */ + indentUnit, overrideIndent) { + this.string = string; + this.tabSize = tabSize; + this.indentUnit = indentUnit; + this.overrideIndent = overrideIndent; + /** + The current position on the line. + */ + this.pos = 0; + /** + The start position of the current token. + */ + this.start = 0; + this.lastColumnPos = 0; + this.lastColumnValue = 0; + } + /** + True if we are at the end of the line. + */ + eol() { return this.pos >= this.string.length; } + /** + True if we are at the start of the line. + */ + sol() { return this.pos == 0; } + /** + Get the next code unit after the current position, or undefined + if we're at the end of the line. + */ + peek() { return this.string.charAt(this.pos) || undefined; } + /** + Read the next code unit and advance `this.pos`. + */ + next() { + if (this.pos < this.string.length) + return this.string.charAt(this.pos++); + } + /** + Match the next character against the given string, regular + expression, or predicate. Consume and return it if it matches. + */ + eat(match) { + let ch = this.string.charAt(this.pos); + let ok; + if (typeof match == "string") + ok = ch == match; + else + ok = ch && (match instanceof RegExp ? match.test(ch) : match(ch)); + if (ok) { + ++this.pos; + return ch; + } + } + /** + Continue matching characters that match the given string, + regular expression, or predicate function. Return true if any + characters were consumed. + */ + eatWhile(match) { + let start = this.pos; + while (this.eat(match)) { } + return this.pos > start; + } + /** + Consume whitespace ahead of `this.pos`. Return true if any was + found. + */ + eatSpace() { + let start = this.pos; + while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) + ++this.pos; + return this.pos > start; + } + /** + Move to the end of the line. + */ + skipToEnd() { this.pos = this.string.length; } + /** + Move to directly before the given character, if found on the + current line. + */ + skipTo(ch) { + let found = this.string.indexOf(ch, this.pos); + if (found > -1) { + this.pos = found; + return true; + } + } + /** + Move back `n` characters. + */ + backUp(n) { this.pos -= n; } + /** + Get the column position at `this.pos`. + */ + column() { + if (this.lastColumnPos < this.start) { + this.lastColumnValue = countCol(this.string, this.start, this.tabSize, this.lastColumnPos, this.lastColumnValue); + this.lastColumnPos = this.start; + } + return this.lastColumnValue; + } + /** + Get the indentation column of the current line. + */ + indentation() { + var _a; + return (_a = this.overrideIndent) !== null && _a !== void 0 ? _a : countCol(this.string, null, this.tabSize); + } + /** + Match the input against the given string or regular expression + (which should start with a `^`). Return true or the regexp match + if it matches. + + Unless `consume` is set to `false`, this will move `this.pos` + past the matched text. + + When matching a string `caseInsensitive` can be set to true to + make the match case-insensitive. + */ + match(pattern, consume, caseInsensitive) { + if (typeof pattern == "string") { + let cased = (str) => caseInsensitive ? str.toLowerCase() : str; + let substr = this.string.substr(this.pos, pattern.length); + if (cased(substr) == cased(pattern)) { + if (consume !== false) + this.pos += pattern.length; + return true; + } + else + return null; + } + else { + let match = this.string.slice(this.pos).match(pattern); + if (match && match.index > 0) + return null; + if (match && consume !== false) + this.pos += match[0].length; + return match; + } + } + /** + Get the current token. + */ + current() { return this.string.slice(this.start, this.pos); } + } + + function fullParser(spec) { + return { + name: spec.name || "", + token: spec.token, + blankLine: spec.blankLine || (() => { }), + startState: spec.startState || (() => true), + copyState: spec.copyState || defaultCopyState, + indent: spec.indent || (() => null), + languageData: spec.languageData || {}, + tokenTable: spec.tokenTable || noTokens, + mergeTokens: spec.mergeTokens !== false + }; + } + function defaultCopyState(state) { + if (typeof state != "object") + return state; + let newState = {}; + for (let prop in state) { + let val = state[prop]; + newState[prop] = (val instanceof Array ? val.slice() : val); + } + return newState; + } + const IndentedFrom = /*@__PURE__*/new WeakMap(); + /** + A [language](https://codemirror.net/6/docs/ref/#language.Language) class based on a CodeMirror + 5-style [streaming parser](https://codemirror.net/6/docs/ref/#language.StreamParser). + */ + class StreamLanguage extends Language { + constructor(parser) { + let data = defineLanguageFacet(parser.languageData); + let p = fullParser(parser), self; + let impl = new class extends Parser { + createParse(input, fragments, ranges) { + return new Parse$1(self, input, fragments, ranges); + } + }; + super(data, impl, [], parser.name); + this.topNode = docID(data, this); + self = this; + this.streamParser = p; + this.stateAfter = new NodeProp({ perNode: true }); + this.tokenTable = parser.tokenTable ? new TokenTable(p.tokenTable) : defaultTokenTable; + } + /** + Define a stream language. + */ + static define(spec) { return new StreamLanguage(spec); } + /** + @internal + */ + getIndent(cx) { + let from = undefined; + let { overrideIndentation } = cx.options; + if (overrideIndentation) { + from = IndentedFrom.get(cx.state); + if (from != null && from < cx.pos - 1e4) + from = undefined; + } + let start = findState(this, cx.node.tree, cx.node.from, cx.node.from, from !== null && from !== void 0 ? from : cx.pos), statePos, state; + if (start) { + state = start.state; + statePos = start.pos + 1; + } + else { + state = this.streamParser.startState(cx.unit); + statePos = cx.node.from; + } + if (cx.pos - statePos > 10000 /* C.MaxIndentScanDist */) + return null; + while (statePos < cx.pos) { + let line = cx.state.doc.lineAt(statePos), end = Math.min(cx.pos, line.to); + if (line.length) { + let indentation = overrideIndentation ? overrideIndentation(line.from) : -1; + let stream = new StringStream(line.text, cx.state.tabSize, cx.unit, indentation < 0 ? undefined : indentation); + while (stream.pos < end - line.from) + readToken$1(this.streamParser.token, stream, state); + } + else { + this.streamParser.blankLine(state, cx.unit); + } + if (end == cx.pos) + break; + statePos = line.to + 1; + } + let line = cx.lineAt(cx.pos); + if (overrideIndentation && from == null) + IndentedFrom.set(cx.state, line.from); + return this.streamParser.indent(state, /^\s*(.*)/.exec(line.text)[1], cx); + } + get allowsNesting() { return false; } + } + function findState(lang, tree, off, startPos, before) { + let state = off >= startPos && off + tree.length <= before && tree.prop(lang.stateAfter); + if (state) + return { state: lang.streamParser.copyState(state), pos: off + tree.length }; + for (let i = tree.children.length - 1; i >= 0; i--) { + let child = tree.children[i], pos = off + tree.positions[i]; + let found = child instanceof Tree && pos < before && findState(lang, child, pos, startPos, before); + if (found) + return found; + } + return null; + } + function cutTree(lang, tree, from, to, inside) { + if (inside && from <= 0 && to >= tree.length) + return tree; + if (!inside && from == 0 && tree.type == lang.topNode) + inside = true; + for (let i = tree.children.length - 1; i >= 0; i--) { + let pos = tree.positions[i], child = tree.children[i], inner; + if (pos < to && child instanceof Tree) { + if (!(inner = cutTree(lang, child, from - pos, to - pos, inside))) + break; + return !inside ? inner + : new Tree(tree.type, tree.children.slice(0, i).concat(inner), tree.positions.slice(0, i + 1), pos + inner.length); + } + } + return null; + } + function findStartInFragments(lang, fragments, startPos, endPos, editorState) { + for (let f of fragments) { + let from = f.from + (f.openStart ? 25 : 0), to = f.to - (f.openEnd ? 25 : 0); + let found = from <= startPos && to > startPos && findState(lang, f.tree, 0 - f.offset, startPos, to), tree; + if (found && found.pos <= endPos && (tree = cutTree(lang, f.tree, startPos + f.offset, found.pos + f.offset, false))) + return { state: found.state, tree }; + } + return { state: lang.streamParser.startState(editorState ? getIndentUnit(editorState) : 4), tree: Tree.empty }; + } + let Parse$1 = class Parse { + constructor(lang, input, fragments, ranges) { + this.lang = lang; + this.input = input; + this.fragments = fragments; + this.ranges = ranges; + this.stoppedAt = null; + this.chunks = []; + this.chunkPos = []; + this.chunk = []; + this.chunkReused = undefined; + this.rangeIndex = 0; + this.to = ranges[ranges.length - 1].to; + let context = ParseContext.get(), from = ranges[0].from; + let { state, tree } = findStartInFragments(lang, fragments, from, this.to, context === null || context === void 0 ? void 0 : context.state); + this.state = state; + this.parsedPos = this.chunkStart = from + tree.length; + for (let i = 0; i < tree.children.length; i++) { + this.chunks.push(tree.children[i]); + this.chunkPos.push(tree.positions[i]); + } + if (context && this.parsedPos < context.viewport.from - 100000 /* C.MaxDistanceBeforeViewport */ && + ranges.some(r => r.from <= context.viewport.from && r.to >= context.viewport.from)) { + this.state = this.lang.streamParser.startState(getIndentUnit(context.state)); + context.skipUntilInView(this.parsedPos, context.viewport.from); + this.parsedPos = context.viewport.from; + } + this.moveRangeIndex(); + } + advance() { + let context = ParseContext.get(); + let parseEnd = this.stoppedAt == null ? this.to : Math.min(this.to, this.stoppedAt); + let end = Math.min(parseEnd, this.chunkStart + 2048 /* C.ChunkSize */); + if (context) + end = Math.min(end, context.viewport.to); + while (this.parsedPos < end) + this.parseLine(context); + if (this.chunkStart < this.parsedPos) + this.finishChunk(); + if (this.parsedPos >= parseEnd) + return this.finish(); + if (context && this.parsedPos >= context.viewport.to) { + context.skipUntilInView(this.parsedPos, parseEnd); + return this.finish(); + } + return null; + } + stopAt(pos) { + this.stoppedAt = pos; + } + lineAfter(pos) { + let chunk = this.input.chunk(pos); + if (!this.input.lineChunks) { + let eol = chunk.indexOf("\n"); + if (eol > -1) + chunk = chunk.slice(0, eol); + } + else if (chunk == "\n") { + chunk = ""; + } + return pos + chunk.length <= this.to ? chunk : chunk.slice(0, this.to - pos); + } + nextLine() { + let from = this.parsedPos, line = this.lineAfter(from), end = from + line.length; + for (let index = this.rangeIndex;;) { + let rangeEnd = this.ranges[index].to; + if (rangeEnd >= end) + break; + line = line.slice(0, rangeEnd - (end - line.length)); + index++; + if (index == this.ranges.length) + break; + let rangeStart = this.ranges[index].from; + let after = this.lineAfter(rangeStart); + line += after; + end = rangeStart + after.length; + } + return { line, end }; + } + skipGapsTo(pos, offset, side) { + for (;;) { + let end = this.ranges[this.rangeIndex].to, offPos = pos + offset; + if (side > 0 ? end > offPos : end >= offPos) + break; + let start = this.ranges[++this.rangeIndex].from; + offset += start - end; + } + return offset; + } + moveRangeIndex() { + while (this.ranges[this.rangeIndex].to < this.parsedPos) + this.rangeIndex++; + } + emitToken(id, from, to, offset) { + let size = 4; + if (this.ranges.length > 1) { + offset = this.skipGapsTo(from, offset, 1); + from += offset; + let len0 = this.chunk.length; + offset = this.skipGapsTo(to, offset, -1); + to += offset; + size += this.chunk.length - len0; + } + let last = this.chunk.length - 4; + if (this.lang.streamParser.mergeTokens && size == 4 && last >= 0 && + this.chunk[last] == id && this.chunk[last + 2] == from) + this.chunk[last + 2] = to; + else + this.chunk.push(id, from, to, size); + return offset; + } + parseLine(context) { + let { line, end } = this.nextLine(), offset = 0, { streamParser } = this.lang; + let stream = new StringStream(line, context ? context.state.tabSize : 4, context ? getIndentUnit(context.state) : 2); + if (stream.eol()) { + streamParser.blankLine(this.state, stream.indentUnit); + } + else { + while (!stream.eol()) { + let token = readToken$1(streamParser.token, stream, this.state); + if (token) + offset = this.emitToken(this.lang.tokenTable.resolve(token), this.parsedPos + stream.start, this.parsedPos + stream.pos, offset); + if (stream.start > 10000 /* C.MaxLineLength */) + break; + } + } + this.parsedPos = end; + this.moveRangeIndex(); + if (this.parsedPos < this.to) + this.parsedPos++; + } + finishChunk() { + let tree = Tree.build({ + buffer: this.chunk, + start: this.chunkStart, + length: this.parsedPos - this.chunkStart, + nodeSet, + topID: 0, + maxBufferLength: 2048 /* C.ChunkSize */, + reused: this.chunkReused + }); + tree = new Tree(tree.type, tree.children, tree.positions, tree.length, [[this.lang.stateAfter, this.lang.streamParser.copyState(this.state)]]); + this.chunks.push(tree); + this.chunkPos.push(this.chunkStart - this.ranges[0].from); + this.chunk = []; + this.chunkReused = undefined; + this.chunkStart = this.parsedPos; + } + finish() { + return new Tree(this.lang.topNode, this.chunks, this.chunkPos, this.parsedPos - this.ranges[0].from).balance(); + } + }; + function readToken$1(token, stream, state) { + stream.start = stream.pos; + for (let i = 0; i < 10; i++) { + let result = token(stream, state); + if (stream.pos > stream.start) + return result; + } + throw new Error("Stream parser failed to advance stream."); + } const noTokens = /*@__PURE__*/Object.create(null); const typeArray = [NodeType.none]; + const nodeSet = /*@__PURE__*/new NodeSet(typeArray); const warned = []; // Cache of node types by name and tags const byTag = /*@__PURE__*/Object.create(null); @@ -19587,6 +20058,16 @@ ["property", "propertyName"] ]) defaultTable[legacyName] = /*@__PURE__*/createTokenType(noTokens, name); + class TokenTable { + constructor(extra) { + this.extra = extra; + this.table = Object.assign(Object.create(null), defaultTable); + } + resolve(tag) { + return !tag ? 0 : this.table[tag] || (this.table[tag] = createTokenType(this.extra, tag)); + } + } + const defaultTokenTable = /*@__PURE__*/new TokenTable(noTokens); function warnForPart(part, msg) { if (warned.indexOf(part) > -1) return; @@ -19632,6 +20113,14 @@ typeArray.push(type); return type.id; } + function docID(data, lang) { + let type = NodeType.define({ id: typeArray.length, name: "Document", props: [ + languageDataProp.add(() => data), + indentNodeProp.add(() => cx => lang.getIndent(cx)) + ], top: true }); + typeArray.push(type); + return type; + } ({ rtl: /*@__PURE__*/Decoration.mark({ class: "cm-iso", inclusive: true, attributes: { dir: "rtl" }, bidiIsolate: Direction.RTL }), ltr: /*@__PURE__*/Decoration.mark({ class: "cm-iso", inclusive: true, attributes: { dir: "ltr" }, bidiIsolate: Direction.LTR })}); @@ -26597,6 +27086,146 @@ return new LanguageSupport(jsonLanguage); } + var Location = { + PRE_SUBJECT : 0, + WRITING_SUB_URI : 1, + WRITING_BNODE_URI : 2, + PRE_PRED : 3, + WRITING_PRED_URI : 4, + PRE_OBJ : 5, + WRITING_OBJ_URI : 6, + WRITING_OBJ_BNODE : 7, + WRITING_OBJ_LITERAL : 8, + WRITING_LIT_LANG : 9, + WRITING_LIT_TYPE : 10, + POST_OBJ : 11, + ERROR : 12 + }; + function transitState(currState, c) { + var currLocation = currState.location; + var ret; + + // Opening. + if (currLocation == Location.PRE_SUBJECT && c == '<') ret = Location.WRITING_SUB_URI; + else if(currLocation == Location.PRE_SUBJECT && c == '_') ret = Location.WRITING_BNODE_URI; + else if(currLocation == Location.PRE_PRED && c == '<') ret = Location.WRITING_PRED_URI; + else if(currLocation == Location.PRE_OBJ && c == '<') ret = Location.WRITING_OBJ_URI; + else if(currLocation == Location.PRE_OBJ && c == '_') ret = Location.WRITING_OBJ_BNODE; + else if(currLocation == Location.PRE_OBJ && c == '"') ret = Location.WRITING_OBJ_LITERAL; + + // Closing. + else if(currLocation == Location.WRITING_SUB_URI && c == '>') ret = Location.PRE_PRED; + else if(currLocation == Location.WRITING_BNODE_URI && c == ' ') ret = Location.PRE_PRED; + else if(currLocation == Location.WRITING_PRED_URI && c == '>') ret = Location.PRE_OBJ; + else if(currLocation == Location.WRITING_OBJ_URI && c == '>') ret = Location.POST_OBJ; + else if(currLocation == Location.WRITING_OBJ_BNODE && c == ' ') ret = Location.POST_OBJ; + else if(currLocation == Location.WRITING_OBJ_LITERAL && c == '"') ret = Location.POST_OBJ; + else if(currLocation == Location.WRITING_LIT_LANG && c == ' ') ret = Location.POST_OBJ; + else if(currLocation == Location.WRITING_LIT_TYPE && c == '>') ret = Location.POST_OBJ; + + // Closing typed and language literal. + else if(currLocation == Location.WRITING_OBJ_LITERAL && c == '@') ret = Location.WRITING_LIT_LANG; + else if(currLocation == Location.WRITING_OBJ_LITERAL && c == '^') ret = Location.WRITING_LIT_TYPE; + + // Spaces. + else if( c == ' ' && + ( + currLocation == Location.PRE_SUBJECT || + currLocation == Location.PRE_PRED || + currLocation == Location.PRE_OBJ || + currLocation == Location.POST_OBJ + ) + ) ret = currLocation; + + // Reset. + else if(currLocation == Location.POST_OBJ && c == '.') ret = Location.PRE_SUBJECT; + + // Error + else ret = Location.ERROR; + + currState.location=ret; + } + + const ntriples = { + name: "ntriples", + startState: function() { + return { + location : Location.PRE_SUBJECT, + uris : [], + anchors : [], + bnodes : [], + langs : [], + types : [] + }; + }, + token: function(stream, state) { + var ch = stream.next(); + if(ch == '<') { + transitState(state, ch); + var parsedURI = ''; + stream.eatWhile( function(c) { if( c != '#' && c != '>' ) { parsedURI += c; return true; } return false;} ); + state.uris.push(parsedURI); + if( stream.match('#', false) ) return 'variable'; + stream.next(); + transitState(state, '>'); + return 'variable'; + } + if(ch == '#') { + var parsedAnchor = ''; + stream.eatWhile(function(c) { if(c != '>' && c != ' ') { parsedAnchor+= c; return true; } return false;}); + state.anchors.push(parsedAnchor); + return 'url'; + } + if(ch == '>') { + transitState(state, '>'); + return 'variable'; + } + if(ch == '_') { + transitState(state, ch); + var parsedBNode = ''; + stream.eatWhile(function(c) { if( c != ' ' ) { parsedBNode += c; return true; } return false;}); + state.bnodes.push(parsedBNode); + stream.next(); + transitState(state, ' '); + return 'builtin'; + } + if(ch == '"') { + transitState(state, ch); + stream.eatWhile( function(c) { return c != '"'; } ); + stream.next(); + if( stream.peek() != '@' && stream.peek() != '^' ) { + transitState(state, '"'); + } + return 'string'; + } + if( ch == '@' ) { + transitState(state, '@'); + var parsedLang = ''; + stream.eatWhile(function(c) { if( c != ' ' ) { parsedLang += c; return true; } return false;}); + state.langs.push(parsedLang); + stream.next(); + transitState(state, ' '); + return 'string.special'; + } + if( ch == '^' ) { + stream.next(); + transitState(state, '^'); + var parsedType = ''; + stream.eatWhile(function(c) { if( c != '>' ) { parsedType += c; return true; } return false;} ); + state.types.push(parsedType); + stream.next(); + transitState(state, '>'); + return 'variable'; + } + if( ch == ' ' ) { + transitState(state, ch); + } + if( ch == '.' ) { + transitState(state, ch); + } + } + }; + /* globals: jsonld */ @@ -26676,12 +27305,14 @@ }); } + const language = new Compartment(); + const readOnlyEditor = new EditorView({ parent: document.getElementById('read-only-editor'), doc: `{}`, extensions: [ basicSetup, - json(), + language.of(json()), EditorState.readOnly.of(true), EditorView.editable.of(false), EditorView.contentAttributes.of({tabindex: '0'}) @@ -26694,8 +27325,14 @@ changes: { from: 0, to: _editor.state.doc.length, - insert: JSON.stringify(doc, null, 2) - } + insert: typeof(doc) === 'object' + ? JSON.stringify(doc, null, 2) + : doc + }, + // set the correct language + effects: language.reconfigure(typeof(doc) === 'object' + ? json() + : StreamLanguage.define(ntriples)) }); } } @@ -26789,6 +27426,17 @@ this.parseError = err.message; } break; + case 'nquads': + // TODO: this should happen elsewhere...like a watcher + const options = {format: 'application/n-quads', ...this.options}; + try { + const output = await jsonld.toRDF(this.doc, options); + setEditorValue(readOnlyEditor, output); + this.parseError = ''; + } catch(err) { + this.parseError = err.message; + } + break; default: setEditorValue(readOnlyEditor, {}); } diff --git a/playground/next/editor.mjs b/playground/next/editor.mjs index 568b85d5c..b6f9b0258 100644 --- a/playground/next/editor.mjs +++ b/playground/next/editor.mjs @@ -3,9 +3,11 @@ import {autocompletion, completeFromList} from '@codemirror/autocomplete'; import {EditorView, basicSetup} from 'codemirror'; import {createApp} from "petite-vue"; -import {EditorState} from '@codemirror/state' +import {Compartment, EditorState} from '@codemirror/state' import {indentWithTab} from '@codemirror/commands'; import {json, jsonParseLinter} from "@codemirror/lang-json"; +import {StreamLanguage} from '@codemirror/language'; +import {ntriples} from '@codemirror/legacy-modes/mode/ntriples'; import {keymap} from '@codemirror/view'; import {linter} from '@codemirror/lint'; @@ -86,12 +88,14 @@ function initEditor(id, content, varName) { }); } +const language = new Compartment(); + const readOnlyEditor = new EditorView({ parent: document.getElementById('read-only-editor'), doc: `{}`, extensions: [ basicSetup, - json(), + language.of(json()), EditorState.readOnly.of(true), EditorView.editable.of(false), EditorView.contentAttributes.of({tabindex: '0'}) @@ -104,8 +108,14 @@ function setEditorValue(_editor, doc) { changes: { from: 0, to: _editor.state.doc.length, - insert: JSON.stringify(doc, null, 2) - } + insert: typeof(doc) === 'object' + ? JSON.stringify(doc, null, 2) + : doc + }, + // set the correct language + effects: language.reconfigure(typeof(doc) === 'object' + ? json() + : StreamLanguage.define(ntriples)) }); } } @@ -199,6 +209,17 @@ window.app = createApp({ this.parseError = err.message; } break; + case 'nquads': + // TODO: this should happen elsewhere...like a watcher + const options = {format: 'application/n-quads', ...this.options}; + try { + const output = await jsonld.toRDF(this.doc, options); + setEditorValue(readOnlyEditor, output); + this.parseError = ''; + } catch(err) { + this.parseError = err.message; + } + break; default: setEditorValue(readOnlyEditor, {}); } diff --git a/playground/next/index.html b/playground/next/index.html index 53522cd02..c74d48fd4 100644 --- a/playground/next/index.html +++ b/playground/next/index.html @@ -254,6 +254,7 @@

JSON-LD Playground

Compacted
Flattened
Framed
+
N-Quads
From 5f3b3d4cd0783295c40d56505a51e2d49c95a214 Mon Sep 17 00:00:00 2001 From: Benjamin Young Date: Mon, 19 May 2025 14:32:30 -0400 Subject: [PATCH 33/54] Add Canonized tab and output. --- playground/next/editor.bundle.js | 18 ++++++++++++++++-- playground/next/editor.mjs | 18 ++++++++++++++++-- playground/next/index.html | 1 + 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/playground/next/editor.bundle.js b/playground/next/editor.bundle.js index 94d3ef111..faeb71d4d 100644 --- a/playground/next/editor.bundle.js +++ b/playground/next/editor.bundle.js @@ -27428,9 +27428,23 @@ break; case 'nquads': // TODO: this should happen elsewhere...like a watcher - const options = {format: 'application/n-quads', ...this.options}; try { - const output = await jsonld.toRDF(this.doc, options); + const output = await jsonld.toRDF(this.doc, { + format: 'application/n-quads', + ...this.options + }); + setEditorValue(readOnlyEditor, output); + this.parseError = ''; + } catch(err) { + this.parseError = err.message; + } + break; + case 'canonized': + // TODO: this should happen elsewhere...like a watcher + try { + const output = await jsonld.canonize(this.doc, { + format: 'application/n-quads', ...this.options + }); setEditorValue(readOnlyEditor, output); this.parseError = ''; } catch(err) { diff --git a/playground/next/editor.mjs b/playground/next/editor.mjs index b6f9b0258..cf05b0cd8 100644 --- a/playground/next/editor.mjs +++ b/playground/next/editor.mjs @@ -211,9 +211,23 @@ window.app = createApp({ break; case 'nquads': // TODO: this should happen elsewhere...like a watcher - const options = {format: 'application/n-quads', ...this.options}; try { - const output = await jsonld.toRDF(this.doc, options); + const output = await jsonld.toRDF(this.doc, { + format: 'application/n-quads', + ...this.options + }); + setEditorValue(readOnlyEditor, output); + this.parseError = ''; + } catch(err) { + this.parseError = err.message; + } + break; + case 'canonized': + // TODO: this should happen elsewhere...like a watcher + try { + const output = await jsonld.canonize(this.doc, { + format: 'application/n-quads', ...this.options + }); setEditorValue(readOnlyEditor, output); this.parseError = ''; } catch(err) { diff --git a/playground/next/index.html b/playground/next/index.html index c74d48fd4..44c53b63b 100644 --- a/playground/next/index.html +++ b/playground/next/index.html @@ -255,6 +255,7 @@

JSON-LD Playground

Flattened
Framed
N-Quads
+
Canonized
From 27519207ae0cc61dade16c6ea3c62e2134546de0 Mon Sep 17 00:00:00 2001 From: Benjamin Young Date: Mon, 19 May 2025 17:23:34 -0400 Subject: [PATCH 34/54] Add Table output. This mostly matches the current implementation with one notable exception: it outputs the datatype of XSD Strings or RDF langString values (which were previously hidden). It is not clear to me why hiding those would be useful and instead would seem to make the output in the table less accurate. We can hide those later if people feel there is value in doing that. --- _layouts/fomantic.liquid | 1 + playground/next/editor.bundle.js | 14 +++++++++++ playground/next/editor.mjs | 14 +++++++++++ playground/next/index.html | 42 +++++++++++++++++++++++++++++++- 4 files changed, 70 insertions(+), 1 deletion(-) diff --git a/_layouts/fomantic.liquid b/_layouts/fomantic.liquid index d502db967..bc5510e8f 100644 --- a/_layouts/fomantic.liquid +++ b/_layouts/fomantic.liquid @@ -25,6 +25,7 @@ .ui.top.menu .header.item { border: none; padding-left: 0; } .ui.top.menu .header.item > span { font-size: 150%; } .ui.top.menu .header.item > img { margin-right: 1rem; } + .ui.fitted.tab.segment > .ui.table { border-left: 0; border-top: 0; border-right: 0; margin-top: 0; } /* RDF Font gets priority */ i.rdf-icon-rdf.icon { font-family: 'rdf-font'; } diff --git a/playground/next/editor.bundle.js b/playground/next/editor.bundle.js index faeb71d4d..c050b9e15 100644 --- a/playground/next/editor.bundle.js +++ b/playground/next/editor.bundle.js @@ -27341,6 +27341,7 @@ doc: {}, contextDoc: {}, frameDoc: {}, + tableQuads: {}, parseError: '', inputTab: 'json-ld', outputTab: 'expanded', @@ -27360,6 +27361,9 @@ } return ''; }, + get hasTableQuads() { + return Object.keys(this.tableQuads).length > 0; + }, // methods async loadExample(file) { const rv = await fetch(`/examples/playground/${file}`); @@ -27451,6 +27455,16 @@ this.parseError = err.message; } break; + case 'table': + // TODO: this should happen elsewhere...like a watcher + try { + const output = await jsonld.toRDF(this.doc, this.options); + this.tableQuads = output; + this.parseError = ''; + } catch(err) { + this.parseError = err.message; + } + break; default: setEditorValue(readOnlyEditor, {}); } diff --git a/playground/next/editor.mjs b/playground/next/editor.mjs index cf05b0cd8..3b333d1fe 100644 --- a/playground/next/editor.mjs +++ b/playground/next/editor.mjs @@ -124,6 +124,7 @@ window.app = createApp({ doc: {}, contextDoc: {}, frameDoc: {}, + tableQuads: {}, parseError: '', inputTab: 'json-ld', outputTab: 'expanded', @@ -143,6 +144,9 @@ window.app = createApp({ } return ''; }, + get hasTableQuads() { + return Object.keys(this.tableQuads).length > 0; + }, // methods async loadExample(file) { const rv = await fetch(`/examples/playground/${file}`); @@ -234,6 +238,16 @@ window.app = createApp({ this.parseError = err.message; } break; + case 'table': + // TODO: this should happen elsewhere...like a watcher + try { + const output = await jsonld.toRDF(this.doc, this.options); + this.tableQuads = output; + this.parseError = ''; + } catch(err) { + this.parseError = err.message; + } + break; default: setEditorValue(readOnlyEditor, {}); } diff --git a/playground/next/index.html b/playground/next/index.html index 44c53b63b..7f22a0bc3 100644 --- a/playground/next/index.html +++ b/playground/next/index.html @@ -256,9 +256,49 @@

JSON-LD Playground

Framed
N-Quads
Canonized
+
Table
-
+
+ + + + + + + + + + + + + + + + + + + +
SubjectPredicateObjectLanguageDatatypeGraph
+ + + + + + + + + + +
From c23e45d5328f5505718a66a577faac09592038df Mon Sep 17 00:00:00 2001 From: Benjamin Young Date: Tue, 20 May 2025 10:41:56 -0400 Subject: [PATCH 35/54] Add remote document loading for main editor. --- playground/next/editor.bundle.js | 13 +++++++++++++ playground/next/editor.mjs | 13 +++++++++++++ playground/next/index.html | 9 +++++++++ 3 files changed, 35 insertions(+) diff --git a/playground/next/editor.bundle.js b/playground/next/editor.bundle.js index c050b9e15..343d77000 100644 --- a/playground/next/editor.bundle.js +++ b/playground/next/editor.bundle.js @@ -27342,6 +27342,7 @@ contextDoc: {}, frameDoc: {}, tableQuads: {}, + remoteDocURL: '', parseError: '', inputTab: 'json-ld', outputTab: 'expanded', @@ -27365,6 +27366,18 @@ return Object.keys(this.tableQuads).length > 0; }, // methods + async retrieveDoc() { + try { + const rv = await fetch(this.remoteDocURL); + if (!rv.ok) { + throw new Error(`HTTP error status: ${rv.status}`); + } + this.doc = await rv.json(); + setEditorValue(this.mainEditor, this.doc); + } catch (err) { + this.parseError = err.message; + } + }, async loadExample(file) { const rv = await fetch(`/examples/playground/${file}`); this.doc = await rv.json(); diff --git a/playground/next/editor.mjs b/playground/next/editor.mjs index 3b333d1fe..9989d9932 100644 --- a/playground/next/editor.mjs +++ b/playground/next/editor.mjs @@ -125,6 +125,7 @@ window.app = createApp({ contextDoc: {}, frameDoc: {}, tableQuads: {}, + remoteDocURL: '', parseError: '', inputTab: 'json-ld', outputTab: 'expanded', @@ -148,6 +149,18 @@ window.app = createApp({ return Object.keys(this.tableQuads).length > 0; }, // methods + async retrieveDoc() { + try { + const rv = await fetch(this.remoteDocURL); + if (!rv.ok) { + throw new Error(`HTTP error status: ${rv.status}`); + } + this.doc = await rv.json(); + setEditorValue(this.mainEditor, this.doc); + } catch (err) { + this.parseError = err.message; + } + }, async loadExample(file) { const rv = await fetch(`/examples/playground/${file}`); this.doc = await rv.json(); diff --git a/playground/next/index.html b/playground/next/index.html index 7f22a0bc3..6a41932c1 100644 --- a/playground/next/index.html +++ b/playground/next/index.html @@ -99,6 +99,15 @@

JSON-LD Playground

Date: Tue, 20 May 2025 10:43:34 -0400 Subject: [PATCH 36/54] Clear remoteDocURL; avoid state confusion on edit. The current Playground locks the editor which then leaves the user hunting for how to unlock the editor. This approach clears the URL field and keeps the editor unlocked, so editing can happen. --- playground/next/editor.bundle.js | 2 ++ playground/next/editor.mjs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/playground/next/editor.bundle.js b/playground/next/editor.bundle.js index 343d77000..932219989 100644 --- a/playground/next/editor.bundle.js +++ b/playground/next/editor.bundle.js @@ -27374,6 +27374,8 @@ } this.doc = await rv.json(); setEditorValue(this.mainEditor, this.doc); + // clear the remoteDocURL to avoid confusion around state + this.remoteDocURL = ''; } catch (err) { this.parseError = err.message; } diff --git a/playground/next/editor.mjs b/playground/next/editor.mjs index 9989d9932..f1468293e 100644 --- a/playground/next/editor.mjs +++ b/playground/next/editor.mjs @@ -157,6 +157,8 @@ window.app = createApp({ } this.doc = await rv.json(); setEditorValue(this.mainEditor, this.doc); + // clear the remoteDocURL to avoid confusion around state + this.remoteDocURL = ''; } catch (err) { this.parseError = err.message; } From e09c9d5f3e93853e80c04e21840a551cb63849ca Mon Sep 17 00:00:00 2001 From: Benjamin Young Date: Tue, 20 May 2025 12:14:32 -0400 Subject: [PATCH 37/54] Fix how context is passed into compact. --- playground/next/editor.bundle.js | 2 +- playground/next/editor.mjs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/playground/next/editor.bundle.js b/playground/next/editor.bundle.js index 932219989..7be439143 100644 --- a/playground/next/editor.bundle.js +++ b/playground/next/editor.bundle.js @@ -27417,7 +27417,7 @@ } setEditorValue(this.contextEditor, this.contextDoc); try { - const compacted = await jsonld.compact(this.doc, context['@context'] || {}, this.options); + const compacted = await jsonld.compact(this.doc, {'@context': context['@context'] || {}}, this.options); setEditorValue(readOnlyEditor, compacted); this.parseError = ''; } catch(err) { diff --git a/playground/next/editor.mjs b/playground/next/editor.mjs index f1468293e..08741c0fc 100644 --- a/playground/next/editor.mjs +++ b/playground/next/editor.mjs @@ -200,7 +200,7 @@ window.app = createApp({ } setEditorValue(this.contextEditor, this.contextDoc); try { - const compacted = await jsonld.compact(this.doc, context['@context'] || {}, this.options); + const compacted = await jsonld.compact(this.doc, {'@context': context['@context'] || {}}, this.options); setEditorValue(readOnlyEditor, compacted); this.parseError = ''; } catch(err) { From 791099817238ea361a1b615f113d0086724f6c3a Mon Sep 17 00:00:00 2001 From: Benjamin Young Date: Tue, 20 May 2025 12:35:11 -0400 Subject: [PATCH 38/54] Add side editor for flattened. Not sure how that got missed... --- playground/next/editor.bundle.js | 14 ++++++++++---- playground/next/editor.mjs | 14 ++++++++++---- playground/next/index.html | 6 +++--- 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/playground/next/editor.bundle.js b/playground/next/editor.bundle.js index 7be439143..1060476eb 100644 --- a/playground/next/editor.bundle.js +++ b/playground/next/editor.bundle.js @@ -27357,7 +27357,7 @@ }, // computed get editorColumns() { - if (this.outputTab === 'compacted' || this.outputTab === 'framed') { + if (['compacted', 'flattened', 'framed'].indexOf(this.outputTab) > -1) { return 'two column'; } return ''; @@ -27395,6 +27395,7 @@ }, async setOutputTab(value) { this.outputTab = value; + let context = this.contextDoc; switch (this.outputTab) { case 'expanded': // TODO: this should happen elsewhere...like a watcher @@ -27407,7 +27408,6 @@ } break; case 'compacted': - let context = this.contextDoc; if (JSON.stringify(context) === '{}' && '@context' in this.doc) { // no context set yet, so copy in the main document's context = { @@ -27425,9 +27425,15 @@ } break; case 'flattened': - // TODO: this should happen elsewhere...like a watcher + if (JSON.stringify(context) === '{}' && '@context' in this.doc) { + // no context set yet, so copy in the main document's + context = { + '@context': this.doc['@context'] + }; + this.contextDoc = context; + } try { - const flattened = await jsonld.flatten(this.doc, {}, this.options); + const flattened = await jsonld.flatten(this.doc, {'@context': context['@context'] || {}}, this.options); setEditorValue(readOnlyEditor, flattened); this.parseError = ''; } catch(err) { diff --git a/playground/next/editor.mjs b/playground/next/editor.mjs index 08741c0fc..f715d108a 100644 --- a/playground/next/editor.mjs +++ b/playground/next/editor.mjs @@ -140,7 +140,7 @@ window.app = createApp({ }, // computed get editorColumns() { - if (this.outputTab === 'compacted' || this.outputTab === 'framed') { + if (['compacted', 'flattened', 'framed'].indexOf(this.outputTab) > -1) { return 'two column'; } return ''; @@ -178,6 +178,7 @@ window.app = createApp({ }, async setOutputTab(value) { this.outputTab = value; + let context = this.contextDoc; switch (this.outputTab) { case 'expanded': // TODO: this should happen elsewhere...like a watcher @@ -190,7 +191,6 @@ window.app = createApp({ } break; case 'compacted': - let context = this.contextDoc; if (JSON.stringify(context) === '{}' && '@context' in this.doc) { // no context set yet, so copy in the main document's context = { @@ -208,9 +208,15 @@ window.app = createApp({ } break; case 'flattened': - // TODO: this should happen elsewhere...like a watcher + if (JSON.stringify(context) === '{}' && '@context' in this.doc) { + // no context set yet, so copy in the main document's + context = { + '@context': this.doc['@context'] + }; + this.contextDoc = context; + } try { - const flattened = await jsonld.flatten(this.doc, {}, this.options); + const flattened = await jsonld.flatten(this.doc, {'@context': context['@context'] || {}}, this.options); setEditorValue(readOnlyEditor, flattened); this.parseError = ''; } catch(err) { diff --git a/playground/next/index.html b/playground/next/index.html index 6a41932c1..6b4d12c48 100644 --- a/playground/next/index.html +++ b/playground/next/index.html @@ -235,19 +235,19 @@

JSON-LD Playground

Date: Tue, 20 May 2025 16:42:42 -0400 Subject: [PATCH 40/54] Debounce the editors to prevent constant parsing. This was showing up especially when editing a `@context` value which would result in one failed fetch for every character typed until the full URL was completed. Now, the parsing will happen one second after typing is complete. --- playground/next/editor.bundle.js | 28 ++++++++++++++++++++-------- playground/next/editor.mjs | 28 ++++++++++++++++++++-------- 2 files changed, 40 insertions(+), 16 deletions(-) diff --git a/playground/next/editor.bundle.js b/playground/next/editor.bundle.js index 4df4e1e85..aa1954bc3 100644 --- a/playground/next/editor.bundle.js +++ b/playground/next/editor.bundle.js @@ -27278,16 +27278,28 @@ // TODO: the next two functions could probably become a petite-vue component function editorListener(docName) { + let changes = []; // keep the changes as a list; then pick the last one + let timer; // we only want one timer, once the last one is done, use the result + function debounce(fn, delay) { + return (...args) => { + clearTimeout(timer); + timer = setTimeout(() => fn(...args), delay); + }; + } + return EditorView.updateListener.of((update) => { if (update.docChanged) { - // set the global `doc` to the latest string from the editor - try { - const parsed = JSON.parse(update.state.sliceDoc(0, update.state.doc.length)); - this[docName] = parsed; - this.parseError = ''; - } catch (err) { - this.parseError = err.message; - } } + changes.push(update.state.doc.toString()); + debounce((docName) => { + // set the global `doc` to the latest string from the editor + try { + const parsed = JSON.parse(changes[changes.length-1]); + this[docName] = parsed; + this.parseError = ''; + } catch (err) { + this.parseError = err.message; + } }, 1000).call(this, docName); + } }); } function initEditor(id, content, varName) { diff --git a/playground/next/editor.mjs b/playground/next/editor.mjs index 7ecd29845..7e78e9724 100644 --- a/playground/next/editor.mjs +++ b/playground/next/editor.mjs @@ -60,16 +60,28 @@ const jsonLdAtTerms = [ // TODO: the next two functions could probably become a petite-vue component function editorListener(docName) { + let changes = []; // keep the changes as a list; then pick the last one + let timer; // we only want one timer, once the last one is done, use the result + function debounce(fn, delay) { + return (...args) => { + clearTimeout(timer); + timer = setTimeout(() => fn(...args), delay); + }; + } + return EditorView.updateListener.of((update) => { if (update.docChanged) { - // set the global `doc` to the latest string from the editor - try { - const parsed = JSON.parse(update.state.sliceDoc(0, update.state.doc.length)); - this[docName] = parsed; - this.parseError = ''; - } catch (err) { - this.parseError = err.message; - }; + changes.push(update.state.doc.toString()); + debounce((docName) => { + // set the global `doc` to the latest string from the editor + try { + const parsed = JSON.parse(changes[changes.length-1]); + this[docName] = parsed; + this.parseError = ''; + } catch (err) { + this.parseError = err.message; + }; + }, 1000).call(this, docName); } }); } From 29f3d6414261de8b08e70247b35a65871b958e5c Mon Sep 17 00:00:00 2001 From: Benjamin Young Date: Tue, 20 May 2025 16:54:26 -0400 Subject: [PATCH 41/54] Fix incorrectly placed setEditorValues. This was causing framed and compacted side editors to be unusable. --- playground/next/editor.bundle.js | 7 +++---- playground/next/editor.mjs | 7 +++---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/playground/next/editor.bundle.js b/playground/next/editor.bundle.js index aa1954bc3..66bf97067 100644 --- a/playground/next/editor.bundle.js +++ b/playground/next/editor.bundle.js @@ -27423,8 +27423,10 @@ if (file === 'library.jsonld') { const frame = await fetch(`/examples/playground/library-frame.jsonld`); this.frameDoc = await frame.json(); + setEditorValue(this.frameEditor, this.frameDoc); } else { this.frameDoc = {}; + setEditorValue(this.frameEditor, this.frameDoc); } this.setOutputTab(this.outputTab); }, @@ -27450,7 +27452,6 @@ }; this.contextDoc = context; } - setEditorValue(this.contextEditor, this.contextDoc); try { const compacted = await jsonld.compact(this.doc, {'@context': context['@context'] || {}}, this.options); setEditorValue(readOnlyEditor, compacted); @@ -27476,10 +27477,8 @@ } break; case 'framed': - const frameDoc = this.frameDoc; - setEditorValue(this.frameEditor, frameDoc); try { - const framed = await jsonld.frame(this.doc, frameDoc, this.options); + const framed = await jsonld.frame(this.doc, this.frameDoc, this.options); setEditorValue(readOnlyEditor, framed); this.parseError = ''; } catch(err) { diff --git a/playground/next/editor.mjs b/playground/next/editor.mjs index 7e78e9724..f9326abd4 100644 --- a/playground/next/editor.mjs +++ b/playground/next/editor.mjs @@ -206,8 +206,10 @@ window.app = createApp({ if (file === 'library.jsonld') { const frame = await fetch(`/examples/playground/library-frame.jsonld`); this.frameDoc = await frame.json(); + setEditorValue(this.frameEditor, this.frameDoc); } else { this.frameDoc = {}; + setEditorValue(this.frameEditor, this.frameDoc); } this.setOutputTab(this.outputTab); }, @@ -233,7 +235,6 @@ window.app = createApp({ }; this.contextDoc = context; } - setEditorValue(this.contextEditor, this.contextDoc); try { const compacted = await jsonld.compact(this.doc, {'@context': context['@context'] || {}}, this.options); setEditorValue(readOnlyEditor, compacted); @@ -259,10 +260,8 @@ window.app = createApp({ } break; case 'framed': - const frameDoc = this.frameDoc; - setEditorValue(this.frameEditor, frameDoc); try { - const framed = await jsonld.frame(this.doc, frameDoc, this.options); + const framed = await jsonld.frame(this.doc, this.frameDoc, this.options); setEditorValue(readOnlyEditor, framed); this.parseError = ''; } catch(err) { From f8c766f2299b120a8f8b9bc185bd74359e6be6c6 Mon Sep 17 00:00:00 2001 From: Benjamin Young Date: Tue, 20 May 2025 16:55:21 -0400 Subject: [PATCH 42/54] Run setOutputTab on any data changes. This may still be a bit too often, but at least it is now more legible. However...the function name feels a bit odd... --- playground/next/editor.bundle.js | 9 +-------- playground/next/editor.mjs | 9 +-------- playground/next/index.html | 9 ++++----- 3 files changed, 6 insertions(+), 21 deletions(-) diff --git a/playground/next/editor.bundle.js b/playground/next/editor.bundle.js index 66bf97067..10705b24b 100644 --- a/playground/next/editor.bundle.js +++ b/playground/next/editor.bundle.js @@ -27431,7 +27431,7 @@ this.setOutputTab(this.outputTab); }, async setOutputTab(value) { - this.outputTab = value; + if (value) this.outputTab = value; let context = this.contextDoc; switch (this.outputTab) { case 'expanded': @@ -27524,13 +27524,6 @@ setEditorValue(readOnlyEditor, {}); } }, - async docChanged(docName, v) { - // TODO: see if this could work as a "component" - if (docName === 'main') this.doc = v; - if (docName === 'context') this.contextDoc = v; - if (docName === 'frame') this.frameDoc = v; - this.setOutputTab(this.outputTab); - }, initContextEditor() { this.contextEditor = initEditor.call(this, 'context-editor', this.contextDoc, 'contextDoc'); diff --git a/playground/next/editor.mjs b/playground/next/editor.mjs index f9326abd4..64633fcdf 100644 --- a/playground/next/editor.mjs +++ b/playground/next/editor.mjs @@ -214,7 +214,7 @@ window.app = createApp({ this.setOutputTab(this.outputTab); }, async setOutputTab(value) { - this.outputTab = value; + if (value) this.outputTab = value; let context = this.contextDoc; switch (this.outputTab) { case 'expanded': @@ -307,13 +307,6 @@ window.app = createApp({ setEditorValue(readOnlyEditor, {}); } }, - async docChanged(docName, v) { - // TODO: see if this could work as a "component" - if (docName === 'main') this.doc = v; - if (docName === 'context') this.contextDoc = v; - if (docName === 'frame') this.frameDoc = v; - this.setOutputTab(this.outputTab); - }, initContextEditor() { this.contextEditor = initEditor.call(this, 'context-editor', this.contextDoc, 'contextDoc'); diff --git a/playground/next/index.html b/playground/next/index.html index 56cb374fb..317a9fc52 100644 --- a/playground/next/index.html +++ b/playground/next/index.html @@ -29,7 +29,7 @@

JSON-LD Playground

Shift+Tab) to navigate out of the editor.

-
+
-
+
@@ -255,10 +254,10 @@

JSON-LD Playground

-
-
From 47e1dd2d8bbaee5f0c4d6407de5eb92f53d1e8f7 Mon Sep 17 00:00:00 2001 From: Benjamin Young Date: Tue, 20 May 2025 17:00:22 -0400 Subject: [PATCH 43/54] Reorder dependencies. We only have devDependencies because everything is staticly built and stored in the repo prior to production deployment. --- package.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/package.json b/package.json index 8266afb25..8a87df549 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "@codemirror/commands": "^6.8.1", "@codemirror/lang-javascript": "^6.2.3", "@codemirror/lang-json": "^6.0.1", + "@codemirror/legacy-modes": "^6.5.1", "@codemirror/lint": "^6.8.5", "@codemirror/state": "^6.5.2", "@codemirror/view": "^6.36.7", @@ -25,8 +26,5 @@ "petite-vue": "^0.4.1", "rollup": "^4.40.1", "wrangler": "^4.6.0" - }, - "dependencies": { - "@codemirror/legacy-modes": "^6.5.1" } } From d5245dfed290d53a545fa7abb77012f1784dac1c Mon Sep 17 00:00:00 2001 From: Benjamin Young Date: Wed, 21 May 2025 09:47:35 -0400 Subject: [PATCH 44/54] Add YAML-LD output tab. --- package.json | 4 +- playground/next/editor.bundle.js | 8253 +++++++++++++++++++++++++++++- playground/next/editor.mjs | 15 +- playground/next/index.html | 1 + 4 files changed, 8121 insertions(+), 152 deletions(-) diff --git a/package.json b/package.json index 8a87df549..6791af9f1 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "@codemirror/commands": "^6.8.1", "@codemirror/lang-javascript": "^6.2.3", "@codemirror/lang-json": "^6.0.1", + "@codemirror/lang-yaml": "^6.1.2", "@codemirror/legacy-modes": "^6.5.1", "@codemirror/lint": "^6.8.5", "@codemirror/state": "^6.5.2", @@ -25,6 +26,7 @@ "codemirror": "^6.0.1", "petite-vue": "^0.4.1", "rollup": "^4.40.1", - "wrangler": "^4.6.0" + "wrangler": "^4.6.0", + "yaml": "^2.8.0" } } diff --git a/playground/next/editor.bundle.js b/playground/next/editor.bundle.js index 10705b24b..d56c7d3f3 100644 --- a/playground/next/editor.bundle.js +++ b/playground/next/editor.bundle.js @@ -3948,7 +3948,7 @@ reach the column. Pass `strict` true to make it return -1 in that situation. */ - function findColumn(string, col, tabSize, strict) { + function findColumn$1(string, col, tabSize, strict) { for (let i = 0, n = 0;;) { if (n >= col) return i; @@ -7875,7 +7875,7 @@ into += line * view.viewState.heightOracle.lineLength; } let content = view.state.sliceDoc(block.from, block.to); - return block.from + findColumn(content, into, view.state.tabSize); + return block.from + findColumn$1(content, into, view.state.tabSize); } // In case of a high line height, Safari's caretRangeFromPoint treats // the space between lines as belonging to the last character of the @@ -13809,12 +13809,12 @@ let startCol = Math.min(a.col, b.col), endCol = Math.max(a.col, b.col); for (let i = startLine; i <= endLine; i++) { let line = state.doc.line(i); - let start = findColumn(line.text, startCol, state.tabSize, true); + let start = findColumn$1(line.text, startCol, state.tabSize, true); if (start < 0) { ranges.push(EditorSelection.cursor(line.to)); } else { - let end = findColumn(line.text, endCol, state.tabSize); + let end = findColumn$1(line.text, endCol, state.tabSize); ranges.push(EditorSelection.range(line.from + start, line.from + end)); } } @@ -16925,7 +16925,7 @@ /** A superclass that parsers should extend. */ - class Parser { + let Parser$1 = class Parser { /** Start a parse, returning a [partial parse](#common.PartialParse) object. [`fragments`](#common.TreeFragment) can be passed in to @@ -16953,7 +16953,7 @@ return done; } } - } + }; class StringInput { constructor(string) { this.string = string; @@ -16985,7 +16985,7 @@ be picked up by regular highlighters (though you can derive them from standard tags to allow highlighters to fall back to those). */ - class Tag { + let Tag$1 = class Tag { /** @internal */ @@ -17057,7 +17057,7 @@ return Modifier.get(tag.base || tag, tag.modified.concat(mod).sort((a, b) => a.id - b.id)); }; } - } + }; let nextModifierID = 0; class Modifier { constructor(name) { @@ -17071,7 +17071,7 @@ let exists = mods[0].instances.find(t => t.base == base && sameArray(mods, t.modified)); if (exists) return exists; - let set = [], tag = new Tag(base.name, set, base, mods); + let set = [], tag = new Tag$1(base.name, set, base, mods); for (let m of mods) m.instances.push(tag); let configs = powerSet(mods); @@ -17363,8 +17363,8 @@ rule = rule.next; return rule || null; } - const t = Tag.define; - const comment = t(), name = t(), typeName = t(name), propertyName = t(name), literal = t(), string = t(literal), number = t(literal), content = t(), heading = t(content), keyword = t(), operator = t(), punctuation = t(), bracket = t(punctuation), meta = t(); + const t = Tag$1.define; + const comment = t(), name = t(), typeName = t(name), propertyName = t(name), literal = t(), string$1 = t(literal), number = t(literal), content = t(), heading = t(content), keyword = t(), operator = t(), punctuation = t(), bracket = t(punctuation), meta = t(); /** The default set of highlighting [tags](#highlight.Tag). @@ -17449,19 +17449,19 @@ /** A string [literal](#highlight.tags.literal). */ - string, + string: string$1, /** A documentation [string](#highlight.tags.string). */ - docString: t(string), + docString: t(string$1), /** A character literal (subtag of [string](#highlight.tags.string)). */ - character: t(string), + character: t(string$1), /** An attribute value (subtag of [string](#highlight.tags.string)). */ - attributeValue: t(string), + attributeValue: t(string$1), /** A number [literal](#highlight.tags.literal). */ @@ -17716,31 +17716,31 @@ given element is being defined. Expected to be used with the various [name](#highlight.tags.name) tags. */ - definition: Tag.defineModifier("definition"), + definition: Tag$1.defineModifier("definition"), /** [Modifier](#highlight.Tag^defineModifier) that indicates that something is constant. Mostly expected to be used with [variable names](#highlight.tags.variableName). */ - constant: Tag.defineModifier("constant"), + constant: Tag$1.defineModifier("constant"), /** [Modifier](#highlight.Tag^defineModifier) used to indicate that a [variable](#highlight.tags.variableName) or [property name](#highlight.tags.propertyName) is being called or defined as a function. */ - function: Tag.defineModifier("function"), + function: Tag$1.defineModifier("function"), /** [Modifier](#highlight.Tag^defineModifier) that can be applied to [names](#highlight.tags.name) to indicate that they belong to the language's standard environment. */ - standard: Tag.defineModifier("standard"), + standard: Tag$1.defineModifier("standard"), /** [Modifier](#highlight.Tag^defineModifier) that indicates a given [names](#highlight.tags.name) is local to some scope. */ - local: Tag.defineModifier("local"), + local: Tag$1.defineModifier("local"), /** A generic variant [modifier](#highlight.Tag^defineModifier) that can be used to tag language-specific alternative variants of @@ -17749,11 +17749,11 @@ [variable name](#highlight.tags.variableName) tags, since those come up a lot. */ - special: Tag.defineModifier("special") + special: Tag$1.defineModifier("special") }; for (let name in tags) { let val = tags[name]; - if (val instanceof Tag) + if (val instanceof Tag$1) val.name = name; } /** @@ -18258,7 +18258,7 @@ promise resolves. */ static getSkippingParser(until) { - return new class extends Parser { + return new class extends Parser$1 { createParse(input, fragments, ranges) { let from = ranges[0].from, to = ranges[ranges.length - 1].to; let parser = { @@ -18787,10 +18787,24 @@ pos = next.to; } } + /** + An indentation strategy for delimited (usually bracketed) nodes. + Will, by default, indent one unit more than the parent's base + indent unless the line starts with a closing token. When `align` + is true and there are non-skipped nodes on the node's opening + line, the content of the node will be aligned with the end of the + opening node, like this: + + foo(bar, + baz) + */ + function delimitedIndent({ closing, align = true, units = 1 }) { + return (context) => delimitedStrategy(context, align, units, closing); + } function delimitedStrategy(context, align, units, closing, closedAt) { let after = context.textAfter, space = after.match(/^\s*/)[0].length; let closed = closing && after.slice(space, space + closing.length) == closing || closedAt == context.pos + space; - let aligned = bracketedAligned(context) ; + let aligned = align ? bracketedAligned(context) : null; if (aligned) return closed ? context.column(aligned.from) : context.column(aligned.to); return context.baseIndent + (closed ? 0 : context.unit * units); @@ -19778,7 +19792,7 @@ constructor(parser) { let data = defineLanguageFacet(parser.languageData); let p = fullParser(parser), self; - let impl = new class extends Parser { + let impl = new class extends Parser$1 { createParse(input, fragments, ranges) { return new Parse$1(self, input, fragments, ranges); } @@ -26100,6 +26114,29 @@ } TokenGroup.prototype.contextual = TokenGroup.prototype.fallback = TokenGroup.prototype.extend = false; TokenGroup.prototype.fallback = TokenGroup.prototype.extend = false; + /** + `@external tokens` declarations in the grammar should resolve to + an instance of this class. + */ + class ExternalTokenizer { + /** + Create a tokenizer. The first argument is the function that, + given an input stream, scans for the types of tokens it + recognizes at the stream's position, and calls + [`acceptToken`](#lr.InputStream.acceptToken) when it finds + one. + */ + constructor( + /** + @internal + */ + token, options = {}) { + this.token = token; + this.contextual = !!options.contextual; + this.fallback = !!options.fallback; + this.extend = !!options.extend; + } + } // Tokenizer data is stored a big uint16 array containing, for each // state: // @@ -26688,12 +26725,38 @@ } allows(term) { return !this.disabled || this.disabled[term] == 0; } } + const id = x => x; + /** + Context trackers are used to track stateful context (such as + indentation in the Python grammar, or parent elements in the XML + grammar) needed by external tokenizers. You declare them in a + grammar file as `@context exportName from "module"`. + + Context values should be immutable, and can be updated (replaced) + on shift or reduce actions. + + The export used in a `@context` declaration should be of this + type. + */ + class ContextTracker { + /** + Define a context tracker. + */ + constructor(spec) { + this.start = spec.start; + this.shift = spec.shift || id; + this.reduce = spec.reduce || id; + this.reuse = spec.reuse || id; + this.hash = spec.hash || (() => 0); + this.strict = spec.strict !== false; + } + } /** Holds the parse tables for a given grammar, as generated by `lezer-generator`, and provides [methods](#common.Parser) to parse content with. */ - class LRParser extends Parser { + class LRParser extends Parser$1 { /** @internal */ @@ -27005,7 +27068,7 @@ }); // This file was generated by lezer-generator. You probably shouldn't edit it. - const parser = LRParser.deserialize({ + const parser$1 = LRParser.deserialize({ version: 14, states: "$bOVQPOOOOQO'#Cb'#CbOnQPO'#CeOvQPO'#ClOOQO'#Cr'#CrQOQPOOOOQO'#Cg'#CgO}QPO'#CfO!SQPO'#CtOOQO,59P,59PO![QPO,59PO!aQPO'#CuOOQO,59W,59WO!iQPO,59WOVQPO,59QOqQPO'#CmO!nQPO,59`OOQO1G.k1G.kOVQPO'#CnO!vQPO,59aOOQO1G.r1G.rOOQO1G.l1G.lOOQO,59X,59XOOQO-E6k-E6kOOQO,59Y,59YOOQO-E6l-E6l", stateData: "#O~OeOS~OQSORSOSSOTSOWQO_ROgPO~OVXOgUO~O^[O~PVO[^O~O]_OVhX~OVaO~O]bO^iX~O^dO~O]_OVha~O]bO^ia~O", @@ -27063,7 +27126,7 @@ */ const jsonLanguage = /*@__PURE__*/LRLanguage.define({ name: "json", - parser: /*@__PURE__*/parser.configure({ + parser: /*@__PURE__*/parser$1.configure({ props: [ /*@__PURE__*/indentNodeProp.add({ Object: /*@__PURE__*/continuedIndent({ except: /^\s*\}/ }), @@ -27226,140 +27289,8026 @@ } }; - /* globals: jsonld */ - - - // Setup JSON-LD documentLoader - const xhrDocumentLoader = jsonld.documentLoaders.xhr(); - // FIXME: add UI to let users control and set context mapping - jsonld.documentLoader = function(url) { - // rewrite URLs that we know have secure JSON-LD Contexts - if(url === 'http://schema.org/' || url === 'http://schema.org') { - url = 'https://schema.org/'; - } + const ALIAS = Symbol.for('yaml.alias'); + const DOC = Symbol.for('yaml.document'); + const MAP = Symbol.for('yaml.map'); + const PAIR = Symbol.for('yaml.pair'); + const SCALAR$1 = Symbol.for('yaml.scalar'); + const SEQ = Symbol.for('yaml.seq'); + const NODE_TYPE = Symbol.for('yaml.node.type'); + const isAlias = (node) => !!node && typeof node === 'object' && node[NODE_TYPE] === ALIAS; + const isDocument = (node) => !!node && typeof node === 'object' && node[NODE_TYPE] === DOC; + const isMap = (node) => !!node && typeof node === 'object' && node[NODE_TYPE] === MAP; + const isPair = (node) => !!node && typeof node === 'object' && node[NODE_TYPE] === PAIR; + const isScalar$1 = (node) => !!node && typeof node === 'object' && node[NODE_TYPE] === SCALAR$1; + const isSeq = (node) => !!node && typeof node === 'object' && node[NODE_TYPE] === SEQ; + function isCollection$1(node) { + if (node && typeof node === 'object') + switch (node[NODE_TYPE]) { + case MAP: + case SEQ: + return true; + } + return false; + } + function isNode(node) { + if (node && typeof node === 'object') + switch (node[NODE_TYPE]) { + case ALIAS: + case MAP: + case SCALAR$1: + case SEQ: + return true; + } + return false; + } + const hasAnchor = (node) => (isScalar$1(node) || isCollection$1(node)) && !!node.anchor; - // if a non-HTTPS URL, use the proxy since we run in HTTPS only mode - if(!url.startsWith('https://')) { - url = [ - location.protocol, - '//', - location.host, - // NOTE: using hard-coded path so file can be shared with dev page - //location.pathname, - '/playground/', - 'proxy?url=', - url - ].join(''); - } + const BREAK$1 = Symbol('break visit'); + const SKIP$1 = Symbol('skip children'); + const REMOVE$1 = Symbol('remove node'); + /** + * Apply a visitor to an AST node or document. + * + * Walks through the tree (depth-first) starting from `node`, calling a + * `visitor` function with three arguments: + * - `key`: For sequence values and map `Pair`, the node's index in the + * collection. Within a `Pair`, `'key'` or `'value'`, correspondingly. + * `null` for the root node. + * - `node`: The current node. + * - `path`: The ancestry of the current node. + * + * The return value of the visitor may be used to control the traversal: + * - `undefined` (default): Do nothing and continue + * - `visit.SKIP`: Do not visit the children of this node, continue with next + * sibling + * - `visit.BREAK`: Terminate traversal completely + * - `visit.REMOVE`: Remove the current node, then continue with the next one + * - `Node`: Replace the current node, then continue by visiting it + * - `number`: While iterating the items of a sequence or map, set the index + * of the next step. This is useful especially if the index of the current + * node has changed. + * + * If `visitor` is a single function, it will be called with all values + * encountered in the tree, including e.g. `null` values. Alternatively, + * separate visitor functions may be defined for each `Map`, `Pair`, `Seq`, + * `Alias` and `Scalar` node. To define the same visitor function for more than + * one node type, use the `Collection` (map and seq), `Value` (map, seq & scalar) + * and `Node` (alias, map, seq & scalar) targets. Of all these, only the most + * specific defined one will be used for each node. + */ + function visit$1(node, visitor) { + const visitor_ = initVisitor(visitor); + if (isDocument(node)) { + const cd = visit_(null, node.contents, visitor_, Object.freeze([node])); + if (cd === REMOVE$1) + node.contents = null; + } + else + visit_(null, node, visitor_, Object.freeze([])); + } + // Without the `as symbol` casts, TS declares these in the `visit` + // namespace using `var`, but then complains about that because + // `unique symbol` must be `const`. + /** Terminate visit traversal completely */ + visit$1.BREAK = BREAK$1; + /** Do not visit the children of the current node */ + visit$1.SKIP = SKIP$1; + /** Remove the current node */ + visit$1.REMOVE = REMOVE$1; + function visit_(key, node, visitor, path) { + const ctrl = callVisitor(key, node, visitor, path); + if (isNode(ctrl) || isPair(ctrl)) { + replaceNode(key, path, ctrl); + return visit_(key, ctrl, visitor, path); + } + if (typeof ctrl !== 'symbol') { + if (isCollection$1(node)) { + path = Object.freeze(path.concat(node)); + for (let i = 0; i < node.items.length; ++i) { + const ci = visit_(i, node.items[i], visitor, path); + if (typeof ci === 'number') + i = ci - 1; + else if (ci === BREAK$1) + return BREAK$1; + else if (ci === REMOVE$1) { + node.items.splice(i, 1); + i -= 1; + } + } + } + else if (isPair(node)) { + path = Object.freeze(path.concat(node)); + const ck = visit_('key', node.key, visitor, path); + if (ck === BREAK$1) + return BREAK$1; + else if (ck === REMOVE$1) + node.key = null; + const cv = visit_('value', node.value, visitor, path); + if (cv === BREAK$1) + return BREAK$1; + else if (cv === REMOVE$1) + node.value = null; + } + } + return ctrl; + } + /** + * Apply an async visitor to an AST node or document. + * + * Walks through the tree (depth-first) starting from `node`, calling a + * `visitor` function with three arguments: + * - `key`: For sequence values and map `Pair`, the node's index in the + * collection. Within a `Pair`, `'key'` or `'value'`, correspondingly. + * `null` for the root node. + * - `node`: The current node. + * - `path`: The ancestry of the current node. + * + * The return value of the visitor may be used to control the traversal: + * - `Promise`: Must resolve to one of the following values + * - `undefined` (default): Do nothing and continue + * - `visit.SKIP`: Do not visit the children of this node, continue with next + * sibling + * - `visit.BREAK`: Terminate traversal completely + * - `visit.REMOVE`: Remove the current node, then continue with the next one + * - `Node`: Replace the current node, then continue by visiting it + * - `number`: While iterating the items of a sequence or map, set the index + * of the next step. This is useful especially if the index of the current + * node has changed. + * + * If `visitor` is a single function, it will be called with all values + * encountered in the tree, including e.g. `null` values. Alternatively, + * separate visitor functions may be defined for each `Map`, `Pair`, `Seq`, + * `Alias` and `Scalar` node. To define the same visitor function for more than + * one node type, use the `Collection` (map and seq), `Value` (map, seq & scalar) + * and `Node` (alias, map, seq & scalar) targets. Of all these, only the most + * specific defined one will be used for each node. + */ + async function visitAsync(node, visitor) { + const visitor_ = initVisitor(visitor); + if (isDocument(node)) { + const cd = await visitAsync_(null, node.contents, visitor_, Object.freeze([node])); + if (cd === REMOVE$1) + node.contents = null; + } + else + await visitAsync_(null, node, visitor_, Object.freeze([])); + } + // Without the `as symbol` casts, TS declares these in the `visit` + // namespace using `var`, but then complains about that because + // `unique symbol` must be `const`. + /** Terminate visit traversal completely */ + visitAsync.BREAK = BREAK$1; + /** Do not visit the children of the current node */ + visitAsync.SKIP = SKIP$1; + /** Remove the current node */ + visitAsync.REMOVE = REMOVE$1; + async function visitAsync_(key, node, visitor, path) { + const ctrl = await callVisitor(key, node, visitor, path); + if (isNode(ctrl) || isPair(ctrl)) { + replaceNode(key, path, ctrl); + return visitAsync_(key, ctrl, visitor, path); + } + if (typeof ctrl !== 'symbol') { + if (isCollection$1(node)) { + path = Object.freeze(path.concat(node)); + for (let i = 0; i < node.items.length; ++i) { + const ci = await visitAsync_(i, node.items[i], visitor, path); + if (typeof ci === 'number') + i = ci - 1; + else if (ci === BREAK$1) + return BREAK$1; + else if (ci === REMOVE$1) { + node.items.splice(i, 1); + i -= 1; + } + } + } + else if (isPair(node)) { + path = Object.freeze(path.concat(node)); + const ck = await visitAsync_('key', node.key, visitor, path); + if (ck === BREAK$1) + return BREAK$1; + else if (ck === REMOVE$1) + node.key = null; + const cv = await visitAsync_('value', node.value, visitor, path); + if (cv === BREAK$1) + return BREAK$1; + else if (cv === REMOVE$1) + node.value = null; + } + } + return ctrl; + } + function initVisitor(visitor) { + if (typeof visitor === 'object' && + (visitor.Collection || visitor.Node || visitor.Value)) { + return Object.assign({ + Alias: visitor.Node, + Map: visitor.Node, + Scalar: visitor.Node, + Seq: visitor.Node + }, visitor.Value && { + Map: visitor.Value, + Scalar: visitor.Value, + Seq: visitor.Value + }, visitor.Collection && { + Map: visitor.Collection, + Seq: visitor.Collection + }, visitor); + } + return visitor; + } + function callVisitor(key, node, visitor, path) { + if (typeof visitor === 'function') + return visitor(key, node, path); + if (isMap(node)) + return visitor.Map?.(key, node, path); + if (isSeq(node)) + return visitor.Seq?.(key, node, path); + if (isPair(node)) + return visitor.Pair?.(key, node, path); + if (isScalar$1(node)) + return visitor.Scalar?.(key, node, path); + if (isAlias(node)) + return visitor.Alias?.(key, node, path); + return undefined; + } + function replaceNode(key, path, node) { + const parent = path[path.length - 1]; + if (isCollection$1(parent)) { + parent.items[key] = node; + } + else if (isPair(parent)) { + if (key === 'key') + parent.key = node; + else + parent.value = node; + } + else if (isDocument(parent)) { + parent.contents = node; + } + else { + const pt = isAlias(parent) ? 'alias' : 'scalar'; + throw new Error(`Cannot replace node with ${pt} parent`); + } + } - return xhrDocumentLoader(url); + const escapeChars = { + '!': '%21', + ',': '%2C', + '[': '%5B', + ']': '%5D', + '{': '%7B', + '}': '%7D' }; + const escapeTagName = (tn) => tn.replace(/[!,[\]{}]/g, ch => escapeChars[ch]); + class Directives { + constructor(yaml, tags) { + /** + * The directives-end/doc-start marker `---`. If `null`, a marker may still be + * included in the document's stringified representation. + */ + this.docStart = null; + /** The doc-end marker `...`. */ + this.docEnd = false; + this.yaml = Object.assign({}, Directives.defaultYaml, yaml); + this.tags = Object.assign({}, Directives.defaultTags, tags); + } + clone() { + const copy = new Directives(this.yaml, this.tags); + copy.docStart = this.docStart; + return copy; + } + /** + * During parsing, get a Directives instance for the current document and + * update the stream state according to the current version's spec. + */ + atDocument() { + const res = new Directives(this.yaml, this.tags); + switch (this.yaml.version) { + case '1.1': + this.atNextDocument = true; + break; + case '1.2': + this.atNextDocument = false; + this.yaml = { + explicit: Directives.defaultYaml.explicit, + version: '1.2' + }; + this.tags = Object.assign({}, Directives.defaultTags); + break; + } + return res; + } + /** + * @param onError - May be called even if the action was successful + * @returns `true` on success + */ + add(line, onError) { + if (this.atNextDocument) { + this.yaml = { explicit: Directives.defaultYaml.explicit, version: '1.1' }; + this.tags = Object.assign({}, Directives.defaultTags); + this.atNextDocument = false; + } + const parts = line.trim().split(/[ \t]+/); + const name = parts.shift(); + switch (name) { + case '%TAG': { + if (parts.length !== 2) { + onError(0, '%TAG directive should contain exactly two parts'); + if (parts.length < 2) + return false; + } + const [handle, prefix] = parts; + this.tags[handle] = prefix; + return true; + } + case '%YAML': { + this.yaml.explicit = true; + if (parts.length !== 1) { + onError(0, '%YAML directive should contain exactly one part'); + return false; + } + const [version] = parts; + if (version === '1.1' || version === '1.2') { + this.yaml.version = version; + return true; + } + else { + const isValid = /^\d+\.\d+$/.test(version); + onError(6, `Unsupported YAML version ${version}`, isValid); + return false; + } + } + default: + onError(0, `Unknown directive ${name}`, true); + return false; + } + } + /** + * Resolves a tag, matching handles to those defined in %TAG directives. + * + * @returns Resolved tag, which may also be the non-specific tag `'!'` or a + * `'!local'` tag, or `null` if unresolvable. + */ + tagName(source, onError) { + if (source === '!') + return '!'; // non-specific tag + if (source[0] !== '!') { + onError(`Not a valid tag: ${source}`); + return null; + } + if (source[1] === '<') { + const verbatim = source.slice(2, -1); + if (verbatim === '!' || verbatim === '!!') { + onError(`Verbatim tags aren't resolved, so ${source} is invalid.`); + return null; + } + if (source[source.length - 1] !== '>') + onError('Verbatim tags must end with a >'); + return verbatim; + } + const [, handle, suffix] = source.match(/^(.*!)([^!]*)$/s); + if (!suffix) + onError(`The ${source} tag has no suffix`); + const prefix = this.tags[handle]; + if (prefix) { + try { + return prefix + decodeURIComponent(suffix); + } + catch (error) { + onError(String(error)); + return null; + } + } + if (handle === '!') + return source; // local tag + onError(`Could not resolve tag: ${source}`); + return null; + } + /** + * Given a fully resolved tag, returns its printable string form, + * taking into account current tag prefixes and defaults. + */ + tagString(tag) { + for (const [handle, prefix] of Object.entries(this.tags)) { + if (tag.startsWith(prefix)) + return handle + escapeTagName(tag.substring(prefix.length)); + } + return tag[0] === '!' ? tag : `!<${tag}>`; + } + toString(doc) { + const lines = this.yaml.explicit + ? [`%YAML ${this.yaml.version || '1.2'}`] + : []; + const tagEntries = Object.entries(this.tags); + let tagNames; + if (doc && tagEntries.length > 0 && isNode(doc.contents)) { + const tags = {}; + visit$1(doc.contents, (_key, node) => { + if (isNode(node) && node.tag) + tags[node.tag] = true; + }); + tagNames = Object.keys(tags); + } + else + tagNames = []; + for (const [handle, prefix] of tagEntries) { + if (handle === '!!' && prefix === 'tag:yaml.org,2002:') + continue; + if (!doc || tagNames.some(tn => tn.startsWith(prefix))) + lines.push(`%TAG ${handle} ${prefix}`); + } + return lines.join('\n'); + } + } + Directives.defaultYaml = { explicit: false, version: '1.2' }; + Directives.defaultTags = { '!!': 'tag:yaml.org,2002:' }; - const jsonLdAtTerms = [ - { label: "@context", type: "keyword", info: "Defines the JSON-LD context" }, - { label: "@id", type: "keyword", info: "Specifies the unique identifier of an entity" }, - { label: "@type", type: "keyword", info: "Defines the type of an entity" }, - { label: "@value", type: "keyword", info: "Represents the value of a node" }, - { label: "@language", type: "keyword", info: "Specifies the language of a string value" }, - { label: "@graph", type: "keyword", info: "Represents a named graph" }, - { label: "@list", type: "keyword", info: "Denotes an ordered list" }, - { label: "@set", type: "keyword", info: "Denotes an unordered set" }, - { label: "@reverse", type: "keyword", info: "Defines reverse properties" }, - { label: "@index", type: "keyword", info: "Specifies an index for ordered data" }, - { label: "@base", type: "keyword", info: "Defines the base IRI" }, - { label: "@vocab", type: "keyword", info: "Defines the default vocabulary" }, - { label: "@container", type: "keyword", info: "Specifies container types for properties" }, - { label: "@nest", type: "keyword", info: "Allows nesting of properties" }, - { label: "@prefix", type: "keyword", info: "Defines a prefix mapping" }, - { label: "@propagate", type: "keyword", info: "Controls context propagation" }, - { label: "@protected", type: "keyword", info: "Prevents term overrides" }, - { label: "@version", type: "keyword", info: "Specifies the JSON-LD version" } - ]; - - // TODO: the next two functions could probably become a petite-vue component - function editorListener(docName) { - let changes = []; // keep the changes as a list; then pick the last one - let timer; // we only want one timer, once the last one is done, use the result - function debounce(fn, delay) { - return (...args) => { - clearTimeout(timer); - timer = setTimeout(() => fn(...args), delay); - }; - } - - return EditorView.updateListener.of((update) => { - if (update.docChanged) { - changes.push(update.state.doc.toString()); - debounce((docName) => { - // set the global `doc` to the latest string from the editor - try { - const parsed = JSON.parse(changes[changes.length-1]); - this[docName] = parsed; - this.parseError = ''; - } catch (err) { - this.parseError = err.message; - } }, 1000).call(this, docName); + /** + * Verify that the input string is a valid anchor. + * + * Will throw on errors. + */ + function anchorIsValid(anchor) { + if (/[\x00-\x19\s,[\]{}]/.test(anchor)) { + const sa = JSON.stringify(anchor); + const msg = `Anchor must not contain whitespace or control characters: ${sa}`; + throw new Error(msg); } - }); + return true; } - function initEditor(id, content, varName) { - return new EditorView({ - parent: document.getElementById(id), - doc: JSON.stringify(content, null, 2), - extensions: [ - basicSetup, - keymap.of([indentWithTab]), - json(), - linter(jsonParseLinter()), - autocompletion({override: [completeFromList(jsonLdAtTerms)]}), - editorListener.call(this, varName) - ] - }); + function anchorNames(root) { + const anchors = new Set(); + visit$1(root, { + Value(_key, node) { + if (node.anchor) + anchors.add(node.anchor); + } + }); + return anchors; + } + /** Find a new anchor name with the given `prefix` and a one-indexed suffix. */ + function findNewAnchor(prefix, exclude) { + for (let i = 1; true; ++i) { + const name = `${prefix}${i}`; + if (!exclude.has(name)) + return name; + } + } + function createNodeAnchors(doc, prefix) { + const aliasObjects = []; + const sourceObjects = new Map(); + let prevAnchors = null; + return { + onAnchor: (source) => { + aliasObjects.push(source); + prevAnchors ?? (prevAnchors = anchorNames(doc)); + const anchor = findNewAnchor(prefix, prevAnchors); + prevAnchors.add(anchor); + return anchor; + }, + /** + * With circular references, the source node is only resolved after all + * of its child nodes are. This is why anchors are set only after all of + * the nodes have been created. + */ + setAnchors: () => { + for (const source of aliasObjects) { + const ref = sourceObjects.get(source); + if (typeof ref === 'object' && + ref.anchor && + (isScalar$1(ref.node) || isCollection$1(ref.node))) { + ref.node.anchor = ref.anchor; + } + else { + const error = new Error('Failed to resolve repeated object (this should not happen)'); + error.source = source; + throw error; + } + } + }, + sourceObjects + }; } - const language = new Compartment(); + /** + * Applies the JSON.parse reviver algorithm as defined in the ECMA-262 spec, + * in section 24.5.1.1 "Runtime Semantics: InternalizeJSONProperty" of the + * 2021 edition: https://tc39.es/ecma262/#sec-json.parse + * + * Includes extensions for handling Map and Set objects. + */ + function applyReviver(reviver, obj, key, val) { + if (val && typeof val === 'object') { + if (Array.isArray(val)) { + for (let i = 0, len = val.length; i < len; ++i) { + const v0 = val[i]; + const v1 = applyReviver(reviver, val, String(i), v0); + // eslint-disable-next-line @typescript-eslint/no-array-delete + if (v1 === undefined) + delete val[i]; + else if (v1 !== v0) + val[i] = v1; + } + } + else if (val instanceof Map) { + for (const k of Array.from(val.keys())) { + const v0 = val.get(k); + const v1 = applyReviver(reviver, val, k, v0); + if (v1 === undefined) + val.delete(k); + else if (v1 !== v0) + val.set(k, v1); + } + } + else if (val instanceof Set) { + for (const v0 of Array.from(val)) { + const v1 = applyReviver(reviver, val, v0, v0); + if (v1 === undefined) + val.delete(v0); + else if (v1 !== v0) { + val.delete(v0); + val.add(v1); + } + } + } + else { + for (const [k, v0] of Object.entries(val)) { + const v1 = applyReviver(reviver, val, k, v0); + if (v1 === undefined) + delete val[k]; + else if (v1 !== v0) + val[k] = v1; + } + } + } + return reviver.call(obj, key, val); + } - const readOnlyEditor = new EditorView({ - parent: document.getElementById('read-only-editor'), - doc: `{}`, - extensions: [ - basicSetup, - language.of(json()), - EditorState.readOnly.of(true), - EditorView.editable.of(false), - EditorView.contentAttributes.of({tabindex: '0'}) - ] - }); + /** + * Recursively convert any node or its contents to native JavaScript + * + * @param value - The input value + * @param arg - If `value` defines a `toJSON()` method, use this + * as its first argument + * @param ctx - Conversion context, originally set in Document#toJS(). If + * `{ keep: true }` is not set, output should be suitable for JSON + * stringification. + */ + function toJS(value, arg, ctx) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + if (Array.isArray(value)) + return value.map((v, i) => toJS(v, String(i), ctx)); + if (value && typeof value.toJSON === 'function') { + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + if (!ctx || !hasAnchor(value)) + return value.toJSON(arg, ctx); + const data = { aliasCount: 0, count: 1, res: undefined }; + ctx.anchors.set(value, data); + ctx.onCreate = res => { + data.res = res; + delete ctx.onCreate; + }; + const res = value.toJSON(arg, ctx); + if (ctx.onCreate) + ctx.onCreate(res); + return res; + } + if (typeof value === 'bigint' && !ctx?.keep) + return Number(value); + return value; + } - function setEditorValue(_editor, doc) { - if (_editor) { - _editor.dispatch({ - changes: { - from: 0, - to: _editor.state.doc.length, - insert: typeof(doc) === 'object' - ? JSON.stringify(doc, null, 2) - : doc - }, - // set the correct language - effects: language.reconfigure(typeof(doc) === 'object' - ? json() - : StreamLanguage.define(ntriples)) - }); - } + class NodeBase { + constructor(type) { + Object.defineProperty(this, NODE_TYPE, { value: type }); + } + /** Create a copy of this node. */ + clone() { + const copy = Object.create(Object.getPrototypeOf(this), Object.getOwnPropertyDescriptors(this)); + if (this.range) + copy.range = this.range.slice(); + return copy; + } + /** A plain JavaScript representation of this node. */ + toJS(doc, { mapAsMap, maxAliasCount, onAnchor, reviver } = {}) { + if (!isDocument(doc)) + throw new TypeError('A document argument is required'); + const ctx = { + anchors: new Map(), + doc, + keep: true, + mapAsMap: mapAsMap === true, + mapKeyWarned: false, + maxAliasCount: typeof maxAliasCount === 'number' ? maxAliasCount : 100 + }; + const res = toJS(this, '', ctx); + if (typeof onAnchor === 'function') + for (const { count, res } of ctx.anchors.values()) + onAnchor(res, count); + return typeof reviver === 'function' + ? applyReviver(reviver, { '': res }, '', res) + : res; + } } - window.app = Qe({ - doc: {}, - contextDoc: {}, - frameDoc: {}, - tableQuads: {}, - remoteDocURL: '', - remoteSideDocURL: '', - parseError: '', - inputTab: 'json-ld', - outputTab: 'expanded', - options: { + let Alias$1 = class Alias extends NodeBase { + constructor(source) { + super(ALIAS); + this.source = source; + Object.defineProperty(this, 'tag', { + set() { + throw new Error('Alias nodes cannot have tags'); + } + }); + } + /** + * Resolve the value of this alias within `doc`, finding the last + * instance of the `source` anchor before this node. + */ + resolve(doc, ctx) { + let nodes; + if (ctx?.aliasResolveCache) { + nodes = ctx.aliasResolveCache; + } + else { + nodes = []; + visit$1(doc, { + Node: (_key, node) => { + if (isAlias(node) || hasAnchor(node)) + nodes.push(node); + } + }); + if (ctx) + ctx.aliasResolveCache = nodes; + } + let found = undefined; + for (const node of nodes) { + if (node === this) + break; + if (node.anchor === this.source) + found = node; + } + return found; + } + toJSON(_arg, ctx) { + if (!ctx) + return { source: this.source }; + const { anchors, doc, maxAliasCount } = ctx; + const source = this.resolve(doc, ctx); + if (!source) { + const msg = `Unresolved alias (the anchor must be set before the alias): ${this.source}`; + throw new ReferenceError(msg); + } + let data = anchors.get(source); + if (!data) { + // Resolve anchors for Node.prototype.toJS() + toJS(source, null, ctx); + data = anchors.get(source); + } + /* istanbul ignore if */ + if (!data || data.res === undefined) { + const msg = 'This should not happen: Alias anchor was not resolved?'; + throw new ReferenceError(msg); + } + if (maxAliasCount >= 0) { + data.count += 1; + if (data.aliasCount === 0) + data.aliasCount = getAliasCount(doc, source, anchors); + if (data.count * data.aliasCount > maxAliasCount) { + const msg = 'Excessive alias count indicates a resource exhaustion attack'; + throw new ReferenceError(msg); + } + } + return data.res; + } + toString(ctx, _onComment, _onChompKeep) { + const src = `*${this.source}`; + if (ctx) { + anchorIsValid(this.source); + if (ctx.options.verifyAliasOrder && !ctx.anchors.has(this.source)) { + const msg = `Unresolved alias (the anchor must be set before the alias): ${this.source}`; + throw new Error(msg); + } + if (ctx.implicitKey) + return `${src} `; + } + return src; + } + }; + function getAliasCount(doc, node, anchors) { + if (isAlias(node)) { + const source = node.resolve(doc); + const anchor = anchors && source && anchors.get(source); + return anchor ? anchor.count * anchor.aliasCount : 0; + } + else if (isCollection$1(node)) { + let count = 0; + for (const item of node.items) { + const c = getAliasCount(doc, item, anchors); + if (c > count) + count = c; + } + return count; + } + else if (isPair(node)) { + const kc = getAliasCount(doc, node.key, anchors); + const vc = getAliasCount(doc, node.value, anchors); + return Math.max(kc, vc); + } + return 1; + } + + const isScalarValue = (value) => !value || (typeof value !== 'function' && typeof value !== 'object'); + class Scalar extends NodeBase { + constructor(value) { + super(SCALAR$1); + this.value = value; + } + toJSON(arg, ctx) { + return ctx?.keep ? this.value : toJS(this.value, arg, ctx); + } + toString() { + return String(this.value); + } + } + Scalar.BLOCK_FOLDED = 'BLOCK_FOLDED'; + Scalar.BLOCK_LITERAL = 'BLOCK_LITERAL'; + Scalar.PLAIN = 'PLAIN'; + Scalar.QUOTE_DOUBLE = 'QUOTE_DOUBLE'; + Scalar.QUOTE_SINGLE = 'QUOTE_SINGLE'; + + const defaultTagPrefix = 'tag:yaml.org,2002:'; + function findTagObject(value, tagName, tags) { + if (tagName) { + const match = tags.filter(t => t.tag === tagName); + const tagObj = match.find(t => !t.format) ?? match[0]; + if (!tagObj) + throw new Error(`Tag ${tagName} not found`); + return tagObj; + } + return tags.find(t => t.identify?.(value) && !t.format); + } + function createNode(value, tagName, ctx) { + if (isDocument(value)) + value = value.contents; + if (isNode(value)) + return value; + if (isPair(value)) { + const map = ctx.schema[MAP].createNode?.(ctx.schema, null, ctx); + map.items.push(value); + return map; + } + if (value instanceof String || + value instanceof Number || + value instanceof Boolean || + (typeof BigInt !== 'undefined' && value instanceof BigInt) // not supported everywhere + ) { + // https://tc39.es/ecma262/#sec-serializejsonproperty + value = value.valueOf(); + } + const { aliasDuplicateObjects, onAnchor, onTagObj, schema, sourceObjects } = ctx; + // Detect duplicate references to the same object & use Alias nodes for all + // after first. The `ref` wrapper allows for circular references to resolve. + let ref = undefined; + if (aliasDuplicateObjects && value && typeof value === 'object') { + ref = sourceObjects.get(value); + if (ref) { + ref.anchor ?? (ref.anchor = onAnchor(value)); + return new Alias$1(ref.anchor); + } + else { + ref = { anchor: null, node: null }; + sourceObjects.set(value, ref); + } + } + if (tagName?.startsWith('!!')) + tagName = defaultTagPrefix + tagName.slice(2); + let tagObj = findTagObject(value, tagName, schema.tags); + if (!tagObj) { + if (value && typeof value.toJSON === 'function') { + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + value = value.toJSON(); + } + if (!value || typeof value !== 'object') { + const node = new Scalar(value); + if (ref) + ref.node = node; + return node; + } + tagObj = + value instanceof Map + ? schema[MAP] + : Symbol.iterator in Object(value) + ? schema[SEQ] + : schema[MAP]; + } + if (onTagObj) { + onTagObj(tagObj); + delete ctx.onTagObj; + } + const node = tagObj?.createNode + ? tagObj.createNode(ctx.schema, value, ctx) + : typeof tagObj?.nodeClass?.from === 'function' + ? tagObj.nodeClass.from(ctx.schema, value, ctx) + : new Scalar(value); + if (tagName) + node.tag = tagName; + else if (!tagObj.default) + node.tag = tagObj.tag; + if (ref) + ref.node = node; + return node; + } + + function collectionFromPath(schema, path, value) { + let v = value; + for (let i = path.length - 1; i >= 0; --i) { + const k = path[i]; + if (typeof k === 'number' && Number.isInteger(k) && k >= 0) { + const a = []; + a[k] = v; + v = a; + } + else { + v = new Map([[k, v]]); + } + } + return createNode(v, undefined, { + aliasDuplicateObjects: false, + keepUndefined: false, + onAnchor: () => { + throw new Error('This should not happen, please report a bug.'); + }, + schema, + sourceObjects: new Map() + }); + } + // Type guard is intentionally a little wrong so as to be more useful, + // as it does not cover untypable empty non-string iterables (e.g. []). + const isEmptyPath = (path) => path == null || + (typeof path === 'object' && !!path[Symbol.iterator]().next().done); + class Collection extends NodeBase { + constructor(type, schema) { + super(type); + Object.defineProperty(this, 'schema', { + value: schema, + configurable: true, + enumerable: false, + writable: true + }); + } + /** + * Create a copy of this collection. + * + * @param schema - If defined, overwrites the original's schema + */ + clone(schema) { + const copy = Object.create(Object.getPrototypeOf(this), Object.getOwnPropertyDescriptors(this)); + if (schema) + copy.schema = schema; + copy.items = copy.items.map(it => isNode(it) || isPair(it) ? it.clone(schema) : it); + if (this.range) + copy.range = this.range.slice(); + return copy; + } + /** + * Adds a value to the collection. For `!!map` and `!!omap` the value must + * be a Pair instance or a `{ key, value }` object, which may not have a key + * that already exists in the map. + */ + addIn(path, value) { + if (isEmptyPath(path)) + this.add(value); + else { + const [key, ...rest] = path; + const node = this.get(key, true); + if (isCollection$1(node)) + node.addIn(rest, value); + else if (node === undefined && this.schema) + this.set(key, collectionFromPath(this.schema, rest, value)); + else + throw new Error(`Expected YAML collection at ${key}. Remaining path: ${rest}`); + } + } + /** + * Removes a value from the collection. + * @returns `true` if the item was found and removed. + */ + deleteIn(path) { + const [key, ...rest] = path; + if (rest.length === 0) + return this.delete(key); + const node = this.get(key, true); + if (isCollection$1(node)) + return node.deleteIn(rest); + else + throw new Error(`Expected YAML collection at ${key}. Remaining path: ${rest}`); + } + /** + * Returns item at `key`, or `undefined` if not found. By default unwraps + * scalar values from their surrounding node; to disable set `keepScalar` to + * `true` (collections are always returned intact). + */ + getIn(path, keepScalar) { + const [key, ...rest] = path; + const node = this.get(key, true); + if (rest.length === 0) + return !keepScalar && isScalar$1(node) ? node.value : node; + else + return isCollection$1(node) ? node.getIn(rest, keepScalar) : undefined; + } + hasAllNullValues(allowScalar) { + return this.items.every(node => { + if (!isPair(node)) + return false; + const n = node.value; + return (n == null || + (allowScalar && + isScalar$1(n) && + n.value == null && + !n.commentBefore && + !n.comment && + !n.tag)); + }); + } + /** + * Checks if the collection includes a value with the key `key`. + */ + hasIn(path) { + const [key, ...rest] = path; + if (rest.length === 0) + return this.has(key); + const node = this.get(key, true); + return isCollection$1(node) ? node.hasIn(rest) : false; + } + /** + * Sets a value in this collection. For `!!set`, `value` needs to be a + * boolean to add/remove the item from the set. + */ + setIn(path, value) { + const [key, ...rest] = path; + if (rest.length === 0) { + this.set(key, value); + } + else { + const node = this.get(key, true); + if (isCollection$1(node)) + node.setIn(rest, value); + else if (node === undefined && this.schema) + this.set(key, collectionFromPath(this.schema, rest, value)); + else + throw new Error(`Expected YAML collection at ${key}. Remaining path: ${rest}`); + } + } + } + + /** + * Stringifies a comment. + * + * Empty comment lines are left empty, + * lines consisting of a single space are replaced by `#`, + * and all other lines are prefixed with a `#`. + */ + const stringifyComment = (str) => str.replace(/^(?!$)(?: $)?/gm, '#'); + function indentComment(comment, indent) { + if (/^\n+$/.test(comment)) + return comment.substring(1); + return indent ? comment.replace(/^(?! *$)/gm, indent) : comment; + } + const lineComment = (str, indent, comment) => str.endsWith('\n') + ? indentComment(comment, indent) + : comment.includes('\n') + ? '\n' + indentComment(comment, indent) + : (str.endsWith(' ') ? '' : ' ') + comment; + + const FOLD_FLOW = 'flow'; + const FOLD_BLOCK = 'block'; + const FOLD_QUOTED = 'quoted'; + /** + * Tries to keep input at up to `lineWidth` characters, splitting only on spaces + * not followed by newlines or spaces unless `mode` is `'quoted'`. Lines are + * terminated with `\n` and started with `indent`. + */ + function foldFlowLines(text, indent, mode = 'flow', { indentAtStart, lineWidth = 80, minContentWidth = 20, onFold, onOverflow } = {}) { + if (!lineWidth || lineWidth < 0) + return text; + if (lineWidth < minContentWidth) + minContentWidth = 0; + const endStep = Math.max(1 + minContentWidth, 1 + lineWidth - indent.length); + if (text.length <= endStep) + return text; + const folds = []; + const escapedFolds = {}; + let end = lineWidth - indent.length; + if (typeof indentAtStart === 'number') { + if (indentAtStart > lineWidth - Math.max(2, minContentWidth)) + folds.push(0); + else + end = lineWidth - indentAtStart; + } + let split = undefined; + let prev = undefined; + let overflow = false; + let i = -1; + let escStart = -1; + let escEnd = -1; + if (mode === FOLD_BLOCK) { + i = consumeMoreIndentedLines(text, i, indent.length); + if (i !== -1) + end = i + endStep; + } + for (let ch; (ch = text[(i += 1)]);) { + if (mode === FOLD_QUOTED && ch === '\\') { + escStart = i; + switch (text[i + 1]) { + case 'x': + i += 3; + break; + case 'u': + i += 5; + break; + case 'U': + i += 9; + break; + default: + i += 1; + } + escEnd = i; + } + if (ch === '\n') { + if (mode === FOLD_BLOCK) + i = consumeMoreIndentedLines(text, i, indent.length); + end = i + indent.length + endStep; + split = undefined; + } + else { + if (ch === ' ' && + prev && + prev !== ' ' && + prev !== '\n' && + prev !== '\t') { + // space surrounded by non-space can be replaced with newline + indent + const next = text[i + 1]; + if (next && next !== ' ' && next !== '\n' && next !== '\t') + split = i; + } + if (i >= end) { + if (split) { + folds.push(split); + end = split + endStep; + split = undefined; + } + else if (mode === FOLD_QUOTED) { + // white-space collected at end may stretch past lineWidth + while (prev === ' ' || prev === '\t') { + prev = ch; + ch = text[(i += 1)]; + overflow = true; + } + // Account for newline escape, but don't break preceding escape + const j = i > escEnd + 1 ? i - 2 : escStart - 1; + // Bail out if lineWidth & minContentWidth are shorter than an escape string + if (escapedFolds[j]) + return text; + folds.push(j); + escapedFolds[j] = true; + end = j + endStep; + split = undefined; + } + else { + overflow = true; + } + } + } + prev = ch; + } + if (overflow && onOverflow) + onOverflow(); + if (folds.length === 0) + return text; + if (onFold) + onFold(); + let res = text.slice(0, folds[0]); + for (let i = 0; i < folds.length; ++i) { + const fold = folds[i]; + const end = folds[i + 1] || text.length; + if (fold === 0) + res = `\n${indent}${text.slice(0, end)}`; + else { + if (mode === FOLD_QUOTED && escapedFolds[fold]) + res += `${text[fold]}\\`; + res += `\n${indent}${text.slice(fold + 1, end)}`; + } + } + return res; + } + /** + * Presumes `i + 1` is at the start of a line + * @returns index of last newline in more-indented block + */ + function consumeMoreIndentedLines(text, i, indent) { + let end = i; + let start = i + 1; + let ch = text[start]; + while (ch === ' ' || ch === '\t') { + if (i < start + indent) { + ch = text[++i]; + } + else { + do { + ch = text[++i]; + } while (ch && ch !== '\n'); + end = i; + start = i + 1; + ch = text[start]; + } + } + return end; + } + + const getFoldOptions = (ctx, isBlock) => ({ + indentAtStart: isBlock ? ctx.indent.length : ctx.indentAtStart, + lineWidth: ctx.options.lineWidth, + minContentWidth: ctx.options.minContentWidth + }); + // Also checks for lines starting with %, as parsing the output as YAML 1.1 will + // presume that's starting a new document. + const containsDocumentMarker = (str) => /^(%|---|\.\.\.)/m.test(str); + function lineLengthOverLimit(str, lineWidth, indentLength) { + if (!lineWidth || lineWidth < 0) + return false; + const limit = lineWidth - indentLength; + const strLen = str.length; + if (strLen <= limit) + return false; + for (let i = 0, start = 0; i < strLen; ++i) { + if (str[i] === '\n') { + if (i - start > limit) + return true; + start = i + 1; + if (strLen - start <= limit) + return false; + } + } + return true; + } + function doubleQuotedString(value, ctx) { + const json = JSON.stringify(value); + if (ctx.options.doubleQuotedAsJSON) + return json; + const { implicitKey } = ctx; + const minMultiLineLength = ctx.options.doubleQuotedMinMultiLineLength; + const indent = ctx.indent || (containsDocumentMarker(value) ? ' ' : ''); + let str = ''; + let start = 0; + for (let i = 0, ch = json[i]; ch; ch = json[++i]) { + if (ch === ' ' && json[i + 1] === '\\' && json[i + 2] === 'n') { + // space before newline needs to be escaped to not be folded + str += json.slice(start, i) + '\\ '; + i += 1; + start = i; + ch = '\\'; + } + if (ch === '\\') + switch (json[i + 1]) { + case 'u': + { + str += json.slice(start, i); + const code = json.substr(i + 2, 4); + switch (code) { + case '0000': + str += '\\0'; + break; + case '0007': + str += '\\a'; + break; + case '000b': + str += '\\v'; + break; + case '001b': + str += '\\e'; + break; + case '0085': + str += '\\N'; + break; + case '00a0': + str += '\\_'; + break; + case '2028': + str += '\\L'; + break; + case '2029': + str += '\\P'; + break; + default: + if (code.substr(0, 2) === '00') + str += '\\x' + code.substr(2); + else + str += json.substr(i, 6); + } + i += 5; + start = i + 1; + } + break; + case 'n': + if (implicitKey || + json[i + 2] === '"' || + json.length < minMultiLineLength) { + i += 1; + } + else { + // folding will eat first newline + str += json.slice(start, i) + '\n\n'; + while (json[i + 2] === '\\' && + json[i + 3] === 'n' && + json[i + 4] !== '"') { + str += '\n'; + i += 2; + } + str += indent; + // space after newline needs to be escaped to not be folded + if (json[i + 2] === ' ') + str += '\\'; + i += 1; + start = i + 1; + } + break; + default: + i += 1; + } + } + str = start ? str + json.slice(start) : json; + return implicitKey + ? str + : foldFlowLines(str, indent, FOLD_QUOTED, getFoldOptions(ctx, false)); + } + function singleQuotedString(value, ctx) { + if (ctx.options.singleQuote === false || + (ctx.implicitKey && value.includes('\n')) || + /[ \t]\n|\n[ \t]/.test(value) // single quoted string can't have leading or trailing whitespace around newline + ) + return doubleQuotedString(value, ctx); + const indent = ctx.indent || (containsDocumentMarker(value) ? ' ' : ''); + const res = "'" + value.replace(/'/g, "''").replace(/\n+/g, `$&\n${indent}`) + "'"; + return ctx.implicitKey + ? res + : foldFlowLines(res, indent, FOLD_FLOW, getFoldOptions(ctx, false)); + } + function quotedString(value, ctx) { + const { singleQuote } = ctx.options; + let qs; + if (singleQuote === false) + qs = doubleQuotedString; + else { + const hasDouble = value.includes('"'); + const hasSingle = value.includes("'"); + if (hasDouble && !hasSingle) + qs = singleQuotedString; + else if (hasSingle && !hasDouble) + qs = doubleQuotedString; + else + qs = singleQuote ? singleQuotedString : doubleQuotedString; + } + return qs(value, ctx); + } + // The negative lookbehind avoids a polynomial search, + // but isn't supported yet on Safari: https://caniuse.com/js-regexp-lookbehind + let blockEndNewlines; + try { + blockEndNewlines = new RegExp('(^|(?\n'; + // determine chomping from whitespace at value end + let chomp; + let endStart; + for (endStart = value.length; endStart > 0; --endStart) { + const ch = value[endStart - 1]; + if (ch !== '\n' && ch !== '\t' && ch !== ' ') + break; + } + let end = value.substring(endStart); + const endNlPos = end.indexOf('\n'); + if (endNlPos === -1) { + chomp = '-'; // strip + } + else if (value === end || endNlPos !== end.length - 1) { + chomp = '+'; // keep + if (onChompKeep) + onChompKeep(); + } + else { + chomp = ''; // clip + } + if (end) { + value = value.slice(0, -end.length); + if (end[end.length - 1] === '\n') + end = end.slice(0, -1); + end = end.replace(blockEndNewlines, `$&${indent}`); + } + // determine indent indicator from whitespace at value start + let startWithSpace = false; + let startEnd; + let startNlPos = -1; + for (startEnd = 0; startEnd < value.length; ++startEnd) { + const ch = value[startEnd]; + if (ch === ' ') + startWithSpace = true; + else if (ch === '\n') + startNlPos = startEnd; + else + break; + } + let start = value.substring(0, startNlPos < startEnd ? startNlPos + 1 : startEnd); + if (start) { + value = value.substring(start.length); + start = start.replace(/\n+/g, `$&${indent}`); + } + const indentSize = indent ? '2' : '1'; // root is at -1 + // Leading | or > is added later + let header = (startWithSpace ? indentSize : '') + chomp; + if (comment) { + header += ' ' + commentString(comment.replace(/ ?[\r\n]+/g, ' ')); + if (onComment) + onComment(); + } + if (!literal) { + const foldedValue = value + .replace(/\n+/g, '\n$&') + .replace(/(?:^|\n)([\t ].*)(?:([\n\t ]*)\n(?![\n\t ]))?/g, '$1$2') // more-indented lines aren't folded + // ^ more-ind. ^ empty ^ capture next empty lines only at end of indent + .replace(/\n+/g, `$&${indent}`); + let literalFallback = false; + const foldOptions = getFoldOptions(ctx, true); + if (blockQuote !== 'folded' && type !== Scalar.BLOCK_FOLDED) { + foldOptions.onOverflow = () => { + literalFallback = true; + }; + } + const body = foldFlowLines(`${start}${foldedValue}${end}`, indent, FOLD_BLOCK, foldOptions); + if (!literalFallback) + return `>${header}\n${indent}${body}`; + } + value = value.replace(/\n+/g, `$&${indent}`); + return `|${header}\n${indent}${start}${value}${end}`; + } + function plainString(item, ctx, onComment, onChompKeep) { + const { type, value } = item; + const { actualString, implicitKey, indent, indentStep, inFlow } = ctx; + if ((implicitKey && value.includes('\n')) || + (inFlow && /[[\]{},]/.test(value))) { + return quotedString(value, ctx); + } + if (/^[\n\t ,[\]{}#&*!|>'"%@`]|^[?-]$|^[?-][ \t]|[\n:][ \t]|[ \t]\n|[\n\t ]#|[\n\t :]$/.test(value)) { + // not allowed: + // - '-' or '?' + // - start with an indicator character (except [?:-]) or /[?-] / + // - '\n ', ': ' or ' \n' anywhere + // - '#' not preceded by a non-space char + // - end with ' ' or ':' + return implicitKey || inFlow || !value.includes('\n') + ? quotedString(value, ctx) + : blockString(item, ctx, onComment, onChompKeep); + } + if (!implicitKey && + !inFlow && + type !== Scalar.PLAIN && + value.includes('\n')) { + // Where allowed & type not set explicitly, prefer block style for multiline strings + return blockString(item, ctx, onComment, onChompKeep); + } + if (containsDocumentMarker(value)) { + if (indent === '') { + ctx.forceBlockIndent = true; + return blockString(item, ctx, onComment, onChompKeep); + } + else if (implicitKey && indent === indentStep) { + return quotedString(value, ctx); + } + } + const str = value.replace(/\n+/g, `$&\n${indent}`); + // Verify that output will be parsed as a string, as e.g. plain numbers and + // booleans get parsed with those types in v1.2 (e.g. '42', 'true' & '0.9e-3'), + // and others in v1.1. + if (actualString) { + const test = (tag) => tag.default && tag.tag !== 'tag:yaml.org,2002:str' && tag.test?.test(str); + const { compat, tags } = ctx.doc.schema; + if (tags.some(test) || compat?.some(test)) + return quotedString(value, ctx); + } + return implicitKey + ? str + : foldFlowLines(str, indent, FOLD_FLOW, getFoldOptions(ctx, false)); + } + function stringifyString(item, ctx, onComment, onChompKeep) { + const { implicitKey, inFlow } = ctx; + const ss = typeof item.value === 'string' + ? item + : Object.assign({}, item, { value: String(item.value) }); + let { type } = item; + if (type !== Scalar.QUOTE_DOUBLE) { + // force double quotes on control characters & unpaired surrogates + if (/[\x00-\x08\x0b-\x1f\x7f-\x9f\u{D800}-\u{DFFF}]/u.test(ss.value)) + type = Scalar.QUOTE_DOUBLE; + } + const _stringify = (_type) => { + switch (_type) { + case Scalar.BLOCK_FOLDED: + case Scalar.BLOCK_LITERAL: + return implicitKey || inFlow + ? quotedString(ss.value, ctx) // blocks are not valid inside flow containers + : blockString(ss, ctx, onComment, onChompKeep); + case Scalar.QUOTE_DOUBLE: + return doubleQuotedString(ss.value, ctx); + case Scalar.QUOTE_SINGLE: + return singleQuotedString(ss.value, ctx); + case Scalar.PLAIN: + return plainString(ss, ctx, onComment, onChompKeep); + default: + return null; + } + }; + let res = _stringify(type); + if (res === null) { + const { defaultKeyType, defaultStringType } = ctx.options; + const t = (implicitKey && defaultKeyType) || defaultStringType; + res = _stringify(t); + if (res === null) + throw new Error(`Unsupported default string type ${t}`); + } + return res; + } + + function createStringifyContext(doc, options) { + const opt = Object.assign({ + blockQuote: true, + commentString: stringifyComment, + defaultKeyType: null, + defaultStringType: 'PLAIN', + directives: null, + doubleQuotedAsJSON: false, + doubleQuotedMinMultiLineLength: 40, + falseStr: 'false', + flowCollectionPadding: true, + indentSeq: true, + lineWidth: 80, + minContentWidth: 20, + nullStr: 'null', + simpleKeys: false, + singleQuote: null, + trueStr: 'true', + verifyAliasOrder: true + }, doc.schema.toStringOptions, options); + let inFlow; + switch (opt.collectionStyle) { + case 'block': + inFlow = false; + break; + case 'flow': + inFlow = true; + break; + default: + inFlow = null; + } + return { + anchors: new Set(), + doc, + flowCollectionPadding: opt.flowCollectionPadding ? ' ' : '', + indent: '', + indentStep: typeof opt.indent === 'number' ? ' '.repeat(opt.indent) : ' ', + inFlow, + options: opt + }; + } + function getTagObject(tags, item) { + if (item.tag) { + const match = tags.filter(t => t.tag === item.tag); + if (match.length > 0) + return match.find(t => t.format === item.format) ?? match[0]; + } + let tagObj = undefined; + let obj; + if (isScalar$1(item)) { + obj = item.value; + let match = tags.filter(t => t.identify?.(obj)); + if (match.length > 1) { + const testMatch = match.filter(t => t.test); + if (testMatch.length > 0) + match = testMatch; + } + tagObj = + match.find(t => t.format === item.format) ?? match.find(t => !t.format); + } + else { + obj = item; + tagObj = tags.find(t => t.nodeClass && obj instanceof t.nodeClass); + } + if (!tagObj) { + const name = obj?.constructor?.name ?? (obj === null ? 'null' : typeof obj); + throw new Error(`Tag not resolved for ${name} value`); + } + return tagObj; + } + // needs to be called before value stringifier to allow for circular anchor refs + function stringifyProps(node, tagObj, { anchors, doc }) { + if (!doc.directives) + return ''; + const props = []; + const anchor = (isScalar$1(node) || isCollection$1(node)) && node.anchor; + if (anchor && anchorIsValid(anchor)) { + anchors.add(anchor); + props.push(`&${anchor}`); + } + const tag = node.tag ?? (tagObj.default ? null : tagObj.tag); + if (tag) + props.push(doc.directives.tagString(tag)); + return props.join(' '); + } + function stringify$2(item, ctx, onComment, onChompKeep) { + if (isPair(item)) + return item.toString(ctx, onComment, onChompKeep); + if (isAlias(item)) { + if (ctx.doc.directives) + return item.toString(ctx); + if (ctx.resolvedAliases?.has(item)) { + throw new TypeError(`Cannot stringify circular structure without alias nodes`); + } + else { + if (ctx.resolvedAliases) + ctx.resolvedAliases.add(item); + else + ctx.resolvedAliases = new Set([item]); + item = item.resolve(ctx.doc); + } + } + let tagObj = undefined; + const node = isNode(item) + ? item + : ctx.doc.createNode(item, { onTagObj: o => (tagObj = o) }); + tagObj ?? (tagObj = getTagObject(ctx.doc.schema.tags, node)); + const props = stringifyProps(node, tagObj, ctx); + if (props.length > 0) + ctx.indentAtStart = (ctx.indentAtStart ?? 0) + props.length + 1; + const str = typeof tagObj.stringify === 'function' + ? tagObj.stringify(node, ctx, onComment, onChompKeep) + : isScalar$1(node) + ? stringifyString(node, ctx, onComment, onChompKeep) + : node.toString(ctx, onComment, onChompKeep); + if (!props) + return str; + return isScalar$1(node) || str[0] === '{' || str[0] === '[' + ? `${props} ${str}` + : `${props}\n${ctx.indent}${str}`; + } + + function stringifyPair({ key, value }, ctx, onComment, onChompKeep) { + const { allNullValues, doc, indent, indentStep, options: { commentString, indentSeq, simpleKeys } } = ctx; + let keyComment = (isNode(key) && key.comment) || null; + if (simpleKeys) { + if (keyComment) { + throw new Error('With simple keys, key nodes cannot have comments'); + } + if (isCollection$1(key) || (!isNode(key) && typeof key === 'object')) { + const msg = 'With simple keys, collection cannot be used as a key value'; + throw new Error(msg); + } + } + let explicitKey = !simpleKeys && + (!key || + (keyComment && value == null && !ctx.inFlow) || + isCollection$1(key) || + (isScalar$1(key) + ? key.type === Scalar.BLOCK_FOLDED || key.type === Scalar.BLOCK_LITERAL + : typeof key === 'object')); + ctx = Object.assign({}, ctx, { + allNullValues: false, + implicitKey: !explicitKey && (simpleKeys || !allNullValues), + indent: indent + indentStep + }); + let keyCommentDone = false; + let chompKeep = false; + let str = stringify$2(key, ctx, () => (keyCommentDone = true), () => (chompKeep = true)); + if (!explicitKey && !ctx.inFlow && str.length > 1024) { + if (simpleKeys) + throw new Error('With simple keys, single line scalar must not span more than 1024 characters'); + explicitKey = true; + } + if (ctx.inFlow) { + if (allNullValues || value == null) { + if (keyCommentDone && onComment) + onComment(); + return str === '' ? '?' : explicitKey ? `? ${str}` : str; + } + } + else if ((allNullValues && !simpleKeys) || (value == null && explicitKey)) { + str = `? ${str}`; + if (keyComment && !keyCommentDone) { + str += lineComment(str, ctx.indent, commentString(keyComment)); + } + else if (chompKeep && onChompKeep) + onChompKeep(); + return str; + } + if (keyCommentDone) + keyComment = null; + if (explicitKey) { + if (keyComment) + str += lineComment(str, ctx.indent, commentString(keyComment)); + str = `? ${str}\n${indent}:`; + } + else { + str = `${str}:`; + if (keyComment) + str += lineComment(str, ctx.indent, commentString(keyComment)); + } + let vsb, vcb, valueComment; + if (isNode(value)) { + vsb = !!value.spaceBefore; + vcb = value.commentBefore; + valueComment = value.comment; + } + else { + vsb = false; + vcb = null; + valueComment = null; + if (value && typeof value === 'object') + value = doc.createNode(value); + } + ctx.implicitKey = false; + if (!explicitKey && !keyComment && isScalar$1(value)) + ctx.indentAtStart = str.length + 1; + chompKeep = false; + if (!indentSeq && + indentStep.length >= 2 && + !ctx.inFlow && + !explicitKey && + isSeq(value) && + !value.flow && + !value.tag && + !value.anchor) { + // If indentSeq === false, consider '- ' as part of indentation where possible + ctx.indent = ctx.indent.substring(2); + } + let valueCommentDone = false; + const valueStr = stringify$2(value, ctx, () => (valueCommentDone = true), () => (chompKeep = true)); + let ws = ' '; + if (keyComment || vsb || vcb) { + ws = vsb ? '\n' : ''; + if (vcb) { + const cs = commentString(vcb); + ws += `\n${indentComment(cs, ctx.indent)}`; + } + if (valueStr === '' && !ctx.inFlow) { + if (ws === '\n') + ws = '\n\n'; + } + else { + ws += `\n${ctx.indent}`; + } + } + else if (!explicitKey && isCollection$1(value)) { + const vs0 = valueStr[0]; + const nl0 = valueStr.indexOf('\n'); + const hasNewline = nl0 !== -1; + const flow = ctx.inFlow ?? value.flow ?? value.items.length === 0; + if (hasNewline || !flow) { + let hasPropsLine = false; + if (hasNewline && (vs0 === '&' || vs0 === '!')) { + let sp0 = valueStr.indexOf(' '); + if (vs0 === '&' && + sp0 !== -1 && + sp0 < nl0 && + valueStr[sp0 + 1] === '!') { + sp0 = valueStr.indexOf(' ', sp0 + 1); + } + if (sp0 === -1 || nl0 < sp0) + hasPropsLine = true; + } + if (!hasPropsLine) + ws = `\n${ctx.indent}`; + } + } + else if (valueStr === '' || valueStr[0] === '\n') { + ws = ''; + } + str += ws + valueStr; + if (ctx.inFlow) { + if (valueCommentDone && onComment) + onComment(); + } + else if (valueComment && !valueCommentDone) { + str += lineComment(str, ctx.indent, commentString(valueComment)); + } + else if (chompKeep && onChompKeep) { + onChompKeep(); + } + return str; + } + + function warn(logLevel, warning) { + if (logLevel === 'debug' || logLevel === 'warn') { + console.warn(warning); + } + } + + // If the value associated with a merge key is a single mapping node, each of + // its key/value pairs is inserted into the current mapping, unless the key + // already exists in it. If the value associated with the merge key is a + // sequence, then this sequence is expected to contain mapping nodes and each + // of these nodes is merged in turn according to its order in the sequence. + // Keys in mapping nodes earlier in the sequence override keys specified in + // later mapping nodes. -- http://yaml.org/type/merge.html + const MERGE_KEY = '<<'; + const merge = { + identify: value => value === MERGE_KEY || + (typeof value === 'symbol' && value.description === MERGE_KEY), + default: 'key', + tag: 'tag:yaml.org,2002:merge', + test: /^<<$/, + resolve: () => Object.assign(new Scalar(Symbol(MERGE_KEY)), { + addToJSMap: addMergeToJSMap + }), + stringify: () => MERGE_KEY + }; + const isMergeKey = (ctx, key) => (merge.identify(key) || + (isScalar$1(key) && + (!key.type || key.type === Scalar.PLAIN) && + merge.identify(key.value))) && + ctx?.doc.schema.tags.some(tag => tag.tag === merge.tag && tag.default); + function addMergeToJSMap(ctx, map, value) { + value = ctx && isAlias(value) ? value.resolve(ctx.doc) : value; + if (isSeq(value)) + for (const it of value.items) + mergeValue(ctx, map, it); + else if (Array.isArray(value)) + for (const it of value) + mergeValue(ctx, map, it); + else + mergeValue(ctx, map, value); + } + function mergeValue(ctx, map, value) { + const source = ctx && isAlias(value) ? value.resolve(ctx.doc) : value; + if (!isMap(source)) + throw new Error('Merge sources must be maps or map aliases'); + const srcMap = source.toJSON(null, ctx, Map); + for (const [key, value] of srcMap) { + if (map instanceof Map) { + if (!map.has(key)) + map.set(key, value); + } + else if (map instanceof Set) { + map.add(key); + } + else if (!Object.prototype.hasOwnProperty.call(map, key)) { + Object.defineProperty(map, key, { + value, + writable: true, + enumerable: true, + configurable: true + }); + } + } + return map; + } + + function addPairToJSMap(ctx, map, { key, value }) { + if (isNode(key) && key.addToJSMap) + key.addToJSMap(ctx, map, value); + // TODO: Should drop this special case for bare << handling + else if (isMergeKey(ctx, key)) + addMergeToJSMap(ctx, map, value); + else { + const jsKey = toJS(key, '', ctx); + if (map instanceof Map) { + map.set(jsKey, toJS(value, jsKey, ctx)); + } + else if (map instanceof Set) { + map.add(jsKey); + } + else { + const stringKey = stringifyKey(key, jsKey, ctx); + const jsValue = toJS(value, stringKey, ctx); + if (stringKey in map) + Object.defineProperty(map, stringKey, { + value: jsValue, + writable: true, + enumerable: true, + configurable: true + }); + else + map[stringKey] = jsValue; + } + } + return map; + } + function stringifyKey(key, jsKey, ctx) { + if (jsKey === null) + return ''; + // eslint-disable-next-line @typescript-eslint/no-base-to-string + if (typeof jsKey !== 'object') + return String(jsKey); + if (isNode(key) && ctx?.doc) { + const strCtx = createStringifyContext(ctx.doc, {}); + strCtx.anchors = new Set(); + for (const node of ctx.anchors.keys()) + strCtx.anchors.add(node.anchor); + strCtx.inFlow = true; + strCtx.inStringifyKey = true; + const strKey = key.toString(strCtx); + if (!ctx.mapKeyWarned) { + let jsonStr = JSON.stringify(strKey); + if (jsonStr.length > 40) + jsonStr = jsonStr.substring(0, 36) + '..."'; + warn(ctx.doc.options.logLevel, `Keys with collection values will be stringified due to JS Object restrictions: ${jsonStr}. Set mapAsMap: true to use object keys.`); + ctx.mapKeyWarned = true; + } + return strKey; + } + return JSON.stringify(jsKey); + } + + function createPair(key, value, ctx) { + const k = createNode(key, undefined, ctx); + const v = createNode(value, undefined, ctx); + return new Pair(k, v); + } + class Pair { + constructor(key, value = null) { + Object.defineProperty(this, NODE_TYPE, { value: PAIR }); + this.key = key; + this.value = value; + } + clone(schema) { + let { key, value } = this; + if (isNode(key)) + key = key.clone(schema); + if (isNode(value)) + value = value.clone(schema); + return new Pair(key, value); + } + toJSON(_, ctx) { + const pair = ctx?.mapAsMap ? new Map() : {}; + return addPairToJSMap(ctx, pair, this); + } + toString(ctx, onComment, onChompKeep) { + return ctx?.doc + ? stringifyPair(this, ctx, onComment, onChompKeep) + : JSON.stringify(this); + } + } + + function stringifyCollection(collection, ctx, options) { + const flow = ctx.inFlow ?? collection.flow; + const stringify = flow ? stringifyFlowCollection : stringifyBlockCollection; + return stringify(collection, ctx, options); + } + function stringifyBlockCollection({ comment, items }, ctx, { blockItemPrefix, flowChars, itemIndent, onChompKeep, onComment }) { + const { indent, options: { commentString } } = ctx; + const itemCtx = Object.assign({}, ctx, { indent: itemIndent, type: null }); + let chompKeep = false; // flag for the preceding node's status + const lines = []; + for (let i = 0; i < items.length; ++i) { + const item = items[i]; + let comment = null; + if (isNode(item)) { + if (!chompKeep && item.spaceBefore) + lines.push(''); + addCommentBefore(ctx, lines, item.commentBefore, chompKeep); + if (item.comment) + comment = item.comment; + } + else if (isPair(item)) { + const ik = isNode(item.key) ? item.key : null; + if (ik) { + if (!chompKeep && ik.spaceBefore) + lines.push(''); + addCommentBefore(ctx, lines, ik.commentBefore, chompKeep); + } + } + chompKeep = false; + let str = stringify$2(item, itemCtx, () => (comment = null), () => (chompKeep = true)); + if (comment) + str += lineComment(str, itemIndent, commentString(comment)); + if (chompKeep && comment) + chompKeep = false; + lines.push(blockItemPrefix + str); + } + let str; + if (lines.length === 0) { + str = flowChars.start + flowChars.end; + } + else { + str = lines[0]; + for (let i = 1; i < lines.length; ++i) { + const line = lines[i]; + str += line ? `\n${indent}${line}` : '\n'; + } + } + if (comment) { + str += '\n' + indentComment(commentString(comment), indent); + if (onComment) + onComment(); + } + else if (chompKeep && onChompKeep) + onChompKeep(); + return str; + } + function stringifyFlowCollection({ items }, ctx, { flowChars, itemIndent }) { + const { indent, indentStep, flowCollectionPadding: fcPadding, options: { commentString } } = ctx; + itemIndent += indentStep; + const itemCtx = Object.assign({}, ctx, { + indent: itemIndent, + inFlow: true, + type: null + }); + let reqNewline = false; + let linesAtValue = 0; + const lines = []; + for (let i = 0; i < items.length; ++i) { + const item = items[i]; + let comment = null; + if (isNode(item)) { + if (item.spaceBefore) + lines.push(''); + addCommentBefore(ctx, lines, item.commentBefore, false); + if (item.comment) + comment = item.comment; + } + else if (isPair(item)) { + const ik = isNode(item.key) ? item.key : null; + if (ik) { + if (ik.spaceBefore) + lines.push(''); + addCommentBefore(ctx, lines, ik.commentBefore, false); + if (ik.comment) + reqNewline = true; + } + const iv = isNode(item.value) ? item.value : null; + if (iv) { + if (iv.comment) + comment = iv.comment; + if (iv.commentBefore) + reqNewline = true; + } + else if (item.value == null && ik?.comment) { + comment = ik.comment; + } + } + if (comment) + reqNewline = true; + let str = stringify$2(item, itemCtx, () => (comment = null)); + if (i < items.length - 1) + str += ','; + if (comment) + str += lineComment(str, itemIndent, commentString(comment)); + if (!reqNewline && (lines.length > linesAtValue || str.includes('\n'))) + reqNewline = true; + lines.push(str); + linesAtValue = lines.length; + } + const { start, end } = flowChars; + if (lines.length === 0) { + return start + end; + } + else { + if (!reqNewline) { + const len = lines.reduce((sum, line) => sum + line.length + 2, 2); + reqNewline = ctx.options.lineWidth > 0 && len > ctx.options.lineWidth; + } + if (reqNewline) { + let str = start; + for (const line of lines) + str += line ? `\n${indentStep}${indent}${line}` : '\n'; + return `${str}\n${indent}${end}`; + } + else { + return `${start}${fcPadding}${lines.join(' ')}${fcPadding}${end}`; + } + } + } + function addCommentBefore({ indent, options: { commentString } }, lines, comment, chompKeep) { + if (comment && chompKeep) + comment = comment.replace(/^\n+/, ''); + if (comment) { + const ic = indentComment(commentString(comment), indent); + lines.push(ic.trimStart()); // Avoid double indent on first line + } + } + + function findPair(items, key) { + const k = isScalar$1(key) ? key.value : key; + for (const it of items) { + if (isPair(it)) { + if (it.key === key || it.key === k) + return it; + if (isScalar$1(it.key) && it.key.value === k) + return it; + } + } + return undefined; + } + class YAMLMap extends Collection { + static get tagName() { + return 'tag:yaml.org,2002:map'; + } + constructor(schema) { + super(MAP, schema); + this.items = []; + } + /** + * A generic collection parsing method that can be extended + * to other node classes that inherit from YAMLMap + */ + static from(schema, obj, ctx) { + const { keepUndefined, replacer } = ctx; + const map = new this(schema); + const add = (key, value) => { + if (typeof replacer === 'function') + value = replacer.call(obj, key, value); + else if (Array.isArray(replacer) && !replacer.includes(key)) + return; + if (value !== undefined || keepUndefined) + map.items.push(createPair(key, value, ctx)); + }; + if (obj instanceof Map) { + for (const [key, value] of obj) + add(key, value); + } + else if (obj && typeof obj === 'object') { + for (const key of Object.keys(obj)) + add(key, obj[key]); + } + if (typeof schema.sortMapEntries === 'function') { + map.items.sort(schema.sortMapEntries); + } + return map; + } + /** + * Adds a value to the collection. + * + * @param overwrite - If not set `true`, using a key that is already in the + * collection will throw. Otherwise, overwrites the previous value. + */ + add(pair, overwrite) { + let _pair; + if (isPair(pair)) + _pair = pair; + else if (!pair || typeof pair !== 'object' || !('key' in pair)) { + // In TypeScript, this never happens. + _pair = new Pair(pair, pair?.value); + } + else + _pair = new Pair(pair.key, pair.value); + const prev = findPair(this.items, _pair.key); + const sortEntries = this.schema?.sortMapEntries; + if (prev) { + if (!overwrite) + throw new Error(`Key ${_pair.key} already set`); + // For scalars, keep the old node & its comments and anchors + if (isScalar$1(prev.value) && isScalarValue(_pair.value)) + prev.value.value = _pair.value; + else + prev.value = _pair.value; + } + else if (sortEntries) { + const i = this.items.findIndex(item => sortEntries(_pair, item) < 0); + if (i === -1) + this.items.push(_pair); + else + this.items.splice(i, 0, _pair); + } + else { + this.items.push(_pair); + } + } + delete(key) { + const it = findPair(this.items, key); + if (!it) + return false; + const del = this.items.splice(this.items.indexOf(it), 1); + return del.length > 0; + } + get(key, keepScalar) { + const it = findPair(this.items, key); + const node = it?.value; + return (!keepScalar && isScalar$1(node) ? node.value : node) ?? undefined; + } + has(key) { + return !!findPair(this.items, key); + } + set(key, value) { + this.add(new Pair(key, value), true); + } + /** + * @param ctx - Conversion context, originally set in Document#toJS() + * @param {Class} Type - If set, forces the returned collection type + * @returns Instance of Type, Map, or Object + */ + toJSON(_, ctx, Type) { + const map = Type ? new Type() : ctx?.mapAsMap ? new Map() : {}; + if (ctx?.onCreate) + ctx.onCreate(map); + for (const item of this.items) + addPairToJSMap(ctx, map, item); + return map; + } + toString(ctx, onComment, onChompKeep) { + if (!ctx) + return JSON.stringify(this); + for (const item of this.items) { + if (!isPair(item)) + throw new Error(`Map items must all be pairs; found ${JSON.stringify(item)} instead`); + } + if (!ctx.allNullValues && this.hasAllNullValues(false)) + ctx = Object.assign({}, ctx, { allNullValues: true }); + return stringifyCollection(this, ctx, { + blockItemPrefix: '', + flowChars: { start: '{', end: '}' }, + itemIndent: ctx.indent || '', + onChompKeep, + onComment + }); + } + } + + const map = { + collection: 'map', + default: true, + nodeClass: YAMLMap, + tag: 'tag:yaml.org,2002:map', + resolve(map, onError) { + if (!isMap(map)) + onError('Expected a mapping for this tag'); + return map; + }, + createNode: (schema, obj, ctx) => YAMLMap.from(schema, obj, ctx) + }; + + class YAMLSeq extends Collection { + static get tagName() { + return 'tag:yaml.org,2002:seq'; + } + constructor(schema) { + super(SEQ, schema); + this.items = []; + } + add(value) { + this.items.push(value); + } + /** + * Removes a value from the collection. + * + * `key` must contain a representation of an integer for this to succeed. + * It may be wrapped in a `Scalar`. + * + * @returns `true` if the item was found and removed. + */ + delete(key) { + const idx = asItemIndex(key); + if (typeof idx !== 'number') + return false; + const del = this.items.splice(idx, 1); + return del.length > 0; + } + get(key, keepScalar) { + const idx = asItemIndex(key); + if (typeof idx !== 'number') + return undefined; + const it = this.items[idx]; + return !keepScalar && isScalar$1(it) ? it.value : it; + } + /** + * Checks if the collection includes a value with the key `key`. + * + * `key` must contain a representation of an integer for this to succeed. + * It may be wrapped in a `Scalar`. + */ + has(key) { + const idx = asItemIndex(key); + return typeof idx === 'number' && idx < this.items.length; + } + /** + * Sets a value in this collection. For `!!set`, `value` needs to be a + * boolean to add/remove the item from the set. + * + * If `key` does not contain a representation of an integer, this will throw. + * It may be wrapped in a `Scalar`. + */ + set(key, value) { + const idx = asItemIndex(key); + if (typeof idx !== 'number') + throw new Error(`Expected a valid index, not ${key}.`); + const prev = this.items[idx]; + if (isScalar$1(prev) && isScalarValue(value)) + prev.value = value; + else + this.items[idx] = value; + } + toJSON(_, ctx) { + const seq = []; + if (ctx?.onCreate) + ctx.onCreate(seq); + let i = 0; + for (const item of this.items) + seq.push(toJS(item, String(i++), ctx)); + return seq; + } + toString(ctx, onComment, onChompKeep) { + if (!ctx) + return JSON.stringify(this); + return stringifyCollection(this, ctx, { + blockItemPrefix: '- ', + flowChars: { start: '[', end: ']' }, + itemIndent: (ctx.indent || '') + ' ', + onChompKeep, + onComment + }); + } + static from(schema, obj, ctx) { + const { replacer } = ctx; + const seq = new this(schema); + if (obj && Symbol.iterator in Object(obj)) { + let i = 0; + for (let it of obj) { + if (typeof replacer === 'function') { + const key = obj instanceof Set ? it : String(i++); + it = replacer.call(obj, key, it); + } + seq.items.push(createNode(it, undefined, ctx)); + } + } + return seq; + } + } + function asItemIndex(key) { + let idx = isScalar$1(key) ? key.value : key; + if (idx && typeof idx === 'string') + idx = Number(idx); + return typeof idx === 'number' && Number.isInteger(idx) && idx >= 0 + ? idx + : null; + } + + const seq = { + collection: 'seq', + default: true, + nodeClass: YAMLSeq, + tag: 'tag:yaml.org,2002:seq', + resolve(seq, onError) { + if (!isSeq(seq)) + onError('Expected a sequence for this tag'); + return seq; + }, + createNode: (schema, obj, ctx) => YAMLSeq.from(schema, obj, ctx) + }; + + const string = { + identify: value => typeof value === 'string', + default: true, + tag: 'tag:yaml.org,2002:str', + resolve: str => str, + stringify(item, ctx, onComment, onChompKeep) { + ctx = Object.assign({ actualString: true }, ctx); + return stringifyString(item, ctx, onComment, onChompKeep); + } + }; + + const nullTag = { + identify: value => value == null, + createNode: () => new Scalar(null), + default: true, + tag: 'tag:yaml.org,2002:null', + test: /^(?:~|[Nn]ull|NULL)?$/, + resolve: () => new Scalar(null), + stringify: ({ source }, ctx) => typeof source === 'string' && nullTag.test.test(source) + ? source + : ctx.options.nullStr + }; + + const boolTag = { + identify: value => typeof value === 'boolean', + default: true, + tag: 'tag:yaml.org,2002:bool', + test: /^(?:[Tt]rue|TRUE|[Ff]alse|FALSE)$/, + resolve: str => new Scalar(str[0] === 't' || str[0] === 'T'), + stringify({ source, value }, ctx) { + if (source && boolTag.test.test(source)) { + const sv = source[0] === 't' || source[0] === 'T'; + if (value === sv) + return source; + } + return value ? ctx.options.trueStr : ctx.options.falseStr; + } + }; + + function stringifyNumber({ format, minFractionDigits, tag, value }) { + if (typeof value === 'bigint') + return String(value); + const num = typeof value === 'number' ? value : Number(value); + if (!isFinite(num)) + return isNaN(num) ? '.nan' : num < 0 ? '-.inf' : '.inf'; + let n = JSON.stringify(value); + if (!format && + minFractionDigits && + (!tag || tag === 'tag:yaml.org,2002:float') && + /^\d/.test(n)) { + let i = n.indexOf('.'); + if (i < 0) { + i = n.length; + n += '.'; + } + let d = minFractionDigits - (n.length - i - 1); + while (d-- > 0) + n += '0'; + } + return n; + } + + const floatNaN$1 = { + identify: value => typeof value === 'number', + default: true, + tag: 'tag:yaml.org,2002:float', + test: /^(?:[-+]?\.(?:inf|Inf|INF)|\.nan|\.NaN|\.NAN)$/, + resolve: str => str.slice(-3).toLowerCase() === 'nan' + ? NaN + : str[0] === '-' + ? Number.NEGATIVE_INFINITY + : Number.POSITIVE_INFINITY, + stringify: stringifyNumber + }; + const floatExp$1 = { + identify: value => typeof value === 'number', + default: true, + tag: 'tag:yaml.org,2002:float', + format: 'EXP', + test: /^[-+]?(?:\.[0-9]+|[0-9]+(?:\.[0-9]*)?)[eE][-+]?[0-9]+$/, + resolve: str => parseFloat(str), + stringify(node) { + const num = Number(node.value); + return isFinite(num) ? num.toExponential() : stringifyNumber(node); + } + }; + const float$1 = { + identify: value => typeof value === 'number', + default: true, + tag: 'tag:yaml.org,2002:float', + test: /^[-+]?(?:\.[0-9]+|[0-9]+\.[0-9]*)$/, + resolve(str) { + const node = new Scalar(parseFloat(str)); + const dot = str.indexOf('.'); + if (dot !== -1 && str[str.length - 1] === '0') + node.minFractionDigits = str.length - dot - 1; + return node; + }, + stringify: stringifyNumber + }; + + const intIdentify$2 = (value) => typeof value === 'bigint' || Number.isInteger(value); + const intResolve$1 = (str, offset, radix, { intAsBigInt }) => (intAsBigInt ? BigInt(str) : parseInt(str.substring(offset), radix)); + function intStringify$1(node, radix, prefix) { + const { value } = node; + if (intIdentify$2(value) && value >= 0) + return prefix + value.toString(radix); + return stringifyNumber(node); + } + const intOct$1 = { + identify: value => intIdentify$2(value) && value >= 0, + default: true, + tag: 'tag:yaml.org,2002:int', + format: 'OCT', + test: /^0o[0-7]+$/, + resolve: (str, _onError, opt) => intResolve$1(str, 2, 8, opt), + stringify: node => intStringify$1(node, 8, '0o') + }; + const int$1 = { + identify: intIdentify$2, + default: true, + tag: 'tag:yaml.org,2002:int', + test: /^[-+]?[0-9]+$/, + resolve: (str, _onError, opt) => intResolve$1(str, 0, 10, opt), + stringify: stringifyNumber + }; + const intHex$1 = { + identify: value => intIdentify$2(value) && value >= 0, + default: true, + tag: 'tag:yaml.org,2002:int', + format: 'HEX', + test: /^0x[0-9a-fA-F]+$/, + resolve: (str, _onError, opt) => intResolve$1(str, 2, 16, opt), + stringify: node => intStringify$1(node, 16, '0x') + }; + + const schema$2 = [ + map, + seq, + string, + nullTag, + boolTag, + intOct$1, + int$1, + intHex$1, + floatNaN$1, + floatExp$1, + float$1 + ]; + + function intIdentify$1(value) { + return typeof value === 'bigint' || Number.isInteger(value); + } + const stringifyJSON = ({ value }) => JSON.stringify(value); + const jsonScalars = [ + { + identify: value => typeof value === 'string', + default: true, + tag: 'tag:yaml.org,2002:str', + resolve: str => str, + stringify: stringifyJSON + }, + { + identify: value => value == null, + createNode: () => new Scalar(null), + default: true, + tag: 'tag:yaml.org,2002:null', + test: /^null$/, + resolve: () => null, + stringify: stringifyJSON + }, + { + identify: value => typeof value === 'boolean', + default: true, + tag: 'tag:yaml.org,2002:bool', + test: /^true$|^false$/, + resolve: str => str === 'true', + stringify: stringifyJSON + }, + { + identify: intIdentify$1, + default: true, + tag: 'tag:yaml.org,2002:int', + test: /^-?(?:0|[1-9][0-9]*)$/, + resolve: (str, _onError, { intAsBigInt }) => intAsBigInt ? BigInt(str) : parseInt(str, 10), + stringify: ({ value }) => intIdentify$1(value) ? value.toString() : JSON.stringify(value) + }, + { + identify: value => typeof value === 'number', + default: true, + tag: 'tag:yaml.org,2002:float', + test: /^-?(?:0|[1-9][0-9]*)(?:\.[0-9]*)?(?:[eE][-+]?[0-9]+)?$/, + resolve: str => parseFloat(str), + stringify: stringifyJSON + } + ]; + const jsonError = { + default: true, + tag: '', + test: /^/, + resolve(str, onError) { + onError(`Unresolved plain scalar ${JSON.stringify(str)}`); + return str; + } + }; + const schema$1 = [map, seq].concat(jsonScalars, jsonError); + + const binary = { + identify: value => value instanceof Uint8Array, // Buffer inherits from Uint8Array + default: false, + tag: 'tag:yaml.org,2002:binary', + /** + * Returns a Buffer in node and an Uint8Array in browsers + * + * To use the resulting buffer as an image, you'll want to do something like: + * + * const blob = new Blob([buffer], { type: 'image/jpeg' }) + * document.querySelector('#photo').src = URL.createObjectURL(blob) + */ + resolve(src, onError) { + if (typeof atob === 'function') { + // On IE 11, atob() can't handle newlines + const str = atob(src.replace(/[\n\r]/g, '')); + const buffer = new Uint8Array(str.length); + for (let i = 0; i < str.length; ++i) + buffer[i] = str.charCodeAt(i); + return buffer; + } + else { + onError('This environment does not support reading binary tags; either Buffer or atob is required'); + return src; + } + }, + stringify({ comment, type, value }, ctx, onComment, onChompKeep) { + if (!value) + return ''; + const buf = value; // checked earlier by binary.identify() + let str; + if (typeof btoa === 'function') { + let s = ''; + for (let i = 0; i < buf.length; ++i) + s += String.fromCharCode(buf[i]); + str = btoa(s); + } + else { + throw new Error('This environment does not support writing binary tags; either Buffer or btoa is required'); + } + type ?? (type = Scalar.BLOCK_LITERAL); + if (type !== Scalar.QUOTE_DOUBLE) { + const lineWidth = Math.max(ctx.options.lineWidth - ctx.indent.length, ctx.options.minContentWidth); + const n = Math.ceil(str.length / lineWidth); + const lines = new Array(n); + for (let i = 0, o = 0; i < n; ++i, o += lineWidth) { + lines[i] = str.substr(o, lineWidth); + } + str = lines.join(type === Scalar.BLOCK_LITERAL ? '\n' : ' '); + } + return stringifyString({ comment, type, value: str }, ctx, onComment, onChompKeep); + } + }; + + function resolvePairs(seq, onError) { + if (isSeq(seq)) { + for (let i = 0; i < seq.items.length; ++i) { + let item = seq.items[i]; + if (isPair(item)) + continue; + else if (isMap(item)) { + if (item.items.length > 1) + onError('Each pair must have its own sequence indicator'); + const pair = item.items[0] || new Pair(new Scalar(null)); + if (item.commentBefore) + pair.key.commentBefore = pair.key.commentBefore + ? `${item.commentBefore}\n${pair.key.commentBefore}` + : item.commentBefore; + if (item.comment) { + const cn = pair.value ?? pair.key; + cn.comment = cn.comment + ? `${item.comment}\n${cn.comment}` + : item.comment; + } + item = pair; + } + seq.items[i] = isPair(item) ? item : new Pair(item); + } + } + else + onError('Expected a sequence for this tag'); + return seq; + } + function createPairs(schema, iterable, ctx) { + const { replacer } = ctx; + const pairs = new YAMLSeq(schema); + pairs.tag = 'tag:yaml.org,2002:pairs'; + let i = 0; + if (iterable && Symbol.iterator in Object(iterable)) + for (let it of iterable) { + if (typeof replacer === 'function') + it = replacer.call(iterable, String(i++), it); + let key, value; + if (Array.isArray(it)) { + if (it.length === 2) { + key = it[0]; + value = it[1]; + } + else + throw new TypeError(`Expected [key, value] tuple: ${it}`); + } + else if (it && it instanceof Object) { + const keys = Object.keys(it); + if (keys.length === 1) { + key = keys[0]; + value = it[key]; + } + else { + throw new TypeError(`Expected tuple with one key, not ${keys.length} keys`); + } + } + else { + key = it; + } + pairs.items.push(createPair(key, value, ctx)); + } + return pairs; + } + const pairs = { + collection: 'seq', + default: false, + tag: 'tag:yaml.org,2002:pairs', + resolve: resolvePairs, + createNode: createPairs + }; + + class YAMLOMap extends YAMLSeq { + constructor() { + super(); + this.add = YAMLMap.prototype.add.bind(this); + this.delete = YAMLMap.prototype.delete.bind(this); + this.get = YAMLMap.prototype.get.bind(this); + this.has = YAMLMap.prototype.has.bind(this); + this.set = YAMLMap.prototype.set.bind(this); + this.tag = YAMLOMap.tag; + } + /** + * If `ctx` is given, the return type is actually `Map`, + * but TypeScript won't allow widening the signature of a child method. + */ + toJSON(_, ctx) { + if (!ctx) + return super.toJSON(_); + const map = new Map(); + if (ctx?.onCreate) + ctx.onCreate(map); + for (const pair of this.items) { + let key, value; + if (isPair(pair)) { + key = toJS(pair.key, '', ctx); + value = toJS(pair.value, key, ctx); + } + else { + key = toJS(pair, '', ctx); + } + if (map.has(key)) + throw new Error('Ordered maps must not include duplicate keys'); + map.set(key, value); + } + return map; + } + static from(schema, iterable, ctx) { + const pairs = createPairs(schema, iterable, ctx); + const omap = new this(); + omap.items = pairs.items; + return omap; + } + } + YAMLOMap.tag = 'tag:yaml.org,2002:omap'; + const omap = { + collection: 'seq', + identify: value => value instanceof Map, + nodeClass: YAMLOMap, + default: false, + tag: 'tag:yaml.org,2002:omap', + resolve(seq, onError) { + const pairs = resolvePairs(seq, onError); + const seenKeys = []; + for (const { key } of pairs.items) { + if (isScalar$1(key)) { + if (seenKeys.includes(key.value)) { + onError(`Ordered maps must not include duplicate keys: ${key.value}`); + } + else { + seenKeys.push(key.value); + } + } + } + return Object.assign(new YAMLOMap(), pairs); + }, + createNode: (schema, iterable, ctx) => YAMLOMap.from(schema, iterable, ctx) + }; + + function boolStringify({ value, source }, ctx) { + const boolObj = value ? trueTag : falseTag; + if (source && boolObj.test.test(source)) + return source; + return value ? ctx.options.trueStr : ctx.options.falseStr; + } + const trueTag = { + identify: value => value === true, + default: true, + tag: 'tag:yaml.org,2002:bool', + test: /^(?:Y|y|[Yy]es|YES|[Tt]rue|TRUE|[Oo]n|ON)$/, + resolve: () => new Scalar(true), + stringify: boolStringify + }; + const falseTag = { + identify: value => value === false, + default: true, + tag: 'tag:yaml.org,2002:bool', + test: /^(?:N|n|[Nn]o|NO|[Ff]alse|FALSE|[Oo]ff|OFF)$/, + resolve: () => new Scalar(false), + stringify: boolStringify + }; + + const floatNaN = { + identify: value => typeof value === 'number', + default: true, + tag: 'tag:yaml.org,2002:float', + test: /^(?:[-+]?\.(?:inf|Inf|INF)|\.nan|\.NaN|\.NAN)$/, + resolve: (str) => str.slice(-3).toLowerCase() === 'nan' + ? NaN + : str[0] === '-' + ? Number.NEGATIVE_INFINITY + : Number.POSITIVE_INFINITY, + stringify: stringifyNumber + }; + const floatExp = { + identify: value => typeof value === 'number', + default: true, + tag: 'tag:yaml.org,2002:float', + format: 'EXP', + test: /^[-+]?(?:[0-9][0-9_]*)?(?:\.[0-9_]*)?[eE][-+]?[0-9]+$/, + resolve: (str) => parseFloat(str.replace(/_/g, '')), + stringify(node) { + const num = Number(node.value); + return isFinite(num) ? num.toExponential() : stringifyNumber(node); + } + }; + const float = { + identify: value => typeof value === 'number', + default: true, + tag: 'tag:yaml.org,2002:float', + test: /^[-+]?(?:[0-9][0-9_]*)?\.[0-9_]*$/, + resolve(str) { + const node = new Scalar(parseFloat(str.replace(/_/g, ''))); + const dot = str.indexOf('.'); + if (dot !== -1) { + const f = str.substring(dot + 1).replace(/_/g, ''); + if (f[f.length - 1] === '0') + node.minFractionDigits = f.length; + } + return node; + }, + stringify: stringifyNumber + }; + + const intIdentify = (value) => typeof value === 'bigint' || Number.isInteger(value); + function intResolve(str, offset, radix, { intAsBigInt }) { + const sign = str[0]; + if (sign === '-' || sign === '+') + offset += 1; + str = str.substring(offset).replace(/_/g, ''); + if (intAsBigInt) { + switch (radix) { + case 2: + str = `0b${str}`; + break; + case 8: + str = `0o${str}`; + break; + case 16: + str = `0x${str}`; + break; + } + const n = BigInt(str); + return sign === '-' ? BigInt(-1) * n : n; + } + const n = parseInt(str, radix); + return sign === '-' ? -1 * n : n; + } + function intStringify(node, radix, prefix) { + const { value } = node; + if (intIdentify(value)) { + const str = value.toString(radix); + return value < 0 ? '-' + prefix + str.substr(1) : prefix + str; + } + return stringifyNumber(node); + } + const intBin = { + identify: intIdentify, + default: true, + tag: 'tag:yaml.org,2002:int', + format: 'BIN', + test: /^[-+]?0b[0-1_]+$/, + resolve: (str, _onError, opt) => intResolve(str, 2, 2, opt), + stringify: node => intStringify(node, 2, '0b') + }; + const intOct = { + identify: intIdentify, + default: true, + tag: 'tag:yaml.org,2002:int', + format: 'OCT', + test: /^[-+]?0[0-7_]+$/, + resolve: (str, _onError, opt) => intResolve(str, 1, 8, opt), + stringify: node => intStringify(node, 8, '0') + }; + const int = { + identify: intIdentify, + default: true, + tag: 'tag:yaml.org,2002:int', + test: /^[-+]?[0-9][0-9_]*$/, + resolve: (str, _onError, opt) => intResolve(str, 0, 10, opt), + stringify: stringifyNumber + }; + const intHex = { + identify: intIdentify, + default: true, + tag: 'tag:yaml.org,2002:int', + format: 'HEX', + test: /^[-+]?0x[0-9a-fA-F_]+$/, + resolve: (str, _onError, opt) => intResolve(str, 2, 16, opt), + stringify: node => intStringify(node, 16, '0x') + }; + + class YAMLSet extends YAMLMap { + constructor(schema) { + super(schema); + this.tag = YAMLSet.tag; + } + add(key) { + let pair; + if (isPair(key)) + pair = key; + else if (key && + typeof key === 'object' && + 'key' in key && + 'value' in key && + key.value === null) + pair = new Pair(key.key, null); + else + pair = new Pair(key, null); + const prev = findPair(this.items, pair.key); + if (!prev) + this.items.push(pair); + } + /** + * If `keepPair` is `true`, returns the Pair matching `key`. + * Otherwise, returns the value of that Pair's key. + */ + get(key, keepPair) { + const pair = findPair(this.items, key); + return !keepPair && isPair(pair) + ? isScalar$1(pair.key) + ? pair.key.value + : pair.key + : pair; + } + set(key, value) { + if (typeof value !== 'boolean') + throw new Error(`Expected boolean value for set(key, value) in a YAML set, not ${typeof value}`); + const prev = findPair(this.items, key); + if (prev && !value) { + this.items.splice(this.items.indexOf(prev), 1); + } + else if (!prev && value) { + this.items.push(new Pair(key)); + } + } + toJSON(_, ctx) { + return super.toJSON(_, ctx, Set); + } + toString(ctx, onComment, onChompKeep) { + if (!ctx) + return JSON.stringify(this); + if (this.hasAllNullValues(true)) + return super.toString(Object.assign({}, ctx, { allNullValues: true }), onComment, onChompKeep); + else + throw new Error('Set items must all have null values'); + } + static from(schema, iterable, ctx) { + const { replacer } = ctx; + const set = new this(schema); + if (iterable && Symbol.iterator in Object(iterable)) + for (let value of iterable) { + if (typeof replacer === 'function') + value = replacer.call(iterable, value, value); + set.items.push(createPair(value, null, ctx)); + } + return set; + } + } + YAMLSet.tag = 'tag:yaml.org,2002:set'; + const set = { + collection: 'map', + identify: value => value instanceof Set, + nodeClass: YAMLSet, + default: false, + tag: 'tag:yaml.org,2002:set', + createNode: (schema, iterable, ctx) => YAMLSet.from(schema, iterable, ctx), + resolve(map, onError) { + if (isMap(map)) { + if (map.hasAllNullValues(true)) + return Object.assign(new YAMLSet(), map); + else + onError('Set items must all have null values'); + } + else + onError('Expected a mapping for this tag'); + return map; + } + }; + + /** Internal types handle bigint as number, because TS can't figure it out. */ + function parseSexagesimal(str, asBigInt) { + const sign = str[0]; + const parts = sign === '-' || sign === '+' ? str.substring(1) : str; + const num = (n) => asBigInt ? BigInt(n) : Number(n); + const res = parts + .replace(/_/g, '') + .split(':') + .reduce((res, p) => res * num(60) + num(p), num(0)); + return (sign === '-' ? num(-1) * res : res); + } + /** + * hhhh:mm:ss.sss + * + * Internal types handle bigint as number, because TS can't figure it out. + */ + function stringifySexagesimal(node) { + let { value } = node; + let num = (n) => n; + if (typeof value === 'bigint') + num = n => BigInt(n); + else if (isNaN(value) || !isFinite(value)) + return stringifyNumber(node); + let sign = ''; + if (value < 0) { + sign = '-'; + value *= num(-1); + } + const _60 = num(60); + const parts = [value % _60]; // seconds, including ms + if (value < 60) { + parts.unshift(0); // at least one : is required + } + else { + value = (value - parts[0]) / _60; + parts.unshift(value % _60); // minutes + if (value >= 60) { + value = (value - parts[0]) / _60; + parts.unshift(value); // hours + } + } + return (sign + + parts + .map(n => String(n).padStart(2, '0')) + .join(':') + .replace(/000000\d*$/, '') // % 60 may introduce error + ); + } + const intTime = { + identify: value => typeof value === 'bigint' || Number.isInteger(value), + default: true, + tag: 'tag:yaml.org,2002:int', + format: 'TIME', + test: /^[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+$/, + resolve: (str, _onError, { intAsBigInt }) => parseSexagesimal(str, intAsBigInt), + stringify: stringifySexagesimal + }; + const floatTime = { + identify: value => typeof value === 'number', + default: true, + tag: 'tag:yaml.org,2002:float', + format: 'TIME', + test: /^[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\.[0-9_]*$/, + resolve: str => parseSexagesimal(str, false), + stringify: stringifySexagesimal + }; + const timestamp = { + identify: value => value instanceof Date, + default: true, + tag: 'tag:yaml.org,2002:timestamp', + // If the time zone is omitted, the timestamp is assumed to be specified in UTC. The time part + // may be omitted altogether, resulting in a date format. In such a case, the time part is + // assumed to be 00:00:00Z (start of day, UTC). + test: RegExp('^([0-9]{4})-([0-9]{1,2})-([0-9]{1,2})' + // YYYY-Mm-Dd + '(?:' + // time is optional + '(?:t|T|[ \\t]+)' + // t | T | whitespace + '([0-9]{1,2}):([0-9]{1,2}):([0-9]{1,2}(\\.[0-9]+)?)' + // Hh:Mm:Ss(.ss)? + '(?:[ \\t]*(Z|[-+][012]?[0-9](?::[0-9]{2})?))?' + // Z | +5 | -03:30 + ')?$'), + resolve(str) { + const match = str.match(timestamp.test); + if (!match) + throw new Error('!!timestamp expects a date, starting with yyyy-mm-dd'); + const [, year, month, day, hour, minute, second] = match.map(Number); + const millisec = match[7] ? Number((match[7] + '00').substr(1, 3)) : 0; + let date = Date.UTC(year, month - 1, day, hour || 0, minute || 0, second || 0, millisec); + const tz = match[8]; + if (tz && tz !== 'Z') { + let d = parseSexagesimal(tz, false); + if (Math.abs(d) < 30) + d *= 60; + date -= 60000 * d; + } + return new Date(date); + }, + stringify: ({ value }) => value?.toISOString().replace(/(T00:00:00)?\.000Z$/, '') ?? '' + }; + + const schema = [ + map, + seq, + string, + nullTag, + trueTag, + falseTag, + intBin, + intOct, + int, + intHex, + floatNaN, + floatExp, + float, + binary, + merge, + omap, + pairs, + set, + intTime, + floatTime, + timestamp + ]; + + const schemas = new Map([ + ['core', schema$2], + ['failsafe', [map, seq, string]], + ['json', schema$1], + ['yaml11', schema], + ['yaml-1.1', schema] + ]); + const tagsByName = { + binary, + bool: boolTag, + float: float$1, + floatExp: floatExp$1, + floatNaN: floatNaN$1, + floatTime, + int: int$1, + intHex: intHex$1, + intOct: intOct$1, + intTime, + map, + merge, + null: nullTag, + omap, + pairs, + seq, + set, + timestamp + }; + const coreKnownTags = { + 'tag:yaml.org,2002:binary': binary, + 'tag:yaml.org,2002:merge': merge, + 'tag:yaml.org,2002:omap': omap, + 'tag:yaml.org,2002:pairs': pairs, + 'tag:yaml.org,2002:set': set, + 'tag:yaml.org,2002:timestamp': timestamp + }; + function getTags(customTags, schemaName, addMergeTag) { + const schemaTags = schemas.get(schemaName); + if (schemaTags && !customTags) { + return addMergeTag && !schemaTags.includes(merge) + ? schemaTags.concat(merge) + : schemaTags.slice(); + } + let tags = schemaTags; + if (!tags) { + if (Array.isArray(customTags)) + tags = []; + else { + const keys = Array.from(schemas.keys()) + .filter(key => key !== 'yaml11') + .map(key => JSON.stringify(key)) + .join(', '); + throw new Error(`Unknown schema "${schemaName}"; use one of ${keys} or define customTags array`); + } + } + if (Array.isArray(customTags)) { + for (const tag of customTags) + tags = tags.concat(tag); + } + else if (typeof customTags === 'function') { + tags = customTags(tags.slice()); + } + if (addMergeTag) + tags = tags.concat(merge); + return tags.reduce((tags, tag) => { + const tagObj = typeof tag === 'string' ? tagsByName[tag] : tag; + if (!tagObj) { + const tagName = JSON.stringify(tag); + const keys = Object.keys(tagsByName) + .map(key => JSON.stringify(key)) + .join(', '); + throw new Error(`Unknown custom tag ${tagName}; use one of ${keys}`); + } + if (!tags.includes(tagObj)) + tags.push(tagObj); + return tags; + }, []); + } + + const sortMapEntriesByKey = (a, b) => a.key < b.key ? -1 : a.key > b.key ? 1 : 0; + class Schema { + constructor({ compat, customTags, merge, resolveKnownTags, schema, sortMapEntries, toStringDefaults }) { + this.compat = Array.isArray(compat) + ? getTags(compat, 'compat') + : compat + ? getTags(null, compat) + : null; + this.name = (typeof schema === 'string' && schema) || 'core'; + this.knownTags = resolveKnownTags ? coreKnownTags : {}; + this.tags = getTags(customTags, this.name, merge); + this.toStringOptions = toStringDefaults ?? null; + Object.defineProperty(this, MAP, { value: map }); + Object.defineProperty(this, SCALAR$1, { value: string }); + Object.defineProperty(this, SEQ, { value: seq }); + // Used by createMap() + this.sortMapEntries = + typeof sortMapEntries === 'function' + ? sortMapEntries + : sortMapEntries === true + ? sortMapEntriesByKey + : null; + } + clone() { + const copy = Object.create(Schema.prototype, Object.getOwnPropertyDescriptors(this)); + copy.tags = this.tags.slice(); + return copy; + } + } + + function stringifyDocument(doc, options) { + const lines = []; + let hasDirectives = options.directives === true; + if (options.directives !== false && doc.directives) { + const dir = doc.directives.toString(doc); + if (dir) { + lines.push(dir); + hasDirectives = true; + } + else if (doc.directives.docStart) + hasDirectives = true; + } + if (hasDirectives) + lines.push('---'); + const ctx = createStringifyContext(doc, options); + const { commentString } = ctx.options; + if (doc.commentBefore) { + if (lines.length !== 1) + lines.unshift(''); + const cs = commentString(doc.commentBefore); + lines.unshift(indentComment(cs, '')); + } + let chompKeep = false; + let contentComment = null; + if (doc.contents) { + if (isNode(doc.contents)) { + if (doc.contents.spaceBefore && hasDirectives) + lines.push(''); + if (doc.contents.commentBefore) { + const cs = commentString(doc.contents.commentBefore); + lines.push(indentComment(cs, '')); + } + // top-level block scalars need to be indented if followed by a comment + ctx.forceBlockIndent = !!doc.comment; + contentComment = doc.contents.comment; + } + const onChompKeep = contentComment ? undefined : () => (chompKeep = true); + let body = stringify$2(doc.contents, ctx, () => (contentComment = null), onChompKeep); + if (contentComment) + body += lineComment(body, '', commentString(contentComment)); + if ((body[0] === '|' || body[0] === '>') && + lines[lines.length - 1] === '---') { + // Top-level block scalars with a preceding doc marker ought to use the + // same line for their header. + lines[lines.length - 1] = `--- ${body}`; + } + else + lines.push(body); + } + else { + lines.push(stringify$2(doc.contents, ctx)); + } + if (doc.directives?.docEnd) { + if (doc.comment) { + const cs = commentString(doc.comment); + if (cs.includes('\n')) { + lines.push('...'); + lines.push(indentComment(cs, '')); + } + else { + lines.push(`... ${cs}`); + } + } + else { + lines.push('...'); + } + } + else { + let dc = doc.comment; + if (dc && chompKeep) + dc = dc.replace(/^\n+/, ''); + if (dc) { + if ((!chompKeep || contentComment) && lines[lines.length - 1] !== '') + lines.push(''); + lines.push(indentComment(commentString(dc), '')); + } + } + return lines.join('\n') + '\n'; + } + + class Document { + constructor(value, replacer, options) { + /** A comment before this Document */ + this.commentBefore = null; + /** A comment immediately after this Document */ + this.comment = null; + /** Errors encountered during parsing. */ + this.errors = []; + /** Warnings encountered during parsing. */ + this.warnings = []; + Object.defineProperty(this, NODE_TYPE, { value: DOC }); + let _replacer = null; + if (typeof replacer === 'function' || Array.isArray(replacer)) { + _replacer = replacer; + } + else if (options === undefined && replacer) { + options = replacer; + replacer = undefined; + } + const opt = Object.assign({ + intAsBigInt: false, + keepSourceTokens: false, + logLevel: 'warn', + prettyErrors: true, + strict: true, + stringKeys: false, + uniqueKeys: true, + version: '1.2' + }, options); + this.options = opt; + let { version } = opt; + if (options?._directives) { + this.directives = options._directives.atDocument(); + if (this.directives.yaml.explicit) + version = this.directives.yaml.version; + } + else + this.directives = new Directives({ version }); + this.setSchema(version, options); + // @ts-expect-error We can't really know that this matches Contents. + this.contents = + value === undefined ? null : this.createNode(value, _replacer, options); + } + /** + * Create a deep copy of this Document and its contents. + * + * Custom Node values that inherit from `Object` still refer to their original instances. + */ + clone() { + const copy = Object.create(Document.prototype, { + [NODE_TYPE]: { value: DOC } + }); + copy.commentBefore = this.commentBefore; + copy.comment = this.comment; + copy.errors = this.errors.slice(); + copy.warnings = this.warnings.slice(); + copy.options = Object.assign({}, this.options); + if (this.directives) + copy.directives = this.directives.clone(); + copy.schema = this.schema.clone(); + // @ts-expect-error We can't really know that this matches Contents. + copy.contents = isNode(this.contents) + ? this.contents.clone(copy.schema) + : this.contents; + if (this.range) + copy.range = this.range.slice(); + return copy; + } + /** Adds a value to the document. */ + add(value) { + if (assertCollection(this.contents)) + this.contents.add(value); + } + /** Adds a value to the document. */ + addIn(path, value) { + if (assertCollection(this.contents)) + this.contents.addIn(path, value); + } + /** + * Create a new `Alias` node, ensuring that the target `node` has the required anchor. + * + * If `node` already has an anchor, `name` is ignored. + * Otherwise, the `node.anchor` value will be set to `name`, + * or if an anchor with that name is already present in the document, + * `name` will be used as a prefix for a new unique anchor. + * If `name` is undefined, the generated anchor will use 'a' as a prefix. + */ + createAlias(node, name) { + if (!node.anchor) { + const prev = anchorNames(this); + node.anchor = + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + !name || prev.has(name) ? findNewAnchor(name || 'a', prev) : name; + } + return new Alias$1(node.anchor); + } + createNode(value, replacer, options) { + let _replacer = undefined; + if (typeof replacer === 'function') { + value = replacer.call({ '': value }, '', value); + _replacer = replacer; + } + else if (Array.isArray(replacer)) { + const keyToStr = (v) => typeof v === 'number' || v instanceof String || v instanceof Number; + const asStr = replacer.filter(keyToStr).map(String); + if (asStr.length > 0) + replacer = replacer.concat(asStr); + _replacer = replacer; + } + else if (options === undefined && replacer) { + options = replacer; + replacer = undefined; + } + const { aliasDuplicateObjects, anchorPrefix, flow, keepUndefined, onTagObj, tag } = options ?? {}; + const { onAnchor, setAnchors, sourceObjects } = createNodeAnchors(this, + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + anchorPrefix || 'a'); + const ctx = { + aliasDuplicateObjects: aliasDuplicateObjects ?? true, + keepUndefined: keepUndefined ?? false, + onAnchor, + onTagObj, + replacer: _replacer, + schema: this.schema, + sourceObjects + }; + const node = createNode(value, tag, ctx); + if (flow && isCollection$1(node)) + node.flow = true; + setAnchors(); + return node; + } + /** + * Convert a key and a value into a `Pair` using the current schema, + * recursively wrapping all values as `Scalar` or `Collection` nodes. + */ + createPair(key, value, options = {}) { + const k = this.createNode(key, null, options); + const v = this.createNode(value, null, options); + return new Pair(k, v); + } + /** + * Removes a value from the document. + * @returns `true` if the item was found and removed. + */ + delete(key) { + return assertCollection(this.contents) ? this.contents.delete(key) : false; + } + /** + * Removes a value from the document. + * @returns `true` if the item was found and removed. + */ + deleteIn(path) { + if (isEmptyPath(path)) { + if (this.contents == null) + return false; + // @ts-expect-error Presumed impossible if Strict extends false + this.contents = null; + return true; + } + return assertCollection(this.contents) + ? this.contents.deleteIn(path) + : false; + } + /** + * Returns item at `key`, or `undefined` if not found. By default unwraps + * scalar values from their surrounding node; to disable set `keepScalar` to + * `true` (collections are always returned intact). + */ + get(key, keepScalar) { + return isCollection$1(this.contents) + ? this.contents.get(key, keepScalar) + : undefined; + } + /** + * Returns item at `path`, or `undefined` if not found. By default unwraps + * scalar values from their surrounding node; to disable set `keepScalar` to + * `true` (collections are always returned intact). + */ + getIn(path, keepScalar) { + if (isEmptyPath(path)) + return !keepScalar && isScalar$1(this.contents) + ? this.contents.value + : this.contents; + return isCollection$1(this.contents) + ? this.contents.getIn(path, keepScalar) + : undefined; + } + /** + * Checks if the document includes a value with the key `key`. + */ + has(key) { + return isCollection$1(this.contents) ? this.contents.has(key) : false; + } + /** + * Checks if the document includes a value at `path`. + */ + hasIn(path) { + if (isEmptyPath(path)) + return this.contents !== undefined; + return isCollection$1(this.contents) ? this.contents.hasIn(path) : false; + } + /** + * Sets a value in this document. For `!!set`, `value` needs to be a + * boolean to add/remove the item from the set. + */ + set(key, value) { + if (this.contents == null) { + // @ts-expect-error We can't really know that this matches Contents. + this.contents = collectionFromPath(this.schema, [key], value); + } + else if (assertCollection(this.contents)) { + this.contents.set(key, value); + } + } + /** + * Sets a value in this document. For `!!set`, `value` needs to be a + * boolean to add/remove the item from the set. + */ + setIn(path, value) { + if (isEmptyPath(path)) { + // @ts-expect-error We can't really know that this matches Contents. + this.contents = value; + } + else if (this.contents == null) { + // @ts-expect-error We can't really know that this matches Contents. + this.contents = collectionFromPath(this.schema, Array.from(path), value); + } + else if (assertCollection(this.contents)) { + this.contents.setIn(path, value); + } + } + /** + * Change the YAML version and schema used by the document. + * A `null` version disables support for directives, explicit tags, anchors, and aliases. + * It also requires the `schema` option to be given as a `Schema` instance value. + * + * Overrides all previously set schema options. + */ + setSchema(version, options = {}) { + if (typeof version === 'number') + version = String(version); + let opt; + switch (version) { + case '1.1': + if (this.directives) + this.directives.yaml.version = '1.1'; + else + this.directives = new Directives({ version: '1.1' }); + opt = { resolveKnownTags: false, schema: 'yaml-1.1' }; + break; + case '1.2': + case 'next': + if (this.directives) + this.directives.yaml.version = version; + else + this.directives = new Directives({ version }); + opt = { resolveKnownTags: true, schema: 'core' }; + break; + case null: + if (this.directives) + delete this.directives; + opt = null; + break; + default: { + const sv = JSON.stringify(version); + throw new Error(`Expected '1.1', '1.2' or null as first argument, but found: ${sv}`); + } + } + // Not using `instanceof Schema` to allow for duck typing + if (options.schema instanceof Object) + this.schema = options.schema; + else if (opt) + this.schema = new Schema(Object.assign(opt, options)); + else + throw new Error(`With a null YAML version, the { schema: Schema } option is required`); + } + // json & jsonArg are only used from toJSON() + toJS({ json, jsonArg, mapAsMap, maxAliasCount, onAnchor, reviver } = {}) { + const ctx = { + anchors: new Map(), + doc: this, + keep: !json, + mapAsMap: mapAsMap === true, + mapKeyWarned: false, + maxAliasCount: typeof maxAliasCount === 'number' ? maxAliasCount : 100 + }; + const res = toJS(this.contents, jsonArg ?? '', ctx); + if (typeof onAnchor === 'function') + for (const { count, res } of ctx.anchors.values()) + onAnchor(res, count); + return typeof reviver === 'function' + ? applyReviver(reviver, { '': res }, '', res) + : res; + } + /** + * A JSON representation of the document `contents`. + * + * @param jsonArg Used by `JSON.stringify` to indicate the array index or + * property name. + */ + toJSON(jsonArg, onAnchor) { + return this.toJS({ json: true, jsonArg, mapAsMap: false, onAnchor }); + } + /** A YAML representation of the document. */ + toString(options = {}) { + if (this.errors.length > 0) + throw new Error('Document with errors cannot be stringified'); + if ('indent' in options && + (!Number.isInteger(options.indent) || Number(options.indent) <= 0)) { + const s = JSON.stringify(options.indent); + throw new Error(`"indent" option must be a positive integer, not ${s}`); + } + return stringifyDocument(this, options); + } + } + function assertCollection(contents) { + if (isCollection$1(contents)) + return true; + throw new Error('Expected a YAML collection as document contents'); + } + + class YAMLError extends Error { + constructor(name, pos, code, message) { + super(); + this.name = name; + this.code = code; + this.message = message; + this.pos = pos; + } + } + class YAMLParseError extends YAMLError { + constructor(pos, code, message) { + super('YAMLParseError', pos, code, message); + } + } + class YAMLWarning extends YAMLError { + constructor(pos, code, message) { + super('YAMLWarning', pos, code, message); + } + } + const prettifyError = (src, lc) => (error) => { + if (error.pos[0] === -1) + return; + error.linePos = error.pos.map(pos => lc.linePos(pos)); + const { line, col } = error.linePos[0]; + error.message += ` at line ${line}, column ${col}`; + let ci = col - 1; + let lineStr = src + .substring(lc.lineStarts[line - 1], lc.lineStarts[line]) + .replace(/[\n\r]+$/, ''); + // Trim to max 80 chars, keeping col position near the middle + if (ci >= 60 && lineStr.length > 80) { + const trimStart = Math.min(ci - 39, lineStr.length - 79); + lineStr = '…' + lineStr.substring(trimStart); + ci -= trimStart - 1; + } + if (lineStr.length > 80) + lineStr = lineStr.substring(0, 79) + '…'; + // Include previous line in context if pointing at line start + if (line > 1 && /^ *$/.test(lineStr.substring(0, ci))) { + // Regexp won't match if start is trimmed + let prev = src.substring(lc.lineStarts[line - 2], lc.lineStarts[line - 1]); + if (prev.length > 80) + prev = prev.substring(0, 79) + '…\n'; + lineStr = prev + lineStr; + } + if (/[^ ]/.test(lineStr)) { + let count = 1; + const end = error.linePos[1]; + if (end && end.line === line && end.col > col) { + count = Math.max(1, Math.min(end.col - col, 80 - ci)); + } + const pointer = ' '.repeat(ci) + '^'.repeat(count); + error.message += `:\n\n${lineStr}\n${pointer}\n`; + } + }; + + function resolveProps(tokens, { flow, indicator, next, offset, onError, parentIndent, startOnNewline }) { + let spaceBefore = false; + let atNewline = startOnNewline; + let hasSpace = startOnNewline; + let comment = ''; + let commentSep = ''; + let hasNewline = false; + let reqSpace = false; + let tab = null; + let anchor = null; + let tag = null; + let newlineAfterProp = null; + let comma = null; + let found = null; + let start = null; + for (const token of tokens) { + if (reqSpace) { + if (token.type !== 'space' && + token.type !== 'newline' && + token.type !== 'comma') + onError(token.offset, 'MISSING_CHAR', 'Tags and anchors must be separated from the next token by white space'); + reqSpace = false; + } + if (tab) { + if (atNewline && token.type !== 'comment' && token.type !== 'newline') { + onError(tab, 'TAB_AS_INDENT', 'Tabs are not allowed as indentation'); + } + tab = null; + } + switch (token.type) { + case 'space': + // At the doc level, tabs at line start may be parsed + // as leading white space rather than indentation. + // In a flow collection, only the parser handles indent. + if (!flow && + (indicator !== 'doc-start' || next?.type !== 'flow-collection') && + token.source.includes('\t')) { + tab = token; + } + hasSpace = true; + break; + case 'comment': { + if (!hasSpace) + onError(token, 'MISSING_CHAR', 'Comments must be separated from other tokens by white space characters'); + const cb = token.source.substring(1) || ' '; + if (!comment) + comment = cb; + else + comment += commentSep + cb; + commentSep = ''; + atNewline = false; + break; + } + case 'newline': + if (atNewline) { + if (comment) + comment += token.source; + else if (!found || indicator !== 'seq-item-ind') + spaceBefore = true; + } + else + commentSep += token.source; + atNewline = true; + hasNewline = true; + if (anchor || tag) + newlineAfterProp = token; + hasSpace = true; + break; + case 'anchor': + if (anchor) + onError(token, 'MULTIPLE_ANCHORS', 'A node can have at most one anchor'); + if (token.source.endsWith(':')) + onError(token.offset + token.source.length - 1, 'BAD_ALIAS', 'Anchor ending in : is ambiguous', true); + anchor = token; + start ?? (start = token.offset); + atNewline = false; + hasSpace = false; + reqSpace = true; + break; + case 'tag': { + if (tag) + onError(token, 'MULTIPLE_TAGS', 'A node can have at most one tag'); + tag = token; + start ?? (start = token.offset); + atNewline = false; + hasSpace = false; + reqSpace = true; + break; + } + case indicator: + // Could here handle preceding comments differently + if (anchor || tag) + onError(token, 'BAD_PROP_ORDER', `Anchors and tags must be after the ${token.source} indicator`); + if (found) + onError(token, 'UNEXPECTED_TOKEN', `Unexpected ${token.source} in ${flow ?? 'collection'}`); + found = token; + atNewline = + indicator === 'seq-item-ind' || indicator === 'explicit-key-ind'; + hasSpace = false; + break; + case 'comma': + if (flow) { + if (comma) + onError(token, 'UNEXPECTED_TOKEN', `Unexpected , in ${flow}`); + comma = token; + atNewline = false; + hasSpace = false; + break; + } + // else fallthrough + default: + onError(token, 'UNEXPECTED_TOKEN', `Unexpected ${token.type} token`); + atNewline = false; + hasSpace = false; + } + } + const last = tokens[tokens.length - 1]; + const end = last ? last.offset + last.source.length : offset; + if (reqSpace && + next && + next.type !== 'space' && + next.type !== 'newline' && + next.type !== 'comma' && + (next.type !== 'scalar' || next.source !== '')) { + onError(next.offset, 'MISSING_CHAR', 'Tags and anchors must be separated from the next token by white space'); + } + if (tab && + ((atNewline && tab.indent <= parentIndent) || + next?.type === 'block-map' || + next?.type === 'block-seq')) + onError(tab, 'TAB_AS_INDENT', 'Tabs are not allowed as indentation'); + return { + comma, + found, + spaceBefore, + comment, + hasNewline, + anchor, + tag, + newlineAfterProp, + end, + start: start ?? end + }; + } + + function containsNewline(key) { + if (!key) + return null; + switch (key.type) { + case 'alias': + case 'scalar': + case 'double-quoted-scalar': + case 'single-quoted-scalar': + if (key.source.includes('\n')) + return true; + if (key.end) + for (const st of key.end) + if (st.type === 'newline') + return true; + return false; + case 'flow-collection': + for (const it of key.items) { + for (const st of it.start) + if (st.type === 'newline') + return true; + if (it.sep) + for (const st of it.sep) + if (st.type === 'newline') + return true; + if (containsNewline(it.key) || containsNewline(it.value)) + return true; + } + return false; + default: + return true; + } + } + + function flowIndentCheck(indent, fc, onError) { + if (fc?.type === 'flow-collection') { + const end = fc.end[0]; + if (end.indent === indent && + (end.source === ']' || end.source === '}') && + containsNewline(fc)) { + const msg = 'Flow end indicator should be more indented than parent'; + onError(end, 'BAD_INDENT', msg, true); + } + } + } + + function mapIncludes(ctx, items, search) { + const { uniqueKeys } = ctx.options; + if (uniqueKeys === false) + return false; + const isEqual = typeof uniqueKeys === 'function' + ? uniqueKeys + : (a, b) => a === b || (isScalar$1(a) && isScalar$1(b) && a.value === b.value); + return items.some(pair => isEqual(pair.key, search)); + } + + const startColMsg = 'All mapping items must start at the same column'; + function resolveBlockMap({ composeNode, composeEmptyNode }, ctx, bm, onError, tag) { + const NodeClass = tag?.nodeClass ?? YAMLMap; + const map = new NodeClass(ctx.schema); + if (ctx.atRoot) + ctx.atRoot = false; + let offset = bm.offset; + let commentEnd = null; + for (const collItem of bm.items) { + const { start, key, sep, value } = collItem; + // key properties + const keyProps = resolveProps(start, { + indicator: 'explicit-key-ind', + next: key ?? sep?.[0], + offset, + onError, + parentIndent: bm.indent, + startOnNewline: true + }); + const implicitKey = !keyProps.found; + if (implicitKey) { + if (key) { + if (key.type === 'block-seq') + onError(offset, 'BLOCK_AS_IMPLICIT_KEY', 'A block sequence may not be used as an implicit map key'); + else if ('indent' in key && key.indent !== bm.indent) + onError(offset, 'BAD_INDENT', startColMsg); + } + if (!keyProps.anchor && !keyProps.tag && !sep) { + commentEnd = keyProps.end; + if (keyProps.comment) { + if (map.comment) + map.comment += '\n' + keyProps.comment; + else + map.comment = keyProps.comment; + } + continue; + } + if (keyProps.newlineAfterProp || containsNewline(key)) { + onError(key ?? start[start.length - 1], 'MULTILINE_IMPLICIT_KEY', 'Implicit keys need to be on a single line'); + } + } + else if (keyProps.found?.indent !== bm.indent) { + onError(offset, 'BAD_INDENT', startColMsg); + } + // key value + ctx.atKey = true; + const keyStart = keyProps.end; + const keyNode = key + ? composeNode(ctx, key, keyProps, onError) + : composeEmptyNode(ctx, keyStart, start, null, keyProps, onError); + if (ctx.schema.compat) + flowIndentCheck(bm.indent, key, onError); + ctx.atKey = false; + if (mapIncludes(ctx, map.items, keyNode)) + onError(keyStart, 'DUPLICATE_KEY', 'Map keys must be unique'); + // value properties + const valueProps = resolveProps(sep ?? [], { + indicator: 'map-value-ind', + next: value, + offset: keyNode.range[2], + onError, + parentIndent: bm.indent, + startOnNewline: !key || key.type === 'block-scalar' + }); + offset = valueProps.end; + if (valueProps.found) { + if (implicitKey) { + if (value?.type === 'block-map' && !valueProps.hasNewline) + onError(offset, 'BLOCK_AS_IMPLICIT_KEY', 'Nested mappings are not allowed in compact mappings'); + if (ctx.options.strict && + keyProps.start < valueProps.found.offset - 1024) + onError(keyNode.range, 'KEY_OVER_1024_CHARS', 'The : indicator must be at most 1024 chars after the start of an implicit block mapping key'); + } + // value value + const valueNode = value + ? composeNode(ctx, value, valueProps, onError) + : composeEmptyNode(ctx, offset, sep, null, valueProps, onError); + if (ctx.schema.compat) + flowIndentCheck(bm.indent, value, onError); + offset = valueNode.range[2]; + const pair = new Pair(keyNode, valueNode); + if (ctx.options.keepSourceTokens) + pair.srcToken = collItem; + map.items.push(pair); + } + else { + // key with no value + if (implicitKey) + onError(keyNode.range, 'MISSING_CHAR', 'Implicit map keys need to be followed by map values'); + if (valueProps.comment) { + if (keyNode.comment) + keyNode.comment += '\n' + valueProps.comment; + else + keyNode.comment = valueProps.comment; + } + const pair = new Pair(keyNode); + if (ctx.options.keepSourceTokens) + pair.srcToken = collItem; + map.items.push(pair); + } + } + if (commentEnd && commentEnd < offset) + onError(commentEnd, 'IMPOSSIBLE', 'Map comment with trailing content'); + map.range = [bm.offset, offset, commentEnd ?? offset]; + return map; + } + + function resolveBlockSeq({ composeNode, composeEmptyNode }, ctx, bs, onError, tag) { + const NodeClass = tag?.nodeClass ?? YAMLSeq; + const seq = new NodeClass(ctx.schema); + if (ctx.atRoot) + ctx.atRoot = false; + if (ctx.atKey) + ctx.atKey = false; + let offset = bs.offset; + let commentEnd = null; + for (const { start, value } of bs.items) { + const props = resolveProps(start, { + indicator: 'seq-item-ind', + next: value, + offset, + onError, + parentIndent: bs.indent, + startOnNewline: true + }); + if (!props.found) { + if (props.anchor || props.tag || value) { + if (value && value.type === 'block-seq') + onError(props.end, 'BAD_INDENT', 'All sequence items must start at the same column'); + else + onError(offset, 'MISSING_CHAR', 'Sequence item without - indicator'); + } + else { + commentEnd = props.end; + if (props.comment) + seq.comment = props.comment; + continue; + } + } + const node = value + ? composeNode(ctx, value, props, onError) + : composeEmptyNode(ctx, props.end, start, null, props, onError); + if (ctx.schema.compat) + flowIndentCheck(bs.indent, value, onError); + offset = node.range[2]; + seq.items.push(node); + } + seq.range = [bs.offset, offset, commentEnd ?? offset]; + return seq; + } + + function resolveEnd(end, offset, reqSpace, onError) { + let comment = ''; + if (end) { + let hasSpace = false; + let sep = ''; + for (const token of end) { + const { source, type } = token; + switch (type) { + case 'space': + hasSpace = true; + break; + case 'comment': { + if (reqSpace && !hasSpace) + onError(token, 'MISSING_CHAR', 'Comments must be separated from other tokens by white space characters'); + const cb = source.substring(1) || ' '; + if (!comment) + comment = cb; + else + comment += sep + cb; + sep = ''; + break; + } + case 'newline': + if (comment) + sep += source; + hasSpace = true; + break; + default: + onError(token, 'UNEXPECTED_TOKEN', `Unexpected ${type} at node end`); + } + offset += source.length; + } + } + return { comment, offset }; + } + + const blockMsg = 'Block collections are not allowed within flow collections'; + const isBlock = (token) => token && (token.type === 'block-map' || token.type === 'block-seq'); + function resolveFlowCollection({ composeNode, composeEmptyNode }, ctx, fc, onError, tag) { + const isMap = fc.start.source === '{'; + const fcName = isMap ? 'flow map' : 'flow sequence'; + const NodeClass = (tag?.nodeClass ?? (isMap ? YAMLMap : YAMLSeq)); + const coll = new NodeClass(ctx.schema); + coll.flow = true; + const atRoot = ctx.atRoot; + if (atRoot) + ctx.atRoot = false; + if (ctx.atKey) + ctx.atKey = false; + let offset = fc.offset + fc.start.source.length; + for (let i = 0; i < fc.items.length; ++i) { + const collItem = fc.items[i]; + const { start, key, sep, value } = collItem; + const props = resolveProps(start, { + flow: fcName, + indicator: 'explicit-key-ind', + next: key ?? sep?.[0], + offset, + onError, + parentIndent: fc.indent, + startOnNewline: false + }); + if (!props.found) { + if (!props.anchor && !props.tag && !sep && !value) { + if (i === 0 && props.comma) + onError(props.comma, 'UNEXPECTED_TOKEN', `Unexpected , in ${fcName}`); + else if (i < fc.items.length - 1) + onError(props.start, 'UNEXPECTED_TOKEN', `Unexpected empty item in ${fcName}`); + if (props.comment) { + if (coll.comment) + coll.comment += '\n' + props.comment; + else + coll.comment = props.comment; + } + offset = props.end; + continue; + } + if (!isMap && ctx.options.strict && containsNewline(key)) + onError(key, // checked by containsNewline() + 'MULTILINE_IMPLICIT_KEY', 'Implicit keys of flow sequence pairs need to be on a single line'); + } + if (i === 0) { + if (props.comma) + onError(props.comma, 'UNEXPECTED_TOKEN', `Unexpected , in ${fcName}`); + } + else { + if (!props.comma) + onError(props.start, 'MISSING_CHAR', `Missing , between ${fcName} items`); + if (props.comment) { + let prevItemComment = ''; + loop: for (const st of start) { + switch (st.type) { + case 'comma': + case 'space': + break; + case 'comment': + prevItemComment = st.source.substring(1); + break loop; + default: + break loop; + } + } + if (prevItemComment) { + let prev = coll.items[coll.items.length - 1]; + if (isPair(prev)) + prev = prev.value ?? prev.key; + if (prev.comment) + prev.comment += '\n' + prevItemComment; + else + prev.comment = prevItemComment; + props.comment = props.comment.substring(prevItemComment.length + 1); + } + } + } + if (!isMap && !sep && !props.found) { + // item is a value in a seq + // → key & sep are empty, start does not include ? or : + const valueNode = value + ? composeNode(ctx, value, props, onError) + : composeEmptyNode(ctx, props.end, sep, null, props, onError); + coll.items.push(valueNode); + offset = valueNode.range[2]; + if (isBlock(value)) + onError(valueNode.range, 'BLOCK_IN_FLOW', blockMsg); + } + else { + // item is a key+value pair + // key value + ctx.atKey = true; + const keyStart = props.end; + const keyNode = key + ? composeNode(ctx, key, props, onError) + : composeEmptyNode(ctx, keyStart, start, null, props, onError); + if (isBlock(key)) + onError(keyNode.range, 'BLOCK_IN_FLOW', blockMsg); + ctx.atKey = false; + // value properties + const valueProps = resolveProps(sep ?? [], { + flow: fcName, + indicator: 'map-value-ind', + next: value, + offset: keyNode.range[2], + onError, + parentIndent: fc.indent, + startOnNewline: false + }); + if (valueProps.found) { + if (!isMap && !props.found && ctx.options.strict) { + if (sep) + for (const st of sep) { + if (st === valueProps.found) + break; + if (st.type === 'newline') { + onError(st, 'MULTILINE_IMPLICIT_KEY', 'Implicit keys of flow sequence pairs need to be on a single line'); + break; + } + } + if (props.start < valueProps.found.offset - 1024) + onError(valueProps.found, 'KEY_OVER_1024_CHARS', 'The : indicator must be at most 1024 chars after the start of an implicit flow sequence key'); + } + } + else if (value) { + if ('source' in value && value.source && value.source[0] === ':') + onError(value, 'MISSING_CHAR', `Missing space after : in ${fcName}`); + else + onError(valueProps.start, 'MISSING_CHAR', `Missing , or : between ${fcName} items`); + } + // value value + const valueNode = value + ? composeNode(ctx, value, valueProps, onError) + : valueProps.found + ? composeEmptyNode(ctx, valueProps.end, sep, null, valueProps, onError) + : null; + if (valueNode) { + if (isBlock(value)) + onError(valueNode.range, 'BLOCK_IN_FLOW', blockMsg); + } + else if (valueProps.comment) { + if (keyNode.comment) + keyNode.comment += '\n' + valueProps.comment; + else + keyNode.comment = valueProps.comment; + } + const pair = new Pair(keyNode, valueNode); + if (ctx.options.keepSourceTokens) + pair.srcToken = collItem; + if (isMap) { + const map = coll; + if (mapIncludes(ctx, map.items, keyNode)) + onError(keyStart, 'DUPLICATE_KEY', 'Map keys must be unique'); + map.items.push(pair); + } + else { + const map = new YAMLMap(ctx.schema); + map.flow = true; + map.items.push(pair); + const endRange = (valueNode ?? keyNode).range; + map.range = [keyNode.range[0], endRange[1], endRange[2]]; + coll.items.push(map); + } + offset = valueNode ? valueNode.range[2] : valueProps.end; + } + } + const expectedEnd = isMap ? '}' : ']'; + const [ce, ...ee] = fc.end; + let cePos = offset; + if (ce && ce.source === expectedEnd) + cePos = ce.offset + ce.source.length; + else { + const name = fcName[0].toUpperCase() + fcName.substring(1); + const msg = atRoot + ? `${name} must end with a ${expectedEnd}` + : `${name} in block collection must be sufficiently indented and end with a ${expectedEnd}`; + onError(offset, atRoot ? 'MISSING_CHAR' : 'BAD_INDENT', msg); + if (ce && ce.source.length !== 1) + ee.unshift(ce); + } + if (ee.length > 0) { + const end = resolveEnd(ee, cePos, ctx.options.strict, onError); + if (end.comment) { + if (coll.comment) + coll.comment += '\n' + end.comment; + else + coll.comment = end.comment; + } + coll.range = [fc.offset, cePos, end.offset]; + } + else { + coll.range = [fc.offset, cePos, cePos]; + } + return coll; + } + + function resolveCollection(CN, ctx, token, onError, tagName, tag) { + const coll = token.type === 'block-map' + ? resolveBlockMap(CN, ctx, token, onError, tag) + : token.type === 'block-seq' + ? resolveBlockSeq(CN, ctx, token, onError, tag) + : resolveFlowCollection(CN, ctx, token, onError, tag); + const Coll = coll.constructor; + // If we got a tagName matching the class, or the tag name is '!', + // then use the tagName from the node class used to create it. + if (tagName === '!' || tagName === Coll.tagName) { + coll.tag = Coll.tagName; + return coll; + } + if (tagName) + coll.tag = tagName; + return coll; + } + function composeCollection(CN, ctx, token, props, onError) { + const tagToken = props.tag; + const tagName = !tagToken + ? null + : ctx.directives.tagName(tagToken.source, msg => onError(tagToken, 'TAG_RESOLVE_FAILED', msg)); + if (token.type === 'block-seq') { + const { anchor, newlineAfterProp: nl } = props; + const lastProp = anchor && tagToken + ? anchor.offset > tagToken.offset + ? anchor + : tagToken + : (anchor ?? tagToken); + if (lastProp && (!nl || nl.offset < lastProp.offset)) { + const message = 'Missing newline after block sequence props'; + onError(lastProp, 'MISSING_CHAR', message); + } + } + const expType = token.type === 'block-map' + ? 'map' + : token.type === 'block-seq' + ? 'seq' + : token.start.source === '{' + ? 'map' + : 'seq'; + // shortcut: check if it's a generic YAMLMap or YAMLSeq + // before jumping into the custom tag logic. + if (!tagToken || + !tagName || + tagName === '!' || + (tagName === YAMLMap.tagName && expType === 'map') || + (tagName === YAMLSeq.tagName && expType === 'seq')) { + return resolveCollection(CN, ctx, token, onError, tagName); + } + let tag = ctx.schema.tags.find(t => t.tag === tagName && t.collection === expType); + if (!tag) { + const kt = ctx.schema.knownTags[tagName]; + if (kt && kt.collection === expType) { + ctx.schema.tags.push(Object.assign({}, kt, { default: false })); + tag = kt; + } + else { + if (kt) { + onError(tagToken, 'BAD_COLLECTION_TYPE', `${kt.tag} used for ${expType} collection, but expects ${kt.collection ?? 'scalar'}`, true); + } + else { + onError(tagToken, 'TAG_RESOLVE_FAILED', `Unresolved tag: ${tagName}`, true); + } + return resolveCollection(CN, ctx, token, onError, tagName); + } + } + const coll = resolveCollection(CN, ctx, token, onError, tagName, tag); + const res = tag.resolve?.(coll, msg => onError(tagToken, 'TAG_RESOLVE_FAILED', msg), ctx.options) ?? coll; + const node = isNode(res) + ? res + : new Scalar(res); + node.range = coll.range; + node.tag = tagName; + if (tag?.format) + node.format = tag.format; + return node; + } + + function resolveBlockScalar(ctx, scalar, onError) { + const start = scalar.offset; + const header = parseBlockScalarHeader(scalar, ctx.options.strict, onError); + if (!header) + return { value: '', type: null, comment: '', range: [start, start, start] }; + const type = header.mode === '>' ? Scalar.BLOCK_FOLDED : Scalar.BLOCK_LITERAL; + const lines = scalar.source ? splitLines(scalar.source) : []; + // determine the end of content & start of chomping + let chompStart = lines.length; + for (let i = lines.length - 1; i >= 0; --i) { + const content = lines[i][1]; + if (content === '' || content === '\r') + chompStart = i; + else + break; + } + // shortcut for empty contents + if (chompStart === 0) { + const value = header.chomp === '+' && lines.length > 0 + ? '\n'.repeat(Math.max(1, lines.length - 1)) + : ''; + let end = start + header.length; + if (scalar.source) + end += scalar.source.length; + return { value, type, comment: header.comment, range: [start, end, end] }; + } + // find the indentation level to trim from start + let trimIndent = scalar.indent + header.indent; + let offset = scalar.offset + header.length; + let contentStart = 0; + for (let i = 0; i < chompStart; ++i) { + const [indent, content] = lines[i]; + if (content === '' || content === '\r') { + if (header.indent === 0 && indent.length > trimIndent) + trimIndent = indent.length; + } + else { + if (indent.length < trimIndent) { + const message = 'Block scalars with more-indented leading empty lines must use an explicit indentation indicator'; + onError(offset + indent.length, 'MISSING_CHAR', message); + } + if (header.indent === 0) + trimIndent = indent.length; + contentStart = i; + if (trimIndent === 0 && !ctx.atRoot) { + const message = 'Block scalar values in collections must be indented'; + onError(offset, 'BAD_INDENT', message); + } + break; + } + offset += indent.length + content.length + 1; + } + // include trailing more-indented empty lines in content + for (let i = lines.length - 1; i >= chompStart; --i) { + if (lines[i][0].length > trimIndent) + chompStart = i + 1; + } + let value = ''; + let sep = ''; + let prevMoreIndented = false; + // leading whitespace is kept intact + for (let i = 0; i < contentStart; ++i) + value += lines[i][0].slice(trimIndent) + '\n'; + for (let i = contentStart; i < chompStart; ++i) { + let [indent, content] = lines[i]; + offset += indent.length + content.length + 1; + const crlf = content[content.length - 1] === '\r'; + if (crlf) + content = content.slice(0, -1); + /* istanbul ignore if already caught in lexer */ + if (content && indent.length < trimIndent) { + const src = header.indent + ? 'explicit indentation indicator' + : 'first line'; + const message = `Block scalar lines must not be less indented than their ${src}`; + onError(offset - content.length - (crlf ? 2 : 1), 'BAD_INDENT', message); + indent = ''; + } + if (type === Scalar.BLOCK_LITERAL) { + value += sep + indent.slice(trimIndent) + content; + sep = '\n'; + } + else if (indent.length > trimIndent || content[0] === '\t') { + // more-indented content within a folded block + if (sep === ' ') + sep = '\n'; + else if (!prevMoreIndented && sep === '\n') + sep = '\n\n'; + value += sep + indent.slice(trimIndent) + content; + sep = '\n'; + prevMoreIndented = true; + } + else if (content === '') { + // empty line + if (sep === '\n') + value += '\n'; + else + sep = '\n'; + } + else { + value += sep + content; + sep = ' '; + prevMoreIndented = false; + } + } + switch (header.chomp) { + case '-': + break; + case '+': + for (let i = chompStart; i < lines.length; ++i) + value += '\n' + lines[i][0].slice(trimIndent); + if (value[value.length - 1] !== '\n') + value += '\n'; + break; + default: + value += '\n'; + } + const end = start + header.length + scalar.source.length; + return { value, type, comment: header.comment, range: [start, end, end] }; + } + function parseBlockScalarHeader({ offset, props }, strict, onError) { + /* istanbul ignore if should not happen */ + if (props[0].type !== 'block-scalar-header') { + onError(props[0], 'IMPOSSIBLE', 'Block scalar header not found'); + return null; + } + const { source } = props[0]; + const mode = source[0]; + let indent = 0; + let chomp = ''; + let error = -1; + for (let i = 1; i < source.length; ++i) { + const ch = source[i]; + if (!chomp && (ch === '-' || ch === '+')) + chomp = ch; + else { + const n = Number(ch); + if (!indent && n) + indent = n; + else if (error === -1) + error = offset + i; + } + } + if (error !== -1) + onError(error, 'UNEXPECTED_TOKEN', `Block scalar header includes extra characters: ${source}`); + let hasSpace = false; + let comment = ''; + let length = source.length; + for (let i = 1; i < props.length; ++i) { + const token = props[i]; + switch (token.type) { + case 'space': + hasSpace = true; + // fallthrough + case 'newline': + length += token.source.length; + break; + case 'comment': + if (strict && !hasSpace) { + const message = 'Comments must be separated from other tokens by white space characters'; + onError(token, 'MISSING_CHAR', message); + } + length += token.source.length; + comment = token.source.substring(1); + break; + case 'error': + onError(token, 'UNEXPECTED_TOKEN', token.message); + length += token.source.length; + break; + /* istanbul ignore next should not happen */ + default: { + const message = `Unexpected token in block scalar header: ${token.type}`; + onError(token, 'UNEXPECTED_TOKEN', message); + const ts = token.source; + if (ts && typeof ts === 'string') + length += ts.length; + } + } + } + return { mode, indent, chomp, comment, length }; + } + /** @returns Array of lines split up as `[indent, content]` */ + function splitLines(source) { + const split = source.split(/\n( *)/); + const first = split[0]; + const m = first.match(/^( *)/); + const line0 = m?.[1] + ? [m[1], first.slice(m[1].length)] + : ['', first]; + const lines = [line0]; + for (let i = 1; i < split.length; i += 2) + lines.push([split[i], split[i + 1]]); + return lines; + } + + function resolveFlowScalar(scalar, strict, onError) { + const { offset, type, source, end } = scalar; + let _type; + let value; + const _onError = (rel, code, msg) => onError(offset + rel, code, msg); + switch (type) { + case 'scalar': + _type = Scalar.PLAIN; + value = plainValue(source, _onError); + break; + case 'single-quoted-scalar': + _type = Scalar.QUOTE_SINGLE; + value = singleQuotedValue(source, _onError); + break; + case 'double-quoted-scalar': + _type = Scalar.QUOTE_DOUBLE; + value = doubleQuotedValue(source, _onError); + break; + /* istanbul ignore next should not happen */ + default: + onError(scalar, 'UNEXPECTED_TOKEN', `Expected a flow scalar value, but found: ${type}`); + return { + value: '', + type: null, + comment: '', + range: [offset, offset + source.length, offset + source.length] + }; + } + const valueEnd = offset + source.length; + const re = resolveEnd(end, valueEnd, strict, onError); + return { + value, + type: _type, + comment: re.comment, + range: [offset, valueEnd, re.offset] + }; + } + function plainValue(source, onError) { + let badChar = ''; + switch (source[0]) { + /* istanbul ignore next should not happen */ + case '\t': + badChar = 'a tab character'; + break; + case ',': + badChar = 'flow indicator character ,'; + break; + case '%': + badChar = 'directive indicator character %'; + break; + case '|': + case '>': { + badChar = `block scalar indicator ${source[0]}`; + break; + } + case '@': + case '`': { + badChar = `reserved character ${source[0]}`; + break; + } + } + if (badChar) + onError(0, 'BAD_SCALAR_START', `Plain value cannot start with ${badChar}`); + return foldLines(source); + } + function singleQuotedValue(source, onError) { + if (source[source.length - 1] !== "'" || source.length === 1) + onError(source.length, 'MISSING_CHAR', "Missing closing 'quote"); + return foldLines(source.slice(1, -1)).replace(/''/g, "'"); + } + function foldLines(source) { + /** + * The negative lookbehind here and in the `re` RegExp is to + * prevent causing a polynomial search time in certain cases. + * + * The try-catch is for Safari, which doesn't support this yet: + * https://caniuse.com/js-regexp-lookbehind + */ + let first, line; + try { + first = new RegExp('(.*?)(? wsStart ? source.slice(wsStart, i + 1) : ch; + } + else { + res += ch; + } + } + if (source[source.length - 1] !== '"' || source.length === 1) + onError(source.length, 'MISSING_CHAR', 'Missing closing "quote'); + return res; + } + /** + * Fold a single newline into a space, multiple newlines to N - 1 newlines. + * Presumes `source[offset] === '\n'` + */ + function foldNewline(source, offset) { + let fold = ''; + let ch = source[offset + 1]; + while (ch === ' ' || ch === '\t' || ch === '\n' || ch === '\r') { + if (ch === '\r' && source[offset + 2] !== '\n') + break; + if (ch === '\n') + fold += '\n'; + offset += 1; + ch = source[offset + 1]; + } + if (!fold) + fold = ' '; + return { fold, offset }; + } + const escapeCodes = { + '0': '\0', // null character + a: '\x07', // bell character + b: '\b', // backspace + e: '\x1b', // escape character + f: '\f', // form feed + n: '\n', // line feed + r: '\r', // carriage return + t: '\t', // horizontal tab + v: '\v', // vertical tab + N: '\u0085', // Unicode next line + _: '\u00a0', // Unicode non-breaking space + L: '\u2028', // Unicode line separator + P: '\u2029', // Unicode paragraph separator + ' ': ' ', + '"': '"', + '/': '/', + '\\': '\\', + '\t': '\t' + }; + function parseCharCode(source, offset, length, onError) { + const cc = source.substr(offset, length); + const ok = cc.length === length && /^[0-9a-fA-F]+$/.test(cc); + const code = ok ? parseInt(cc, 16) : NaN; + if (isNaN(code)) { + const raw = source.substr(offset - 2, length + 2); + onError(offset - 2, 'BAD_DQ_ESCAPE', `Invalid escape sequence ${raw}`); + return raw; + } + return String.fromCodePoint(code); + } + + function composeScalar(ctx, token, tagToken, onError) { + const { value, type, comment, range } = token.type === 'block-scalar' + ? resolveBlockScalar(ctx, token, onError) + : resolveFlowScalar(token, ctx.options.strict, onError); + const tagName = tagToken + ? ctx.directives.tagName(tagToken.source, msg => onError(tagToken, 'TAG_RESOLVE_FAILED', msg)) + : null; + let tag; + if (ctx.options.stringKeys && ctx.atKey) { + tag = ctx.schema[SCALAR$1]; + } + else if (tagName) + tag = findScalarTagByName(ctx.schema, value, tagName, tagToken, onError); + else if (token.type === 'scalar') + tag = findScalarTagByTest(ctx, value, token, onError); + else + tag = ctx.schema[SCALAR$1]; + let scalar; + try { + const res = tag.resolve(value, msg => onError(tagToken ?? token, 'TAG_RESOLVE_FAILED', msg), ctx.options); + scalar = isScalar$1(res) ? res : new Scalar(res); + } + catch (error) { + const msg = error instanceof Error ? error.message : String(error); + onError(tagToken ?? token, 'TAG_RESOLVE_FAILED', msg); + scalar = new Scalar(value); + } + scalar.range = range; + scalar.source = value; + if (type) + scalar.type = type; + if (tagName) + scalar.tag = tagName; + if (tag.format) + scalar.format = tag.format; + if (comment) + scalar.comment = comment; + return scalar; + } + function findScalarTagByName(schema, value, tagName, tagToken, onError) { + if (tagName === '!') + return schema[SCALAR$1]; // non-specific tag + const matchWithTest = []; + for (const tag of schema.tags) { + if (!tag.collection && tag.tag === tagName) { + if (tag.default && tag.test) + matchWithTest.push(tag); + else + return tag; + } + } + for (const tag of matchWithTest) + if (tag.test?.test(value)) + return tag; + const kt = schema.knownTags[tagName]; + if (kt && !kt.collection) { + // Ensure that the known tag is available for stringifying, + // but does not get used by default. + schema.tags.push(Object.assign({}, kt, { default: false, test: undefined })); + return kt; + } + onError(tagToken, 'TAG_RESOLVE_FAILED', `Unresolved tag: ${tagName}`, tagName !== 'tag:yaml.org,2002:str'); + return schema[SCALAR$1]; + } + function findScalarTagByTest({ atKey, directives, schema }, value, token, onError) { + const tag = schema.tags.find(tag => (tag.default === true || (atKey && tag.default === 'key')) && + tag.test?.test(value)) || schema[SCALAR$1]; + if (schema.compat) { + const compat = schema.compat.find(tag => tag.default && tag.test?.test(value)) ?? + schema[SCALAR$1]; + if (tag.tag !== compat.tag) { + const ts = directives.tagString(tag.tag); + const cs = directives.tagString(compat.tag); + const msg = `Value may be parsed as either ${ts} or ${cs}`; + onError(token, 'TAG_RESOLVE_FAILED', msg, true); + } + } + return tag; + } + + function emptyScalarPosition(offset, before, pos) { + if (before) { + pos ?? (pos = before.length); + for (let i = pos - 1; i >= 0; --i) { + let st = before[i]; + switch (st.type) { + case 'space': + case 'comment': + case 'newline': + offset -= st.source.length; + continue; + } + // Technically, an empty scalar is immediately after the last non-empty + // node, but it's more useful to place it after any whitespace. + st = before[++i]; + while (st?.type === 'space') { + offset += st.source.length; + st = before[++i]; + } + break; + } + } + return offset; + } + + const CN = { composeNode, composeEmptyNode }; + function composeNode(ctx, token, props, onError) { + const atKey = ctx.atKey; + const { spaceBefore, comment, anchor, tag } = props; + let node; + let isSrcToken = true; + switch (token.type) { + case 'alias': + node = composeAlias(ctx, token, onError); + if (anchor || tag) + onError(token, 'ALIAS_PROPS', 'An alias node must not specify any properties'); + break; + case 'scalar': + case 'single-quoted-scalar': + case 'double-quoted-scalar': + case 'block-scalar': + node = composeScalar(ctx, token, tag, onError); + if (anchor) + node.anchor = anchor.source.substring(1); + break; + case 'block-map': + case 'block-seq': + case 'flow-collection': + node = composeCollection(CN, ctx, token, props, onError); + if (anchor) + node.anchor = anchor.source.substring(1); + break; + default: { + const message = token.type === 'error' + ? token.message + : `Unsupported token (type: ${token.type})`; + onError(token, 'UNEXPECTED_TOKEN', message); + node = composeEmptyNode(ctx, token.offset, undefined, null, props, onError); + isSrcToken = false; + } + } + if (anchor && node.anchor === '') + onError(anchor, 'BAD_ALIAS', 'Anchor cannot be an empty string'); + if (atKey && + ctx.options.stringKeys && + (!isScalar$1(node) || + typeof node.value !== 'string' || + (node.tag && node.tag !== 'tag:yaml.org,2002:str'))) { + const msg = 'With stringKeys, all keys must be strings'; + onError(tag ?? token, 'NON_STRING_KEY', msg); + } + if (spaceBefore) + node.spaceBefore = true; + if (comment) { + if (token.type === 'scalar' && token.source === '') + node.comment = comment; + else + node.commentBefore = comment; + } + // @ts-expect-error Type checking misses meaning of isSrcToken + if (ctx.options.keepSourceTokens && isSrcToken) + node.srcToken = token; + return node; + } + function composeEmptyNode(ctx, offset, before, pos, { spaceBefore, comment, anchor, tag, end }, onError) { + const token = { + type: 'scalar', + offset: emptyScalarPosition(offset, before, pos), + indent: -1, + source: '' + }; + const node = composeScalar(ctx, token, tag, onError); + if (anchor) { + node.anchor = anchor.source.substring(1); + if (node.anchor === '') + onError(anchor, 'BAD_ALIAS', 'Anchor cannot be an empty string'); + } + if (spaceBefore) + node.spaceBefore = true; + if (comment) { + node.comment = comment; + node.range[2] = end; + } + return node; + } + function composeAlias({ options }, { offset, source, end }, onError) { + const alias = new Alias$1(source.substring(1)); + if (alias.source === '') + onError(offset, 'BAD_ALIAS', 'Alias cannot be an empty string'); + if (alias.source.endsWith(':')) + onError(offset + source.length - 1, 'BAD_ALIAS', 'Alias ending in : is ambiguous', true); + const valueEnd = offset + source.length; + const re = resolveEnd(end, valueEnd, options.strict, onError); + alias.range = [offset, valueEnd, re.offset]; + if (re.comment) + alias.comment = re.comment; + return alias; + } + + function composeDoc(options, directives, { offset, start, value, end }, onError) { + const opts = Object.assign({ _directives: directives }, options); + const doc = new Document(undefined, opts); + const ctx = { + atKey: false, + atRoot: true, + directives: doc.directives, + options: doc.options, + schema: doc.schema + }; + const props = resolveProps(start, { + indicator: 'doc-start', + next: value ?? end?.[0], + offset, + onError, + parentIndent: 0, + startOnNewline: true + }); + if (props.found) { + doc.directives.docStart = true; + if (value && + (value.type === 'block-map' || value.type === 'block-seq') && + !props.hasNewline) + onError(props.end, 'MISSING_CHAR', 'Block collection cannot start on same line with directives-end marker'); + } + // @ts-expect-error If Contents is set, let's trust the user + doc.contents = value + ? composeNode(ctx, value, props, onError) + : composeEmptyNode(ctx, props.end, start, null, props, onError); + const contentEnd = doc.contents.range[2]; + const re = resolveEnd(end, contentEnd, false, onError); + if (re.comment) + doc.comment = re.comment; + doc.range = [offset, contentEnd, re.offset]; + return doc; + } + + function getErrorPos(src) { + if (typeof src === 'number') + return [src, src + 1]; + if (Array.isArray(src)) + return src.length === 2 ? src : [src[0], src[1]]; + const { offset, source } = src; + return [offset, offset + (typeof source === 'string' ? source.length : 1)]; + } + function parsePrelude(prelude) { + let comment = ''; + let atComment = false; + let afterEmptyLine = false; + for (let i = 0; i < prelude.length; ++i) { + const source = prelude[i]; + switch (source[0]) { + case '#': + comment += + (comment === '' ? '' : afterEmptyLine ? '\n\n' : '\n') + + (source.substring(1) || ' '); + atComment = true; + afterEmptyLine = false; + break; + case '%': + if (prelude[i + 1]?.[0] !== '#') + i += 1; + atComment = false; + break; + default: + // This may be wrong after doc-end, but in that case it doesn't matter + if (!atComment) + afterEmptyLine = true; + atComment = false; + } + } + return { comment, afterEmptyLine }; + } + /** + * Compose a stream of CST nodes into a stream of YAML Documents. + * + * ```ts + * import { Composer, Parser } from 'yaml' + * + * const src: string = ... + * const tokens = new Parser().parse(src) + * const docs = new Composer().compose(tokens) + * ``` + */ + class Composer { + constructor(options = {}) { + this.doc = null; + this.atDirectives = false; + this.prelude = []; + this.errors = []; + this.warnings = []; + this.onError = (source, code, message, warning) => { + const pos = getErrorPos(source); + if (warning) + this.warnings.push(new YAMLWarning(pos, code, message)); + else + this.errors.push(new YAMLParseError(pos, code, message)); + }; + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + this.directives = new Directives({ version: options.version || '1.2' }); + this.options = options; + } + decorate(doc, afterDoc) { + const { comment, afterEmptyLine } = parsePrelude(this.prelude); + //console.log({ dc: doc.comment, prelude, comment }) + if (comment) { + const dc = doc.contents; + if (afterDoc) { + doc.comment = doc.comment ? `${doc.comment}\n${comment}` : comment; + } + else if (afterEmptyLine || doc.directives.docStart || !dc) { + doc.commentBefore = comment; + } + else if (isCollection$1(dc) && !dc.flow && dc.items.length > 0) { + let it = dc.items[0]; + if (isPair(it)) + it = it.key; + const cb = it.commentBefore; + it.commentBefore = cb ? `${comment}\n${cb}` : comment; + } + else { + const cb = dc.commentBefore; + dc.commentBefore = cb ? `${comment}\n${cb}` : comment; + } + } + if (afterDoc) { + Array.prototype.push.apply(doc.errors, this.errors); + Array.prototype.push.apply(doc.warnings, this.warnings); + } + else { + doc.errors = this.errors; + doc.warnings = this.warnings; + } + this.prelude = []; + this.errors = []; + this.warnings = []; + } + /** + * Current stream status information. + * + * Mostly useful at the end of input for an empty stream. + */ + streamInfo() { + return { + comment: parsePrelude(this.prelude).comment, + directives: this.directives, + errors: this.errors, + warnings: this.warnings + }; + } + /** + * Compose tokens into documents. + * + * @param forceDoc - If the stream contains no document, still emit a final document including any comments and directives that would be applied to a subsequent document. + * @param endOffset - Should be set if `forceDoc` is also set, to set the document range end and to indicate errors correctly. + */ + *compose(tokens, forceDoc = false, endOffset = -1) { + for (const token of tokens) + yield* this.next(token); + yield* this.end(forceDoc, endOffset); + } + /** Advance the composer by one CST token. */ + *next(token) { + switch (token.type) { + case 'directive': + this.directives.add(token.source, (offset, message, warning) => { + const pos = getErrorPos(token); + pos[0] += offset; + this.onError(pos, 'BAD_DIRECTIVE', message, warning); + }); + this.prelude.push(token.source); + this.atDirectives = true; + break; + case 'document': { + const doc = composeDoc(this.options, this.directives, token, this.onError); + if (this.atDirectives && !doc.directives.docStart) + this.onError(token, 'MISSING_CHAR', 'Missing directives-end/doc-start indicator line'); + this.decorate(doc, false); + if (this.doc) + yield this.doc; + this.doc = doc; + this.atDirectives = false; + break; + } + case 'byte-order-mark': + case 'space': + break; + case 'comment': + case 'newline': + this.prelude.push(token.source); + break; + case 'error': { + const msg = token.source + ? `${token.message}: ${JSON.stringify(token.source)}` + : token.message; + const error = new YAMLParseError(getErrorPos(token), 'UNEXPECTED_TOKEN', msg); + if (this.atDirectives || !this.doc) + this.errors.push(error); + else + this.doc.errors.push(error); + break; + } + case 'doc-end': { + if (!this.doc) { + const msg = 'Unexpected doc-end without preceding document'; + this.errors.push(new YAMLParseError(getErrorPos(token), 'UNEXPECTED_TOKEN', msg)); + break; + } + this.doc.directives.docEnd = true; + const end = resolveEnd(token.end, token.offset + token.source.length, this.doc.options.strict, this.onError); + this.decorate(this.doc, true); + if (end.comment) { + const dc = this.doc.comment; + this.doc.comment = dc ? `${dc}\n${end.comment}` : end.comment; + } + this.doc.range[2] = end.offset; + break; + } + default: + this.errors.push(new YAMLParseError(getErrorPos(token), 'UNEXPECTED_TOKEN', `Unsupported token ${token.type}`)); + } + } + /** + * Call at end of input to yield any remaining document. + * + * @param forceDoc - If the stream contains no document, still emit a final document including any comments and directives that would be applied to a subsequent document. + * @param endOffset - Should be set if `forceDoc` is also set, to set the document range end and to indicate errors correctly. + */ + *end(forceDoc = false, endOffset = -1) { + if (this.doc) { + this.decorate(this.doc, true); + yield this.doc; + this.doc = null; + } + else if (forceDoc) { + const opts = Object.assign({ _directives: this.directives }, this.options); + const doc = new Document(undefined, opts); + if (this.atDirectives) + this.onError(endOffset, 'MISSING_CHAR', 'Missing directives-end indicator line'); + doc.range = [0, endOffset, endOffset]; + this.decorate(doc, false); + yield doc; + } + } + } + + function resolveAsScalar(token, strict = true, onError) { + if (token) { + const _onError = (pos, code, message) => { + const offset = typeof pos === 'number' ? pos : Array.isArray(pos) ? pos[0] : pos.offset; + if (onError) + onError(offset, code, message); + else + throw new YAMLParseError([offset, offset + 1], code, message); + }; + switch (token.type) { + case 'scalar': + case 'single-quoted-scalar': + case 'double-quoted-scalar': + return resolveFlowScalar(token, strict, _onError); + case 'block-scalar': + return resolveBlockScalar({ options: { strict } }, token, _onError); + } + } + return null; + } + /** + * Create a new scalar token with `value` + * + * Values that represent an actual string but may be parsed as a different type should use a `type` other than `'PLAIN'`, + * as this function does not support any schema operations and won't check for such conflicts. + * + * @param value The string representation of the value, which will have its content properly indented. + * @param context.end Comments and whitespace after the end of the value, or after the block scalar header. If undefined, a newline will be added. + * @param context.implicitKey Being within an implicit key may affect the resolved type of the token's value. + * @param context.indent The indent level of the token. + * @param context.inFlow Is this scalar within a flow collection? This may affect the resolved type of the token's value. + * @param context.offset The offset position of the token. + * @param context.type The preferred type of the scalar token. If undefined, the previous type of the `token` will be used, defaulting to `'PLAIN'`. + */ + function createScalarToken(value, context) { + const { implicitKey = false, indent, inFlow = false, offset = -1, type = 'PLAIN' } = context; + const source = stringifyString({ type, value }, { + implicitKey, + indent: indent > 0 ? ' '.repeat(indent) : '', + inFlow, + options: { blockQuote: true, lineWidth: -1 } + }); + const end = context.end ?? [ + { type: 'newline', offset: -1, indent, source: '\n' } + ]; + switch (source[0]) { + case '|': + case '>': { + const he = source.indexOf('\n'); + const head = source.substring(0, he); + const body = source.substring(he + 1) + '\n'; + const props = [ + { type: 'block-scalar-header', offset, indent, source: head } + ]; + if (!addEndtoBlockProps(props, end)) + props.push({ type: 'newline', offset: -1, indent, source: '\n' }); + return { type: 'block-scalar', offset, indent, props, source: body }; + } + case '"': + return { type: 'double-quoted-scalar', offset, indent, source, end }; + case "'": + return { type: 'single-quoted-scalar', offset, indent, source, end }; + default: + return { type: 'scalar', offset, indent, source, end }; + } + } + /** + * Set the value of `token` to the given string `value`, overwriting any previous contents and type that it may have. + * + * Best efforts are made to retain any comments previously associated with the `token`, + * though all contents within a collection's `items` will be overwritten. + * + * Values that represent an actual string but may be parsed as a different type should use a `type` other than `'PLAIN'`, + * as this function does not support any schema operations and won't check for such conflicts. + * + * @param token Any token. If it does not include an `indent` value, the value will be stringified as if it were an implicit key. + * @param value The string representation of the value, which will have its content properly indented. + * @param context.afterKey In most cases, values after a key should have an additional level of indentation. + * @param context.implicitKey Being within an implicit key may affect the resolved type of the token's value. + * @param context.inFlow Being within a flow collection may affect the resolved type of the token's value. + * @param context.type The preferred type of the scalar token. If undefined, the previous type of the `token` will be used, defaulting to `'PLAIN'`. + */ + function setScalarValue(token, value, context = {}) { + let { afterKey = false, implicitKey = false, inFlow = false, type } = context; + let indent = 'indent' in token ? token.indent : null; + if (afterKey && typeof indent === 'number') + indent += 2; + if (!type) + switch (token.type) { + case 'single-quoted-scalar': + type = 'QUOTE_SINGLE'; + break; + case 'double-quoted-scalar': + type = 'QUOTE_DOUBLE'; + break; + case 'block-scalar': { + const header = token.props[0]; + if (header.type !== 'block-scalar-header') + throw new Error('Invalid block scalar header'); + type = header.source[0] === '>' ? 'BLOCK_FOLDED' : 'BLOCK_LITERAL'; + break; + } + default: + type = 'PLAIN'; + } + const source = stringifyString({ type, value }, { + implicitKey: implicitKey || indent === null, + indent: indent !== null && indent > 0 ? ' '.repeat(indent) : '', + inFlow, + options: { blockQuote: true, lineWidth: -1 } + }); + switch (source[0]) { + case '|': + case '>': + setBlockScalarValue(token, source); + break; + case '"': + setFlowScalarValue(token, source, 'double-quoted-scalar'); + break; + case "'": + setFlowScalarValue(token, source, 'single-quoted-scalar'); + break; + default: + setFlowScalarValue(token, source, 'scalar'); + } + } + function setBlockScalarValue(token, source) { + const he = source.indexOf('\n'); + const head = source.substring(0, he); + const body = source.substring(he + 1) + '\n'; + if (token.type === 'block-scalar') { + const header = token.props[0]; + if (header.type !== 'block-scalar-header') + throw new Error('Invalid block scalar header'); + header.source = head; + token.source = body; + } + else { + const { offset } = token; + const indent = 'indent' in token ? token.indent : -1; + const props = [ + { type: 'block-scalar-header', offset, indent, source: head } + ]; + if (!addEndtoBlockProps(props, 'end' in token ? token.end : undefined)) + props.push({ type: 'newline', offset: -1, indent, source: '\n' }); + for (const key of Object.keys(token)) + if (key !== 'type' && key !== 'offset') + delete token[key]; + Object.assign(token, { type: 'block-scalar', indent, props, source: body }); + } + } + /** @returns `true` if last token is a newline */ + function addEndtoBlockProps(props, end) { + if (end) + for (const st of end) + switch (st.type) { + case 'space': + case 'comment': + props.push(st); + break; + case 'newline': + props.push(st); + return true; + } + return false; + } + function setFlowScalarValue(token, source, type) { + switch (token.type) { + case 'scalar': + case 'double-quoted-scalar': + case 'single-quoted-scalar': + token.type = type; + token.source = source; + break; + case 'block-scalar': { + const end = token.props.slice(1); + let oa = source.length; + if (token.props[0].type === 'block-scalar-header') + oa -= token.props[0].source.length; + for (const tok of end) + tok.offset += oa; + delete token.props; + Object.assign(token, { type, source, end }); + break; + } + case 'block-map': + case 'block-seq': { + const offset = token.offset + source.length; + const nl = { type: 'newline', offset, indent: token.indent, source: '\n' }; + delete token.items; + Object.assign(token, { type, source, end: [nl] }); + break; + } + default: { + const indent = 'indent' in token ? token.indent : -1; + const end = 'end' in token && Array.isArray(token.end) + ? token.end.filter(st => st.type === 'space' || + st.type === 'comment' || + st.type === 'newline') + : []; + for (const key of Object.keys(token)) + if (key !== 'type' && key !== 'offset') + delete token[key]; + Object.assign(token, { type, indent, source, end }); + } + } + } + + /** + * Stringify a CST document, token, or collection item + * + * Fair warning: This applies no validation whatsoever, and + * simply concatenates the sources in their logical order. + */ + const stringify$1 = (cst) => 'type' in cst ? stringifyToken(cst) : stringifyItem(cst); + function stringifyToken(token) { + switch (token.type) { + case 'block-scalar': { + let res = ''; + for (const tok of token.props) + res += stringifyToken(tok); + return res + token.source; + } + case 'block-map': + case 'block-seq': { + let res = ''; + for (const item of token.items) + res += stringifyItem(item); + return res; + } + case 'flow-collection': { + let res = token.start.source; + for (const item of token.items) + res += stringifyItem(item); + for (const st of token.end) + res += st.source; + return res; + } + case 'document': { + let res = stringifyItem(token); + if (token.end) + for (const st of token.end) + res += st.source; + return res; + } + default: { + let res = token.source; + if ('end' in token && token.end) + for (const st of token.end) + res += st.source; + return res; + } + } + } + function stringifyItem({ start, key, sep, value }) { + let res = ''; + for (const st of start) + res += st.source; + if (key) + res += stringifyToken(key); + if (sep) + for (const st of sep) + res += st.source; + if (value) + res += stringifyToken(value); + return res; + } + + const BREAK = Symbol('break visit'); + const SKIP = Symbol('skip children'); + const REMOVE = Symbol('remove item'); + /** + * Apply a visitor to a CST document or item. + * + * Walks through the tree (depth-first) starting from the root, calling a + * `visitor` function with two arguments when entering each item: + * - `item`: The current item, which included the following members: + * - `start: SourceToken[]` – Source tokens before the key or value, + * possibly including its anchor or tag. + * - `key?: Token | null` – Set for pair values. May then be `null`, if + * the key before the `:` separator is empty. + * - `sep?: SourceToken[]` – Source tokens between the key and the value, + * which should include the `:` map value indicator if `value` is set. + * - `value?: Token` – The value of a sequence item, or of a map pair. + * - `path`: The steps from the root to the current node, as an array of + * `['key' | 'value', number]` tuples. + * + * The return value of the visitor may be used to control the traversal: + * - `undefined` (default): Do nothing and continue + * - `visit.SKIP`: Do not visit the children of this token, continue with + * next sibling + * - `visit.BREAK`: Terminate traversal completely + * - `visit.REMOVE`: Remove the current item, then continue with the next one + * - `number`: Set the index of the next step. This is useful especially if + * the index of the current token has changed. + * - `function`: Define the next visitor for this item. After the original + * visitor is called on item entry, next visitors are called after handling + * a non-empty `key` and when exiting the item. + */ + function visit(cst, visitor) { + if ('type' in cst && cst.type === 'document') + cst = { start: cst.start, value: cst.value }; + _visit(Object.freeze([]), cst, visitor); + } + // Without the `as symbol` casts, TS declares these in the `visit` + // namespace using `var`, but then complains about that because + // `unique symbol` must be `const`. + /** Terminate visit traversal completely */ + visit.BREAK = BREAK; + /** Do not visit the children of the current item */ + visit.SKIP = SKIP; + /** Remove the current item */ + visit.REMOVE = REMOVE; + /** Find the item at `path` from `cst` as the root */ + visit.itemAtPath = (cst, path) => { + let item = cst; + for (const [field, index] of path) { + const tok = item?.[field]; + if (tok && 'items' in tok) { + item = tok.items[index]; + } + else + return undefined; + } + return item; + }; + /** + * Get the immediate parent collection of the item at `path` from `cst` as the root. + * + * Throws an error if the collection is not found, which should never happen if the item itself exists. + */ + visit.parentCollection = (cst, path) => { + const parent = visit.itemAtPath(cst, path.slice(0, -1)); + const field = path[path.length - 1][0]; + const coll = parent?.[field]; + if (coll && 'items' in coll) + return coll; + throw new Error('Parent collection not found'); + }; + function _visit(path, item, visitor) { + let ctrl = visitor(item, path); + if (typeof ctrl === 'symbol') + return ctrl; + for (const field of ['key', 'value']) { + const token = item[field]; + if (token && 'items' in token) { + for (let i = 0; i < token.items.length; ++i) { + const ci = _visit(Object.freeze(path.concat([[field, i]])), token.items[i], visitor); + if (typeof ci === 'number') + i = ci - 1; + else if (ci === BREAK) + return BREAK; + else if (ci === REMOVE) { + token.items.splice(i, 1); + i -= 1; + } + } + if (typeof ctrl === 'function' && field === 'key') + ctrl = ctrl(item, path); + } + } + return typeof ctrl === 'function' ? ctrl(item, path) : ctrl; + } + + /** The byte order mark */ + const BOM = '\u{FEFF}'; + /** Start of doc-mode */ + const DOCUMENT = '\x02'; // C0: Start of Text + /** Unexpected end of flow-mode */ + const FLOW_END = '\x18'; // C0: Cancel + /** Next token is a scalar value */ + const SCALAR = '\x1f'; // C0: Unit Separator + /** @returns `true` if `token` is a flow or block collection */ + const isCollection = (token) => !!token && 'items' in token; + /** @returns `true` if `token` is a flow or block scalar; not an alias */ + const isScalar = (token) => !!token && + (token.type === 'scalar' || + token.type === 'single-quoted-scalar' || + token.type === 'double-quoted-scalar' || + token.type === 'block-scalar'); + /* istanbul ignore next */ + /** Get a printable representation of a lexer token */ + function prettyToken(token) { + switch (token) { + case BOM: + return ''; + case DOCUMENT: + return ''; + case FLOW_END: + return ''; + case SCALAR: + return ''; + default: + return JSON.stringify(token); + } + } + /** Identify the type of a lexer token. May return `null` for unknown tokens. */ + function tokenType(source) { + switch (source) { + case BOM: + return 'byte-order-mark'; + case DOCUMENT: + return 'doc-mode'; + case FLOW_END: + return 'flow-error-end'; + case SCALAR: + return 'scalar'; + case '---': + return 'doc-start'; + case '...': + return 'doc-end'; + case '': + case '\n': + case '\r\n': + return 'newline'; + case '-': + return 'seq-item-ind'; + case '?': + return 'explicit-key-ind'; + case ':': + return 'map-value-ind'; + case '{': + return 'flow-map-start'; + case '}': + return 'flow-map-end'; + case '[': + return 'flow-seq-start'; + case ']': + return 'flow-seq-end'; + case ',': + return 'comma'; + } + switch (source[0]) { + case ' ': + case '\t': + return 'space'; + case '#': + return 'comment'; + case '%': + return 'directive-line'; + case '*': + return 'alias'; + case '&': + return 'anchor'; + case '!': + return 'tag'; + case "'": + return 'single-quoted-scalar'; + case '"': + return 'double-quoted-scalar'; + case '|': + case '>': + return 'block-scalar-header'; + } + return null; + } + + var cst = /*#__PURE__*/Object.freeze({ + __proto__: null, + BOM: BOM, + DOCUMENT: DOCUMENT, + FLOW_END: FLOW_END, + SCALAR: SCALAR, + createScalarToken: createScalarToken, + isCollection: isCollection, + isScalar: isScalar, + prettyToken: prettyToken, + resolveAsScalar: resolveAsScalar, + setScalarValue: setScalarValue, + stringify: stringify$1, + tokenType: tokenType, + visit: visit + }); + + /* + START -> stream + + stream + directive -> line-end -> stream + indent + line-end -> stream + [else] -> line-start + + line-end + comment -> line-end + newline -> . + input-end -> END + + line-start + doc-start -> doc + doc-end -> stream + [else] -> indent -> block-start + + block-start + seq-item-start -> block-start + explicit-key-start -> block-start + map-value-start -> block-start + [else] -> doc + + doc + line-end -> line-start + spaces -> doc + anchor -> doc + tag -> doc + flow-start -> flow -> doc + flow-end -> error -> doc + seq-item-start -> error -> doc + explicit-key-start -> error -> doc + map-value-start -> doc + alias -> doc + quote-start -> quoted-scalar -> doc + block-scalar-header -> line-end -> block-scalar(min) -> line-start + [else] -> plain-scalar(false, min) -> doc + + flow + line-end -> flow + spaces -> flow + anchor -> flow + tag -> flow + flow-start -> flow -> flow + flow-end -> . + seq-item-start -> error -> flow + explicit-key-start -> flow + map-value-start -> flow + alias -> flow + quote-start -> quoted-scalar -> flow + comma -> flow + [else] -> plain-scalar(true, 0) -> flow + + quoted-scalar + quote-end -> . + [else] -> quoted-scalar + + block-scalar(min) + newline + peek(indent < min) -> . + [else] -> block-scalar(min) + + plain-scalar(is-flow, min) + scalar-end(is-flow) -> . + peek(newline + (indent < min)) -> . + [else] -> plain-scalar(min) + */ + function isEmpty(ch) { + switch (ch) { + case undefined: + case ' ': + case '\n': + case '\r': + case '\t': + return true; + default: + return false; + } + } + const hexDigits = new Set('0123456789ABCDEFabcdef'); + const tagChars = new Set("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-#;/?:@&=+$_.!~*'()"); + const flowIndicatorChars = new Set(',[]{}'); + const invalidAnchorChars = new Set(' ,[]{}\n\r\t'); + const isNotAnchorChar = (ch) => !ch || invalidAnchorChars.has(ch); + /** + * Splits an input string into lexical tokens, i.e. smaller strings that are + * easily identifiable by `tokens.tokenType()`. + * + * Lexing starts always in a "stream" context. Incomplete input may be buffered + * until a complete token can be emitted. + * + * In addition to slices of the original input, the following control characters + * may also be emitted: + * + * - `\x02` (Start of Text): A document starts with the next token + * - `\x18` (Cancel): Unexpected end of flow-mode (indicates an error) + * - `\x1f` (Unit Separator): Next token is a scalar value + * - `\u{FEFF}` (Byte order mark): Emitted separately outside documents + */ + class Lexer { + constructor() { + /** + * Flag indicating whether the end of the current buffer marks the end of + * all input + */ + this.atEnd = false; + /** + * Explicit indent set in block scalar header, as an offset from the current + * minimum indent, so e.g. set to 1 from a header `|2+`. Set to -1 if not + * explicitly set. + */ + this.blockScalarIndent = -1; + /** + * Block scalars that include a + (keep) chomping indicator in their header + * include trailing empty lines, which are otherwise excluded from the + * scalar's contents. + */ + this.blockScalarKeep = false; + /** Current input */ + this.buffer = ''; + /** + * Flag noting whether the map value indicator : can immediately follow this + * node within a flow context. + */ + this.flowKey = false; + /** Count of surrounding flow collection levels. */ + this.flowLevel = 0; + /** + * Minimum level of indentation required for next lines to be parsed as a + * part of the current scalar value. + */ + this.indentNext = 0; + /** Indentation level of the current line. */ + this.indentValue = 0; + /** Position of the next \n character. */ + this.lineEndPos = null; + /** Stores the state of the lexer if reaching the end of incpomplete input */ + this.next = null; + /** A pointer to `buffer`; the current position of the lexer. */ + this.pos = 0; + } + /** + * Generate YAML tokens from the `source` string. If `incomplete`, + * a part of the last line may be left as a buffer for the next call. + * + * @returns A generator of lexical tokens + */ + *lex(source, incomplete = false) { + if (source) { + if (typeof source !== 'string') + throw TypeError('source is not a string'); + this.buffer = this.buffer ? this.buffer + source : source; + this.lineEndPos = null; + } + this.atEnd = !incomplete; + let next = this.next ?? 'stream'; + while (next && (incomplete || this.hasChars(1))) + next = yield* this.parseNext(next); + } + atLineEnd() { + let i = this.pos; + let ch = this.buffer[i]; + while (ch === ' ' || ch === '\t') + ch = this.buffer[++i]; + if (!ch || ch === '#' || ch === '\n') + return true; + if (ch === '\r') + return this.buffer[i + 1] === '\n'; + return false; + } + charAt(n) { + return this.buffer[this.pos + n]; + } + continueScalar(offset) { + let ch = this.buffer[offset]; + if (this.indentNext > 0) { + let indent = 0; + while (ch === ' ') + ch = this.buffer[++indent + offset]; + if (ch === '\r') { + const next = this.buffer[indent + offset + 1]; + if (next === '\n' || (!next && !this.atEnd)) + return offset + indent + 1; + } + return ch === '\n' || indent >= this.indentNext || (!ch && !this.atEnd) + ? offset + indent + : -1; + } + if (ch === '-' || ch === '.') { + const dt = this.buffer.substr(offset, 3); + if ((dt === '---' || dt === '...') && isEmpty(this.buffer[offset + 3])) + return -1; + } + return offset; + } + getLine() { + let end = this.lineEndPos; + if (typeof end !== 'number' || (end !== -1 && end < this.pos)) { + end = this.buffer.indexOf('\n', this.pos); + this.lineEndPos = end; + } + if (end === -1) + return this.atEnd ? this.buffer.substring(this.pos) : null; + if (this.buffer[end - 1] === '\r') + end -= 1; + return this.buffer.substring(this.pos, end); + } + hasChars(n) { + return this.pos + n <= this.buffer.length; + } + setNext(state) { + this.buffer = this.buffer.substring(this.pos); + this.pos = 0; + this.lineEndPos = null; + this.next = state; + return null; + } + peek(n) { + return this.buffer.substr(this.pos, n); + } + *parseNext(next) { + switch (next) { + case 'stream': + return yield* this.parseStream(); + case 'line-start': + return yield* this.parseLineStart(); + case 'block-start': + return yield* this.parseBlockStart(); + case 'doc': + return yield* this.parseDocument(); + case 'flow': + return yield* this.parseFlowCollection(); + case 'quoted-scalar': + return yield* this.parseQuotedScalar(); + case 'block-scalar': + return yield* this.parseBlockScalar(); + case 'plain-scalar': + return yield* this.parsePlainScalar(); + } + } + *parseStream() { + let line = this.getLine(); + if (line === null) + return this.setNext('stream'); + if (line[0] === BOM) { + yield* this.pushCount(1); + line = line.substring(1); + } + if (line[0] === '%') { + let dirEnd = line.length; + let cs = line.indexOf('#'); + while (cs !== -1) { + const ch = line[cs - 1]; + if (ch === ' ' || ch === '\t') { + dirEnd = cs - 1; + break; + } + else { + cs = line.indexOf('#', cs + 1); + } + } + while (true) { + const ch = line[dirEnd - 1]; + if (ch === ' ' || ch === '\t') + dirEnd -= 1; + else + break; + } + const n = (yield* this.pushCount(dirEnd)) + (yield* this.pushSpaces(true)); + yield* this.pushCount(line.length - n); // possible comment + this.pushNewline(); + return 'stream'; + } + if (this.atLineEnd()) { + const sp = yield* this.pushSpaces(true); + yield* this.pushCount(line.length - sp); + yield* this.pushNewline(); + return 'stream'; + } + yield DOCUMENT; + return yield* this.parseLineStart(); + } + *parseLineStart() { + const ch = this.charAt(0); + if (!ch && !this.atEnd) + return this.setNext('line-start'); + if (ch === '-' || ch === '.') { + if (!this.atEnd && !this.hasChars(4)) + return this.setNext('line-start'); + const s = this.peek(3); + if ((s === '---' || s === '...') && isEmpty(this.charAt(3))) { + yield* this.pushCount(3); + this.indentValue = 0; + this.indentNext = 0; + return s === '---' ? 'doc' : 'stream'; + } + } + this.indentValue = yield* this.pushSpaces(false); + if (this.indentNext > this.indentValue && !isEmpty(this.charAt(1))) + this.indentNext = this.indentValue; + return yield* this.parseBlockStart(); + } + *parseBlockStart() { + const [ch0, ch1] = this.peek(2); + if (!ch1 && !this.atEnd) + return this.setNext('block-start'); + if ((ch0 === '-' || ch0 === '?' || ch0 === ':') && isEmpty(ch1)) { + const n = (yield* this.pushCount(1)) + (yield* this.pushSpaces(true)); + this.indentNext = this.indentValue + 1; + this.indentValue += n; + return yield* this.parseBlockStart(); + } + return 'doc'; + } + *parseDocument() { + yield* this.pushSpaces(true); + const line = this.getLine(); + if (line === null) + return this.setNext('doc'); + let n = yield* this.pushIndicators(); + switch (line[n]) { + case '#': + yield* this.pushCount(line.length - n); + // fallthrough + case undefined: + yield* this.pushNewline(); + return yield* this.parseLineStart(); + case '{': + case '[': + yield* this.pushCount(1); + this.flowKey = false; + this.flowLevel = 1; + return 'flow'; + case '}': + case ']': + // this is an error + yield* this.pushCount(1); + return 'doc'; + case '*': + yield* this.pushUntil(isNotAnchorChar); + return 'doc'; + case '"': + case "'": + return yield* this.parseQuotedScalar(); + case '|': + case '>': + n += yield* this.parseBlockScalarHeader(); + n += yield* this.pushSpaces(true); + yield* this.pushCount(line.length - n); + yield* this.pushNewline(); + return yield* this.parseBlockScalar(); + default: + return yield* this.parsePlainScalar(); + } + } + *parseFlowCollection() { + let nl, sp; + let indent = -1; + do { + nl = yield* this.pushNewline(); + if (nl > 0) { + sp = yield* this.pushSpaces(false); + this.indentValue = indent = sp; + } + else { + sp = 0; + } + sp += yield* this.pushSpaces(true); + } while (nl + sp > 0); + const line = this.getLine(); + if (line === null) + return this.setNext('flow'); + if ((indent !== -1 && indent < this.indentNext && line[0] !== '#') || + (indent === 0 && + (line.startsWith('---') || line.startsWith('...')) && + isEmpty(line[3]))) { + // Allowing for the terminal ] or } at the same (rather than greater) + // indent level as the initial [ or { is technically invalid, but + // failing here would be surprising to users. + const atFlowEndMarker = indent === this.indentNext - 1 && + this.flowLevel === 1 && + (line[0] === ']' || line[0] === '}'); + if (!atFlowEndMarker) { + // this is an error + this.flowLevel = 0; + yield FLOW_END; + return yield* this.parseLineStart(); + } + } + let n = 0; + while (line[n] === ',') { + n += yield* this.pushCount(1); + n += yield* this.pushSpaces(true); + this.flowKey = false; + } + n += yield* this.pushIndicators(); + switch (line[n]) { + case undefined: + return 'flow'; + case '#': + yield* this.pushCount(line.length - n); + return 'flow'; + case '{': + case '[': + yield* this.pushCount(1); + this.flowKey = false; + this.flowLevel += 1; + return 'flow'; + case '}': + case ']': + yield* this.pushCount(1); + this.flowKey = true; + this.flowLevel -= 1; + return this.flowLevel ? 'flow' : 'doc'; + case '*': + yield* this.pushUntil(isNotAnchorChar); + return 'flow'; + case '"': + case "'": + this.flowKey = true; + return yield* this.parseQuotedScalar(); + case ':': { + const next = this.charAt(1); + if (this.flowKey || isEmpty(next) || next === ',') { + this.flowKey = false; + yield* this.pushCount(1); + yield* this.pushSpaces(true); + return 'flow'; + } + } + // fallthrough + default: + this.flowKey = false; + return yield* this.parsePlainScalar(); + } + } + *parseQuotedScalar() { + const quote = this.charAt(0); + let end = this.buffer.indexOf(quote, this.pos + 1); + if (quote === "'") { + while (end !== -1 && this.buffer[end + 1] === "'") + end = this.buffer.indexOf("'", end + 2); + } + else { + // double-quote + while (end !== -1) { + let n = 0; + while (this.buffer[end - 1 - n] === '\\') + n += 1; + if (n % 2 === 0) + break; + end = this.buffer.indexOf('"', end + 1); + } + } + // Only looking for newlines within the quotes + const qb = this.buffer.substring(0, end); + let nl = qb.indexOf('\n', this.pos); + if (nl !== -1) { + while (nl !== -1) { + const cs = this.continueScalar(nl + 1); + if (cs === -1) + break; + nl = qb.indexOf('\n', cs); + } + if (nl !== -1) { + // this is an error caused by an unexpected unindent + end = nl - (qb[nl - 1] === '\r' ? 2 : 1); + } + } + if (end === -1) { + if (!this.atEnd) + return this.setNext('quoted-scalar'); + end = this.buffer.length; + } + yield* this.pushToIndex(end + 1, false); + return this.flowLevel ? 'flow' : 'doc'; + } + *parseBlockScalarHeader() { + this.blockScalarIndent = -1; + this.blockScalarKeep = false; + let i = this.pos; + while (true) { + const ch = this.buffer[++i]; + if (ch === '+') + this.blockScalarKeep = true; + else if (ch > '0' && ch <= '9') + this.blockScalarIndent = Number(ch) - 1; + else if (ch !== '-') + break; + } + return yield* this.pushUntil(ch => isEmpty(ch) || ch === '#'); + } + *parseBlockScalar() { + let nl = this.pos - 1; // may be -1 if this.pos === 0 + let indent = 0; + let ch; + loop: for (let i = this.pos; (ch = this.buffer[i]); ++i) { + switch (ch) { + case ' ': + indent += 1; + break; + case '\n': + nl = i; + indent = 0; + break; + case '\r': { + const next = this.buffer[i + 1]; + if (!next && !this.atEnd) + return this.setNext('block-scalar'); + if (next === '\n') + break; + } // fallthrough + default: + break loop; + } + } + if (!ch && !this.atEnd) + return this.setNext('block-scalar'); + if (indent >= this.indentNext) { + if (this.blockScalarIndent === -1) + this.indentNext = indent; + else { + this.indentNext = + this.blockScalarIndent + (this.indentNext === 0 ? 1 : this.indentNext); + } + do { + const cs = this.continueScalar(nl + 1); + if (cs === -1) + break; + nl = this.buffer.indexOf('\n', cs); + } while (nl !== -1); + if (nl === -1) { + if (!this.atEnd) + return this.setNext('block-scalar'); + nl = this.buffer.length; + } + } + // Trailing insufficiently indented tabs are invalid. + // To catch that during parsing, we include them in the block scalar value. + let i = nl + 1; + ch = this.buffer[i]; + while (ch === ' ') + ch = this.buffer[++i]; + if (ch === '\t') { + while (ch === '\t' || ch === ' ' || ch === '\r' || ch === '\n') + ch = this.buffer[++i]; + nl = i - 1; + } + else if (!this.blockScalarKeep) { + do { + let i = nl - 1; + let ch = this.buffer[i]; + if (ch === '\r') + ch = this.buffer[--i]; + const lastChar = i; // Drop the line if last char not more indented + while (ch === ' ') + ch = this.buffer[--i]; + if (ch === '\n' && i >= this.pos && i + 1 + indent > lastChar) + nl = i; + else + break; + } while (true); + } + yield SCALAR; + yield* this.pushToIndex(nl + 1, true); + return yield* this.parseLineStart(); + } + *parsePlainScalar() { + const inFlow = this.flowLevel > 0; + let end = this.pos - 1; + let i = this.pos - 1; + let ch; + while ((ch = this.buffer[++i])) { + if (ch === ':') { + const next = this.buffer[i + 1]; + if (isEmpty(next) || (inFlow && flowIndicatorChars.has(next))) + break; + end = i; + } + else if (isEmpty(ch)) { + let next = this.buffer[i + 1]; + if (ch === '\r') { + if (next === '\n') { + i += 1; + ch = '\n'; + next = this.buffer[i + 1]; + } + else + end = i; + } + if (next === '#' || (inFlow && flowIndicatorChars.has(next))) + break; + if (ch === '\n') { + const cs = this.continueScalar(i + 1); + if (cs === -1) + break; + i = Math.max(i, cs - 2); // to advance, but still account for ' #' + } + } + else { + if (inFlow && flowIndicatorChars.has(ch)) + break; + end = i; + } + } + if (!ch && !this.atEnd) + return this.setNext('plain-scalar'); + yield SCALAR; + yield* this.pushToIndex(end + 1, true); + return inFlow ? 'flow' : 'doc'; + } + *pushCount(n) { + if (n > 0) { + yield this.buffer.substr(this.pos, n); + this.pos += n; + return n; + } + return 0; + } + *pushToIndex(i, allowEmpty) { + const s = this.buffer.slice(this.pos, i); + if (s) { + yield s; + this.pos += s.length; + return s.length; + } + else if (allowEmpty) + yield ''; + return 0; + } + *pushIndicators() { + switch (this.charAt(0)) { + case '!': + return ((yield* this.pushTag()) + + (yield* this.pushSpaces(true)) + + (yield* this.pushIndicators())); + case '&': + return ((yield* this.pushUntil(isNotAnchorChar)) + + (yield* this.pushSpaces(true)) + + (yield* this.pushIndicators())); + case '-': // this is an error + case '?': // this is an error outside flow collections + case ':': { + const inFlow = this.flowLevel > 0; + const ch1 = this.charAt(1); + if (isEmpty(ch1) || (inFlow && flowIndicatorChars.has(ch1))) { + if (!inFlow) + this.indentNext = this.indentValue + 1; + else if (this.flowKey) + this.flowKey = false; + return ((yield* this.pushCount(1)) + + (yield* this.pushSpaces(true)) + + (yield* this.pushIndicators())); + } + } + } + return 0; + } + *pushTag() { + if (this.charAt(1) === '<') { + let i = this.pos + 2; + let ch = this.buffer[i]; + while (!isEmpty(ch) && ch !== '>') + ch = this.buffer[++i]; + return yield* this.pushToIndex(ch === '>' ? i + 1 : i, false); + } + else { + let i = this.pos + 1; + let ch = this.buffer[i]; + while (ch) { + if (tagChars.has(ch)) + ch = this.buffer[++i]; + else if (ch === '%' && + hexDigits.has(this.buffer[i + 1]) && + hexDigits.has(this.buffer[i + 2])) { + ch = this.buffer[(i += 3)]; + } + else + break; + } + return yield* this.pushToIndex(i, false); + } + } + *pushNewline() { + const ch = this.buffer[this.pos]; + if (ch === '\n') + return yield* this.pushCount(1); + else if (ch === '\r' && this.charAt(1) === '\n') + return yield* this.pushCount(2); + else + return 0; + } + *pushSpaces(allowTabs) { + let i = this.pos - 1; + let ch; + do { + ch = this.buffer[++i]; + } while (ch === ' ' || (allowTabs && ch === '\t')); + const n = i - this.pos; + if (n > 0) { + yield this.buffer.substr(this.pos, n); + this.pos = i; + } + return n; + } + *pushUntil(test) { + let i = this.pos; + let ch = this.buffer[i]; + while (!test(ch)) + ch = this.buffer[++i]; + return yield* this.pushToIndex(i, false); + } + } + + /** + * Tracks newlines during parsing in order to provide an efficient API for + * determining the one-indexed `{ line, col }` position for any offset + * within the input. + */ + class LineCounter { + constructor() { + this.lineStarts = []; + /** + * Should be called in ascending order. Otherwise, call + * `lineCounter.lineStarts.sort()` before calling `linePos()`. + */ + this.addNewLine = (offset) => this.lineStarts.push(offset); + /** + * Performs a binary search and returns the 1-indexed { line, col } + * position of `offset`. If `line === 0`, `addNewLine` has never been + * called or `offset` is before the first known newline. + */ + this.linePos = (offset) => { + let low = 0; + let high = this.lineStarts.length; + while (low < high) { + const mid = (low + high) >> 1; // Math.floor((low + high) / 2) + if (this.lineStarts[mid] < offset) + low = mid + 1; + else + high = mid; + } + if (this.lineStarts[low] === offset) + return { line: low + 1, col: 1 }; + if (low === 0) + return { line: 0, col: offset }; + const start = this.lineStarts[low - 1]; + return { line: low, col: offset - start + 1 }; + }; + } + } + + function includesToken(list, type) { + for (let i = 0; i < list.length; ++i) + if (list[i].type === type) + return true; + return false; + } + function findNonEmptyIndex(list) { + for (let i = 0; i < list.length; ++i) { + switch (list[i].type) { + case 'space': + case 'comment': + case 'newline': + break; + default: + return i; + } + } + return -1; + } + function isFlowToken(token) { + switch (token?.type) { + case 'alias': + case 'scalar': + case 'single-quoted-scalar': + case 'double-quoted-scalar': + case 'flow-collection': + return true; + default: + return false; + } + } + function getPrevProps(parent) { + switch (parent.type) { + case 'document': + return parent.start; + case 'block-map': { + const it = parent.items[parent.items.length - 1]; + return it.sep ?? it.start; + } + case 'block-seq': + return parent.items[parent.items.length - 1].start; + /* istanbul ignore next should not happen */ + default: + return []; + } + } + /** Note: May modify input array */ + function getFirstKeyStartProps(prev) { + if (prev.length === 0) + return []; + let i = prev.length; + loop: while (--i >= 0) { + switch (prev[i].type) { + case 'doc-start': + case 'explicit-key-ind': + case 'map-value-ind': + case 'seq-item-ind': + case 'newline': + break loop; + } + } + while (prev[++i]?.type === 'space') { + /* loop */ + } + return prev.splice(i, prev.length); + } + function fixFlowSeqItems(fc) { + if (fc.start.type === 'flow-seq-start') { + for (const it of fc.items) { + if (it.sep && + !it.value && + !includesToken(it.start, 'explicit-key-ind') && + !includesToken(it.sep, 'map-value-ind')) { + if (it.key) + it.value = it.key; + delete it.key; + if (isFlowToken(it.value)) { + if (it.value.end) + Array.prototype.push.apply(it.value.end, it.sep); + else + it.value.end = it.sep; + } + else + Array.prototype.push.apply(it.start, it.sep); + delete it.sep; + } + } + } + } + /** + * A YAML concrete syntax tree (CST) parser + * + * ```ts + * const src: string = ... + * for (const token of new Parser().parse(src)) { + * // token: Token + * } + * ``` + * + * To use the parser with a user-provided lexer: + * + * ```ts + * function* parse(source: string, lexer: Lexer) { + * const parser = new Parser() + * for (const lexeme of lexer.lex(source)) + * yield* parser.next(lexeme) + * yield* parser.end() + * } + * + * const src: string = ... + * const lexer = new Lexer() + * for (const token of parse(src, lexer)) { + * // token: Token + * } + * ``` + */ + class Parser { + /** + * @param onNewLine - If defined, called separately with the start position of + * each new line (in `parse()`, including the start of input). + */ + constructor(onNewLine) { + /** If true, space and sequence indicators count as indentation */ + this.atNewLine = true; + /** If true, next token is a scalar value */ + this.atScalar = false; + /** Current indentation level */ + this.indent = 0; + /** Current offset since the start of parsing */ + this.offset = 0; + /** On the same line with a block map key */ + this.onKeyLine = false; + /** Top indicates the node that's currently being built */ + this.stack = []; + /** The source of the current token, set in parse() */ + this.source = ''; + /** The type of the current token, set in parse() */ + this.type = ''; + // Must be defined after `next()` + this.lexer = new Lexer(); + this.onNewLine = onNewLine; + } + /** + * Parse `source` as a YAML stream. + * If `incomplete`, a part of the last line may be left as a buffer for the next call. + * + * Errors are not thrown, but yielded as `{ type: 'error', message }` tokens. + * + * @returns A generator of tokens representing each directive, document, and other structure. + */ + *parse(source, incomplete = false) { + if (this.onNewLine && this.offset === 0) + this.onNewLine(0); + for (const lexeme of this.lexer.lex(source, incomplete)) + yield* this.next(lexeme); + if (!incomplete) + yield* this.end(); + } + /** + * Advance the parser by the `source` of one lexical token. + */ + *next(source) { + this.source = source; + if (this.atScalar) { + this.atScalar = false; + yield* this.step(); + this.offset += source.length; + return; + } + const type = tokenType(source); + if (!type) { + const message = `Not a YAML token: ${source}`; + yield* this.pop({ type: 'error', offset: this.offset, message, source }); + this.offset += source.length; + } + else if (type === 'scalar') { + this.atNewLine = false; + this.atScalar = true; + this.type = 'scalar'; + } + else { + this.type = type; + yield* this.step(); + switch (type) { + case 'newline': + this.atNewLine = true; + this.indent = 0; + if (this.onNewLine) + this.onNewLine(this.offset + source.length); + break; + case 'space': + if (this.atNewLine && source[0] === ' ') + this.indent += source.length; + break; + case 'explicit-key-ind': + case 'map-value-ind': + case 'seq-item-ind': + if (this.atNewLine) + this.indent += source.length; + break; + case 'doc-mode': + case 'flow-error-end': + return; + default: + this.atNewLine = false; + } + this.offset += source.length; + } + } + /** Call at end of input to push out any remaining constructions */ + *end() { + while (this.stack.length > 0) + yield* this.pop(); + } + get sourceToken() { + const st = { + type: this.type, + offset: this.offset, + indent: this.indent, + source: this.source + }; + return st; + } + *step() { + const top = this.peek(1); + if (this.type === 'doc-end' && (!top || top.type !== 'doc-end')) { + while (this.stack.length > 0) + yield* this.pop(); + this.stack.push({ + type: 'doc-end', + offset: this.offset, + source: this.source + }); + return; + } + if (!top) + return yield* this.stream(); + switch (top.type) { + case 'document': + return yield* this.document(top); + case 'alias': + case 'scalar': + case 'single-quoted-scalar': + case 'double-quoted-scalar': + return yield* this.scalar(top); + case 'block-scalar': + return yield* this.blockScalar(top); + case 'block-map': + return yield* this.blockMap(top); + case 'block-seq': + return yield* this.blockSequence(top); + case 'flow-collection': + return yield* this.flowCollection(top); + case 'doc-end': + return yield* this.documentEnd(top); + } + /* istanbul ignore next should not happen */ + yield* this.pop(); + } + peek(n) { + return this.stack[this.stack.length - n]; + } + *pop(error) { + const token = error ?? this.stack.pop(); + /* istanbul ignore if should not happen */ + if (!token) { + const message = 'Tried to pop an empty stack'; + yield { type: 'error', offset: this.offset, source: '', message }; + } + else if (this.stack.length === 0) { + yield token; + } + else { + const top = this.peek(1); + if (token.type === 'block-scalar') { + // Block scalars use their parent rather than header indent + token.indent = 'indent' in top ? top.indent : 0; + } + else if (token.type === 'flow-collection' && top.type === 'document') { + // Ignore all indent for top-level flow collections + token.indent = 0; + } + if (token.type === 'flow-collection') + fixFlowSeqItems(token); + switch (top.type) { + case 'document': + top.value = token; + break; + case 'block-scalar': + top.props.push(token); // error + break; + case 'block-map': { + const it = top.items[top.items.length - 1]; + if (it.value) { + top.items.push({ start: [], key: token, sep: [] }); + this.onKeyLine = true; + return; + } + else if (it.sep) { + it.value = token; + } + else { + Object.assign(it, { key: token, sep: [] }); + this.onKeyLine = !it.explicitKey; + return; + } + break; + } + case 'block-seq': { + const it = top.items[top.items.length - 1]; + if (it.value) + top.items.push({ start: [], value: token }); + else + it.value = token; + break; + } + case 'flow-collection': { + const it = top.items[top.items.length - 1]; + if (!it || it.value) + top.items.push({ start: [], key: token, sep: [] }); + else if (it.sep) + it.value = token; + else + Object.assign(it, { key: token, sep: [] }); + return; + } + /* istanbul ignore next should not happen */ + default: + yield* this.pop(); + yield* this.pop(token); + } + if ((top.type === 'document' || + top.type === 'block-map' || + top.type === 'block-seq') && + (token.type === 'block-map' || token.type === 'block-seq')) { + const last = token.items[token.items.length - 1]; + if (last && + !last.sep && + !last.value && + last.start.length > 0 && + findNonEmptyIndex(last.start) === -1 && + (token.indent === 0 || + last.start.every(st => st.type !== 'comment' || st.indent < token.indent))) { + if (top.type === 'document') + top.end = last.start; + else + top.items.push({ start: last.start }); + token.items.splice(-1, 1); + } + } + } + } + *stream() { + switch (this.type) { + case 'directive-line': + yield { type: 'directive', offset: this.offset, source: this.source }; + return; + case 'byte-order-mark': + case 'space': + case 'comment': + case 'newline': + yield this.sourceToken; + return; + case 'doc-mode': + case 'doc-start': { + const doc = { + type: 'document', + offset: this.offset, + start: [] + }; + if (this.type === 'doc-start') + doc.start.push(this.sourceToken); + this.stack.push(doc); + return; + } + } + yield { + type: 'error', + offset: this.offset, + message: `Unexpected ${this.type} token in YAML stream`, + source: this.source + }; + } + *document(doc) { + if (doc.value) + return yield* this.lineEnd(doc); + switch (this.type) { + case 'doc-start': { + if (findNonEmptyIndex(doc.start) !== -1) { + yield* this.pop(); + yield* this.step(); + } + else + doc.start.push(this.sourceToken); + return; + } + case 'anchor': + case 'tag': + case 'space': + case 'comment': + case 'newline': + doc.start.push(this.sourceToken); + return; + } + const bv = this.startBlockValue(doc); + if (bv) + this.stack.push(bv); + else { + yield { + type: 'error', + offset: this.offset, + message: `Unexpected ${this.type} token in YAML document`, + source: this.source + }; + } + } + *scalar(scalar) { + if (this.type === 'map-value-ind') { + const prev = getPrevProps(this.peek(2)); + const start = getFirstKeyStartProps(prev); + let sep; + if (scalar.end) { + sep = scalar.end; + sep.push(this.sourceToken); + delete scalar.end; + } + else + sep = [this.sourceToken]; + const map = { + type: 'block-map', + offset: scalar.offset, + indent: scalar.indent, + items: [{ start, key: scalar, sep }] + }; + this.onKeyLine = true; + this.stack[this.stack.length - 1] = map; + } + else + yield* this.lineEnd(scalar); + } + *blockScalar(scalar) { + switch (this.type) { + case 'space': + case 'comment': + case 'newline': + scalar.props.push(this.sourceToken); + return; + case 'scalar': + scalar.source = this.source; + // block-scalar source includes trailing newline + this.atNewLine = true; + this.indent = 0; + if (this.onNewLine) { + let nl = this.source.indexOf('\n') + 1; + while (nl !== 0) { + this.onNewLine(this.offset + nl); + nl = this.source.indexOf('\n', nl) + 1; + } + } + yield* this.pop(); + break; + /* istanbul ignore next should not happen */ + default: + yield* this.pop(); + yield* this.step(); + } + } + *blockMap(map) { + const it = map.items[map.items.length - 1]; + // it.sep is true-ish if pair already has key or : separator + switch (this.type) { + case 'newline': + this.onKeyLine = false; + if (it.value) { + const end = 'end' in it.value ? it.value.end : undefined; + const last = Array.isArray(end) ? end[end.length - 1] : undefined; + if (last?.type === 'comment') + end?.push(this.sourceToken); + else + map.items.push({ start: [this.sourceToken] }); + } + else if (it.sep) { + it.sep.push(this.sourceToken); + } + else { + it.start.push(this.sourceToken); + } + return; + case 'space': + case 'comment': + if (it.value) { + map.items.push({ start: [this.sourceToken] }); + } + else if (it.sep) { + it.sep.push(this.sourceToken); + } + else { + if (this.atIndentedComment(it.start, map.indent)) { + const prev = map.items[map.items.length - 2]; + const end = prev?.value?.end; + if (Array.isArray(end)) { + Array.prototype.push.apply(end, it.start); + end.push(this.sourceToken); + map.items.pop(); + return; + } + } + it.start.push(this.sourceToken); + } + return; + } + if (this.indent >= map.indent) { + const atMapIndent = !this.onKeyLine && this.indent === map.indent; + const atNextItem = atMapIndent && + (it.sep || it.explicitKey) && + this.type !== 'seq-item-ind'; + // For empty nodes, assign newline-separated not indented empty tokens to following node + let start = []; + if (atNextItem && it.sep && !it.value) { + const nl = []; + for (let i = 0; i < it.sep.length; ++i) { + const st = it.sep[i]; + switch (st.type) { + case 'newline': + nl.push(i); + break; + case 'space': + break; + case 'comment': + if (st.indent > map.indent) + nl.length = 0; + break; + default: + nl.length = 0; + } + } + if (nl.length >= 2) + start = it.sep.splice(nl[1]); + } + switch (this.type) { + case 'anchor': + case 'tag': + if (atNextItem || it.value) { + start.push(this.sourceToken); + map.items.push({ start }); + this.onKeyLine = true; + } + else if (it.sep) { + it.sep.push(this.sourceToken); + } + else { + it.start.push(this.sourceToken); + } + return; + case 'explicit-key-ind': + if (!it.sep && !it.explicitKey) { + it.start.push(this.sourceToken); + it.explicitKey = true; + } + else if (atNextItem || it.value) { + start.push(this.sourceToken); + map.items.push({ start, explicitKey: true }); + } + else { + this.stack.push({ + type: 'block-map', + offset: this.offset, + indent: this.indent, + items: [{ start: [this.sourceToken], explicitKey: true }] + }); + } + this.onKeyLine = true; + return; + case 'map-value-ind': + if (it.explicitKey) { + if (!it.sep) { + if (includesToken(it.start, 'newline')) { + Object.assign(it, { key: null, sep: [this.sourceToken] }); + } + else { + const start = getFirstKeyStartProps(it.start); + this.stack.push({ + type: 'block-map', + offset: this.offset, + indent: this.indent, + items: [{ start, key: null, sep: [this.sourceToken] }] + }); + } + } + else if (it.value) { + map.items.push({ start: [], key: null, sep: [this.sourceToken] }); + } + else if (includesToken(it.sep, 'map-value-ind')) { + this.stack.push({ + type: 'block-map', + offset: this.offset, + indent: this.indent, + items: [{ start, key: null, sep: [this.sourceToken] }] + }); + } + else if (isFlowToken(it.key) && + !includesToken(it.sep, 'newline')) { + const start = getFirstKeyStartProps(it.start); + const key = it.key; + const sep = it.sep; + sep.push(this.sourceToken); + // @ts-expect-error type guard is wrong here + delete it.key; + // @ts-expect-error type guard is wrong here + delete it.sep; + this.stack.push({ + type: 'block-map', + offset: this.offset, + indent: this.indent, + items: [{ start, key, sep }] + }); + } + else if (start.length > 0) { + // Not actually at next item + it.sep = it.sep.concat(start, this.sourceToken); + } + else { + it.sep.push(this.sourceToken); + } + } + else { + if (!it.sep) { + Object.assign(it, { key: null, sep: [this.sourceToken] }); + } + else if (it.value || atNextItem) { + map.items.push({ start, key: null, sep: [this.sourceToken] }); + } + else if (includesToken(it.sep, 'map-value-ind')) { + this.stack.push({ + type: 'block-map', + offset: this.offset, + indent: this.indent, + items: [{ start: [], key: null, sep: [this.sourceToken] }] + }); + } + else { + it.sep.push(this.sourceToken); + } + } + this.onKeyLine = true; + return; + case 'alias': + case 'scalar': + case 'single-quoted-scalar': + case 'double-quoted-scalar': { + const fs = this.flowScalar(this.type); + if (atNextItem || it.value) { + map.items.push({ start, key: fs, sep: [] }); + this.onKeyLine = true; + } + else if (it.sep) { + this.stack.push(fs); + } + else { + Object.assign(it, { key: fs, sep: [] }); + this.onKeyLine = true; + } + return; + } + default: { + const bv = this.startBlockValue(map); + if (bv) { + if (bv.type === 'block-seq') { + if (!it.explicitKey && + it.sep && + !includesToken(it.sep, 'newline')) { + yield* this.pop({ + type: 'error', + offset: this.offset, + message: 'Unexpected block-seq-ind on same line with key', + source: this.source + }); + return; + } + } + else if (atMapIndent) { + map.items.push({ start }); + } + this.stack.push(bv); + return; + } + } + } + } + yield* this.pop(); + yield* this.step(); + } + *blockSequence(seq) { + const it = seq.items[seq.items.length - 1]; + switch (this.type) { + case 'newline': + if (it.value) { + const end = 'end' in it.value ? it.value.end : undefined; + const last = Array.isArray(end) ? end[end.length - 1] : undefined; + if (last?.type === 'comment') + end?.push(this.sourceToken); + else + seq.items.push({ start: [this.sourceToken] }); + } + else + it.start.push(this.sourceToken); + return; + case 'space': + case 'comment': + if (it.value) + seq.items.push({ start: [this.sourceToken] }); + else { + if (this.atIndentedComment(it.start, seq.indent)) { + const prev = seq.items[seq.items.length - 2]; + const end = prev?.value?.end; + if (Array.isArray(end)) { + Array.prototype.push.apply(end, it.start); + end.push(this.sourceToken); + seq.items.pop(); + return; + } + } + it.start.push(this.sourceToken); + } + return; + case 'anchor': + case 'tag': + if (it.value || this.indent <= seq.indent) + break; + it.start.push(this.sourceToken); + return; + case 'seq-item-ind': + if (this.indent !== seq.indent) + break; + if (it.value || includesToken(it.start, 'seq-item-ind')) + seq.items.push({ start: [this.sourceToken] }); + else + it.start.push(this.sourceToken); + return; + } + if (this.indent > seq.indent) { + const bv = this.startBlockValue(seq); + if (bv) { + this.stack.push(bv); + return; + } + } + yield* this.pop(); + yield* this.step(); + } + *flowCollection(fc) { + const it = fc.items[fc.items.length - 1]; + if (this.type === 'flow-error-end') { + let top; + do { + yield* this.pop(); + top = this.peek(1); + } while (top && top.type === 'flow-collection'); + } + else if (fc.end.length === 0) { + switch (this.type) { + case 'comma': + case 'explicit-key-ind': + if (!it || it.sep) + fc.items.push({ start: [this.sourceToken] }); + else + it.start.push(this.sourceToken); + return; + case 'map-value-ind': + if (!it || it.value) + fc.items.push({ start: [], key: null, sep: [this.sourceToken] }); + else if (it.sep) + it.sep.push(this.sourceToken); + else + Object.assign(it, { key: null, sep: [this.sourceToken] }); + return; + case 'space': + case 'comment': + case 'newline': + case 'anchor': + case 'tag': + if (!it || it.value) + fc.items.push({ start: [this.sourceToken] }); + else if (it.sep) + it.sep.push(this.sourceToken); + else + it.start.push(this.sourceToken); + return; + case 'alias': + case 'scalar': + case 'single-quoted-scalar': + case 'double-quoted-scalar': { + const fs = this.flowScalar(this.type); + if (!it || it.value) + fc.items.push({ start: [], key: fs, sep: [] }); + else if (it.sep) + this.stack.push(fs); + else + Object.assign(it, { key: fs, sep: [] }); + return; + } + case 'flow-map-end': + case 'flow-seq-end': + fc.end.push(this.sourceToken); + return; + } + const bv = this.startBlockValue(fc); + /* istanbul ignore else should not happen */ + if (bv) + this.stack.push(bv); + else { + yield* this.pop(); + yield* this.step(); + } + } + else { + const parent = this.peek(2); + if (parent.type === 'block-map' && + ((this.type === 'map-value-ind' && parent.indent === fc.indent) || + (this.type === 'newline' && + !parent.items[parent.items.length - 1].sep))) { + yield* this.pop(); + yield* this.step(); + } + else if (this.type === 'map-value-ind' && + parent.type !== 'flow-collection') { + const prev = getPrevProps(parent); + const start = getFirstKeyStartProps(prev); + fixFlowSeqItems(fc); + const sep = fc.end.splice(1, fc.end.length); + sep.push(this.sourceToken); + const map = { + type: 'block-map', + offset: fc.offset, + indent: fc.indent, + items: [{ start, key: fc, sep }] + }; + this.onKeyLine = true; + this.stack[this.stack.length - 1] = map; + } + else { + yield* this.lineEnd(fc); + } + } + } + flowScalar(type) { + if (this.onNewLine) { + let nl = this.source.indexOf('\n') + 1; + while (nl !== 0) { + this.onNewLine(this.offset + nl); + nl = this.source.indexOf('\n', nl) + 1; + } + } + return { + type, + offset: this.offset, + indent: this.indent, + source: this.source + }; + } + startBlockValue(parent) { + switch (this.type) { + case 'alias': + case 'scalar': + case 'single-quoted-scalar': + case 'double-quoted-scalar': + return this.flowScalar(this.type); + case 'block-scalar-header': + return { + type: 'block-scalar', + offset: this.offset, + indent: this.indent, + props: [this.sourceToken], + source: '' + }; + case 'flow-map-start': + case 'flow-seq-start': + return { + type: 'flow-collection', + offset: this.offset, + indent: this.indent, + start: this.sourceToken, + items: [], + end: [] + }; + case 'seq-item-ind': + return { + type: 'block-seq', + offset: this.offset, + indent: this.indent, + items: [{ start: [this.sourceToken] }] + }; + case 'explicit-key-ind': { + this.onKeyLine = true; + const prev = getPrevProps(parent); + const start = getFirstKeyStartProps(prev); + start.push(this.sourceToken); + return { + type: 'block-map', + offset: this.offset, + indent: this.indent, + items: [{ start, explicitKey: true }] + }; + } + case 'map-value-ind': { + this.onKeyLine = true; + const prev = getPrevProps(parent); + const start = getFirstKeyStartProps(prev); + return { + type: 'block-map', + offset: this.offset, + indent: this.indent, + items: [{ start, key: null, sep: [this.sourceToken] }] + }; + } + } + return null; + } + atIndentedComment(start, indent) { + if (this.type !== 'comment') + return false; + if (this.indent <= indent) + return false; + return start.every(st => st.type === 'newline' || st.type === 'space'); + } + *documentEnd(docEnd) { + if (this.type !== 'doc-mode') { + if (docEnd.end) + docEnd.end.push(this.sourceToken); + else + docEnd.end = [this.sourceToken]; + if (this.type === 'newline') + yield* this.pop(); + } + } + *lineEnd(token) { + switch (this.type) { + case 'comma': + case 'doc-start': + case 'doc-end': + case 'flow-seq-end': + case 'flow-map-end': + case 'map-value-ind': + yield* this.pop(); + yield* this.step(); + break; + case 'newline': + this.onKeyLine = false; + // fallthrough + case 'space': + case 'comment': + default: + // all other values are errors + if (token.end) + token.end.push(this.sourceToken); + else + token.end = [this.sourceToken]; + if (this.type === 'newline') + yield* this.pop(); + } + } + } + + function parseOptions(options) { + const prettyErrors = options.prettyErrors !== false; + const lineCounter = options.lineCounter || (prettyErrors && new LineCounter()) || null; + return { lineCounter, prettyErrors }; + } + /** + * Parse the input as a stream of YAML documents. + * + * Documents should be separated from each other by `...` or `---` marker lines. + * + * @returns If an empty `docs` array is returned, it will be of type + * EmptyStream and contain additional stream information. In + * TypeScript, you should use `'empty' in docs` as a type guard for it. + */ + function parseAllDocuments(source, options = {}) { + const { lineCounter, prettyErrors } = parseOptions(options); + const parser = new Parser(lineCounter?.addNewLine); + const composer = new Composer(options); + const docs = Array.from(composer.compose(parser.parse(source))); + if (prettyErrors && lineCounter) + for (const doc of docs) { + doc.errors.forEach(prettifyError(source, lineCounter)); + doc.warnings.forEach(prettifyError(source, lineCounter)); + } + if (docs.length > 0) + return docs; + return Object.assign([], { empty: true }, composer.streamInfo()); + } + /** Parse an input string into a single YAML.Document */ + function parseDocument(source, options = {}) { + const { lineCounter, prettyErrors } = parseOptions(options); + const parser = new Parser(lineCounter?.addNewLine); + const composer = new Composer(options); + // `doc` is always set by compose.end(true) at the very latest + let doc = null; + for (const _doc of composer.compose(parser.parse(source), true, source.length)) { + if (!doc) + doc = _doc; + else if (doc.options.logLevel !== 'silent') { + doc.errors.push(new YAMLParseError(_doc.range.slice(0, 2), 'MULTIPLE_DOCS', 'Source contains multiple documents; please use YAML.parseAllDocuments()')); + break; + } + } + if (prettyErrors && lineCounter) { + doc.errors.forEach(prettifyError(source, lineCounter)); + doc.warnings.forEach(prettifyError(source, lineCounter)); + } + return doc; + } + function parse(src, reviver, options) { + let _reviver = undefined; + if (typeof reviver === 'function') { + _reviver = reviver; + } + else if (options === undefined && reviver && typeof reviver === 'object') { + options = reviver; + } + const doc = parseDocument(src, options); + if (!doc) + return null; + doc.warnings.forEach(warning => warn(doc.options.logLevel, warning)); + if (doc.errors.length > 0) { + if (doc.options.logLevel !== 'silent') + throw doc.errors[0]; + else + doc.errors = []; + } + return doc.toJS(Object.assign({ reviver: _reviver }, options)); + } + function stringify(value, replacer, options) { + let _replacer = null; + if (typeof replacer === 'function' || Array.isArray(replacer)) { + _replacer = replacer; + } + else if (options === undefined && replacer) { + options = replacer; + } + if (typeof options === 'string') + options = options.length; + if (typeof options === 'number') { + const indent = Math.round(options); + options = indent < 1 ? undefined : indent > 8 ? { indent: 8 } : { indent }; + } + if (value === undefined) { + const { keepUndefined } = options ?? replacer ?? {}; + if (!keepUndefined) + return undefined; + } + if (isDocument(value) && !_replacer) + return value.toString(options); + return new Document(value, _replacer, options).toString(options); + } + + var YAML = /*#__PURE__*/Object.freeze({ + __proto__: null, + Alias: Alias$1, + CST: cst, + Composer: Composer, + Document: Document, + Lexer: Lexer, + LineCounter: LineCounter, + Pair: Pair, + Parser: Parser, + Scalar: Scalar, + Schema: Schema, + YAMLError: YAMLError, + YAMLMap: YAMLMap, + YAMLParseError: YAMLParseError, + YAMLSeq: YAMLSeq, + YAMLWarning: YAMLWarning, + isAlias: isAlias, + isCollection: isCollection$1, + isDocument: isDocument, + isMap: isMap, + isNode: isNode, + isPair: isPair, + isScalar: isScalar$1, + isSeq: isSeq, + parse: parse, + parseAllDocuments: parseAllDocuments, + parseDocument: parseDocument, + stringify: stringify, + visit: visit$1, + visitAsync: visitAsync + }); + + // This file was generated by lezer-generator. You probably shouldn't edit it. + const blockEnd = 63, + eof = 64, + DirectiveEnd = 1, + DocEnd = 2, + sequenceStartMark = 3, + sequenceContinueMark = 4, + explicitMapStartMark = 5, + explicitMapContinueMark = 6, + flowMapMark = 7, + mapStartMark = 65, + mapContinueMark = 66, + Literal = 8, + QuotedLiteral = 9, + Anchor = 10, + Alias = 11, + Tag = 12, + BlockLiteralContent = 13, + BracketL = 19, + FlowSequence = 20, + Colon = 29, + BraceL = 33, + FlowMapping = 34, + BlockLiteralHeader = 47; + + const + type_Top = 0, // Top document level + type_Seq = 1, // Block sequence + type_Map = 2, // Block mapping + type_Flow = 3, // Inside flow content + type_Lit = 4; // Block literal with explicit indentation + + class Context { + constructor(parent, depth, type) { + this.parent = parent; + this.depth = depth; + this.type = type; + this.hash = (parent ? parent.hash + parent.hash << 8 : 0) + depth + (depth << 4) + type; + } + } + + Context.top = new Context(null, -1, type_Top); + + function findColumn(input, pos) { + for (let col = 0, p = pos - input.pos - 1;; p--, col++) { + let ch = input.peek(p); + if (isBreakSpace(ch) || ch == -1) return col + } + } + + function isNonBreakSpace(ch) { + return ch == 32 || ch == 9 + } + + function isBreakSpace(ch) { + return ch == 10 || ch == 13 + } + + function isSpace(ch) { + return isNonBreakSpace(ch) || isBreakSpace(ch) + } + + function isSep(ch) { + return ch < 0 || isSpace(ch) + } + + const indentation = new ContextTracker({ + start: Context.top, + reduce(context, term) { + return context.type == type_Flow && (term == FlowSequence || term == FlowMapping) ? context.parent : context + }, + shift(context, term, stack, input) { + if (term == sequenceStartMark) + return new Context(context, findColumn(input, input.pos), type_Seq) + if (term == mapStartMark || term == explicitMapStartMark) + return new Context(context, findColumn(input, input.pos), type_Map) + if (term == blockEnd) + return context.parent + if (term == BracketL || term == BraceL) + return new Context(context, 0, type_Flow) + if (term == BlockLiteralContent && context.type == type_Lit) + return context.parent + if (term == BlockLiteralHeader) { + let indent = /[1-9]/.exec(input.read(input.pos, stack.pos)); + if (indent) return new Context(context, context.depth + (+indent[0]), type_Lit) + } + return context + }, + hash(context) { return context.hash } + }); + + function three(input, ch, off = 0) { + return input.peek(off) == ch && input.peek(off + 1) == ch && input.peek(off + 2) == ch && isSep(input.peek(off + 3)) + } + + const newlines = new ExternalTokenizer((input, stack) => { + if (input.next == -1 && stack.canShift(eof)) + return input.acceptToken(eof) + let prev = input.peek(-1); + if ((isBreakSpace(prev) || prev < 0) && stack.context.type != type_Flow) { + if (three(input, 45 /* '-' */)) { + if (stack.canShift(blockEnd)) input.acceptToken(blockEnd); + else return input.acceptToken(DirectiveEnd, 3) + } + if (three(input, 46 /* '.' */)) { + if (stack.canShift(blockEnd)) input.acceptToken(blockEnd); + else return input.acceptToken(DocEnd, 3) + } + let depth = 0; + while (input.next == 32 /* ' ' */) { depth++; input.advance(); } + if ((depth < stack.context.depth || + depth == stack.context.depth && stack.context.type == type_Seq && + (input.next != 45 /* '-' */ || !isSep(input.peek(1)))) && + // Not blank + input.next != -1 && !isBreakSpace(input.next) && input.next != 35 /* '#' */) + input.acceptToken(blockEnd, -depth); + } + }, {contextual: true}); + + const blockMark = new ExternalTokenizer((input, stack) => { + if (stack.context.type == type_Flow) { + if (input.next == 63 /* '?' */) { + input.advance(); + if (isSep(input.next)) input.acceptToken(flowMapMark); + } + return + } + if (input.next == 45 /* '-' */) { + input.advance(); + if (isSep(input.next)) + input.acceptToken(stack.context.type == type_Seq && stack.context.depth == findColumn(input, input.pos - 1) + ? sequenceContinueMark : sequenceStartMark); + } else if (input.next == 63 /* '?' */) { + input.advance(); + if (isSep(input.next)) + input.acceptToken(stack.context.type == type_Map && stack.context.depth == findColumn(input, input.pos - 1) + ? explicitMapContinueMark : explicitMapStartMark); + } else { + let start = input.pos; + // Scan over a potential key to see if it is followed by a colon. + for (;;) { + if (isNonBreakSpace(input.next)) { + if (input.pos == start) return + input.advance(); + } else if (input.next == 33 /* '!' */) { + readTag(input); + } else if (input.next == 38 /* '&' */) { + readAnchor(input); + } else if (input.next == 42 /* '*' */) { + readAnchor(input); + break + } else if (input.next == 39 /* "'" */ || input.next == 34 /* '"' */) { + if (readQuoted(input, true)) break + return + } else if (input.next == 91 /* '[' */ || input.next == 123 /* '{' */) { + if (!scanBrackets(input)) return + break + } else { + readPlain(input, true, false, 0); + break + } + } + while (isNonBreakSpace(input.next)) input.advance(); + if (input.next == 58 /* ':' */) { + if (input.pos == start && stack.canShift(Colon)) return + let after = input.peek(1); + if (isSep(after)) + input.acceptTokenTo(stack.context.type == type_Map && stack.context.depth == findColumn(input, start) + ? mapContinueMark : mapStartMark, start); + } + } + }, {contextual: true}); + + function uriChar(ch) { + return ch > 32 && ch < 127 && ch != 34 && ch != 37 && ch != 44 && ch != 60 && + ch != 62 && ch != 92 && ch != 94 && ch != 96 && ch != 123 && ch != 124 && ch != 125 + } + + function hexChar(ch) { + return ch >= 48 && ch <= 57 || ch >= 97 && ch <= 102 || ch >= 65 && ch <= 70 + } + + function readUriChar(input, quoted) { + if (input.next == 37 /* '%' */) { + input.advance(); + if (hexChar(input.next)) input.advance(); + if (hexChar(input.next)) input.advance(); + return true + } else if (uriChar(input.next) || quoted && input.next == 44 /* ',' */) { + input.advance(); + return true + } + return false + } + + function readTag(input) { + input.advance(); // ! + if (input.next == 60 /* '<' */) { + input.advance(); + for (;;) { + if (!readUriChar(input, true)) { + if (input.next == 62 /* '>' */) input.advance(); + break + } + } + } else { + while (readUriChar(input, false)) {} + } + } + + function readAnchor(input) { + input.advance(); + while (!isSep(input.next) && charTag(input.tag) != "f") input.advance(); + } + + function readQuoted(input, scan) { + let quote = input.next, lineBreak = false, start = input.pos; + input.advance(); + for (;;) { + let ch = input.next; + if (ch < 0) break + input.advance(); + if (ch == quote) { + if (ch == 39 /* "'" */) { + if (input.next == 39) input.advance(); + else break + } else { + break + } + } else if (ch == 92 /* "\\" */ && quote == 34 /* '"' */) { + if (input.next >= 0) input.advance(); + } else if (isBreakSpace(ch)) { + if (scan) return false + lineBreak = true; + } else if (scan && input.pos >= start + 1024) { + return false + } + } + return !lineBreak + } + + function scanBrackets(input) { + for (let stack = [], end = input.pos + 1024;;) { + if (input.next == 91 /* '[' */ || input.next == 123 /* '{' */) { + stack.push(input.next); + input.advance(); + } else if (input.next == 39 /* "'" */ || input.next == 34 /* '"' */) { + if (!readQuoted(input, true)) return false + } else if (input.next == 93 /* ']' */ || input.next == 125 /* '}' */) { + if (stack[stack.length - 1] != input.next - 2) return false + stack.pop(); + input.advance(); + if (!stack.length) return true + } else if (input.next < 0 || input.pos > end || isBreakSpace(input.next)) { + return false + } else { + input.advance(); + } + } + } + + // "Safe char" info for char codes 33 to 125. s: safe, i: indicator, f: flow indicator + const charTable = "iiisiiissisfissssssssssssisssiiissssssssssssssssssssssssssfsfssissssssssssssssssssssssssssfif"; + + function charTag(ch) { + if (ch < 33) return "u" + if (ch > 125) return "s" + return charTable[ch - 33] + } + + function isSafe(ch, inFlow) { + let tag = charTag(ch); + return tag != "u" && !(inFlow && tag == "f") + } + + function readPlain(input, scan, inFlow, indent) { + if (charTag(input.next) == "s" || + (input.next == 63 /* '?' */ || input.next == 58 /* ':' */ || input.next == 45 /* '-' */) && + isSafe(input.peek(1), inFlow)) { + input.advance(); + } else { + return false + } + let start = input.pos; + for (;;) { + let next = input.next, off = 0, lineIndent = indent + 1; + while (isSpace(next)) { + if (isBreakSpace(next)) { + if (scan) return false + lineIndent = 0; + } else { + lineIndent++; + } + next = input.peek(++off); + } + let safe = next >= 0 && + (next == 58 /* ':' */ ? isSafe(input.peek(off + 1), inFlow) : + next == 35 /* '#' */ ? input.peek(off - 1) != 32 /* ' ' */ : + isSafe(next, inFlow)); + if (!safe || !inFlow && lineIndent <= indent || + lineIndent == 0 && !inFlow && (three(input, 45, off) || three(input, 46, off))) + break + if (scan && charTag(next) == "f") return false + for (let i = off; i >= 0; i--) input.advance(); + if (scan && input.pos > start + 1024) return false + } + return true + } + + const literals = new ExternalTokenizer((input, stack) => { + if (input.next == 33 /* '!' */) { + readTag(input); + input.acceptToken(Tag); + } else if (input.next == 38 /* '&' */ || input.next == 42 /* '*' */) { + let token = input.next == 38 ? Anchor : Alias; + readAnchor(input); + input.acceptToken(token); + } else if (input.next == 39 /* "'" */ || input.next == 34 /* '"' */) { + readQuoted(input, false); + input.acceptToken(QuotedLiteral); + } else if (readPlain(input, false, stack.context.type == type_Flow, stack.context.depth)) { + input.acceptToken(Literal); + } + }); + + const blockLiteral = new ExternalTokenizer((input, stack) => { + let indent = stack.context.type == type_Lit ? stack.context.depth : -1, upto = input.pos; + scan: for (;;) { + let depth = 0, next = input.next; + while (next == 32 /* ' ' */) next = input.peek(++depth); + if (!depth && (three(input, 45, depth) || three(input, 46, depth))) break + if (!isBreakSpace(next)) { + if (indent < 0) indent = Math.max(stack.context.depth + 1, depth); + if (depth < indent) break + } + for (;;) { + if (input.next < 0) break scan + let isBreak = isBreakSpace(input.next); + input.advance(); + if (isBreak) continue scan + upto = input.pos; + } + } + input.acceptTokenTo(BlockLiteralContent, upto); + }); + + const yamlHighlighting = styleTags({ + DirectiveName: tags.keyword, + DirectiveContent: tags.attributeValue, + "DirectiveEnd DocEnd": tags.meta, + QuotedLiteral: tags.string, + BlockLiteralHeader: tags.special(tags.string), + BlockLiteralContent: tags.content, + Literal: tags.content, + "Key/Literal Key/QuotedLiteral": tags.definition(tags.propertyName), + "Anchor Alias": tags.labelName, + Tag: tags.typeName, + Comment: tags.lineComment, + ": , -": tags.separator, + "?": tags.punctuation, + "[ ]": tags.squareBracket, + "{ }": tags.brace + }); + + // This file was generated by lezer-generator. You probably shouldn't edit it. + const parser = LRParser.deserialize({ + version: 14, + states: "5lQ!ZQgOOO#PQfO'#CpO#uQfO'#DOOOQR'#Dv'#DvO$qQgO'#DRO%gQdO'#DUO%nQgO'#DUO&ROaO'#D[OOQR'#Du'#DuO&{QgO'#D^O'rQgO'#D`OOQR'#Dt'#DtO(iOqO'#DbOOQP'#Dj'#DjO(zQaO'#CmO)YQgO'#CmOOQP'#Cm'#CmQ)jQaOOQ)uQgOOQ]QgOOO*PQdO'#CrO*nQdO'#CtOOQO'#Dw'#DwO+]Q`O'#CxO+hQdO'#CwO+rQ`O'#CwOOQO'#Cv'#CvO+wQdO'#CvOOQO'#Cq'#CqO,UQ`O,59[O,^QfO,59[OOQR,59[,59[OOQO'#Cx'#CxO,eQ`O'#DPO,pQdO'#DPOOQO'#Dx'#DxO,zQdO'#DxO-XQ`O,59jO-aQfO,59jOOQR,59j,59jOOQR'#DS'#DSO-hQcO,59mO-sQgO'#DVO.TQ`O'#DVO.YQcO,59pOOQR'#DX'#DXO#|QfO'#DWO.hQcO'#DWOOQR,59v,59vO.yOWO,59vO/OOaO,59vO/WOaO,59vO/cQgO'#D_OOQR,59x,59xO0VQgO'#DaOOQR,59z,59zOOQP,59|,59|O0yOaO,59|O1ROaO,59|O1aOqO,59|OOQP-E7h-E7hO1oQgO,59XOOQP,59X,59XO2PQaO'#DeO2_QgO'#DeO2oQgO'#DkOOQP'#Dk'#DkQ)jQaOOO3PQdO'#CsOOQO,59^,59^O3kQdO'#CuOOQO,59`,59`OOQO,59c,59cO4VQdO,59cO4aQdO'#CzO4kQ`O'#CzOOQO,59b,59bOOQU,5:Q,5:QOOQR1G.v1G.vO4pQ`O1G.vOOQU-E7d-E7dO4xQdO,59kOOQO,59k,59kO5SQdO'#DQO5^Q`O'#DQOOQO,5:d,5:dOOQU,5:R,5:ROOQR1G/U1G/UO5cQ`O1G/UOOQU-E7e-E7eO5kQgO'#DhO5xQcO1G/XOOQR1G/X1G/XOOQR,59q,59qO6TQgO,59qO6eQdO'#DiO6lQgO'#DiO7PQcO1G/[OOQR1G/[1G/[OOQR,59r,59rO#|QfO,59rOOQR1G/b1G/bO7_OWO1G/bO7dOaO1G/bOOQR,59y,59yOOQR,59{,59{OOQP1G/h1G/hO7lOaO1G/hO7tOaO1G/hO8POaO1G/hOOQP1G.s1G.sO8_QgO,5:POOQP,5:P,5:POOQP,5:V,5:VOOQP-E7i-E7iOOQO,59_,59_OOQO,59a,59aOOQO1G.}1G.}OOQO,59f,59fO8oQdO,59fOOQR7+$b7+$bP,XQ`O'#DfOOQO1G/V1G/VOOQO,59l,59lO8yQdO,59lOOQR7+$p7+$pP9TQ`O'#DgOOQR'#DT'#DTOOQR,5:S,5:SOOQR-E7f-E7fOOQR7+$s7+$sOOQR1G/]1G/]O9YQgO'#DYO9jQ`O'#DYOOQR,5:T,5:TO#|QfO'#DZO9oQcO'#DZOOQR-E7g-E7gOOQR7+$v7+$vOOQR1G/^1G/^OOQR7+$|7+$|O:QOWO7+$|OOQP7+%S7+%SO:VOaO7+%SO:_OaO7+%SOOQP1G/k1G/kOOQO1G/Q1G/QOOQO1G/W1G/WOOQR,59t,59tO:jQgO,59tOOQR,59u,59uO#|QfO,59uOOQR<YAN>Y", + stateData: ";S~O!fOS!gOS^OS~OP_OQbORSOTUOWROXROYYOZZO[XOcPOqQO!PVO!V[O!cTO~O`cO~P]OVkOWROXROYeOZfO[dOcPOmhOqQO~OboO~P!bOVtOWROXROYeOZfO[dOcPOmrOqQO~OpwO~P#WORSOTUOWROXROYYOZZO[XOcPOqQO!PVO!cTO~OSvP!avP!bvP~P#|OWROXROYeOZfO[dOcPOqQO~OmzO~P%OOm!OOUzP!azP!bzP!dzP~P#|O^!SO!b!QO!f!TO!g!RO~ORSOTUOWROXROcPOqQO!PVO!cTO~OY!UOP!QXQ!QX!V!QX!`!QXS!QX!a!QX!b!QXU!QXm!QX!d!QX~P&aO[!WOP!SXQ!SX!V!SX!`!SXS!SX!a!SX!b!SXU!SXm!SX!d!SX~P&aO^!ZO!W![O!b!YO!f!]O!g!YO~OP!_O!V[OQaX!`aX~OPaXQaX!VaX!`aX~P#|OP!bOQ!cO!V[O~OP_O!V[O~P#|OWROXROY!fOcPOqQObfXmfXofXpfX~OWROXRO[!hOcPOqQObhXmhXohXphX~ObeXmlXoeX~ObkXokX~P%OOm!kO~Om!lObnPonP~P%OOb!pOo!oO~Ob!pO~P!bOm!sOosXpsX~OosXpsX~P%OOm!uOotPptP~P%OOo!xOp!yO~Op!yO~P#WOS!|O!a#OO!b#OO~OUyX!ayX!byX!dyX~P#|Om#QO~OU#SO!a#UO!b#UO!d#RO~Om#WOUzX!azX!bzX!dzX~O]#XO~O!b#XO!g#YO~O^#ZO!b#XO!g#YO~OP!RXQ!RX!V!RX!`!RXS!RX!a!RX!b!RXU!RXm!RX!d!RX~P&aOP!TXQ!TX!V!TX!`!TXS!TX!a!TX!b!TXU!TXm!TX!d!TX~P&aO!b#^O!g#^O~O^#_O!b#^O!f#`O!g#^O~O^#_O!W#aO!b#^O!g#^O~OPaaQaa!Vaa!`aa~P#|OP#cO!V[OQ!XX!`!XX~OP!XXQ!XX!V!XX!`!XX~P#|OP_O!V[OQ!_X!`!_X~P#|OWROXROcPOqQObgXmgXogXpgX~OWROXROcPOqQObiXmiXoiXpiX~Obkaoka~P%OObnXonX~P%OOm#kO~Ob#lOo!oO~Oosapsa~P%OOotXptX~P%OOm#pO~Oo!xOp#qO~OSwP!awP!bwP~P#|OS!|O!a#vO!b#vO~OUya!aya!bya!dya~P#|Om#xO~P%OOm#{OU}P!a}P!b}P!d}P~P#|OU#SO!a$OO!b$OO!d#RO~O]$QO~O!b$QO!g$RO~O!b$SO!g$SO~O^$TO!b$SO!g$SO~O^$TO!b$SO!f$UO!g$SO~OP!XaQ!Xa!V!Xa!`!Xa~P#|Obnaona~P%OOotapta~P%OOo!xO~OU|X!a|X!b|X!d|X~P#|Om$ZO~Om$]OU}X!a}X!b}X!d}X~O]$^O~O!b$_O!g$_O~O^$`O!b$_O!g$_O~OU|a!a|a!b|a!d|a~P#|O!b$cO!g$cO~O", + goto: ",]!mPPPPPPPPPPPPPPPPP!nPP!v#v#|$`#|$c$f$j$nP%VPPP!v%Y%^%a%{&O%a&R&U&X&_&b%aP&e&{&e'O'RPP']'a'g'm's'y(XPPPPPPPP(_)e*X+c,VUaObcR#e!c!{ROPQSTUXY_bcdehknrtvz!O!U!W!_!b!c!f!h!k!l!s!u!|#Q#R#S#W#c#k#p#x#{$Z$]QmPR!qnqfPQThknrtv!k!l!s!u#R#k#pR!gdR!ieTlPnTjPnSiPnSqQvQ{TQ!mkQ!trQ!vtR#y#RR!nkTsQvR!wt!RWOSUXY_bcz!O!U!W!_!b!c!|#Q#S#W#c#x#{$Z$]RySR#t!|R|TR|UQ!PUR#|#SR#z#RR#z#SyZOSU_bcz!O!_!b!c!|#Q#S#W#c#x#{$Z$]R!VXR!XYa]O^abc!a!c!eT!da!eQnPR!rnQvQR!{vQ!}yR#u!}Q#T|R#}#TW^Obc!cS!^^!aT!aa!eQ!eaR#f!eW`Obc!cQxSS}U#SQ!`_Q#PzQ#V!OQ#b!_Q#d!bQ#s!|Q#w#QQ$P#WQ$V#cQ$Y#xQ$[#{Q$a$ZR$b$]xZOSU_bcz!O!_!b!c!|#Q#S#W#c#x#{$Z$]Q!VXQ!XYQ#[!UR#]!W!QWOSUXY_bcz!O!U!W!_!b!c!|#Q#S#W#c#x#{$Z$]pfPQThknrtv!k!l!s!u#R#k#pQ!gdQ!ieQ#g!fR#h!hSgPn^pQTkrtv#RQ!jhQ#i!kQ#j!lQ#n!sQ#o!uQ$W#kR$X#pQuQR!zv", + nodeNames: "⚠ DirectiveEnd DocEnd - - ? ? ? Literal QuotedLiteral Anchor Alias Tag BlockLiteralContent Comment Stream BOM Document ] [ FlowSequence Item Tagged Anchored Anchored Tagged FlowMapping Pair Key : Pair , } { FlowMapping Pair Pair BlockSequence Item Item BlockMapping Pair Pair Key Pair Pair BlockLiteral BlockLiteralHeader Tagged Anchored Anchored Tagged Directive DirectiveName DirectiveContent Document", + maxTerm: 74, + context: indentation, + nodeProps: [ + ["isolate", -3,8,9,14,""], + ["openedBy", 18,"[",32,"{"], + ["closedBy", 19,"]",33,"}"] + ], + propSources: [yamlHighlighting], + skippedNodes: [0], + repeatNodeCount: 6, + tokenData: "-Y~RnOX#PXY$QYZ$]Z]#P]^$]^p#Ppq$Qqs#Pst$btu#Puv$yv|#P|}&e}![#P![!]'O!]!`#P!`!a'i!a!}#P!}#O*g#O#P#P#P#Q+Q#Q#o#P#o#p+k#p#q'i#q#r,U#r;'S#P;'S;=`#z<%l?HT#P?HT?HU,o?HUO#PQ#UU!WQOY#PZp#Ppq#hq;'S#P;'S;=`#z<%lO#PQ#kTOY#PZs#Pt;'S#P;'S;=`#z<%lO#PQ#}P;=`<%l#P~$VQ!f~XY$Qpq$Q~$bO!g~~$gS^~OY$bZ;'S$b;'S;=`$s<%lO$b~$vP;=`<%l$bR%OX!WQOX%kXY#PZ]%k]^#P^p%kpq#hq;'S%k;'S;=`&_<%lO%kR%rX!WQ!VPOX%kXY#PZ]%k]^#P^p%kpq#hq;'S%k;'S;=`&_<%lO%kR&bP;=`<%l%kR&lUoP!WQOY#PZp#Ppq#hq;'S#P;'S;=`#z<%lO#PR'VUmP!WQOY#PZp#Ppq#hq;'S#P;'S;=`#z<%lO#PR'p[!PP!WQOY#PZp#Ppq#hq{#P{|(f|}#P}!O(f!O!R#P!R![)p![;'S#P;'S;=`#z<%lO#PR(mW!PP!WQOY#PZp#Ppq#hq!R#P!R![)V![;'S#P;'S;=`#z<%lO#PR)^U!PP!WQOY#PZp#Ppq#hq;'S#P;'S;=`#z<%lO#PR)wY!PP!WQOY#PZp#Ppq#hq{#P{|)V|}#P}!O)V!O;'S#P;'S;=`#z<%lO#PR*nUcP!WQOY#PZp#Ppq#hq;'S#P;'S;=`#z<%lO#PR+XUbP!WQOY#PZp#Ppq#hq;'S#P;'S;=`#z<%lO#PR+rUqP!WQOY#PZp#Ppq#hq;'S#P;'S;=`#z<%lO#PR,]UpP!WQOY#PZp#Ppq#hq;'S#P;'S;=`#z<%lO#PR,vU`P!WQOY#PZp#Ppq#hq;'S#P;'S;=`#z<%lO#P", + tokenizers: [newlines, blockMark, literals, blockLiteral, 0, 1], + topRules: {"Stream":[0,15]}, + tokenPrec: 0 + }); + + /** + A language provider based on the [Lezer YAML + parser](https://github.com/lezer-parser/yaml), extended with + highlighting and indentation information. + */ + const yamlLanguage = /*@__PURE__*/LRLanguage.define({ + name: "yaml", + parser: /*@__PURE__*/parser.configure({ + props: [ + /*@__PURE__*/indentNodeProp.add({ + Stream: cx => { + for (let before = cx.node.resolve(cx.pos, -1); before && before.to >= cx.pos; before = before.parent) { + if (before.name == "BlockLiteralContent" && before.from < before.to) + return cx.baseIndentFor(before); + if (before.name == "BlockLiteral") + return cx.baseIndentFor(before) + cx.unit; + if (before.name == "BlockSequence" || before.name == "BlockMapping") + return cx.column(before.from, 1); + if (before.name == "QuotedLiteral") + return null; + if (before.name == "Literal") { + let col = cx.column(before.from, 1); + if (col == cx.lineIndent(before.from, 1)) + return col; // Start on own line + if (before.to > cx.pos) + return null; + } + } + return null; + }, + FlowMapping: /*@__PURE__*/delimitedIndent({ closing: "}" }), + FlowSequence: /*@__PURE__*/delimitedIndent({ closing: "]" }), + }), + /*@__PURE__*/foldNodeProp.add({ + "FlowMapping FlowSequence": foldInside, + "Item Pair BlockLiteral": (node, state) => ({ from: state.doc.lineAt(node.from).to, to: node.to }) + }) + ] + }), + languageData: { + commentTokens: { line: "#" }, + indentOnInput: /^\s*[\]\}]$/, + } + }); + /** + Language support for YAML. + */ + function yaml() { + return new LanguageSupport(yamlLanguage); + } + + /* globals: jsonld */ + + + // Setup JSON-LD documentLoader + const xhrDocumentLoader = jsonld.documentLoaders.xhr(); + // FIXME: add UI to let users control and set context mapping + jsonld.documentLoader = function(url) { + // rewrite URLs that we know have secure JSON-LD Contexts + if(url === 'http://schema.org/' || url === 'http://schema.org') { + url = 'https://schema.org/'; + } + + // if a non-HTTPS URL, use the proxy since we run in HTTPS only mode + if(!url.startsWith('https://')) { + url = [ + location.protocol, + '//', + location.host, + // NOTE: using hard-coded path so file can be shared with dev page + //location.pathname, + '/playground/', + 'proxy?url=', + url + ].join(''); + } + + return xhrDocumentLoader(url); + }; + + const jsonLdAtTerms = [ + { label: "@context", type: "keyword", info: "Defines the JSON-LD context" }, + { label: "@id", type: "keyword", info: "Specifies the unique identifier of an entity" }, + { label: "@type", type: "keyword", info: "Defines the type of an entity" }, + { label: "@value", type: "keyword", info: "Represents the value of a node" }, + { label: "@language", type: "keyword", info: "Specifies the language of a string value" }, + { label: "@graph", type: "keyword", info: "Represents a named graph" }, + { label: "@list", type: "keyword", info: "Denotes an ordered list" }, + { label: "@set", type: "keyword", info: "Denotes an unordered set" }, + { label: "@reverse", type: "keyword", info: "Defines reverse properties" }, + { label: "@index", type: "keyword", info: "Specifies an index for ordered data" }, + { label: "@base", type: "keyword", info: "Defines the base IRI" }, + { label: "@vocab", type: "keyword", info: "Defines the default vocabulary" }, + { label: "@container", type: "keyword", info: "Specifies container types for properties" }, + { label: "@nest", type: "keyword", info: "Allows nesting of properties" }, + { label: "@prefix", type: "keyword", info: "Defines a prefix mapping" }, + { label: "@propagate", type: "keyword", info: "Controls context propagation" }, + { label: "@protected", type: "keyword", info: "Prevents term overrides" }, + { label: "@version", type: "keyword", info: "Specifies the JSON-LD version" } + ]; + + // TODO: the next two functions could probably become a petite-vue component + function editorListener(docName) { + let changes = []; // keep the changes as a list; then pick the last one + let timer; // we only want one timer, once the last one is done, use the result + function debounce(fn, delay) { + return (...args) => { + clearTimeout(timer); + timer = setTimeout(() => fn(...args), delay); + }; + } + + return EditorView.updateListener.of((update) => { + if (update.docChanged) { + changes.push(update.state.doc.toString()); + debounce((docName) => { + // set the global `doc` to the latest string from the editor + try { + const parsed = JSON.parse(changes[changes.length-1]); + this[docName] = parsed; + this.parseError = ''; + } catch (err) { + this.parseError = err.message; + } }, 1000).call(this, docName); + } + }); + } + function initEditor(id, content, varName) { + return new EditorView({ + parent: document.getElementById(id), + doc: JSON.stringify(content, null, 2), + extensions: [ + basicSetup, + keymap.of([indentWithTab]), + json(), + linter(jsonParseLinter()), + autocompletion({override: [completeFromList(jsonLdAtTerms)]}), + editorListener.call(this, varName) + ] + }); + } + + const language = new Compartment(); + + const readOnlyEditor = new EditorView({ + parent: document.getElementById('read-only-editor'), + doc: `{}`, + extensions: [ + basicSetup, + language.of(json()), + EditorState.readOnly.of(true), + EditorView.editable.of(false), + EditorView.contentAttributes.of({tabindex: '0'}) + ] + }); + + function setEditorValue(_editor, doc, lang) { + console.trace(arguments); + if (_editor) { + // set the correct language + language.reconfigure(json()); + if (lang === 'yaml') language.reconfigure(yaml()); + else language.reconfigure(StreamLanguage.define(ntriples)); + + _editor.dispatch({ + changes: { + from: 0, + to: _editor.state.doc.length, + insert: typeof(doc) === 'object' + ? JSON.stringify(doc, null, 2) + : doc + }, + // set the correct language + effects: language.reconfigure(typeof(doc) === 'object' + ? json() + : StreamLanguage.define(ntriples)) + }); + } + } + + window.app = Qe({ + doc: {}, + contextDoc: {}, + frameDoc: {}, + tableQuads: {}, + yamlLD: '', + remoteDocURL: '', + remoteSideDocURL: '', + parseError: '', + inputTab: 'json-ld', + outputTab: 'expanded', + options: { processingMode: '', base: '', baseUrl: '', @@ -27520,6 +35469,10 @@ this.parseError = err.message; } break; + case 'yamlld': + this.yamlLD = YAML.stringify(this.doc); + setEditorValue(readOnlyEditor, this.yamlLD, 'yaml'); + break; default: setEditorValue(readOnlyEditor, {}); } diff --git a/playground/next/editor.mjs b/playground/next/editor.mjs index 64633fcdf..909989db6 100644 --- a/playground/next/editor.mjs +++ b/playground/next/editor.mjs @@ -10,6 +10,8 @@ import {StreamLanguage} from '@codemirror/language'; import {ntriples} from '@codemirror/legacy-modes/mode/ntriples'; import {keymap} from '@codemirror/view'; import {linter} from '@codemirror/lint'; +import YAML from 'yaml'; +import {yaml} from '@codemirror/lang-yaml'; // Setup JSON-LD documentLoader const xhrDocumentLoader = jsonld.documentLoaders.xhr(); @@ -114,8 +116,14 @@ const readOnlyEditor = new EditorView({ ] }); -function setEditorValue(_editor, doc) { +function setEditorValue(_editor, doc, lang) { + // TODO: this runs more often than it should (because v-effect); maybe debounce if (_editor) { + // set the correct language + let effects = language.reconfigure(json()); + if (lang === 'yaml') effects = language.reconfigure(yaml()); + else effects = language.reconfigure(StreamLanguage.define(ntriples)); + _editor.dispatch({ changes: { from: 0, @@ -137,6 +145,7 @@ window.app = createApp({ contextDoc: {}, frameDoc: {}, tableQuads: {}, + yamlLD: '', remoteDocURL: '', remoteSideDocURL: '', parseError: '', @@ -303,6 +312,10 @@ window.app = createApp({ this.parseError = err.message; } break; + case 'yamlld': + this.yamlLD = YAML.stringify(this.doc); + setEditorValue(readOnlyEditor, this.yamlLD, 'yaml'); + break; default: setEditorValue(readOnlyEditor, {}); } diff --git a/playground/next/index.html b/playground/next/index.html index 317a9fc52..6c259d23f 100644 --- a/playground/next/index.html +++ b/playground/next/index.html @@ -274,6 +274,7 @@

JSON-LD Playground

N-Quads
Canonized
Table
+
YAML-LD
From afad490a3bed9af848883af1f563d505c36151a1 Mon Sep 17 00:00:00 2001 From: Benjamin Young Date: Wed, 21 May 2025 11:29:05 -0400 Subject: [PATCH 45/54] Simplify and fix table tab toggling. Once the table tab was clicked it would stay there...because it was checking for data availability...not UX state. --- playground/next/editor.bundle.js | 3 --- playground/next/editor.mjs | 3 --- playground/next/index.html | 4 ++-- 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/playground/next/editor.bundle.js b/playground/next/editor.bundle.js index d56c7d3f3..a281847e9 100644 --- a/playground/next/editor.bundle.js +++ b/playground/next/editor.bundle.js @@ -35324,9 +35324,6 @@ } return ''; }, - get hasTableQuads() { - return Object.keys(this.tableQuads).length > 0; - }, get sideDoc() { if (this.outputTab === 'framed') { return 'frameDoc'; diff --git a/playground/next/editor.mjs b/playground/next/editor.mjs index 909989db6..6c75e61ff 100644 --- a/playground/next/editor.mjs +++ b/playground/next/editor.mjs @@ -167,9 +167,6 @@ window.app = createApp({ } return ''; }, - get hasTableQuads() { - return Object.keys(this.tableQuads).length > 0; - }, get sideDoc() { if (this.outputTab === 'framed') { return 'frameDoc'; diff --git a/playground/next/index.html b/playground/next/index.html index 6c259d23f..cba632483 100644 --- a/playground/next/index.html +++ b/playground/next/index.html @@ -277,8 +277,8 @@

JSON-LD Playground

YAML-LD
-
- +
+
From cdaf346dd472dce3d3ab3a09980c061fa134148c Mon Sep 17 00:00:00 2001 From: Benjamin Young Date: Thu, 22 May 2025 10:05:13 -0400 Subject: [PATCH 46/54] Add CBOR-LD tab. Very basic info to start. --- package.json | 2 + playground/next/editor.mjs | 30 +++++++++++ playground/next/index.html | 101 ++++++++++++++++++++++--------------- rollup.config.mjs | 6 ++- 4 files changed, 98 insertions(+), 41 deletions(-) diff --git a/package.json b/package.json index 6791af9f1..1a06c2061 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,8 @@ "@codemirror/lint": "^6.8.5", "@codemirror/state": "^6.5.2", "@codemirror/view": "^6.36.7", + "@digitalbazaar/cborld": "^8.0.0", + "@rollup/plugin-commonjs": "^28.0.3", "@rollup/plugin-node-resolve": "^16.0.1", "codemirror": "^6.0.1", "petite-vue": "^0.4.1", diff --git a/playground/next/editor.mjs b/playground/next/editor.mjs index 6c75e61ff..d38737785 100644 --- a/playground/next/editor.mjs +++ b/playground/next/editor.mjs @@ -12,6 +12,7 @@ import {keymap} from '@codemirror/view'; import {linter} from '@codemirror/lint'; import YAML from 'yaml'; import {yaml} from '@codemirror/lang-yaml'; +import * as cborld from '@digitalbazaar/cborld'; // Setup JSON-LD documentLoader const xhrDocumentLoader = jsonld.documentLoaders.xhr(); @@ -146,6 +147,13 @@ window.app = createApp({ frameDoc: {}, tableQuads: {}, yamlLD: '', + cborLD: { + bytes: {}, + hex: '', + jsonldSize: 0, + size: 0, + percentage: 0 + }, remoteDocURL: '', remoteSideDocURL: '', parseError: '', @@ -313,6 +321,28 @@ window.app = createApp({ this.yamlLD = YAML.stringify(this.doc); setEditorValue(readOnlyEditor, this.yamlLD, 'yaml'); break; + case 'cborld': + try { + this.cborLD.jsonldSize = JSON.stringify(this.doc).length; + this.cborLD.bytes = await cborld.encode({ + jsonldDocument: this.doc, + documentLoader: jsonld.documentLoader, + // use standard compression (set to `0` to use no compression) + registryEntryId: 1 + }); + this.cborLD.size = this.cborLD.bytes.length; + this.cborLD.hex = Array.from(this.cborLD.bytes, byte => + byte.toString(16).padStart(2, '0')).join(''); + this.cborLD.percentage = + Math.floor(((this.cborLD.jsonldSize - this.cborLD.size) / this.cborLD.jsonldSize) * 100); + setEditorValue(readOnlyEditor, this.cborLD.bytes, 'cbor'); + this.parseError = ''; + } catch (err) { + // TODO: currently, the editor keeps it's old value...unupdated... + this.parseError = err.message; + console.error(err); + } + break; default: setEditorValue(readOnlyEditor, {}); } diff --git a/playground/next/index.html b/playground/next/index.html index cba632483..f78f09ea4 100644 --- a/playground/next/index.html +++ b/playground/next/index.html @@ -275,48 +275,69 @@

JSON-LD Playground

Canonized
Table
YAML-LD
+
CBOR-LD
-
-
Subject
- - - - - - - - - - - - - - - - - - -
SubjectPredicateObjectLanguageDatatypeGraph
- - - - - - - - - - -
+
+
+
+ + + + + + + + + + + + + + + + + + + +
SubjectPredicateObjectLanguageDatatypeGraph
+ + + + + + + + + + +
+
+
+ + + + + + + + + + + + + +
JSON-LD Size bytes
CBOR-LD Size bytes
Compression%
Hex
+
+
diff --git a/rollup.config.mjs b/rollup.config.mjs index d4a282cc4..76afbfad9 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -1,3 +1,4 @@ +import commonjs from "@rollup/plugin-commonjs" import {nodeResolve} from "@rollup/plugin-node-resolve" export default { input: "./playground/next/editor.mjs", @@ -5,5 +6,8 @@ export default { file: "./playground/next/editor.bundle.js", format: "iife" }, - plugins: [nodeResolve()] + plugins: [ + nodeResolve({browser: true}), + commonjs() + ] } From e17f1baff2b98e8d99746637ec7188e42616e2a7 Mon Sep 17 00:00:00 2001 From: Benjamin Young Date: Thu, 22 May 2025 10:13:11 -0400 Subject: [PATCH 47/54] Add CBOR2 diagnostics in place of bytes list. Much more useful, generally. --- package.json | 1 + playground/next/editor.bundle.js | 5181 +++++++++++++++++++++++++++--- playground/next/editor.mjs | 5 +- playground/next/index.html | 4 +- 4 files changed, 4698 insertions(+), 493 deletions(-) diff --git a/package.json b/package.json index 1a06c2061..3d6657c0e 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "@digitalbazaar/cborld": "^8.0.0", "@rollup/plugin-commonjs": "^28.0.3", "@rollup/plugin-node-resolve": "^16.0.1", + "cbor2": "^2.0.1", "codemirror": "^6.0.1", "petite-vue": "^0.4.1", "rollup": "^4.40.1", diff --git a/playground/next/editor.bundle.js b/playground/next/editor.bundle.js index a281847e9..6a552ccda 100644 --- a/playground/next/editor.bundle.js +++ b/playground/next/editor.bundle.js @@ -165,7 +165,7 @@ Return a cursor that iterates over the given range of lines, _without_ returning the line breaks between, and yielding empty strings for empty lines. - + When `from` and `to` are given, they should be 1-based line numbers. */ iterLines(from, to) { @@ -615,16 +615,16 @@ /** The position of the start of the line. */ - from, + from, /** The position at the end of the line (_before_ the line break, or at the end of document for the last line). */ - to, + to, /** This line's line number (1-based). */ - number, + number, /** The line's content. */ @@ -780,7 +780,7 @@ `fromA`/`toA` provides the extent of the change in the starting document, `fromB`/`toB` the extent of the replacement in the changed document. - + When `individual` is true, adjacent changes (which are kept separate for [position mapping](https://codemirror.net/6/docs/ref/#state.ChangeDesc.mapPos)) are reported separately. @@ -889,7 +889,7 @@ with exactly that length. */ class ChangeSet extends ChangeDesc { - constructor(sections, + constructor(sections, /** @internal */ @@ -943,7 +943,7 @@ applied to the document produced by applying `other`. When `before` is `true`, order changes as if `this` comes before `other`, otherwise (the default) treat `other` as coming first. - + Given two changes `A` and `B`, `A.compose(B.map(A))` and `B.compose(A.map(B, true))` will produce the same document. This provides a basic form of [operational @@ -956,7 +956,7 @@ each, with the range in the original document (`fromA`-`toA`) and the range that replaces it in the new document (`fromB`-`toB`). - + When `individual` is true, adjacent changes are reported separately. */ @@ -1340,7 +1340,7 @@ /** The lower boundary of the range. */ - from, + from, /** The upper boundary of the range. */ @@ -1448,7 +1448,7 @@ The ranges in the selection, sorted by position. Ranges cannot overlap (but they may touch, if they aren't empty). */ - ranges, + ranges, /** The index of the _main_ range in the selection (which is usually the range that was added last). @@ -1607,11 +1607,11 @@ /** @internal */ - combine, + combine, /** @internal */ - compareInput, + compareInput, /** @internal */ @@ -1649,7 +1649,7 @@ state. You must take care to declare the parts of the state that this value depends on, since your function is only called again for a new state when one of those parts changed. - + In cases where your value depends only on a single field, you'll want to use the [`from`](https://codemirror.net/6/docs/ref/#state.Facet.from) method instead. */ @@ -1809,7 +1809,7 @@ /** @internal */ - id, createF, updateF, compareF, + id, createF, updateF, compareF, /** @internal */ @@ -2130,7 +2130,7 @@ /** The annotation type. */ - type, + type, /** The value of this annotation. */ @@ -2192,7 +2192,7 @@ /** @internal */ - type, + type, /** The value of this effect. */ @@ -2264,24 +2264,24 @@ /** The state from which the transaction starts. */ - startState, + startState, /** The document changes made by this transaction. */ - changes, + changes, /** The selection set by this transaction, or undefined if it doesn't explicitly set a selection. */ - selection, + selection, /** The effects added to the transaction. */ - effects, + effects, /** @internal */ - annotations, + annotations, /** Whether the selection should be scrolled into view after this transaction is dispatched. @@ -2602,15 +2602,15 @@ /** @internal */ - config, + config, /** The current document. */ - doc, + doc, /** The current selection. */ - selection, + selection, /** @internal */ @@ -2846,7 +2846,7 @@ Look up a translation for the given phrase (via the [`phrases`](https://codemirror.net/6/docs/ref/#state.EditorState^phrases) facet), or return the original string if no translation is found. - + If additional arguments are passed, they will be inserted in place of markers like `$1` (for the first value) and `$2`, etc. A single `$` is equivalent to `$1`, and `$$` will produce a @@ -2870,9 +2870,9 @@ /** Find the values for a given language data field, provided by the the [`languageData`](https://codemirror.net/6/docs/ref/#state.EditorState^languageData) facet. - + Examples of language data fields are... - + - [`"commentTokens"`](https://codemirror.net/6/docs/ref/#commands.CommentTokens) for specifying comment syntax. - [`"autocomplete"`](https://codemirror.net/6/docs/ref/#autocomplete.autocompletion^config.override) @@ -2897,7 +2897,7 @@ Return a function that can categorize strings (expected to represent a single [grapheme cluster](https://codemirror.net/6/docs/ref/#state.findClusterBreak)) into one of: - + - Word (contains an alphanumeric character or a character explicitly listed in the local language's `"wordChars"` language data, which should be a string) @@ -3099,11 +3099,11 @@ /** The range's start position. */ - from, + from, /** Its end position. */ - to, + to, /** The value associated with this range. */ @@ -3123,7 +3123,7 @@ return a.from - b.from || a.value.startSide - b.value.startSide; } class Chunk { - constructor(from, to, value, + constructor(from, to, value, // Chunks are marked with the largest point that occurs // in them (or -1 for no points), so that scans that are // only interested in points (such as the @@ -3202,15 +3202,15 @@ /** @internal */ - chunkPos, + chunkPos, /** @internal */ - chunk, + chunk, /** @internal */ - nextLayer, + nextLayer, /** @internal */ @@ -3253,7 +3253,7 @@ /** Update the range set, optionally adding new ranges or filtering out existing ones. - + (Note: The type parameter is just there as a kludge to work around TypeScript variance issues that prevented `RangeSet` from being a subtype of `RangeSet` when `X` is a subtype of @@ -3359,12 +3359,12 @@ Iterate over two groups of sets, calling methods on `comparator` to notify it of possible differences. */ - static compare(oldSets, newSets, + static compare(oldSets, newSets, /** This indicates how the underlying data changed between these ranges, and is needed to synchronize the iteration. */ - textDiff, comparator, + textDiff, comparator, /** Can be used to ignore all non-point ranges, and points below the given size. When -1, all ranges are compared. @@ -3375,9 +3375,9 @@ let sharedChunks = findSharedChunks(a, b, textDiff); let sideA = new SpanCursor(a, sharedChunks, minPointSize); let sideB = new SpanCursor(b, sharedChunks, minPointSize); - textDiff.iterGaps((fromA, fromB, length) => compare(sideA, fromA, sideB, fromB, length, comparator)); + textDiff.iterGaps((fromA, fromB, length) => compare$1(sideA, fromA, sideB, fromB, length, comparator)); if (textDiff.empty && textDiff.length == 0) - compare(sideA, 0, sideB, 0, 0, comparator); + compare$1(sideA, 0, sideB, 0, 0, comparator); } /** Compare the contents of two groups of range sets, returning true @@ -3412,7 +3412,7 @@ [`SpanIterator.span`](https://codemirror.net/6/docs/ref/#state.SpanIterator.span)) at the end of the iteration. */ - static spans(sets, from, to, iterator, + static spans(sets, from, to, iterator, /** When given and greater than -1, only points of at least this size are taken into account. @@ -3867,7 +3867,7 @@ return open; } } - function compare(a, startA, b, startB, length, comparator) { + function compare$1(a, startA, b, startB, length, comparator) { a.goto(startA); b.goto(startB); let endB = startB + length; @@ -4219,15 +4219,15 @@ var ie$2 = typeof navigator != "undefined" && /MSIE \d|Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(navigator.userAgent); // Fill in the digit keys - for (var i = 0; i < 10; i++) base[48 + i] = base[96 + i] = String(i); + for (var i$2 = 0; i$2 < 10; i$2++) base[48 + i$2] = base[96 + i$2] = String(i$2); // The function keys - for (var i = 1; i <= 24; i++) base[i + 111] = "F" + i; + for (var i$2 = 1; i$2 <= 24; i$2++) base[i$2 + 111] = "F" + i$2; // And the alphabetic keys - for (var i = 65; i <= 90; i++) { - base[i] = String.fromCharCode(i + 32); - shift[i] = String.fromCharCode(i); + for (var i$2 = 65; i$2 <= 90; i$2++) { + base[i$2] = String.fromCharCode(i$2 + 32); + shift[i$2] = String.fromCharCode(i$2); } // For each code that doesn't have a shift-equivalent, copy the base name @@ -5517,15 +5517,15 @@ /** @internal */ - startSide, + startSide, /** @internal */ - endSide, + endSide, /** @internal */ - widget, + widget, /** The config object used to create this decoration. You can include additional properties in there to store metadata about @@ -6187,11 +6187,11 @@ /** The start of the span (relative to the start of the line). */ - from, + from, /** The end of the span. */ - to, + to, /** The ["bidi level"](https://unicode.org/reports/tr9/#Basic_Display_Algorithm) @@ -6626,7 +6626,7 @@ }); const scrollHandler = /*@__PURE__*/Facet.define(); class ScrollTarget { - constructor(range, y = "nearest", x = "nearest", yMargin = 5, xMargin = 5, + constructor(range, y = "nearest", x = "nearest", yMargin = 5, xMargin = 5, // This data structure is abused to also store precise scroll // snapshots, instead of a `scrollIntoView` request. When this // flag is `true`, `range` points at a position in the reference @@ -6688,15 +6688,15 @@ /** @internal */ - id, + id, /** @internal */ - create, + create, /** @internal */ - domEventHandlers, + domEventHandlers, /** @internal */ @@ -6906,11 +6906,11 @@ /** The editor view that the update is associated with. */ - view, + view, /** The new editor state. */ - state, + state, /** The transactions involved in the update. May be empty. */ @@ -9376,20 +9376,20 @@ /** The start of the element in the document. */ - from, + from, /** The length of the element. */ - length, + length, /** The top position of the element (relative to the top of the document). */ - top, + top, /** Its height. */ - height, + height, /** @internal Weird packed field that holds an array of children for composite blocks, a decoration for block widgets, and a @@ -12256,7 +12256,7 @@ When the start position was the last one on the line, the returned position will be across the line break. If there is no further line, the original position is returned. - + By default, this method moves over a single cluster. The optional `by` argument can be used to move across more. It will be called with the first cluster as argument, and should return @@ -12300,7 +12300,7 @@ it defaults to moving to the next line (including wrapped lines). Otherwise, `distance` should provide a positive distance in pixels. - + When `start` has a [`goalColumn`](https://codemirror.net/6/docs/ref/#state.SelectionRange.goalColumn), the vertical motion will use that as a target horizontal position. Otherwise, @@ -12315,7 +12315,7 @@ Find the DOM parent node and offset (child offset if `node` is an element, character offset when it is a text node) at the given document position. - + Note that for positions that aren't currently in `visibleRanges`, the resulting DOM position isn't necessarily meaningful (it may just point before or after a placeholder @@ -12492,7 +12492,7 @@ only affects the editor's own scrollable element, not parents. See also [`EditorViewConfig.scrollTo`](https://codemirror.net/6/docs/ref/#view.EditorViewConfig.scrollTo). - + The effect should be used with a document identical to the one it was created for. Failing to do so is not an error, but may not scroll to the expected position. You can @@ -12508,7 +12508,7 @@ for Tab and Shift-Tab, letting the browser's default focus-changing behavior go through instead. This is useful to prevent trapping keyboard users in your editor. - + Without argument, this toggles the mode. With a boolean, it enables (true) or disables it (false). Given a number, it temporarily enables the mode until that number of milliseconds @@ -12553,14 +12553,14 @@ [`style-mod`](https://github.com/marijnh/style-mod#documentation) style spec providing the styles for the theme. These will be prefixed with a generated class for the style. - + Because the selectors will be prefixed with a scope class, rule that directly match the editor's [wrapper element](https://codemirror.net/6/docs/ref/#view.EditorView.dom)—to which the scope class will be added—need to be explicitly differentiated by adding an `&` to the selector for that element—for example `&.cm-focused`. - + When `dark` is set to true, the theme will be marked as dark, which will cause the `&dark` rules from [base themes](https://codemirror.net/6/docs/ref/#view.EditorView^baseTheme) to be used (as opposed to @@ -13032,19 +13032,19 @@ Create a marker with the given class and dimensions. If `width` is null, the DOM element will get no width style. */ - constructor(className, + constructor(className, /** The left position of the marker (in pixels, document-relative). */ - left, + left, /** The top position of the marker. */ - top, + top, /** The width of the marker, or null if it shouldn't get a width assigned. */ - width, + width, /** The height of the marker. */ @@ -15374,7 +15374,7 @@ /** The inner tree. */ - tree, + tree, /** If this is null, this tree replaces the entire node (it will be included in the regular iteration instead of its host @@ -15384,7 +15384,7 @@ only entered by [`resolveInner`](#common.Tree.resolveInner) and [`enter`](#common.SyntaxNode.enter). */ - overlay, + overlay, /** The parser used to create this subtree. */ @@ -15415,16 +15415,16 @@ same name within a node set should play the same semantic role. */ - name, + name, /** @internal */ - props, + props, /** The id of this node in its set. Corresponds to the term ids used in the parser. */ - id, + id, /** @internal */ @@ -15618,20 +15618,20 @@ /** The type of the top node. */ - type, + type, /** This node's child nodes. */ - children, + children, /** The positions (offsets relative to the start of this tree) of the children. */ - positions, + positions, /** The total length of this tree */ - length, + length, /** Per-node [node props](#common.NodeProp) to associate with this node. */ @@ -15703,7 +15703,7 @@ position. If 1, it'll move into nodes that start at the position. With 0, it'll only enter nodes that cover the position from both sides. - + Note that this will not enter [overlays](#common.MountedTree.overlay), and you often want [`resolveInner`](#common.Tree.resolveInner) instead. @@ -15828,11 +15828,11 @@ /** The buffer's content. */ - buffer, + buffer, /** The total length of the group of nodes in the buffer. */ - length, + length, /** The node set used in this buffer. */ @@ -15977,7 +15977,7 @@ get next() { return this.parent; } } class TreeNode extends BaseNode { - constructor(_tree, from, + constructor(_tree, from, // Index in parent node, set to -1 if the node is not a direct child of _parent.node (overlay) index, _parent) { super(); @@ -16220,7 +16220,7 @@ /** @internal */ - constructor(node, + constructor(node, /** @internal */ @@ -16770,17 +16770,17 @@ } function balanceRange( // The type the balanced tree's inner nodes. - balanceType, + balanceType, // The direct children and their positions - children, positions, + children, positions, // The index range in children/positions to use - from, to, + from, to, // The start position of the nodes, relative to their parent. - start, + start, // Length of the outer node - length, + length, // Function to build the top node of the balanced tree - mkTop, + mkTop, // Function to build internal nodes for the balanced tree mkTree) { let total = 0; @@ -16839,15 +16839,15 @@ This refers to an offset in the _updated_ document (as opposed to the original tree). */ - from, + from, /** The end of the unchanged range. */ - to, + to, /** The tree that this fragment is based on. */ - tree, + tree, /** The offset between the fragment's tree and the document that this fragment can be used against. Add this when going from @@ -16930,7 +16930,7 @@ Start a parse, returning a [partial parse](#common.PartialParse) object. [`fragments`](#common.TreeFragment) can be passed in to make the parse incremental. - + By default, the entire input is parsed. You can pass `ranges`, which should be a sorted array of non-empty, non-overlapping ranges, to parse only those ranges. The tree returned in that @@ -16993,17 +16993,17 @@ /** The optional name of the base tag @internal */ - name, + name, /** The set of this tag and all its parent tags, starting with this one itself and sorted in order of decreasing specificity. */ - set, + set, /** The base unmodified tag that this one is based on, if it's modified @internal */ - base, + base, /** The modifiers applied to this.base @internal */ @@ -17043,7 +17043,7 @@ same modifier to a twice tag will return the same value (`m1(t1) == m1(t1)`) and applying multiple modifiers will, regardless or order, produce the same tag (`m1(m2(t1)) == m2(m1(t1))`). - + When multiple modifiers are applied to a given base tag, each smaller set of modifiers is registered as a parent, so that for example `m1(m2(m3(t1)))` is a subtype of `m1(m2(t1))`, @@ -17252,17 +17252,17 @@ [`highlightCode`](#highlight.highlightCode) function is easier to use. */ - function highlightTree(tree, highlighter, + function highlightTree(tree, highlighter, /** Assign styling to a region of the text. Will be called, in order of position, for any ranges where more than zero classes apply. `classes` is a space separated string of CSS classes. */ - putStyle, + putStyle, /** The start of the range to highlight. */ - from = 0, + from = 0, /** The end of the range. */ @@ -17363,8 +17363,8 @@ rule = rule.next; return rule || null; } - const t = Tag$1.define; - const comment = t(), name = t(), typeName = t(name), propertyName = t(name), literal = t(), string$1 = t(literal), number = t(literal), content = t(), heading = t(content), keyword = t(), operator = t(), punctuation = t(), bracket = t(punctuation), meta = t(); + const t$1 = Tag$1.define; + const comment = t$1(), name = t$1(), typeName = t$1(name), propertyName = t$1(name), literal = t$1(), string$1 = t$1(literal), number = t$1(literal), content = t$1(), heading = t$1(content), keyword = t$1(), operator = t$1(), punctuation = t$1(), bracket = t$1(punctuation), meta = t$1(); /** The default set of highlighting [tags](#highlight.Tag). @@ -17393,15 +17393,15 @@ /** A line [comment](#highlight.tags.comment). */ - lineComment: t(comment), + lineComment: t$1(comment), /** A block [comment](#highlight.tags.comment). */ - blockComment: t(comment), + blockComment: t$1(comment), /** A documentation [comment](#highlight.tags.comment). */ - docComment: t(comment), + docComment: t$1(comment), /** Any kind of identifier. */ @@ -17409,7 +17409,7 @@ /** The [name](#highlight.tags.name) of a variable. */ - variableName: t(name), + variableName: t$1(name), /** A type [name](#highlight.tags.name). */ @@ -17417,7 +17417,7 @@ /** A tag name (subtag of [`typeName`](#highlight.tags.typeName)). */ - tagName: t(typeName), + tagName: t$1(typeName), /** A property or field [name](#highlight.tags.name). */ @@ -17425,23 +17425,23 @@ /** An attribute name (subtag of [`propertyName`](#highlight.tags.propertyName)). */ - attributeName: t(propertyName), + attributeName: t$1(propertyName), /** The [name](#highlight.tags.name) of a class. */ - className: t(name), + className: t$1(name), /** A label [name](#highlight.tags.name). */ - labelName: t(name), + labelName: t$1(name), /** A namespace [name](#highlight.tags.name). */ - namespace: t(name), + namespace: t$1(name), /** The [name](#highlight.tags.name) of a macro. */ - macroName: t(name), + macroName: t$1(name), /** A literal value. */ @@ -17453,15 +17453,15 @@ /** A documentation [string](#highlight.tags.string). */ - docString: t(string$1), + docString: t$1(string$1), /** A character literal (subtag of [string](#highlight.tags.string)). */ - character: t(string$1), + character: t$1(string$1), /** An attribute value (subtag of [string](#highlight.tags.string)). */ - attributeValue: t(string$1), + attributeValue: t$1(string$1), /** A number [literal](#highlight.tags.literal). */ @@ -17469,32 +17469,32 @@ /** An integer [number](#highlight.tags.number) literal. */ - integer: t(number), + integer: t$1(number), /** A floating-point [number](#highlight.tags.number) literal. */ - float: t(number), + float: t$1(number), /** A boolean [literal](#highlight.tags.literal). */ - bool: t(literal), + bool: t$1(literal), /** Regular expression [literal](#highlight.tags.literal). */ - regexp: t(literal), + regexp: t$1(literal), /** An escape [literal](#highlight.tags.literal), for example a backslash escape in a string. */ - escape: t(literal), + escape: t$1(literal), /** A color [literal](#highlight.tags.literal). */ - color: t(literal), + color: t$1(literal), /** A URL [literal](#highlight.tags.literal). */ - url: t(literal), + url: t$1(literal), /** A language keyword. */ @@ -17503,40 +17503,40 @@ The [keyword](#highlight.tags.keyword) for the self or this object. */ - self: t(keyword), + self: t$1(keyword), /** The [keyword](#highlight.tags.keyword) for null. */ - null: t(keyword), + null: t$1(keyword), /** A [keyword](#highlight.tags.keyword) denoting some atomic value. */ - atom: t(keyword), + atom: t$1(keyword), /** A [keyword](#highlight.tags.keyword) that represents a unit. */ - unit: t(keyword), + unit: t$1(keyword), /** A modifier [keyword](#highlight.tags.keyword). */ - modifier: t(keyword), + modifier: t$1(keyword), /** A [keyword](#highlight.tags.keyword) that acts as an operator. */ - operatorKeyword: t(keyword), + operatorKeyword: t$1(keyword), /** A control-flow related [keyword](#highlight.tags.keyword). */ - controlKeyword: t(keyword), + controlKeyword: t$1(keyword), /** A [keyword](#highlight.tags.keyword) that defines something. */ - definitionKeyword: t(keyword), + definitionKeyword: t$1(keyword), /** A [keyword](#highlight.tags.keyword) related to defining or interfacing with modules. */ - moduleKeyword: t(keyword), + moduleKeyword: t$1(keyword), /** An operator. */ @@ -17544,39 +17544,39 @@ /** An [operator](#highlight.tags.operator) that dereferences something. */ - derefOperator: t(operator), + derefOperator: t$1(operator), /** Arithmetic-related [operator](#highlight.tags.operator). */ - arithmeticOperator: t(operator), + arithmeticOperator: t$1(operator), /** Logical [operator](#highlight.tags.operator). */ - logicOperator: t(operator), + logicOperator: t$1(operator), /** Bit [operator](#highlight.tags.operator). */ - bitwiseOperator: t(operator), + bitwiseOperator: t$1(operator), /** Comparison [operator](#highlight.tags.operator). */ - compareOperator: t(operator), + compareOperator: t$1(operator), /** [Operator](#highlight.tags.operator) that updates its operand. */ - updateOperator: t(operator), + updateOperator: t$1(operator), /** [Operator](#highlight.tags.operator) that defines something. */ - definitionOperator: t(operator), + definitionOperator: t$1(operator), /** Type-related [operator](#highlight.tags.operator). */ - typeOperator: t(operator), + typeOperator: t$1(operator), /** Control-flow [operator](#highlight.tags.operator). */ - controlOperator: t(operator), + controlOperator: t$1(operator), /** Program or markup punctuation. */ @@ -17585,7 +17585,7 @@ [Punctuation](#highlight.tags.punctuation) that separates things. */ - separator: t(punctuation), + separator: t$1(punctuation), /** Bracket-style [punctuation](#highlight.tags.punctuation). */ @@ -17594,22 +17594,22 @@ Angle [brackets](#highlight.tags.bracket) (usually `<` and `>` tokens). */ - angleBracket: t(bracket), + angleBracket: t$1(bracket), /** Square [brackets](#highlight.tags.bracket) (usually `[` and `]` tokens). */ - squareBracket: t(bracket), + squareBracket: t$1(bracket), /** Parentheses (usually `(` and `)` tokens). Subtag of [bracket](#highlight.tags.bracket). */ - paren: t(bracket), + paren: t$1(bracket), /** Braces (usually `{` and `}` tokens). Subtag of [bracket](#highlight.tags.bracket). */ - brace: t(bracket), + brace: t$1(bracket), /** Content, for example plain text in XML or markup documents. */ @@ -17621,77 +17621,77 @@ /** A level 1 [heading](#highlight.tags.heading). */ - heading1: t(heading), + heading1: t$1(heading), /** A level 2 [heading](#highlight.tags.heading). */ - heading2: t(heading), + heading2: t$1(heading), /** A level 3 [heading](#highlight.tags.heading). */ - heading3: t(heading), + heading3: t$1(heading), /** A level 4 [heading](#highlight.tags.heading). */ - heading4: t(heading), + heading4: t$1(heading), /** A level 5 [heading](#highlight.tags.heading). */ - heading5: t(heading), + heading5: t$1(heading), /** A level 6 [heading](#highlight.tags.heading). */ - heading6: t(heading), + heading6: t$1(heading), /** A prose [content](#highlight.tags.content) separator (such as a horizontal rule). */ - contentSeparator: t(content), + contentSeparator: t$1(content), /** [Content](#highlight.tags.content) that represents a list. */ - list: t(content), + list: t$1(content), /** [Content](#highlight.tags.content) that represents a quote. */ - quote: t(content), + quote: t$1(content), /** [Content](#highlight.tags.content) that is emphasized. */ - emphasis: t(content), + emphasis: t$1(content), /** [Content](#highlight.tags.content) that is styled strong. */ - strong: t(content), + strong: t$1(content), /** [Content](#highlight.tags.content) that is part of a link. */ - link: t(content), + link: t$1(content), /** [Content](#highlight.tags.content) that is styled as code or monospace. */ - monospace: t(content), + monospace: t$1(content), /** [Content](#highlight.tags.content) that has a strike-through style. */ - strikethrough: t(content), + strikethrough: t$1(content), /** Inserted text in a change-tracking format. */ - inserted: t(), + inserted: t$1(), /** Deleted text. */ - deleted: t(), + deleted: t$1(), /** Changed text. */ - changed: t(), + changed: t$1(), /** An invalid or unsyntactic element. */ - invalid: t(), + invalid: t$1(), /** Metadata or meta-instruction. */ @@ -17700,17 +17700,17 @@ [Metadata](#highlight.tags.meta) that applies to the entire document. */ - documentMeta: t(meta), + documentMeta: t$1(meta), /** [Metadata](#highlight.tags.meta) that annotates or adds attributes to a given syntactic element. */ - annotation: t(meta), + annotation: t$1(meta), /** Processing instruction or preprocessor directive. Subtag of [meta](#highlight.tags.meta). */ - processingInstruction: t(meta), + processingInstruction: t$1(meta), /** [Modifier](#highlight.Tag^defineModifier) that indicates that a given element is being defined. Expected to be used with the @@ -17883,7 +17883,7 @@ The [language data](https://codemirror.net/6/docs/ref/#state.EditorState.languageDataAt) facet used for this language. */ - data, parser, extraExtensions = [], + data, parser, extraExtensions = [], /** A language name. */ @@ -18060,23 +18060,23 @@ A parse context provided to parsers working on the editor content. */ class ParseContext { - constructor(parser, + constructor(parser, /** The current editor state. */ - state, + state, /** Tree fragments that can be reused by incremental re-parses. */ - fragments = [], + fragments = [], /** @internal */ - tree, + tree, /** @internal */ - treeLen, + treeLen, /** The current editor viewport (or some overapproximation thereof). Intended to be used for opportunistically avoiding @@ -18085,11 +18085,11 @@ should be called to make sure the parser is restarted when the skipped region becomes visible). */ - viewport, + viewport, /** @internal */ - skipped, + skipped, /** This is where skipping parsers can register a promise that, when resolved, will schedule a new parse. It is cleared when @@ -18253,7 +18253,7 @@ asynchronously loading a nested parser. It'll skip its input and mark it as not-really-parsed, so that the next update will parse it again. - + When `until` is given, a reparse will be scheduled when that promise resolves. */ @@ -18459,7 +18459,7 @@ /** The language object. */ - language, + language, /** An optional set of supporting extensions. When nesting a language in another language, the outer language is encouraged @@ -18561,7 +18561,7 @@ /** The editor state. */ - state, + state, /** @internal */ @@ -18690,11 +18690,11 @@ methods to indentation functions registered on syntax nodes. */ class TreeIndentContext extends IndentContext { - constructor(base, + constructor(base, /** The position at which indentation is being computed. */ - pos, + pos, /** @internal */ @@ -19295,7 +19295,7 @@ that rely on external styling), or a [`style-mod`](https://github.com/marijnh/style-mod#documentation)-style set of CSS properties (which define the styling for those tags). - + The CSS rules created for a highlighter will be emitted in the order of the spec's properties. That means that for elements that have multiple tags associated with them, styles defined further @@ -19610,7 +19610,7 @@ /** The line. */ - string, tabSize, + string, tabSize, /** The current indent unit size. */ @@ -19726,10 +19726,10 @@ Match the input against the given string or regular expression (which should start with a `^`). Return true or the regexp match if it matches. - + Unless `consume` is set to `false`, this will move `this.pos` past the matched text. - + When matching a string `caseInsensitive` can be set to true to make the match case-insensitive. */ @@ -20152,18 +20152,18 @@ /** The editor state that the completion happens in. */ - state, + state, /** The position at which the completion is happening. */ - pos, + pos, /** Indicates whether completion was activated explicitly, or implicitly by typing. The usual way to respond to this is to only return completions when either there is part of a completable entity before the cursor, or `explicit` is true. */ - explicit, + explicit, /** The editor view. May be undefined if the context was created in a situation where there is no such view available, such as @@ -20217,7 +20217,7 @@ Allows you to register abort handlers, which will be called when the query is [aborted](https://codemirror.net/6/docs/ref/#autocomplete.CompletionContext.aborted). - + By default, running queries will not be aborted for regular typing or backspacing, on the assumption that they are likely to return a result with a @@ -22079,14 +22079,14 @@ // events before the first change, in which case a special type of // instance is created which doesn't hold any changes, with // changes == startSelection == undefined - changes, + changes, // The effects associated with this event - effects, + effects, // Accumulated mapping (from addToHistory==false) that should be // applied to events below this one. - mapped, + mapped, // The selection before this event - startSelection, + startSelection, // Stores selection changes after this event, to be used for // selection undo/redo. selectionsAfter) { @@ -23260,12 +23260,12 @@ /** Create a text cursor. The query is the search string, `from` to `to` provides the region to search. - + When `normalize` is given, it will be called, on both the query string and the content it is matched against, before comparing. You can, for example, create a case-insensitive search by passing `s => s.toLowerCase()`. - + Text is always normalized with [`.normalize("NFKD")`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/normalize) (when supported). @@ -25204,7 +25204,7 @@ ]) ])(); - var tt=Object.defineProperty;var nt=(e,t,n)=>t in e?tt(e,t,{enumerable:true,configurable:true,writable:true,value:n}):e[t]=n;var $=(e,t,n)=>(nt(e,typeof t!="symbol"?t+"":t,n),n);function st(e,t){const n=Object.create(null),s=e.split(",");for(let r=0;r!!n[r]}function de(e){if(y(e)){const t={};for(let n=0;n{if(n){const s=n.split(ot);s.length>1&&(t[s[0].trim()]=s[1].trim());}}),t}function me(e){let t="";if(N(e))t=e;else if(y(e))for(let n=0;nI(n,t))}const lt=Object.assign,ft=(e,t)=>{const n=e.indexOf(t);n>-1&&e.splice(n,1);},at=Object.prototype.hasOwnProperty,U=(e,t)=>at.call(e,t),y=Array.isArray,Y=e=>ye(e)==="[object Map]",ge=e=>e instanceof Date,N=e=>typeof e=="string",Q=e=>typeof e=="symbol",S=e=>e!==null&&typeof e=="object",ut=Object.prototype.toString,ye=e=>ut.call(e),pt=e=>ye(e).slice(8,-1),X=e=>N(e)&&e!=="NaN"&&e[0]!=="-"&&""+parseInt(e,10)===e,be=e=>{const t=Object.create(null);return n=>t[n]||(t[n]=e(n))},ht=/-(\w)/g,dt=be(e=>e.replace(ht,(t,n)=>n?n.toUpperCase():"")),mt=/\B([A-Z])/g,xe=be(e=>e.replace(mt,"-$1").toLowerCase()),gt=(e,t)=>!Object.is(e,t),ve=e=>{const t=parseFloat(e);return isNaN(t)?e:t};let yt;function we(e,t){t=t||yt,t&&t.active&&t.effects.push(e);}const _e=e=>{const t=new Set(e);return t.w=0,t.n=0,t},Ee=e=>(e.w&O)>0,$e=e=>(e.n&O)>0,bt=({deps:e})=>{if(e.length)for(let t=0;t{const{deps:t}=e;if(t.length){let n=0;for(let s=0;s0?z[t-1]:void 0;}}stop(){this.active&&(Oe(this),this.onStop&&this.onStop(),this.active=false);}}function Oe(e){const{deps:t}=e;if(t.length){for(let n=0;n{(f==="length"||f>=s)&&o.push(l);});else switch(n!==void 0&&o.push(c.get(n)),t){case "add":y(e)?X(n)&&o.push(c.get("length")):(o.push(c.get(W)),Y(e)&&o.push(c.get(Se)));break;case "delete":y(e)||(o.push(c.get(W)),Y(e)&&o.push(c.get(Se)));break;case "set":Y(e)&&o.push(c.get(W));break}if(o.length===1)o[0]&&Te(o[0]);else {const l=[];for(const f of o)f&&l.push(...f);Te(_e(l));}}function Te(e,t){for(const n of y(e)?e:[...e])(n!==C||n.allowRecurse)&&(n.scheduler?n.scheduler():n.run());}const kt=st("__proto__,__v_isRef,__isVue"),Ae=new Set(Object.getOwnPropertyNames(Symbol).map(e=>Symbol[e]).filter(Q)),Tt=Me(),At=Me(true),Re=Rt();function Rt(){const e={};return ["includes","indexOf","lastIndexOf"].forEach(t=>{e[t]=function(...n){const s=j(this);for(let i=0,c=this.length;i{e[t]=function(...n){Et();const s=j(this)[t].apply(this,n);return ke(),s};}),e}function Me(e=false,t=false){return function(s,r,i){if(r==="__v_isReactive")return !e;if(r==="__v_isReadonly")return e;if(r==="__v_raw"&&i===(e?t?zt:je:t?Bt:Ce).get(s))return s;const c=y(s);if(!e&&c&&U(Re,r))return Reflect.get(Re,r,i);const o=Reflect.get(s,r,i);return (Q(r)?Ae.has(r):kt(r))||(e||F(s,"get",r),t)?o:re(o)?!c||!X(r)?o.value:o:S(o)?e?Ht(o):D(o):o}}const Mt=Ct();function Ct(e=false){return function(n,s,r,i){let c=n[s];if(!e&&!Lt(r)&&(r=j(r),c=j(c),!y(n)&&re(c)&&!re(r)))return c.value=r,true;const o=y(n)&&X(s)?Number(s)Wt.then(e),Ie=e=>{q.includes(e)||q.push(e),oe||(oe=true,V(Ft));},Ft=()=>{for(const e of q)e();q.length=0,oe=false;},qt=/^(spellcheck|draggable|form|list|type)$/,ie=({el:e,get:t,effect:n,arg:s,modifiers:r})=>{let i;s==="class"&&(e._class=e.className),n(()=>{let c=t();if(s)(r==null?void 0:r.camel)&&(s=dt(s)),ce(e,s,c,i);else {for(const o in c)ce(e,o,c[o],i&&i[o]);for(const o in i)(!c||!(o in c))&&ce(e,o,null);}i=c;});},ce=(e,t,n,s)=>{if(t==="class")e.setAttribute("class",me(e._class?[e._class,n]:n)||"");else if(t==="style"){n=de(n);const{style:r}=e;if(!n)e.removeAttribute("style");else if(N(n))n!==s&&(r.cssText=n);else {for(const i in n)le(r,i,n[i]);if(s&&!N(s))for(const i in s)n[i]==null&&le(r,i,"");}}else !(e instanceof SVGElement)&&t in e&&!qt.test(t)?(e[t]=n,t==="value"&&(e._value=n)):t==="true-value"?e._trueValue=n:t==="false-value"?e._falseValue=n:n!=null?e.setAttribute(t,n):e.removeAttribute(t);},Ne=/\s*!important$/,le=(e,t,n)=>{y(n)?n.forEach(s=>le(e,t,s)):t.startsWith("--")?e.setProperty(t,n):Ne.test(n)?e.setProperty(xe(t),n.replace(Ne,""),"important"):e[t]=n;},k=(e,t)=>{const n=e.getAttribute(t);return n!=null&&e.removeAttribute(t),n},T=(e,t,n,s)=>{e.addEventListener(t,n,s);},Jt=/^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\['[^']*?']|\["[^"]*?"]|\[\d+]|\[[A-Za-z_$][\w$]*])*$/,Zt=["ctrl","shift","alt","meta"],Gt={stop:e=>e.stopPropagation(),prevent:e=>e.preventDefault(),self:e=>e.target!==e.currentTarget,ctrl:e=>!e.ctrlKey,shift:e=>!e.shiftKey,alt:e=>!e.altKey,meta:e=>!e.metaKey,left:e=>"button"in e&&e.button!==0,middle:e=>"button"in e&&e.button!==1,right:e=>"button"in e&&e.button!==2,exact:(e,t)=>Zt.some(n=>e[`${n}Key`]&&!t[n])},Ke=({el:e,get:t,exp:n,arg:s,modifiers:r})=>{if(!s)return;let i=Jt.test(n)?t(`(e => ${n}(e))`):t(`($event => { ${n} })`);if(s==="vue:mounted"){V(i);return}else if(s==="vue:unmounted")return ()=>i();if(r){s==="click"&&(r.right&&(s="contextmenu"),r.middle&&(s="mouseup"));const c=i;i=o=>{if(!("key"in o&&!(xe(o.key)in r))){for(const l in r){const f=Gt[l];if(f&&f(o,r))return}return c(o)}};}T(e,s,i,r);},Ut=({el:e,get:t,effect:n})=>{const s=e.style.display;n(()=>{e.style.display=t()?s:"none";});},Be=({el:e,get:t,effect:n})=>{n(()=>{e.textContent=ze(t());});},ze=e=>e==null?"":S(e)?JSON.stringify(e,null,2):String(e),Yt=({el:e,get:t,effect:n})=>{n(()=>{e.innerHTML=t();});},Qt=({el:e,exp:t,get:n,effect:s,modifiers:r})=>{const i=e.type,c=n(`(val) => { ${t} = val }`),{trim:o,number:l=i==="number"}=r||{};if(e.tagName==="SELECT"){const f=e;T(e,"change",()=>{const a=Array.prototype.filter.call(f.options,u=>u.selected).map(u=>l?ve(A(u)):A(u));c(f.multiple?a:a[0]);}),s(()=>{const a=n(),u=f.multiple;for(let p=0,x=f.options.length;p-1:b.selected=a.has(v);else if(I(A(b),a)){f.selectedIndex!==p&&(f.selectedIndex=p);return}}!u&&f.selectedIndex!==-1&&(f.selectedIndex=-1);});}else if(i==="checkbox"){T(e,"change",()=>{const a=n(),u=e.checked;if(y(a)){const p=A(e),x=G(a,p),b=x!==-1;if(u&&!b)c(a.concat(p));else if(!u&&b){const v=[...a];v.splice(x,1),c(v);}}else c(De(e,u));});let f;s(()=>{const a=n();y(a)?e.checked=G(a,A(e))>-1:a!==f&&(e.checked=I(a,De(e,true))),f=a;});}else if(i==="radio"){T(e,"change",()=>{c(A(e));});let f;s(()=>{const a=n();a!==f&&(e.checked=I(a,A(e)));});}else {const f=a=>o?a.trim():l?ve(a):a;T(e,"compositionstart",Xt),T(e,"compositionend",en),T(e,(r==null?void 0:r.lazy)?"change":"input",()=>{e.composing||c(f(e.value));}),o&&T(e,"change",()=>{e.value=e.value.trim();}),s(()=>{if(e.composing)return;const a=e.value,u=n();document.activeElement===e&&f(a)===u||a!==u&&(e.value=u);});}},A=e=>"_value"in e?e._value:e.value,De=(e,t)=>{const n=t?"_trueValue":"_falseValue";return n in e?e[n]:t},Xt=e=>{e.target.composing=true;},en=e=>{const t=e.target;t.composing&&(t.composing=false,tn(t,"input"));},tn=(e,t)=>{const n=document.createEvent("HTMLEvents");n.initEvent(t,true,true),e.dispatchEvent(n);},Ve=Object.create(null),H=(e,t,n)=>He(e,`return(${t})`,n),He=(e,t,n)=>{const s=Ve[t]||(Ve[t]=nn(t));try{return s(e,n)}catch(r){console.error(r);}},nn=e=>{try{return new Function("$data","$el",`with($data){${e}}`)}catch(t){return console.error(`${t.message} in expression: ${e}`),()=>{}}},sn=({el:e,ctx:t,exp:n,effect:s})=>{V(()=>s(()=>He(t.scope,n,e)));},rn={bind:ie,on:Ke,show:Ut,text:Be,html:Yt,model:Qt,effect:sn},on=(e,t,n)=>{const s=e.parentElement,r=new Comment("v-if");s.insertBefore(r,e);const i=[{exp:t,el:e}];let c,o;for(;(c=e.nextElementSibling)&&(o=null,k(c,"v-else")===""||(o=k(c,"v-else-if")));)s.removeChild(c),i.push({exp:o,el:c});const l=e.nextSibling;s.removeChild(e);let f,a=-1;const u=()=>{f&&(s.insertBefore(r,f.el),f.remove(),f=void 0);};return n.effect(()=>{for(let p=0;p{const s=t.match(cn);if(!s)return;const r=e.nextSibling,i=e.parentElement,c=new Text("");i.insertBefore(c,e),i.removeChild(e);const o=s[2].trim();let l=s[1].trim().replace(ln,"").trim(),f,a=false,u,p,x="key",b=e.getAttribute(x)||e.getAttribute(x=":key")||e.getAttribute(x="v-bind:key");b&&(e.removeAttribute(x),x==="key"&&(b=JSON.stringify(b)));let v;(v=l.match(Le))&&(l=l.replace(Le,"").trim(),u=v[1].trim(),v[2]&&(p=v[2].trim())),(v=l.match(fn))&&(f=v[1].split(",").map(m=>m.trim()),a=l[0]==="[");let pe=false,R,L,J;const et=m=>{const w=new Map,h=[];if(y(m))for(let d=0;d{const g={};f?f.forEach((M,E)=>g[M]=w[a?E:M]):g[l]=w,d?(u&&(g[u]=d),p&&(g[p]=h)):u&&(g[u]=h);const P=Ge(n,g),_=b?H(P.scope,b):h;return m.set(_,h),P.key=_,P},he=(m,w)=>{const h=new ue(e,m);return h.key=m.key,h.insert(i,w),h};return n.effect(()=>{const m=H(n.scope,o),w=J;if([L,J]=et(m),!pe)R=L.map(h=>he(h,c)),pe=true;else {for(let _=0;_{let r;return s(()=>{const i=n();t[i]=e,r&&i!==r&&delete t[r],r=i;}),()=>{r&&delete t[r];}},un=/^(?:v-|:|@)/,pn=/\.([\w-]+)/g;let fe=false;const Fe=(e,t)=>{const n=e.nodeType;if(n===1){const s=e;if(s.hasAttribute("v-pre"))return;k(s,"v-cloak");let r;if(r=k(s,"v-if"))return on(s,r,t);if(r=k(s,"v-for"))return an(s,r,t);if((r=k(s,"v-scope"))||r===""){const o=r?H(t.scope,r):{};t=Ge(t,o),o.$template&&hn(s,o.$template);}const i=k(s,"v-once")!=null;i&&(fe=true),(r=k(s,"ref"))&&ae(s,We,`"${r}"`,t),qe(s,t);const c=[];for(const{name:o,value:l}of [...s.attributes])un.test(o)&&o!=="v-cloak"&&(o==="v-model"?c.unshift([o,l]):o[0]==="@"||/^v-on\b/.test(o)?c.push([o,l]):Je(s,o,l,t));for(const[o,l]of c)Je(s,o,l,t);i&&(fe=false);}else if(n===3){const s=e.data;if(s.includes(t.delimiters[0])){let r=[],i=0,c;for(;c=t.delimitersRE.exec(s);){const o=s.slice(i,c.index);o&&r.push(JSON.stringify(o)),r.push(`$s(${c[1]})`),i=c.index+c[0].length;}i{let n=e.firstChild;for(;n;)n=Fe(n,t)||n.nextSibling;},Je=(e,t,n,s)=>{let r,i,c;if(t=t.replace(pn,(o,l)=>((c||(c={}))[l]=true,"")),t[0]===":")r=ie,i=t.slice(1);else if(t[0]==="@")r=Ke,i=t.slice(1);else {const o=t.indexOf(":"),l=o>0?t.slice(2,o):t.slice(2);r=rn[l]||s.dirs[l],i=o>0?t.slice(o+1):void 0;}r&&(r===ie&&i==="ref"&&(r=We),ae(e,r,n,s,i,c),e.removeAttribute(t));},ae=(e,t,n,s,r,i)=>{const c=t({el:e,get:(o=n)=>H(s.scope,o,e),effect:s.effect,ctx:s,exp:n,arg:r,modifiers:i});c&&s.cleanups.push(c);},hn=(e,t)=>{if(t[0]==="#"){const n=document.querySelector(t);e.appendChild(n.content.cloneNode(true));return}e.innerHTML=t;},Ze=e=>{const t={delimiters:["{{","}}"],delimitersRE:/\{\{([^]+?)\}\}/g,...e,scope:e?e.scope:D({}),dirs:e?e.dirs:{},effects:[],blocks:[],cleanups:[],effect:n=>{if(fe)return Ie(n),n;const s=wt(n,{scheduler:()=>Ie(s)});return t.effects.push(s),s}};return t},Ge=(e,t={})=>{const n=e.scope,s=Object.create(n);Object.defineProperties(s,Object.getOwnPropertyDescriptors(t)),s.$refs=Object.create(n.$refs);const r=D(new Proxy(s,{set(i,c,o,l){return l===r&&!i.hasOwnProperty(c)?Reflect.set(n,c,o):Reflect.set(i,c,o,l)}}));return Ue(r),{...e,scope:r}},Ue=e=>{for(const t of Object.keys(e))typeof e[t]=="function"&&(e[t]=e[t].bind(e));};class ue{constructor(t,n,s=false){$(this,"template");$(this,"ctx");$(this,"key");$(this,"parentCtx");$(this,"isFragment");$(this,"start");$(this,"end");this.isFragment=t instanceof HTMLTemplateElement,s?this.template=t:this.isFragment?this.template=t.content.cloneNode(true):this.template=t.cloneNode(true),s?this.ctx=n:(this.parentCtx=n,n.blocks.push(this),this.ctx=Ze(n)),Fe(this.template,this.ctx);}get el(){return this.start||this.template}insert(t,n=null){if(this.isFragment)if(this.start){let s=this.start,r;for(;s&&(r=s.nextSibling,t.insertBefore(s,n),s!==this.end);)s=r;}else this.start=new Text(""),this.end=new Text(""),t.insertBefore(this.end,n),t.insertBefore(this.start,this.end),t.insertBefore(this.template,this.end);else t.insertBefore(this.template,n);}remove(){if(this.parentCtx&&ft(this.parentCtx.blocks,this),this.start){const t=this.start.parentNode;let n=this.start,s;for(;n&&(s=n.nextSibling,t.removeChild(n),n!==this.end);)n=s;}else this.template.parentNode.removeChild(this.template);this.teardown();}teardown(){this.ctx.blocks.forEach(t=>{t.teardown();}),this.ctx.effects.forEach(_t),this.ctx.cleanups.forEach(t=>t());}}const Ye=e=>e.replace(/[-.*+?^${}()|[\]\/\\]/g,"\\$&"),Qe=e=>{const t=Ze();if(e&&(t.scope=D(e),Ue(t.scope),e.$delimiters)){const[s,r]=t.delimiters=e.$delimiters;t.delimitersRE=new RegExp(Ye(s)+"([^]+?)"+Ye(r),"g");}t.scope.$s=ze,t.scope.$nextTick=V,t.scope.$refs=Object.create(null);let n;return {directive(s,r){return r?(t.dirs[s]=r,this):t.dirs[s]},mount(s){if(typeof s=="string"&&(s=document.querySelector(s),!s))return;s=s||document.documentElement;let r;return s.hasAttribute("v-scope")?r=[s]:r=[...s.querySelectorAll("[v-scope]")].filter(i=>!i.matches("[v-scope] [v-scope]")),r.length||(r=[s]),n=r.map(i=>new ue(i,t,true)),this},unmount(){n.forEach(s=>s.teardown());}}},Xe=document.currentScript;Xe&&Xe.hasAttribute("init")&&Qe().mount(); + var tt=Object.defineProperty;var nt=(e,t,n)=>t in e?tt(e,t,{enumerable:true,configurable:true,writable:true,value:n}):e[t]=n;var $$2=(e,t,n)=>(nt(e,typeof t!="symbol"?t+"":t,n),n);function st(e,t){const n=Object.create(null),s=e.split(",");for(let r=0;r!!n[r]}function de(e){if(y$5(e)){const t={};for(let n=0;n{if(n){const s=n.split(ot);s.length>1&&(t[s[0].trim()]=s[1].trim());}}),t}function me(e){let t="";if(N$3(e))t=e;else if(y$5(e))for(let n=0;nI$1(n,t))}const lt=Object.assign,ft=(e,t)=>{const n=e.indexOf(t);n>-1&&e.splice(n,1);},at=Object.prototype.hasOwnProperty,U$3=(e,t)=>at.call(e,t),y$5=Array.isArray,Y$1=e=>ye(e)==="[object Map]",ge=e=>e instanceof Date,N$3=e=>typeof e=="string",Q$1=e=>typeof e=="symbol",S$2=e=>e!==null&&typeof e=="object",ut=Object.prototype.toString,ye=e=>ut.call(e),pt=e=>ye(e).slice(8,-1),X$1=e=>N$3(e)&&e!=="NaN"&&e[0]!=="-"&&""+parseInt(e,10)===e,be=e=>{const t=Object.create(null);return n=>t[n]||(t[n]=e(n))},ht=/-(\w)/g,dt=be(e=>e.replace(ht,(t,n)=>n?n.toUpperCase():"")),mt=/\B([A-Z])/g,xe=be(e=>e.replace(mt,"-$1").toLowerCase()),gt=(e,t)=>!Object.is(e,t),ve=e=>{const t=parseFloat(e);return isNaN(t)?e:t};let yt;function we(e,t){t=t||yt,t&&t.active&&t.effects.push(e);}const _e=e=>{const t=new Set(e);return t.w=0,t.n=0,t},Ee=e=>(e.w&O$3)>0,$e=e=>(e.n&O$3)>0,bt=({deps:e})=>{if(e.length)for(let t=0;t{const{deps:t}=e;if(t.length){let n=0;for(let s=0;s0?z$1[t-1]:void 0;}}stop(){this.active&&(Oe(this),this.onStop&&this.onStop(),this.active=false);}}function Oe(e){const{deps:t}=e;if(t.length){for(let n=0;n{(f==="length"||f>=s)&&o.push(l);});else switch(n!==void 0&&o.push(c.get(n)),t){case "add":y$5(e)?X$1(n)&&o.push(c.get("length")):(o.push(c.get(W)),Y$1(e)&&o.push(c.get(Se)));break;case "delete":y$5(e)||(o.push(c.get(W)),Y$1(e)&&o.push(c.get(Se)));break;case "set":Y$1(e)&&o.push(c.get(W));break}if(o.length===1)o[0]&&Te(o[0]);else {const l=[];for(const f of o)f&&l.push(...f);Te(_e(l));}}function Te(e,t){for(const n of y$5(e)?e:[...e])(n!==C||n.allowRecurse)&&(n.scheduler?n.scheduler():n.run());}const kt=st("__proto__,__v_isRef,__isVue"),Ae=new Set(Object.getOwnPropertyNames(Symbol).map(e=>Symbol[e]).filter(Q$1)),Tt=Me(),At=Me(true),Re=Rt();function Rt(){const e={};return ["includes","indexOf","lastIndexOf"].forEach(t=>{e[t]=function(...n){const s=j$1(this);for(let i=0,c=this.length;i{e[t]=function(...n){Et();const s=j$1(this)[t].apply(this,n);return ke(),s};}),e}function Me(e=false,t=false){return function(s,r,i){if(r==="__v_isReactive")return !e;if(r==="__v_isReadonly")return e;if(r==="__v_raw"&&i===(e?t?zt:je:t?Bt:Ce).get(s))return s;const c=y$5(s);if(!e&&c&&U$3(Re,r))return Reflect.get(Re,r,i);const o=Reflect.get(s,r,i);return (Q$1(r)?Ae.has(r):kt(r))||(e||F$1(s,"get",r),t)?o:re(o)?!c||!X$1(r)?o.value:o:S$2(o)?e?Ht(o):D$1(o):o}}const Mt=Ct();function Ct(e=false){return function(n,s,r,i){let c=n[s];if(!e&&!Lt(r)&&(r=j$1(r),c=j$1(c),!y$5(n)&&re(c)&&!re(r)))return c.value=r,true;const o=y$5(n)&&X$1(s)?Number(s)Wt.then(e),Ie=e=>{q$2.includes(e)||q$2.push(e),oe||(oe=true,V$1(Ft));},Ft=()=>{for(const e of q$2)e();q$2.length=0,oe=false;},qt=/^(spellcheck|draggable|form|list|type)$/,ie=({el:e,get:t,effect:n,arg:s,modifiers:r})=>{let i;s==="class"&&(e._class=e.className),n(()=>{let c=t();if(s)(r==null?void 0:r.camel)&&(s=dt(s)),ce$1(e,s,c,i);else {for(const o in c)ce$1(e,o,c[o],i&&i[o]);for(const o in i)(!c||!(o in c))&&ce$1(e,o,null);}i=c;});},ce$1=(e,t,n,s)=>{if(t==="class")e.setAttribute("class",me(e._class?[e._class,n]:n)||"");else if(t==="style"){n=de(n);const{style:r}=e;if(!n)e.removeAttribute("style");else if(N$3(n))n!==s&&(r.cssText=n);else {for(const i in n)le(r,i,n[i]);if(s&&!N$3(s))for(const i in s)n[i]==null&&le(r,i,"");}}else !(e instanceof SVGElement)&&t in e&&!qt.test(t)?(e[t]=n,t==="value"&&(e._value=n)):t==="true-value"?e._trueValue=n:t==="false-value"?e._falseValue=n:n!=null?e.setAttribute(t,n):e.removeAttribute(t);},Ne=/\s*!important$/,le=(e,t,n)=>{y$5(n)?n.forEach(s=>le(e,t,s)):t.startsWith("--")?e.setProperty(t,n):Ne.test(n)?e.setProperty(xe(t),n.replace(Ne,""),"important"):e[t]=n;},k$3=(e,t)=>{const n=e.getAttribute(t);return n!=null&&e.removeAttribute(t),n},T$3=(e,t,n,s)=>{e.addEventListener(t,n,s);},Jt=/^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\['[^']*?']|\["[^"]*?"]|\[\d+]|\[[A-Za-z_$][\w$]*])*$/,Zt=["ctrl","shift","alt","meta"],Gt={stop:e=>e.stopPropagation(),prevent:e=>e.preventDefault(),self:e=>e.target!==e.currentTarget,ctrl:e=>!e.ctrlKey,shift:e=>!e.shiftKey,alt:e=>!e.altKey,meta:e=>!e.metaKey,left:e=>"button"in e&&e.button!==0,middle:e=>"button"in e&&e.button!==1,right:e=>"button"in e&&e.button!==2,exact:(e,t)=>Zt.some(n=>e[`${n}Key`]&&!t[n])},Ke=({el:e,get:t,exp:n,arg:s,modifiers:r})=>{if(!s)return;let i=Jt.test(n)?t(`(e => ${n}(e))`):t(`($event => { ${n} })`);if(s==="vue:mounted"){V$1(i);return}else if(s==="vue:unmounted")return ()=>i();if(r){s==="click"&&(r.right&&(s="contextmenu"),r.middle&&(s="mouseup"));const c=i;i=o=>{if(!("key"in o&&!(xe(o.key)in r))){for(const l in r){const f=Gt[l];if(f&&f(o,r))return}return c(o)}};}T$3(e,s,i,r);},Ut=({el:e,get:t,effect:n})=>{const s=e.style.display;n(()=>{e.style.display=t()?s:"none";});},Be=({el:e,get:t,effect:n})=>{n(()=>{e.textContent=ze(t());});},ze=e=>e==null?"":S$2(e)?JSON.stringify(e,null,2):String(e),Yt=({el:e,get:t,effect:n})=>{n(()=>{e.innerHTML=t();});},Qt=({el:e,exp:t,get:n,effect:s,modifiers:r})=>{const i=e.type,c=n(`(val) => { ${t} = val }`),{trim:o,number:l=i==="number"}=r||{};if(e.tagName==="SELECT"){const f=e;T$3(e,"change",()=>{const a=Array.prototype.filter.call(f.options,u=>u.selected).map(u=>l?ve(A$3(u)):A$3(u));c(f.multiple?a:a[0]);}),s(()=>{const a=n(),u=f.multiple;for(let p=0,x=f.options.length;p-1:b.selected=a.has(v);else if(I$1(A$3(b),a)){f.selectedIndex!==p&&(f.selectedIndex=p);return}}!u&&f.selectedIndex!==-1&&(f.selectedIndex=-1);});}else if(i==="checkbox"){T$3(e,"change",()=>{const a=n(),u=e.checked;if(y$5(a)){const p=A$3(e),x=G(a,p),b=x!==-1;if(u&&!b)c(a.concat(p));else if(!u&&b){const v=[...a];v.splice(x,1),c(v);}}else c(De(e,u));});let f;s(()=>{const a=n();y$5(a)?e.checked=G(a,A$3(e))>-1:a!==f&&(e.checked=I$1(a,De(e,true))),f=a;});}else if(i==="radio"){T$3(e,"change",()=>{c(A$3(e));});let f;s(()=>{const a=n();a!==f&&(e.checked=I$1(a,A$3(e)));});}else {const f=a=>o?a.trim():l?ve(a):a;T$3(e,"compositionstart",Xt),T$3(e,"compositionend",en),T$3(e,(r==null?void 0:r.lazy)?"change":"input",()=>{e.composing||c(f(e.value));}),o&&T$3(e,"change",()=>{e.value=e.value.trim();}),s(()=>{if(e.composing)return;const a=e.value,u=n();document.activeElement===e&&f(a)===u||a!==u&&(e.value=u);});}},A$3=e=>"_value"in e?e._value:e.value,De=(e,t)=>{const n=t?"_trueValue":"_falseValue";return n in e?e[n]:t},Xt=e=>{e.target.composing=true;},en=e=>{const t=e.target;t.composing&&(t.composing=false,tn(t,"input"));},tn=(e,t)=>{const n=document.createEvent("HTMLEvents");n.initEvent(t,true,true),e.dispatchEvent(n);},Ve=Object.create(null),H$2=(e,t,n)=>He(e,`return(${t})`,n),He=(e,t,n)=>{const s=Ve[t]||(Ve[t]=nn(t));try{return s(e,n)}catch(r){console.error(r);}},nn=e=>{try{return new Function("$data","$el",`with($data){${e}}`)}catch(t){return console.error(`${t.message} in expression: ${e}`),()=>{}}},sn=({el:e,ctx:t,exp:n,effect:s})=>{V$1(()=>s(()=>He(t.scope,n,e)));},rn={bind:ie,on:Ke,show:Ut,text:Be,html:Yt,model:Qt,effect:sn},on=(e,t,n)=>{const s=e.parentElement,r=new Comment("v-if");s.insertBefore(r,e);const i=[{exp:t,el:e}];let c,o;for(;(c=e.nextElementSibling)&&(o=null,k$3(c,"v-else")===""||(o=k$3(c,"v-else-if")));)s.removeChild(c),i.push({exp:o,el:c});const l=e.nextSibling;s.removeChild(e);let f,a=-1;const u=()=>{f&&(s.insertBefore(r,f.el),f.remove(),f=void 0);};return n.effect(()=>{for(let p=0;p{const s=t.match(cn);if(!s)return;const r=e.nextSibling,i=e.parentElement,c=new Text("");i.insertBefore(c,e),i.removeChild(e);const o=s[2].trim();let l=s[1].trim().replace(ln,"").trim(),f,a=false,u,p,x="key",b=e.getAttribute(x)||e.getAttribute(x=":key")||e.getAttribute(x="v-bind:key");b&&(e.removeAttribute(x),x==="key"&&(b=JSON.stringify(b)));let v;(v=l.match(Le))&&(l=l.replace(Le,"").trim(),u=v[1].trim(),v[2]&&(p=v[2].trim())),(v=l.match(fn))&&(f=v[1].split(",").map(m=>m.trim()),a=l[0]==="[");let pe=false,R,L,J;const et=m=>{const w=new Map,h=[];if(y$5(m))for(let d=0;d{const g={};f?f.forEach((M,E)=>g[M]=w[a?E:M]):g[l]=w,d?(u&&(g[u]=d),p&&(g[p]=h)):u&&(g[u]=h);const P=Ge(n,g),_=b?H$2(P.scope,b):h;return m.set(_,h),P.key=_,P},he=(m,w)=>{const h=new ue(e,m);return h.key=m.key,h.insert(i,w),h};return n.effect(()=>{const m=H$2(n.scope,o),w=J;if([L,J]=et(m),!pe)R=L.map(h=>he(h,c)),pe=true;else {for(let _=0;_{let r;return s(()=>{const i=n();t[i]=e,r&&i!==r&&delete t[r],r=i;}),()=>{r&&delete t[r];}},un=/^(?:v-|:|@)/,pn=/\.([\w-]+)/g;let fe=false;const Fe=(e,t)=>{const n=e.nodeType;if(n===1){const s=e;if(s.hasAttribute("v-pre"))return;k$3(s,"v-cloak");let r;if(r=k$3(s,"v-if"))return on(s,r,t);if(r=k$3(s,"v-for"))return an(s,r,t);if((r=k$3(s,"v-scope"))||r===""){const o=r?H$2(t.scope,r):{};t=Ge(t,o),o.$template&&hn(s,o.$template);}const i=k$3(s,"v-once")!=null;i&&(fe=true),(r=k$3(s,"ref"))&&ae(s,We,`"${r}"`,t),qe(s,t);const c=[];for(const{name:o,value:l}of [...s.attributes])un.test(o)&&o!=="v-cloak"&&(o==="v-model"?c.unshift([o,l]):o[0]==="@"||/^v-on\b/.test(o)?c.push([o,l]):Je(s,o,l,t));for(const[o,l]of c)Je(s,o,l,t);i&&(fe=false);}else if(n===3){const s=e.data;if(s.includes(t.delimiters[0])){let r=[],i=0,c;for(;c=t.delimitersRE.exec(s);){const o=s.slice(i,c.index);o&&r.push(JSON.stringify(o)),r.push(`$s(${c[1]})`),i=c.index+c[0].length;}i{let n=e.firstChild;for(;n;)n=Fe(n,t)||n.nextSibling;},Je=(e,t,n,s)=>{let r,i,c;if(t=t.replace(pn,(o,l)=>((c||(c={}))[l]=true,"")),t[0]===":")r=ie,i=t.slice(1);else if(t[0]==="@")r=Ke,i=t.slice(1);else {const o=t.indexOf(":"),l=o>0?t.slice(2,o):t.slice(2);r=rn[l]||s.dirs[l],i=o>0?t.slice(o+1):void 0;}r&&(r===ie&&i==="ref"&&(r=We),ae(e,r,n,s,i,c),e.removeAttribute(t));},ae=(e,t,n,s,r,i)=>{const c=t({el:e,get:(o=n)=>H$2(s.scope,o,e),effect:s.effect,ctx:s,exp:n,arg:r,modifiers:i});c&&s.cleanups.push(c);},hn=(e,t)=>{if(t[0]==="#"){const n=document.querySelector(t);e.appendChild(n.content.cloneNode(true));return}e.innerHTML=t;},Ze=e=>{const t={delimiters:["{{","}}"],delimitersRE:/\{\{([^]+?)\}\}/g,...e,scope:e?e.scope:D$1({}),dirs:e?e.dirs:{},effects:[],blocks:[],cleanups:[],effect:n=>{if(fe)return Ie(n),n;const s=wt(n,{scheduler:()=>Ie(s)});return t.effects.push(s),s}};return t},Ge=(e,t={})=>{const n=e.scope,s=Object.create(n);Object.defineProperties(s,Object.getOwnPropertyDescriptors(t)),s.$refs=Object.create(n.$refs);const r=D$1(new Proxy(s,{set(i,c,o,l){return l===r&&!i.hasOwnProperty(c)?Reflect.set(n,c,o):Reflect.set(i,c,o,l)}}));return Ue(r),{...e,scope:r}},Ue=e=>{for(const t of Object.keys(e))typeof e[t]=="function"&&(e[t]=e[t].bind(e));};class ue{constructor(t,n,s=false){$$2(this,"template");$$2(this,"ctx");$$2(this,"key");$$2(this,"parentCtx");$$2(this,"isFragment");$$2(this,"start");$$2(this,"end");this.isFragment=t instanceof HTMLTemplateElement,s?this.template=t:this.isFragment?this.template=t.content.cloneNode(true):this.template=t.cloneNode(true),s?this.ctx=n:(this.parentCtx=n,n.blocks.push(this),this.ctx=Ze(n)),Fe(this.template,this.ctx);}get el(){return this.start||this.template}insert(t,n=null){if(this.isFragment)if(this.start){let s=this.start,r;for(;s&&(r=s.nextSibling,t.insertBefore(s,n),s!==this.end);)s=r;}else this.start=new Text(""),this.end=new Text(""),t.insertBefore(this.end,n),t.insertBefore(this.start,this.end),t.insertBefore(this.template,this.end);else t.insertBefore(this.template,n);}remove(){if(this.parentCtx&&ft(this.parentCtx.blocks,this),this.start){const t=this.start.parentNode;let n=this.start,s;for(;n&&(s=n.nextSibling,t.removeChild(n),n!==this.end);)n=s;}else this.template.parentNode.removeChild(this.template);this.teardown();}teardown(){this.ctx.blocks.forEach(t=>{t.teardown();}),this.ctx.effects.forEach(_t),this.ctx.cleanups.forEach(t=>t());}}const Ye=e=>e.replace(/[-.*+?^${}()|[\]\/\\]/g,"\\$&"),Qe=e=>{const t=Ze();if(e&&(t.scope=D$1(e),Ue(t.scope),e.$delimiters)){const[s,r]=t.delimiters=e.$delimiters;t.delimitersRE=new RegExp(Ye(s)+"([^]+?)"+Ye(r),"g");}t.scope.$s=ze,t.scope.$nextTick=V$1,t.scope.$refs=Object.create(null);let n;return {directive(s,r){return r?(t.dirs[s]=r,this):t.dirs[s]},mount(s){if(typeof s=="string"&&(s=document.querySelector(s),!s))return;s=s||document.documentElement;let r;return s.hasAttribute("v-scope")?r=[s]:r=[...s.querySelectorAll("[v-scope]")].filter(i=>!i.matches("[v-scope] [v-scope]")),r.length||(r=[s]),n=r.map(i=>new ue(i,t,true)),this},unmount(){n.forEach(s=>s.teardown());}}},Xe=document.currentScript;Xe&&Xe.hasAttribute("init")&&Qe().mount(); /** A parse stack. These are used internally by the parser to track @@ -25220,16 +25220,16 @@ /** The parse that this stack is part of @internal */ - p, + p, /** Holds state, input pos, buffer index triplets for all but the top state @internal */ - stack, + stack, /** The current parse state @internal */ - state, + state, // The position at which the next reduce should take place. This // can be less than `this.pos` when skipped expressions have been // added to the stack (which should be moved outside of the next @@ -25237,24 +25237,24 @@ /** @internal */ - reducePos, + reducePos, /** The input position up to which this stack has parsed. */ - pos, + pos, /** The dynamic score of the stack, including dynamic precedence and error-recovery penalties @internal */ - score, + score, // The output buffer. Holds (type, start, end, size) quads // representing nodes created by the parser, where `size` is // amount of buffer array entries covered by this node. /** @internal */ - buffer, + buffer, // The base offset of the buffer. When stacks are split, the split // instance shared the buffer history with its parent up to // `bufferBase`, which is the absolute offset (including the @@ -25263,15 +25263,15 @@ /** @internal */ - bufferBase, + bufferBase, /** @internal */ - curContext, + curContext, /** @internal */ - lookAhead = 0, + lookAhead = 0, // A parent stack from which this was split off, if any. This is // set up so that it always points to a stack that has some // additional buffer content, never to a stack with an equal @@ -25868,7 +25868,7 @@ /** @internal */ - input, + input, /** @internal */ @@ -25940,7 +25940,7 @@ Look at a code unit near the stream position. `.peek(0)` equals `.next`, `.peek(-1)` gives you the previous character, and so on. - + Note that looking around during tokenizing creates dependencies on potentially far-away content, which may reduce the effectiveness incremental parsing—when looking forward—or even @@ -30629,7 +30629,7 @@ replacer = undefined; } const { aliasDuplicateObjects, anchorPrefix, flow, keepUndefined, onTagObj, tag } = options ?? {}; - const { onAnchor, setAnchors, sourceObjects } = createNodeAnchors(this, + const { onAnchor, setAnchors, sourceObjects } = createNodeAnchors(this, // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing anchorPrefix || 'a'); const ctx = { @@ -32780,7 +32780,7 @@ } /** The byte order mark */ - const BOM = '\u{FEFF}'; + const BOM$1 = '\u{FEFF}'; /** Start of doc-mode */ const DOCUMENT = '\x02'; // C0: Start of Text /** Unexpected end of flow-mode */ @@ -32799,7 +32799,7 @@ /** Get a printable representation of a lexer token */ function prettyToken(token) { switch (token) { - case BOM: + case BOM$1: return ''; case DOCUMENT: return ''; @@ -32814,7 +32814,7 @@ /** Identify the type of a lexer token. May return `null` for unknown tokens. */ function tokenType(source) { switch (source) { - case BOM: + case BOM$1: return 'byte-order-mark'; case DOCUMENT: return 'doc-mode'; @@ -32874,7 +32874,7 @@ var cst = /*#__PURE__*/Object.freeze({ __proto__: null, - BOM: BOM, + BOM: BOM$1, DOCUMENT: DOCUMENT, FLOW_END: FLOW_END, SCALAR: SCALAR, @@ -33133,7 +33133,7 @@ let line = this.getLine(); if (line === null) return this.setNext('stream'); - if (line[0] === BOM) { + if (line[0] === BOM$1) { yield* this.pushCount(1); line = line.substring(1); } @@ -34653,7 +34653,7 @@ } return doc; } - function parse(src, reviver, options) { + function parse$1(src, reviver, options) { let _reviver = undefined; if (typeof reviver === 'function') { _reviver = reviver; @@ -34722,7 +34722,7 @@ isPair: isPair, isScalar: isScalar$1, isSeq: isSeq, - parse: parse, + parse: parse$1, parseAllDocuments: parseAllDocuments, parseDocument: parseDocument, stringify: stringify, @@ -34944,7 +34944,7 @@ input.advance(); while (!isSep(input.next) && charTag(input.tag) != "f") input.advance(); } - + function readQuoted(input, scan) { let quote = input.next, lineBreak = false, start = input.pos; input.advance(); @@ -35168,307 +35168,4508 @@ return new LanguageSupport(yamlLanguage); } - /* globals: jsonld */ + /*! + * Copyright (c) 2021-2024 Digital Bazaar, Inc. All rights reserved. + */ + const KEYWORDS_TABLE = new Map([ + // ordered is important, do not change + ['@context', 0], + ['@type', 2], + ['@id', 4], + ['@value', 6], + // alphabetized after `@context`, `@type`, `@id`, `@value` + // IDs <= 24 represented with 1 byte, IDs > 24 use 2+ bytes + ['@direction', 8], + ['@graph', 10], + ['@included', 12], + ['@index', 14], + ['@json', 16], + ['@language', 18], + ['@list', 20], + ['@nest', 22], + ['@reverse', 24], + // these only appear in frames and contexts, not docs + ['@base', 26], + ['@container', 28], + ['@default', 30], + ['@embed', 32], + ['@explicit', 34], + ['@none', 36], + ['@omitDefault', 38], + ['@prefix', 40], + ['@preserve', 42], + ['@protected', 44], + ['@requireAll', 46], + ['@set', 48], + ['@version', 50], + ['@vocab', 52], + // `@propagate` added later + ['@propagate', 54] + ]); + /** + * These are from the legacy registry. + * + * @see https://digitalbazaar.github.io/cbor-ld-spec/#term-codec-registry + */ + const STRING_TABLE = new Map([ + // 0x00 - 0x0F: reserved + ['https://www.w3.org/ns/activitystreams', 16], + ['https://www.w3.org/2018/credentials/v1', 17], + ['https://www.w3.org/ns/did/v1', 18], + ['https://w3id.org/security/suites/ed25519-2018/v1', 19], + ['https://w3id.org/security/suites/ed25519-2020/v1', 20], + ['https://w3id.org/cit/v1', 21], + ['https://w3id.org/age/v1', 22], + ['https://w3id.org/security/suites/x25519-2020/v1', 23], + ['https://w3id.org/veres-one/v1', 24], + ['https://w3id.org/webkms/v1', 25], + ['https://w3id.org/zcap/v1', 26], + ['https://w3id.org/security/suites/hmac-2019/v1', 27], + ['https://w3id.org/security/suites/aes-2019/v1', 28], + ['https://w3id.org/vaccination/v1', 29], + ['https://w3id.org/vc-revocation-list-2020/v1', 30], + ['https://w3id.org/dcc/v1', 31], + ['https://w3id.org/vc/status-list/v1', 32], + ['https://www.w3.org/ns/credentials/v2', 33], + // 0x22 - 0x2F (34-47): available + ['https://w3id.org/security/data-integrity/v1', 48], + ['https://w3id.org/security/multikey/v1', 49], + // 0x32 (50): reserved (in legacy spec) + // FIXME: Unclear on how to handle the openbadges URLs and versioning. + // This value was never in the spec, but was added here, and potentially + // elsewhere. + ['https://purl.imsglobal.org/spec/ob/v3p0/context.json', 50], + ['https://w3id.org/security/data-integrity/v2', 51] + // 0x34 - 0x36 (52-54): reserved; removed experimental cryptosuite + // registrations + ]); - // Setup JSON-LD documentLoader - const xhrDocumentLoader = jsonld.documentLoaders.xhr(); - // FIXME: add UI to let users control and set context mapping - jsonld.documentLoader = function(url) { - // rewrite URLs that we know have secure JSON-LD Contexts - if(url === 'http://schema.org/' || url === 'http://schema.org') { - url = 'https://schema.org/'; + const URL_SCHEME_TABLE = new Map([ + ['http://', 1], + ['https://', 2], + ['urn:uuid:', 3], + ['data:', 4], + ['did:v1:nym:', 1024], + ['did:key:', 1025] + ]); + + reverseMap(URL_SCHEME_TABLE); + + const cryptosuiteTypedTable = new Map([ + ['ecdsa-rdfc-2019', 1], + ['ecdsa-sd-2023', 2], + ['eddsa-rdfc-2022', 3], + ['ecdsa-xi-2023', 4] + ]); + + const TYPE_TABLE = new Map([ + ['context', STRING_TABLE], + ['url', STRING_TABLE], + ['none', STRING_TABLE], + ['https://w3id.org/security#cryptosuiteString', cryptosuiteTypedTable] + ]); + const FIRST_CUSTOM_TERM_ID = 100; + + function createLegacyTypeTable({typeTable, appContextMap} = {}) { + if(typeTable) { + throw new TypeError( + '"typeTable" must not be passed when using "legacy" mode.'); } - // if a non-HTTPS URL, use the proxy since we run in HTTPS only mode - if(!url.startsWith('https://')) { - url = [ - location.protocol, - '//', - location.host, - // NOTE: using hard-coded path so file can be shared with dev page - //location.pathname, - '/playground/', - 'proxy?url=', - url - ].join(''); + // generate legacy type table + typeTable = new Map(TYPE_TABLE); + + if(appContextMap) { + // add `appContextMap` to legacy mode combined string table + const stringTable = new Map(STRING_TABLE); + for(const [key, value] of appContextMap) { + stringTable.set(key, value); + } + typeTable.set('context', stringTable); + typeTable.set('url', stringTable); + typeTable.set('none', stringTable); } - return xhrDocumentLoader(url); - }; + return typeTable; + } - const jsonLdAtTerms = [ - { label: "@context", type: "keyword", info: "Defines the JSON-LD context" }, - { label: "@id", type: "keyword", info: "Specifies the unique identifier of an entity" }, - { label: "@type", type: "keyword", info: "Defines the type of an entity" }, - { label: "@value", type: "keyword", info: "Represents the value of a node" }, - { label: "@language", type: "keyword", info: "Specifies the language of a string value" }, - { label: "@graph", type: "keyword", info: "Represents a named graph" }, - { label: "@list", type: "keyword", info: "Denotes an ordered list" }, - { label: "@set", type: "keyword", info: "Denotes an unordered set" }, - { label: "@reverse", type: "keyword", info: "Defines reverse properties" }, - { label: "@index", type: "keyword", info: "Specifies an index for ordered data" }, - { label: "@base", type: "keyword", info: "Defines the base IRI" }, - { label: "@vocab", type: "keyword", info: "Defines the default vocabulary" }, - { label: "@container", type: "keyword", info: "Specifies container types for properties" }, - { label: "@nest", type: "keyword", info: "Allows nesting of properties" }, - { label: "@prefix", type: "keyword", info: "Defines a prefix mapping" }, - { label: "@propagate", type: "keyword", info: "Controls context propagation" }, - { label: "@protected", type: "keyword", info: "Prevents term overrides" }, - { label: "@version", type: "keyword", info: "Specifies the JSON-LD version" } - ]; + function createTypeTable({typeTable} = {}) { + // ensure `typeTable` has empty maps for core types + typeTable = new Map(typeTable); + if(!typeTable.has('context')) { + typeTable.set('context', new Map()); + } + if(!typeTable.has('url')) { + typeTable.set('url', new Map()); + } + if(!typeTable.has('none')) { + typeTable.set('none', new Map()); + } + return typeTable; + } - // TODO: the next two functions could probably become a petite-vue component - function editorListener(docName) { - let changes = []; // keep the changes as a list; then pick the last one - let timer; // we only want one timer, once the last one is done, use the result - function debounce(fn, delay) { - return (...args) => { - clearTimeout(timer); - timer = setTimeout(() => fn(...args), delay); - }; + function reverseMap(m) { + return new Map(Array.from(m, e => e.reverse())); + } + + /*! + * Copyright (c) 2020-2024 Digital Bazaar, Inc. All rights reserved. + */ + class CborldError extends Error { + constructor(code, message) { + super(message); + this.code = code; + // backwards compatibility, `this.value` + this.value = code; + this.stack = (new Error(`${code}: ${message}`)).stack; + this.name = this.constructor.name; } + } - return EditorView.updateListener.of((update) => { - if (update.docChanged) { - changes.push(update.state.doc.toString()); - debounce((docName) => { - // set the global `doc` to the latest string from the editor - try { - const parsed = JSON.parse(changes[changes.length-1]); - this[docName] = parsed; - this.parseError = ''; - } catch (err) { - this.parseError = err.message; - } }, 1000).call(this, docName); - } - }); + /*! + * Copyright (c) 2024 Digital Bazaar, Inc. All rights reserved. + */ + + // if a value with `termType` was compressed using the type table, then + // the encoded value will either be a `number` or a `Uint8Array` + const LEGACY_TYPE_TABLE_ENCODED_AS_BYTES = new Set([ + /* Note: In legacy mode, the `url` table values are encoded as integers, + which means that if the legacy global URL table is appended to and an + encoder uses the new version whilst a decoder uses the old version, the + decoder might misinterpret the new entry as some term from a context. This + can also happen with `appContextMap`, but could only occur if the number + of terms used in the contexts in play were greater than 32K. The non-legacy + version of CBOR-LD encodes values from the URL table as bytes, preventing + any possibility of collision -- and any different view of a table would + instead result in an error. */ + 'none', + 'http://www.w3.org/2001/XMLSchema#date', + 'http://www.w3.org/2001/XMLSchema#dateTime' + ]); + const TYPE_TABLE_ENCODED_AS_BYTES = new Set( + LEGACY_TYPE_TABLE_ENCODED_AS_BYTES); + TYPE_TABLE_ENCODED_AS_BYTES.add('url'); + + function bytesFromUint({intValue}) { + let buffer; + let dataview; + if(intValue < 0xFF) { + buffer = new ArrayBuffer(1); + dataview = new DataView(buffer); + dataview.setUint8(0, intValue); + } else if(intValue < 0xFFFF) { + buffer = new ArrayBuffer(2); + dataview = new DataView(buffer); + dataview.setUint16(0, intValue); + } else if(intValue < 0xFFFFFFFF) { + buffer = new ArrayBuffer(4); + dataview = new DataView(buffer); + dataview.setUint32(0, intValue); + } else if(intValue < Number.MAX_SAFE_INTEGER) { + buffer = new ArrayBuffer(8); + dataview = new DataView(buffer); + dataview.setBigUint64(0, BigInt(intValue)); + } else { + throw new CborldError( + 'ERR_COMPRESSION_VALUE_TOO_LARGE', + `Compression value "${intValue}" too large.`); + } + const bytes = new Uint8Array(buffer); + return bytes; + } + + function bytesFromInt({intValue}) { + let buffer; + let dataview; + if(intValue < 0x7F) { + buffer = new ArrayBuffer(1); + dataview = new DataView(buffer); + dataview.setInt8(0, intValue); + } else if(intValue < 0x7FFF) { + buffer = new ArrayBuffer(2); + dataview = new DataView(buffer); + dataview.setInt16(0, intValue); + } else if(intValue < 0x7FFFFFFF) { + buffer = new ArrayBuffer(4); + dataview = new DataView(buffer); + dataview.setInt32(0, intValue); + } else if(intValue < Number.MAX_SAFE_INTEGER) { + buffer = new ArrayBuffer(8); + dataview = new DataView(buffer); + dataview.setBigInt64(0, BigInt(intValue)); + } else { + throw new CborldError( + 'ERR_COMPRESSION_VALUE_TOO_LARGE', + `Compression value "${intValue}" too large.`); + } + const bytes = new Uint8Array(buffer); + return bytes; } - function initEditor(id, content, varName) { - return new EditorView({ - parent: document.getElementById(id), - doc: JSON.stringify(content, null, 2), - extensions: [ - basicSetup, - keymap.of([indentWithTab]), - json(), - linter(jsonParseLinter()), - autocompletion({override: [completeFromList(jsonLdAtTerms)]}), - editorListener.call(this, varName) - ] - }); + + // FIXME: consider moving this out of helpers elsewhere + function getTableType({termInfo, termType}) { + const {term, def} = termInfo; + + // handle `@id`, `@type`, their aliases, and `@vocab` + if(term === '@id' || def['@id'] === '@id' || + term === '@type' || def['@id'] === '@type' || + termType === '@id' || termType === '@vocab') { + return 'url'; + } + return termType ?? 'none'; } - const language = new Compartment(); + /*! + * Copyright (c) 2021-2025 Digital Bazaar, Inc. All rights reserved. + */ - const readOnlyEditor = new EditorView({ - parent: document.getElementById('read-only-editor'), - doc: `{}`, - extensions: [ - basicSetup, - language.of(json()), - EditorState.readOnly.of(true), - EditorView.editable.of(false), - EditorView.contentAttributes.of({tabindex: '0'}) - ] - }); + class ActiveContext { + constructor({ + termMap = new Map(), + previous, + contextLoader = previous?.contextLoader, + } = {}) { + this.termMap = termMap; + this.previous = previous; + this.contextLoader = contextLoader; + + // compute all type terms (`@type` and aliases) + this.typeTerms = ['@type']; + for(const [term, def] of termMap) { + if(def['@id'] === '@type') { + this.typeTerms.push(term); + } + } + } - function setEditorValue(_editor, doc, lang) { - console.trace(arguments); - if (_editor) { - // set the correct language - language.reconfigure(json()); - if (lang === 'yaml') language.reconfigure(yaml()); - else language.reconfigure(StreamLanguage.define(ntriples)); + /** + * Apply the embedded JSON-LD contexts in the given object to produce a new + * active context which can be used to get type information about the object + * prior to applying any type-scoped contexts. + * + * @param {object} options - The options to use. + * @param {object} options.obj - The object to get the active context for. + * + * @returns {Promise} - The active context instance. + */ + async applyEmbeddedContexts({obj}) { + // add any local embedded contexts to active term map + const termMap = await _updateTermMap({ + activeTermMap: this.termMap, + contexts: obj['@context'], + contextLoader: this.contextLoader + }); + return new ActiveContext({termMap, previous: this}); + } - _editor.dispatch({ - changes: { - from: 0, - to: _editor.state.doc.length, - insert: typeof(doc) === 'object' - ? JSON.stringify(doc, null, 2) - : doc - }, - // set the correct language - effects: language.reconfigure(typeof(doc) === 'object' - ? json() - : StreamLanguage.define(ntriples)) + /** + * Apply any property-scoped JSON-LD context associated with the given term + * to produce a new active context that can be used with the values + * associated with the term. + * + * @param {object} options - The options to use. + * @param {string} options.term - The term to get the active context for. + * + * @returns {Promise} - The active context instance. + */ + async applyPropertyScopedContext({term}) { + // always revert active context's term map when recursing into a property + // to remove any non-propagating terms + const termMap = await _updateTermMap({ + activeTermMap: _revertTermMap({activeCtx: this}), + // get context from current active context (not reverted one) + contexts: this.termMap.get(term)?.['@context'], + contextLoader: this.contextLoader, + propertyScope: true }); + return new ActiveContext({termMap, previous: this}); + } + + /** + * Apply any type-scoped JSON-LD contexts associated with the given object + * types to produce a new active context that can be used to get the term + * information for each key in the object. + * + * @param {object} options - The options to use. + * @param {Set} options.objectTypes - The set of object types (strings) to + * to use to produce the new active context. + * + * @returns {Promise} - The active context instance. + */ + async applyTypeScopedContexts({objectTypes}) { + // apply type-scoped contexts in lexicographically type-sorted order + // (per JSON-LD spec) + objectTypes = [...objectTypes].sort(); + let {termMap} = this; + for(const type of objectTypes) { + termMap = await _updateTermMap({ + activeTermMap: termMap, + contexts: termMap.get(type)?.['@context'], + contextLoader: this.contextLoader, + typeScope: true + }); + } + return new ActiveContext({termMap, previous: this}); + } + + getIdForTerm({term, plural}) { + return this.contextLoader.getIdForTerm({term, plural}); + } + + getTermDefinition({term}) { + return this.termMap.get(term) ?? {}; + } + + getTermInfo({id}) { + // get term and term definition + const {term, plural} = this.contextLoader.getTermForId({id}); + const def = this.getTermDefinition({term}); + return {term, termId: id, plural, def}; + } + + getTypeTerms() { + return this.typeTerms; } } - window.app = Qe({ - doc: {}, - contextDoc: {}, - frameDoc: {}, - tableQuads: {}, - yamlLD: '', - remoteDocURL: '', - remoteSideDocURL: '', - parseError: '', - inputTab: 'json-ld', - outputTab: 'expanded', - options: { - processingMode: '', - base: '', - baseUrl: '', - compactArrays: true, - compactToRelative: true, - rdfDirection: '', - safe: '' - }, - // computed - get editorColumns() { - if (['compacted', 'flattened', 'framed'].indexOf(this.outputTab) > -1) { - return 'two column'; + function _deepEqual(obj1, obj2, top = false) { + const isObject1 = obj1 && typeof obj1 === 'object'; + const isObject2 = obj2 && typeof obj2 === 'object'; + if(isObject1 !== isObject2) { + return false; + } + if(!isObject1) { + return obj1 === obj2; + } + const keys1 = Object.keys(obj1); + const keys2 = Object.keys(obj2); + if(keys1.length !== keys2.length) { + return false; + } + for(const k of keys1) { + if(top && (k === 'protected' || k === 'propagate')) { + continue; } - return ''; - }, - get sideDoc() { - if (this.outputTab === 'framed') { - return 'frameDoc'; - } else { - return 'contextDoc'; + if(!_deepEqual(obj1[k], obj2[k])) { + return false; } - }, - get sideEditor() { - if (this.outputTab === 'framed') { - return this.frameEditor; + } + return true; + } + + function _resolveCurie({activeTermMap, context, possibleCurie}) { + if(possibleCurie === undefined || !possibleCurie.includes(':')) { + return possibleCurie; + } + // check for potential CURIE values + const [prefix, ...suffix] = possibleCurie.split(':'); + const prefixDef = context[prefix] ?? activeTermMap.get(prefix); + if(prefixDef === undefined) { + // no CURIE + return possibleCurie; + } + // handle CURIE + const id = typeof prefixDef === 'string' ? prefixDef : prefixDef['@id']; + possibleCurie = id + suffix.join(':'); + return _resolveCurie({activeTermMap, context, possibleCurie}); + } + + function _resolveCuries({activeTermMap, context, newTermMap}) { + for(const [key, def] of newTermMap.entries()) { + const id = def['@id']; + const type = def['@type']; + if(id !== undefined) { + def['@id'] = _resolveCurie({ + activeTermMap, context, possibleCurie: id + }); } else { - return this.contextEditor; + // if `key` is a CURIE/absolute URL, then "@id" can be computed + const resolved = _resolveCurie({ + activeTermMap, context, possibleCurie: key + }); + if(resolved.includes(':')) { + def['@id'] = resolved; + } } - }, - get sideEditorURLFieldPlaceholderText() { - if (this.outputTab === 'framed') { - return 'Frame URL'; - } else { - return 'Context URL'; + if(type !== undefined) { + def['@type'] = _resolveCurie({ + activeTermMap, context, possibleCurie: type + }); } - }, - // methods - async retrieveDoc(_editor, docVar, url) { - try { - const rv = await fetch(url); - if (!rv.ok) { - throw new Error(`HTTP error status: ${rv.status}`); + if(typeof def['@id'] !== 'string') { + throw new CborldError( + 'ERR_INVALID_TERM_DEFINITION', + `Invalid JSON-LD term definition for "${key}"; the "@id" value ` + + 'could not be determined.'); + } + } + } + + function _revertTermMap({activeCtx}) { + const newTermMap = new Map(); + + // keep every propagating term + const {termMap} = activeCtx; + const nonPropagating = []; + for(const [term, def] of termMap) { + if(!def.propagate) { + nonPropagating.push(term); + continue; + } + newTermMap.set(term, def); + } + + // revert every non-propagating term + for(const term of nonPropagating) { + let currentCtx = activeCtx; + let def; + do { + currentCtx = currentCtx.previous; + def = currentCtx.termMap.get(term); + } while(def !== undefined && !def.propagate); + if(def !== undefined) { + newTermMap.set(term, def); + } + } + + return newTermMap; + } + + async function _updateTermMap({ + activeTermMap, contexts = [{}], contextLoader, + propertyScope = false, typeScope = false + }) { + // normalize new contexts to an array + if(!Array.isArray(contexts)) { + contexts = [contexts]; + } + + // set flags based on context scope + const allowProtectedOverride = propertyScope; + const propagateDefault = typeScope ? false : true; + + // load each context + for(let context of contexts) { + // load and get newly resolved context + const entry = await contextLoader.load({context}); + ({context} = entry); + + // clone entry `termMap` for creating new active context + const propagate = context['@propagate'] ?? propagateDefault; + // shallow-copy term definitions and set `propagate` value + const newTermMap = new Map([...entry.termMap.entries()].map( + ([k, v]) => [k, {...v, propagate}])); + + // resolve any CURIE values in definitions + _resolveCuries({activeTermMap, context, newTermMap}); + + // update new terms map based on existing `activeTermMap` + for(const [term, activeDef] of activeTermMap) { + let def = newTermMap.get(term); + if(def !== undefined) { + // disallow overriding of protected terms unless explicitly permitted + if(activeDef.protected) { + if(!allowProtectedOverride && !_deepEqual(def, activeDef, true)) { + throw new CborldError( + 'ERR_PROTECTED_TERM_REDEFINITION', + `Unexpected redefinition of protected term "${term}".`); + } + // ensure `def` remains protected, propagation can change as it + // does not affect protection + def = {...activeDef, propagate: def.propagate}; + } + } else if(context[term] !== null) { + // since `context` does not explictly clear `key`, copy it + newTermMap.set(term, {...activeDef}); } - this[docVar] = await rv.json(); - setEditorValue(_editor, this[docVar]); - // clear the remoteDocURL to avoid confusion around state - this.remoteDocURL = ''; - this.remoteSideDocURL = ''; - } catch (err) { - this.parseError = err.message; } - }, - async loadExample(file) { - const rv = await fetch(`/examples/playground/${file}`); - this.doc = await rv.json(); - setEditorValue(this.mainEditor, this.doc); - // TODO: make this less of a hack...so we can provide other frames - if (file === 'library.jsonld') { - const frame = await fetch(`/examples/playground/library-frame.jsonld`); - this.frameDoc = await frame.json(); - setEditorValue(this.frameEditor, this.frameDoc); - } else { - this.frameDoc = {}; - setEditorValue(this.frameEditor, this.frameDoc); + + // update active term map + activeTermMap = newTermMap; + } + + return activeTermMap; + } + + /*! + * Copyright (c) 2021-2025 Digital Bazaar, Inc. All rights reserved. + */ + + class ContextLoader { + /** + * Creates a new ContextLoader. + * + * @param {object} options - The options to use. + * @param {documentLoaderFunction} options.documentLoader -The document + * loader to use when resolving JSON-LD Context URLs. + * @param {boolean} [options.buildReverseMap=false] - `true` to build a + * reverse map, `false` not to. + */ + constructor({documentLoader, buildReverseMap = false} = {}) { + this.documentLoader = documentLoader; + this.contextMap = new Map(); + this.nextTermId = FIRST_CUSTOM_TERM_ID; + this.termToId = new Map(KEYWORDS_TABLE); + if(buildReverseMap) { + this.idToTerm = reverseMap(KEYWORDS_TABLE); } - this.setOutputTab(this.outputTab); - }, - async setOutputTab(value) { - if (value) this.outputTab = value; - let context = this.contextDoc; - switch (this.outputTab) { - case 'expanded': - // TODO: this should happen elsewhere...like a watcher - try { - const expanded = await jsonld.expand(this.doc, this.options); - setEditorValue(readOnlyEditor, expanded); - this.parseError = ''; - } catch(err) { - this.parseError = err.message; - } - break; - case 'compacted': - if (JSON.stringify(context) === '{}' && '@context' in this.doc) { - // no context set yet, so copy in the main document's - context = { - '@context': this.doc['@context'] - }; - this.contextDoc = context; - } - try { - const compacted = await jsonld.compact(this.doc, {'@context': context['@context'] || {}}, this.options); - setEditorValue(readOnlyEditor, compacted); - this.parseError = ''; - } catch(err) { - this.parseError = err.message; - } - break; - case 'flattened': - if (JSON.stringify(context) === '{}' && '@context' in this.doc) { - // no context set yet, so copy in the main document's - context = { - '@context': this.doc['@context'] - }; - this.contextDoc = context; - } - try { - const flattened = await jsonld.flatten(this.doc, {'@context': context['@context'] || {}}, this.options); - setEditorValue(readOnlyEditor, flattened); - this.parseError = ''; - } catch(err) { - this.parseError = err.message; - } - break; - case 'framed': - try { - const framed = await jsonld.frame(this.doc, this.frameDoc, this.options); - setEditorValue(readOnlyEditor, framed); - this.parseError = ''; - } catch(err) { - this.parseError = err.message; - } - break; - case 'nquads': - // TODO: this should happen elsewhere...like a watcher - try { - const output = await jsonld.toRDF(this.doc, { - format: 'application/n-quads', - ...this.options - }); - setEditorValue(readOnlyEditor, output); - this.parseError = ''; - } catch(err) { - this.parseError = err.message; - } - break; - case 'canonized': - // TODO: this should happen elsewhere...like a watcher - try { - const output = await jsonld.canonize(this.doc, { - format: 'application/n-quads', ...this.options - }); - setEditorValue(readOnlyEditor, output); - this.parseError = ''; - } catch(err) { - this.parseError = err.message; - } - break; - case 'table': - // TODO: this should happen elsewhere...like a watcher - try { - const output = await jsonld.toRDF(this.doc, this.options); - this.tableQuads = output; - this.parseError = ''; - } catch(err) { - this.parseError = err.message; + } + + getIdForTerm({term, plural = false}) { + // check `termToId` table + const id = this.termToId.get(term); + if(id === undefined) { + // return uncompressed `term` as-is + return term; + } + return plural ? id + 1 : id; + } + + getTermForId({id}) { + if(typeof id === 'string') { + // dynamically generate term info for uncompressed term; `plural` + // as `false` will cause the associated value to pass through + // without any valence-related modifications + return {term: id, plural: false}; + } + const plural = (id & 1) === 1; + const term = this.idToTerm.get(plural ? id - 1 : id); + if(term === undefined) { + throw new CborldError( + 'ERR_UNKNOWN_CBORLD_TERM_ID', + `Unknown term ID "${id}" was detected in the CBOR-LD input.`); + } + return {term, plural}; + } + + hasTermId({id}) { + return this.idToTerm.has(id); + } + + async load({context}) { + const entry = this.contextMap.get(context); + if(entry) { + // already loaded, return it + return entry; + } + let ctx = context; + let contextUrl; + if(typeof context === 'string') { + // fetch context + contextUrl = context; + ({'@context': ctx} = await this._getDocument({url: contextUrl})); + } + // FIXME: validate `ctx` to ensure its a valid JSON-LD context value + // add context + return this._addContext({context: ctx, contextUrl}); + } + + async _addContext({context, contextUrl}) { + const {contextMap, termToId, idToTerm} = this; + + // handle `@import` + const importUrl = context['@import']; + if(importUrl) { + let importEntry = contextMap.get(importUrl); + if(!importEntry) { + const {'@context': importCtx} = await this._getDocument({ + url: importUrl + }); + importEntry = await this._addContext({ + context: importCtx, contextUrl: importUrl + }); + } + context = {...importEntry.context, ...context}; + } + + // create context entry + const termMap = new Map(); + const entry = {context, termMap}; + + // process context keys in sorted order to ensure term IDs are assigned + // consistently + const keys = Object.keys(context).sort(); + const isProtected = !!context['@protected']; + for(const key of keys) { + if(KEYWORDS_TABLE.has(key)) { + // skip `KEYWORDS_TABLE` to avoid adding unnecessary term defns + continue; + } + + let def = context[key]; + if(def === null) { + // no term definition + continue; + } + + // normalize definition to an object + if(typeof def === 'string') { + def = {'@id': def}; + } else if(Object.prototype.toString.call(def) !== '[object Object]') { + throw new CborldError( + 'ERR_INVALID_TERM_DEFINITION', + `Invalid JSON-LD term definition for "${key}"; it must be ` + + 'a string or an object.'); + } + + // set term definition + termMap.set(key, {...def, protected: isProtected}); + + // ensure the term has been assigned an ID + if(!termToId.has(key)) { + const id = this.nextTermId; + this.nextTermId += 2; + termToId.set(key, id); + if(idToTerm) { + idToTerm.set(id, key); + } + } + } + + // add entry for context URL or context object + contextMap.set(contextUrl || context, entry); + + return entry; + } + + async _getDocument({url}) { + const {document} = await this.documentLoader(url); + if(typeof document === 'string') { + return JSON.parse(document); + } + return document; + } + } + + /** + * Fetches a resource given a URL and returns it as a string. + * + * @callback documentLoaderFunction + * @param {string} url - The URL to retrieve. + * + * @returns {string} The resource associated with the URL as a string. + */ + + /*! + * Copyright (c) 2021-2025 Digital Bazaar, Inc. All rights reserved. + */ + + class Converter { + /** + * Creates a new Converter for converting CBOR-LD <=> JSON-LD. + * + * @param {object} options - The options to use. + * @param {object} options.strategy - The conversion strategy to use, + * e.g., a compressor or decompressor. + * @param {documentLoaderFunction} options.documentLoader -The document + * loader to use when resolving JSON-LD Context URLs. + * @param {boolean} [options.legacy=false] - True if legacy mode is in + * effect, false if not. + */ + constructor({strategy, documentLoader, legacy = false} = {}) { + this.strategy = strategy; + this.legacy = legacy; + const contextLoader = new ContextLoader({ + documentLoader, buildReverseMap: !!strategy.reverseTypeTable + }); + this.contextLoader = contextLoader; + this.initialActiveCtx = new ActiveContext({contextLoader}); + + // FIXME: consider moving to strategies for better separation of concerns + this.typeTableEncodedAsBytesSet = legacy ? + LEGACY_TYPE_TABLE_ENCODED_AS_BYTES : TYPE_TABLE_ENCODED_AS_BYTES; + } + + /** + * CBOR-LD is a semantic compression format; it uses the contextual + * information provided by JSON-LD contexts to compress JSON-LD objects + * to CBOR-LD (and vice versa). + * + * This `convert()` function is used to either convert from JSON-LD to + * CBOR-LD (compression) or vice versa (decompression). The type of + * conversion (compression or decompression) is called a `strategy`; this + * `strategy` is passed to the `Converter` constructor so it can be used + * during conversion. + * + * When compressing, the conversion maps JSON keys (strings) encountered in + * the JSON-LD object to CBOR-LD IDs (integers) and value encoders. + * + * When decompressing, the conversion maps CBOR-LD IDs (integers) and + * decoded values from value decoders. + * + * A follow-on process can then serialize these abstract outputs to either + * JSON or CBOR by using the `cborg` library. + * + * In order to match each JSON key / CBOR-LD ID with the right context term + * definition, it's important to understand how context is applied in + * JSON-LD, i.e., which context is "active" at a certain place in the input. + * + * There are three ways contexts become active: + * + * 1. Embedded contexts. An embedded context is expressed by using the + * `@context` keyword in a JSON-LD object. It is active on the object + * where it appears and propagates to any nested objects. + * 2. Type-scoped contexts. Such a context is defined in another context + * and bound to a particular type. It will become active based on the + * presence of the `@type` property (or an alias of it) and a matching + * type value. By default, it *does not* propagate to nested objects, + * i.e., it becomes inactive when a nested object does not have a matching + * type. + * 3. Property-scoped contexts. Such a context is defined in another context + * and bound to a particular property. It will become active based on the + * presence of a particular term, i.e., JSON key. By default, it + * propagates, i.e., all terms defined by the context will be applicable + * to the whole subtree associated with the property in the JSON object. + * + * The internal conversion process follows this basic algorithm, which takes + * an input and an output (to be populated): + * + * 1. Converting any contexts in the input (i.e., "embedded contexts") and + * producing an active context for converting other elements in the input. + * Every term in the top-level contexts (excludes scoped-contexts) will be + * auto-assigned CBOR-LD IDs. + * 2. Getting all type information associated with the input and using it + * to update the active context. Any terms from any type-scoped contexts + * will be auto-assigned CBOR-LD IDs. + * 3. For every term => value(s) entry in the input: + * 3.1. Update the active context using any property-scoped contextt + * associated with the term. Any terms in this property-scoped + * context will be auto-assigned CBOR-LD IDs. + * 3.2. Create an array of outputs to be populated from converting + * all of the value(s). + * 3.3. For every value in value(s), perform conversion: + * 3.3.1. If the value is `null`, add it to the outputs as-is and + * continue. + * 3.3.2. Try to use the strategy to convert the value; this involves + * checking the value type and using value-specific codecs, + * only values that must be recursed (e.g., objects and + * arrays) will not be converted by a strategy; add any + * successfully converted value to the outputs and continue. + * 3.2.3. If the value is an array, create an output array and + * recursively convert each of its elements; add the output + * array to the outputs and continue. + * 3.2.4. Create a new output according to the strategy, add it to + * the outputs and recurse with the value as the new input + * and the new output as the new output. + * 3.4. Add an term => value(s) entry to the output using the current term + * information and the outputs as the value(s). + * + * @param {object} options - The options to use. + * @param {Map|object} options.input - The input to convert. + * + * @returns {Promise} - The output. + */ + async convert({input} = {}) { + // handle single or multiple inputs + const isArray = Array.isArray(input); + const inputs = isArray ? input : [input]; + const outputs = []; + // note: could be performed in parallel as long as order is preserved + for(const input of inputs) { + const output = this.strategy.createNewOutput(); + outputs.push(await this._convert({input, output})); + } + return isArray ? outputs : outputs[0]; + } + + async _convert({activeCtx = this.initialActiveCtx, input, output}) { + // convert contexts according to strategy + const {strategy} = this; + activeCtx = await strategy.convertContexts({activeCtx, input, output}); + + // get unique `@type` (and alias) values for the input + const objectTypes = strategy.getObjectTypes({ + activeCtx, input, output, converter: this + }); + + // apply type-scoped contexts + activeCtx = await activeCtx.applyTypeScopedContexts({objectTypes}); + + // walk each term => value(s) input entry to convert them all + const termEntries = strategy.getInputEntries({activeCtx, input}); + for(const [termInfo, value] of termEntries) { + // apply any property-scoped context for `term` to get active context + // to use with each value + const {term} = termInfo; + const valueActiveCtx = await activeCtx.applyPropertyScopedContext({term}); + + // iterate through all values for the current term to produce new outputs + const {plural, def: {'@type': termType}} = termInfo; + const values = plural ? value : [value]; + const outputs = []; + // note: could be performed in parallel as long as order is preserved + for(const value of values) { + outputs.push(await this._convertValue({ + activeCtx: valueActiveCtx, termType, value, termInfo + })); + } + strategy.addOutputEntry({ + termInfo, values: plural ? outputs : outputs[0], output + }); + } + return output; + } + + async _convertValue({activeCtx, termType, value, termInfo}) { + // `null` is never converted + if(value === null) { + return null; + } + // convert value via strategy if possible + let output = this.strategy.convertValue({ + termType, value, termInfo, converter: this + }); + if(output !== undefined) { + return output; + } + if(Array.isArray(value)) { + // recurse into array + const array = value; + const outputs = []; + // note: could be performed in parallel as long as order is preserved + for(const value of array) { + outputs.push(await this._convertValue({ + activeCtx, termType, value, termInfo + })); + } + return outputs; + } + // recurse into object and populate new output + output = this.strategy.createNewOutput(); + return this._convert({activeCtx, input: value, output}); + } + } + + /** + * Fetches a resource given a URL and returns it as a string. + * + * @callback documentLoaderFunction + * @param {string} url - The URL to retrieve. + * + * @returns {string} The resource associated with the URL as a string. + */ + + /*! + * Copyright (c) 2018-2022 Digital Bazaar, Inc. All rights reserved. + */ + /** + * Encode/Decode input according to the "Base64url Encoding" format as specified + * in JSON Web Signature (JWS) RFC7517. A URL safe character set is used and + * trailing '=', line breaks, whitespace, and other characters are omitted. + * + * @module base64url-universal + */ + + const _alphabetIdx = [ + 62, -1, -1, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, + -1, -1, -1, 64, -1, -1, -1, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, + 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, + -1, -1, -1, -1, 63, -1, + 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, + 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 + ]; + + /** + * Decodes input according to the "Base64url Encoding" format as specified + * in JSON Web Signature (JWS) RFC7517. A URL safe character set is used and + * trailing '=', line breaks, whitespace, and other characters are omitted. + * + * @alias module:base64url-universal + * @param {string} input - The data to decode. + * + * @returns {Uint8Array} The decoded value. + */ + function decode$4(input) { + let length = input.length; + const mod4 = length % 4; + if(mod4 === 1) { + throw new Error('Illegal base64 string.'); + } + let diff = 0; + if(mod4 > 0) { + diff = 4 - mod4; + length += diff; + } + + const output = new Uint8Array(length / 4 * 3 - diff); + + let enc1; + let enc2; + let enc3; + let enc4; + let i = 0; + let j = 0; + + while(i < length) { + enc1 = _alphabetIdx[input.charCodeAt(i++) - 45]; + enc2 = _alphabetIdx[input.charCodeAt(i++) - 45]; + + output[j++] = (enc1 << 2) | (enc2 >> 4); + if(i < input.length) { + // can decode at least 2 bytes + enc3 = _alphabetIdx[input.charCodeAt(i++) - 45]; + output[j++] = ((enc2 & 15) << 4) | (enc3 >> 2); + if(i < input.length) { + // can decode 3 bytes + enc4 = _alphabetIdx[input.charCodeAt(i++) - 45]; + output[j++] = ((enc3 & 3) << 6) | enc4; + } + } + } + + return output; + } + + /** + * base64.ts + * + * Licensed under the BSD 3-Clause License. + * http://opensource.org/licenses/BSD-3-Clause + * + * References: + * http://en.wikipedia.org/wiki/Base64 + * + * @author Dan Kogai (https://github.com/dankogai) + */ + const version = '3.7.7'; + /** + * @deprecated use lowercase `version`. + */ + const VERSION = version; + const _hasBuffer = typeof Buffer === 'function'; + const _TD = typeof TextDecoder === 'function' ? new TextDecoder() : undefined; + const _TE = typeof TextEncoder === 'function' ? new TextEncoder() : undefined; + const b64ch = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; + const b64chs = Array.prototype.slice.call(b64ch); + const b64tab = ((a) => { + let tab = {}; + a.forEach((c, i) => tab[c] = i); + return tab; + })(b64chs); + const b64re = /^(?:[A-Za-z\d+\/]{4})*?(?:[A-Za-z\d+\/]{2}(?:==)?|[A-Za-z\d+\/]{3}=?)?$/; + const _fromCC = String.fromCharCode.bind(String); + const _U8Afrom = typeof Uint8Array.from === 'function' + ? Uint8Array.from.bind(Uint8Array) + : (it) => new Uint8Array(Array.prototype.slice.call(it, 0)); + const _mkUriSafe = (src) => src + .replace(/=/g, '').replace(/[+\/]/g, (m0) => m0 == '+' ? '-' : '_'); + const _tidyB64 = (s) => s.replace(/[^A-Za-z0-9\+\/]/g, ''); + /** + * polyfill version of `btoa` + */ + const btoaPolyfill = (bin) => { + // console.log('polyfilled'); + let u32, c0, c1, c2, asc = ''; + const pad = bin.length % 3; + for (let i = 0; i < bin.length;) { + if ((c0 = bin.charCodeAt(i++)) > 255 || + (c1 = bin.charCodeAt(i++)) > 255 || + (c2 = bin.charCodeAt(i++)) > 255) + throw new TypeError('invalid character found'); + u32 = (c0 << 16) | (c1 << 8) | c2; + asc += b64chs[u32 >> 18 & 63] + + b64chs[u32 >> 12 & 63] + + b64chs[u32 >> 6 & 63] + + b64chs[u32 & 63]; + } + return pad ? asc.slice(0, pad - 3) + "===".substring(pad) : asc; + }; + /** + * does what `window.btoa` of web browsers do. + * @param {String} bin binary string + * @returns {string} Base64-encoded string + */ + const _btoa = typeof btoa === 'function' ? (bin) => btoa(bin) + : _hasBuffer ? (bin) => Buffer.from(bin, 'binary').toString('base64') + : btoaPolyfill; + const _fromUint8Array = _hasBuffer + ? (u8a) => Buffer.from(u8a).toString('base64') + : (u8a) => { + // cf. https://stackoverflow.com/questions/12710001/how-to-convert-uint8-array-to-base64-encoded-string/12713326#12713326 + const maxargs = 0x1000; + let strs = []; + for (let i = 0, l = u8a.length; i < l; i += maxargs) { + strs.push(_fromCC.apply(null, u8a.subarray(i, i + maxargs))); + } + return _btoa(strs.join('')); + }; + /** + * converts a Uint8Array to a Base64 string. + * @param {boolean} [urlsafe] URL-and-filename-safe a la RFC4648 §5 + * @returns {string} Base64 string + */ + const fromUint8Array = (u8a, urlsafe = false) => urlsafe ? _mkUriSafe(_fromUint8Array(u8a)) : _fromUint8Array(u8a); + // This trick is found broken https://github.com/dankogai/js-base64/issues/130 + // const utob = (src: string) => unescape(encodeURIComponent(src)); + // reverting good old fationed regexp + const cb_utob = (c) => { + if (c.length < 2) { + var cc = c.charCodeAt(0); + return cc < 0x80 ? c + : cc < 0x800 ? (_fromCC(0xc0 | (cc >>> 6)) + + _fromCC(0x80 | (cc & 0x3f))) + : (_fromCC(0xe0 | ((cc >>> 12) & 0x0f)) + + _fromCC(0x80 | ((cc >>> 6) & 0x3f)) + + _fromCC(0x80 | (cc & 0x3f))); + } + else { + var cc = 0x10000 + + (c.charCodeAt(0) - 0xD800) * 0x400 + + (c.charCodeAt(1) - 0xDC00); + return (_fromCC(0xf0 | ((cc >>> 18) & 0x07)) + + _fromCC(0x80 | ((cc >>> 12) & 0x3f)) + + _fromCC(0x80 | ((cc >>> 6) & 0x3f)) + + _fromCC(0x80 | (cc & 0x3f))); + } + }; + const re_utob = /[\uD800-\uDBFF][\uDC00-\uDFFFF]|[^\x00-\x7F]/g; + /** + * @deprecated should have been internal use only. + * @param {string} src UTF-8 string + * @returns {string} UTF-16 string + */ + const utob = (u) => u.replace(re_utob, cb_utob); + // + const _encode = _hasBuffer + ? (s) => Buffer.from(s, 'utf8').toString('base64') + : _TE + ? (s) => _fromUint8Array(_TE.encode(s)) + : (s) => _btoa(utob(s)); + /** + * converts a UTF-8-encoded string to a Base64 string. + * @param {boolean} [urlsafe] if `true` make the result URL-safe + * @returns {string} Base64 string + */ + const encode$2 = (src, urlsafe = false) => urlsafe + ? _mkUriSafe(_encode(src)) + : _encode(src); + /** + * converts a UTF-8-encoded string to URL-safe Base64 RFC4648 §5. + * @returns {string} Base64 string + */ + const encodeURI = (src) => encode$2(src, true); + // This trick is found broken https://github.com/dankogai/js-base64/issues/130 + // const btou = (src: string) => decodeURIComponent(escape(src)); + // reverting good old fationed regexp + const re_btou = /[\xC0-\xDF][\x80-\xBF]|[\xE0-\xEF][\x80-\xBF]{2}|[\xF0-\xF7][\x80-\xBF]{3}/g; + const cb_btou = (cccc) => { + switch (cccc.length) { + case 4: + var cp = ((0x07 & cccc.charCodeAt(0)) << 18) + | ((0x3f & cccc.charCodeAt(1)) << 12) + | ((0x3f & cccc.charCodeAt(2)) << 6) + | (0x3f & cccc.charCodeAt(3)), offset = cp - 0x10000; + return (_fromCC((offset >>> 10) + 0xD800) + + _fromCC((offset & 0x3FF) + 0xDC00)); + case 3: + return _fromCC(((0x0f & cccc.charCodeAt(0)) << 12) + | ((0x3f & cccc.charCodeAt(1)) << 6) + | (0x3f & cccc.charCodeAt(2))); + default: + return _fromCC(((0x1f & cccc.charCodeAt(0)) << 6) + | (0x3f & cccc.charCodeAt(1))); + } + }; + /** + * @deprecated should have been internal use only. + * @param {string} src UTF-16 string + * @returns {string} UTF-8 string + */ + const btou = (b) => b.replace(re_btou, cb_btou); + /** + * polyfill version of `atob` + */ + const atobPolyfill = (asc) => { + // console.log('polyfilled'); + asc = asc.replace(/\s+/g, ''); + if (!b64re.test(asc)) + throw new TypeError('malformed base64.'); + asc += '=='.slice(2 - (asc.length & 3)); + let u24, bin = '', r1, r2; + for (let i = 0; i < asc.length;) { + u24 = b64tab[asc.charAt(i++)] << 18 + | b64tab[asc.charAt(i++)] << 12 + | (r1 = b64tab[asc.charAt(i++)]) << 6 + | (r2 = b64tab[asc.charAt(i++)]); + bin += r1 === 64 ? _fromCC(u24 >> 16 & 255) + : r2 === 64 ? _fromCC(u24 >> 16 & 255, u24 >> 8 & 255) + : _fromCC(u24 >> 16 & 255, u24 >> 8 & 255, u24 & 255); + } + return bin; + }; + /** + * does what `window.atob` of web browsers do. + * @param {String} asc Base64-encoded string + * @returns {string} binary string + */ + const _atob = typeof atob === 'function' ? (asc) => atob(_tidyB64(asc)) + : _hasBuffer ? (asc) => Buffer.from(asc, 'base64').toString('binary') + : atobPolyfill; + // + const _toUint8Array = _hasBuffer + ? (a) => _U8Afrom(Buffer.from(a, 'base64')) + : (a) => _U8Afrom(_atob(a).split('').map(c => c.charCodeAt(0))); + /** + * converts a Base64 string to a Uint8Array. + */ + const toUint8Array = (a) => _toUint8Array(_unURI(a)); + // + const _decode = _hasBuffer + ? (a) => Buffer.from(a, 'base64').toString('utf8') + : _TD + ? (a) => _TD.decode(_toUint8Array(a)) + : (a) => btou(_atob(a)); + const _unURI = (a) => _tidyB64(a.replace(/[-_]/g, (m0) => m0 == '-' ? '+' : '/')); + /** + * converts a Base64 string to a UTF-8 string. + * @param {String} src Base64 string. Both normal and URL-safe are supported + * @returns {string} UTF-8 string + */ + const decode$3 = (src) => _decode(_unURI(src)); + /** + * check if a value is a valid Base64 string + * @param {String} src a value to check + */ + const isValid = (src) => { + if (typeof src !== 'string') + return false; + const s = src.replace(/\s+/g, '').replace(/={0,2}$/, ''); + return !/[^\s0-9a-zA-Z\+/]/.test(s) || !/[^\s0-9a-zA-Z\-_]/.test(s); + }; + // + const _noEnum = (v) => { + return { + value: v, enumerable: false, writable: true, configurable: true + }; + }; + /** + * extend String.prototype with relevant methods + */ + const extendString = function () { + const _add = (name, body) => Object.defineProperty(String.prototype, name, _noEnum(body)); + _add('fromBase64', function () { return decode$3(this); }); + _add('toBase64', function (urlsafe) { return encode$2(this, urlsafe); }); + _add('toBase64URI', function () { return encode$2(this, true); }); + _add('toBase64URL', function () { return encode$2(this, true); }); + _add('toUint8Array', function () { return toUint8Array(this); }); + }; + /** + * extend Uint8Array.prototype with relevant methods + */ + const extendUint8Array = function () { + const _add = (name, body) => Object.defineProperty(Uint8Array.prototype, name, _noEnum(body)); + _add('toBase64', function (urlsafe) { return fromUint8Array(this, urlsafe); }); + _add('toBase64URI', function () { return fromUint8Array(this, true); }); + _add('toBase64URL', function () { return fromUint8Array(this, true); }); + }; + /** + * extend Builtin prototypes with relevant methods + */ + const extendBuiltins = () => { + extendString(); + extendUint8Array(); + }; + const gBase64 = { + version: version, + VERSION: VERSION, + atob: _atob, + atobPolyfill: atobPolyfill, + btoa: _btoa, + btoaPolyfill: btoaPolyfill, + fromBase64: decode$3, + toBase64: encode$2, + encode: encode$2, + encodeURI: encodeURI, + encodeURL: encodeURI, + utob: utob, + btou: btou, + decode: decode$3, + isValid: isValid, + fromUint8Array: fromUint8Array, + toUint8Array: toUint8Array, + extendString: extendString, + extendUint8Array: extendUint8Array, + extendBuiltins: extendBuiltins + }; + + /** + * Base-N/Base-X encoding/decoding functions. + * + * Original implementation from base-x: + * https://github.com/cryptocoinjs/base-x + * + * Which is MIT licensed: + * + * The MIT License (MIT) + * + * Copyright base-x contributors (c) 2016 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + // baseN alphabet indexes + const _reverseAlphabets = {}; + + /** + * Decodes a baseN-encoded (using the given alphabet) string to a + * Uint8Array. + * + * @param {string} input - The baseN-encoded input string. + * @param {string} alphabet - The alphabet to use for decoding. + * + * @returns {Uint8Array} The decoded bytes in a Uint8Array. + */ + function decode$2(input, alphabet) { + if(typeof input !== 'string') { + throw new TypeError('"input" must be a string.'); + } + if(input.length === 0) { + return new Uint8Array(); + } + + let table = _reverseAlphabets[alphabet]; + if(!table) { + // compute reverse alphabet + table = _reverseAlphabets[alphabet] = []; + for(let i = 0; i < alphabet.length; ++i) { + table[alphabet.charCodeAt(i)] = i; + } + } + + // remove whitespace characters + input = input.replace(/\s/g, ''); + + const base = alphabet.length; + const first = alphabet.charAt(0); + const bytes = [0]; + for(let i = 0; i < input.length; i++) { + const value = table[input.charCodeAt(i)]; + if(value === undefined) { + return; + } + + let carry = value; + for(let j = 0; j < bytes.length; ++j) { + carry += bytes[j] * base; + bytes[j] = carry & 0xff; + carry >>= 8; + } + + while(carry > 0) { + bytes.push(carry & 0xff); + carry >>= 8; + } + } + + // deal with leading zeros + for(let k = 0; input[k] === first && k < input.length - 1; ++k) { + bytes.push(0); + } + + return new Uint8Array(bytes.reverse()); + } + + /*! + * Copyright (c) 2019-2022 Digital Bazaar, Inc. All rights reserved. + */ + + // base58 characters (Bitcoin alphabet) + const alphabet = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'; + + function decode$1(input) { + return decode$2(input, alphabet); + } + + var REGEX = /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$/i; + + function validate(uuid) { + return typeof uuid === 'string' && REGEX.test(uuid); + } + + function parse(uuid) { + if (!validate(uuid)) { + throw TypeError('Invalid UUID'); + } + var v; + var arr = new Uint8Array(16); + + // Parse ########-....-....-....-............ + arr[0] = (v = parseInt(uuid.slice(0, 8), 16)) >>> 24; + arr[1] = v >>> 16 & 0xff; + arr[2] = v >>> 8 & 0xff; + arr[3] = v & 0xff; + + // Parse ........-####-....-....-............ + arr[4] = (v = parseInt(uuid.slice(9, 13), 16)) >>> 8; + arr[5] = v & 0xff; + + // Parse ........-....-####-....-............ + arr[6] = (v = parseInt(uuid.slice(14, 18), 16)) >>> 8; + arr[7] = v & 0xff; + + // Parse ........-....-....-####-............ + arr[8] = (v = parseInt(uuid.slice(19, 23), 16)) >>> 8; + arr[9] = v & 0xff; + + // Parse ........-....-....-....-############ + // (Use "/" to avoid 32-bit truncation when bit-shifting high-order bytes) + arr[10] = (v = parseInt(uuid.slice(24, 36), 16)) / 0x10000000000 & 0xff; + arr[11] = v / 0x100000000 & 0xff; + arr[12] = v >>> 24 & 0xff; + arr[13] = v >>> 16 & 0xff; + arr[14] = v >>> 8 & 0xff; + arr[15] = v & 0xff; + return arr; + } + + /*! + * Copyright (c) 2021-2024 Digital Bazaar, Inc. All rights reserved. + */ + + KEYWORDS_TABLE.get('@context'); + + // browser support + /* eslint-env browser */ + /* eslint-disable-next-line no-unused-vars */ + function inspect(data, options) { + return JSON.stringify(data, null, 2); + } + + // This is an unfortunate replacement for @sindresorhus/is that we need to + // re-implement for performance purposes. In particular the is.observable() + // check is expensive, and unnecessary for our purposes. The values returned + // are compatible with @sindresorhus/is, however. + + const typeofs = [ + 'string', + 'number', + 'bigint', + 'symbol' + ]; + + const objectTypeNames = [ + 'Function', + 'Generator', + 'AsyncGenerator', + 'GeneratorFunction', + 'AsyncGeneratorFunction', + 'AsyncFunction', + 'Observable', + 'Array', + 'Buffer', + 'Object', + 'RegExp', + 'Date', + 'Error', + 'Map', + 'Set', + 'WeakMap', + 'WeakSet', + 'ArrayBuffer', + 'SharedArrayBuffer', + 'DataView', + 'Promise', + 'URL', + 'HTMLElement', + 'Int8Array', + 'Uint8Array', + 'Uint8ClampedArray', + 'Int16Array', + 'Uint16Array', + 'Int32Array', + 'Uint32Array', + 'Float32Array', + 'Float64Array', + 'BigInt64Array', + 'BigUint64Array' + ]; + + /** + * @param {any} value + * @returns {string} + */ + function is (value) { + if (value === null) { + return 'null' + } + if (value === undefined) { + return 'undefined' + } + if (value === true || value === false) { + return 'boolean' + } + const typeOf = typeof value; + if (typeofs.includes(typeOf)) { + return typeOf + } + /* c8 ignore next 4 */ + // not going to bother testing this, it's not going to be valid anyway + if (typeOf === 'function') { + return 'Function' + } + if (Array.isArray(value)) { + return 'Array' + } + if (isBuffer$1(value)) { + return 'Buffer' + } + const objectType = getObjectType(value); + if (objectType) { + return objectType + } + /* c8 ignore next */ + return 'Object' + } + + /** + * @param {any} value + * @returns {boolean} + */ + function isBuffer$1 (value) { + return value && value.constructor && value.constructor.isBuffer && value.constructor.isBuffer.call(null, value) + } + + /** + * @param {any} value + * @returns {string|undefined} + */ + function getObjectType (value) { + const objectTypeName = Object.prototype.toString.call(value).slice(8, -1); + if (objectTypeNames.includes(objectTypeName)) { + return objectTypeName + } + /* c8 ignore next */ + return undefined + } + + class Type { + /** + * @param {number} major + * @param {string} name + * @param {boolean} terminal + */ + constructor (major, name, terminal) { + this.major = major; + this.majorEncoded = major << 5; + this.name = name; + this.terminal = terminal; + } + + /* c8 ignore next 3 */ + toString () { + return `Type[${this.major}].${this.name}` + } + + /** + * @param {Type} typ + * @returns {number} + */ + compare (typ) { + /* c8 ignore next 1 */ + return this.major < typ.major ? -1 : this.major > typ.major ? 1 : 0 + } + } + + // convert to static fields when better supported + Type.uint = new Type(0, 'uint', true); + Type.negint = new Type(1, 'negint', true); + Type.bytes = new Type(2, 'bytes', true); + Type.string = new Type(3, 'string', true); + Type.array = new Type(4, 'array', false); + Type.map = new Type(5, 'map', false); + Type.tag = new Type(6, 'tag', false); // terminal? + Type.float = new Type(7, 'float', true); + Type.false = new Type(7, 'false', true); + Type.true = new Type(7, 'true', true); + Type.null = new Type(7, 'null', true); + Type.undefined = new Type(7, 'undefined', true); + Type.break = new Type(7, 'break', true); + // Type.indefiniteLength = new Type(0, 'indefiniteLength', true) + + class Token { + /** + * @param {Type} type + * @param {any} [value] + * @param {number} [encodedLength] + */ + constructor (type, value, encodedLength) { + this.type = type; + this.value = value; + this.encodedLength = encodedLength; + /** @type {Uint8Array|undefined} */ + this.encodedBytes = undefined; + /** @type {Uint8Array|undefined} */ + this.byteValue = undefined; + } + + /* c8 ignore next 3 */ + toString () { + return `Token[${this.type}].${this.value}` + } + } + + // Use Uint8Array directly in the browser, use Buffer in Node.js but don't + // speak its name directly to avoid bundlers pulling in the `Buffer` polyfill + + // @ts-ignore + const useBuffer = globalThis.process && + // @ts-ignore + !globalThis.process.browser && + // @ts-ignore + globalThis.Buffer && + // @ts-ignore + typeof globalThis.Buffer.isBuffer === 'function'; + + new TextDecoder(); + const textEncoder = new TextEncoder(); + + /** + * @param {Uint8Array} buf + * @returns {boolean} + */ + function isBuffer (buf) { + // @ts-ignore + return useBuffer && globalThis.Buffer.isBuffer(buf) + } + + /** + * @param {Uint8Array|number[]} buf + * @returns {Uint8Array} + */ + function asU8A (buf) { + /* c8 ignore next */ + if (!(buf instanceof Uint8Array)) { + return Uint8Array.from(buf) + } + return isBuffer(buf) ? new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength) : buf + } + + const fromString = useBuffer + ? // eslint-disable-line operator-linebreak + /** + * @param {string} string + */ + (string) => { + return string.length > 64 + ? // eslint-disable-line operator-linebreak + // @ts-ignore + globalThis.Buffer.from(string) + : utf8ToBytes(string) + } + /* c8 ignore next 7 */ + : // eslint-disable-line operator-linebreak + /** + * @param {string} string + */ + (string) => { + return string.length > 64 ? textEncoder.encode(string) : utf8ToBytes(string) + }; + + /** + * Buffer variant not fast enough for what we need + * @param {number[]} arr + * @returns {Uint8Array} + */ + const fromArray = (arr) => { + return Uint8Array.from(arr) + }; + + const slice = useBuffer + ? // eslint-disable-line operator-linebreak + /** + * @param {Uint8Array} bytes + * @param {number} start + * @param {number} end + */ + (bytes, start, end) => { + if (isBuffer(bytes)) { + return new Uint8Array(bytes.subarray(start, end)) + } + return bytes.slice(start, end) + } + /* c8 ignore next 9 */ + : // eslint-disable-line operator-linebreak + /** + * @param {Uint8Array} bytes + * @param {number} start + * @param {number} end + */ + (bytes, start, end) => { + return bytes.slice(start, end) + }; + + const concat = useBuffer + ? // eslint-disable-line operator-linebreak + /** + * @param {Uint8Array[]} chunks + * @param {number} length + * @returns {Uint8Array} + */ + (chunks, length) => { + // might get a stray plain Array here + /* c8 ignore next 1 */ + chunks = chunks.map((c) => c instanceof Uint8Array + ? c + // this case is occasionally missed during test runs so becomes coverage-flaky + /* c8 ignore next 4 */ + : // eslint-disable-line operator-linebreak + // @ts-ignore + globalThis.Buffer.from(c)); + // @ts-ignore + return asU8A(globalThis.Buffer.concat(chunks, length)) + } + /* c8 ignore next 19 */ + : // eslint-disable-line operator-linebreak + /** + * @param {Uint8Array[]} chunks + * @param {number} length + * @returns {Uint8Array} + */ + (chunks, length) => { + const out = new Uint8Array(length); + let off = 0; + for (let b of chunks) { + if (off + b.length > out.length) { + // final chunk that's bigger than we need + b = b.subarray(0, out.length - off); + } + out.set(b, off); + off += b.length; + } + return out + }; + + const alloc = useBuffer + ? // eslint-disable-line operator-linebreak + /** + * @param {number} size + * @returns {Uint8Array} + */ + (size) => { + // we always write over the contents we expose so this should be safe + // @ts-ignore + return globalThis.Buffer.allocUnsafe(size) + } + /* c8 ignore next 8 */ + : // eslint-disable-line operator-linebreak + /** + * @param {number} size + * @returns {Uint8Array} + */ + (size) => { + return new Uint8Array(size) + }; + + /** + * @param {Uint8Array} b1 + * @param {Uint8Array} b2 + * @returns {number} + */ + function compare (b1, b2) { + /* c8 ignore next 5 */ + if (isBuffer(b1) && isBuffer(b2)) { + // probably not possible to get here in the current API + // @ts-ignore Buffer + return b1.compare(b2) + } + for (let i = 0; i < b1.length; i++) { + if (b1[i] === b2[i]) { + continue + } + return b1[i] < b2[i] ? -1 : 1 + } /* c8 ignore next 3 */ + return 0 + } + + // The below code is taken from https://github.com/google/closure-library/blob/8598d87242af59aac233270742c8984e2b2bdbe0/closure/goog/crypt/crypt.js#L117-L143 + // Licensed Apache-2.0. + + /** + * @param {string} str + * @returns {number[]} + */ + function utf8ToBytes (str) { + const out = []; + let p = 0; + for (let i = 0; i < str.length; i++) { + let c = str.charCodeAt(i); + if (c < 128) { + out[p++] = c; + } else if (c < 2048) { + out[p++] = (c >> 6) | 192; + out[p++] = (c & 63) | 128; + } else if ( + ((c & 0xFC00) === 0xD800) && (i + 1) < str.length && + ((str.charCodeAt(i + 1) & 0xFC00) === 0xDC00)) { + // Surrogate Pair + c = 0x10000 + ((c & 0x03FF) << 10) + (str.charCodeAt(++i) & 0x03FF); + out[p++] = (c >> 18) | 240; + out[p++] = ((c >> 12) & 63) | 128; + out[p++] = ((c >> 6) & 63) | 128; + out[p++] = (c & 63) | 128; + } else { + out[p++] = (c >> 12) | 224; + out[p++] = ((c >> 6) & 63) | 128; + out[p++] = (c & 63) | 128; + } + } + return out + } + + /** + * Bl is a list of byte chunks, similar to https://github.com/rvagg/bl but for + * writing rather than reading. + * A Bl object accepts set() operations for individual bytes and copyTo() for + * inserting byte arrays. These write operations don't automatically increment + * the internal cursor so its "length" won't be changed. Instead, increment() + * must be called to extend its length to cover the inserted data. + * The toBytes() call will convert all internal memory to a single Uint8Array of + * the correct length, truncating any data that is stored but hasn't been + * included by an increment(). + * get() can retrieve a single byte. + * All operations (except toBytes()) take an "offset" argument that will perform + * the write at the offset _from the current cursor_. For most operations this + * will be `0` to write at the current cursor position but it can be ahead of + * the current cursor. Negative offsets probably work but are untested. + */ + + + // the ts-ignores in this file are almost all for the `Uint8Array|number[]` duality that exists + // for perf reasons. Consider better approaches to this or removing it entirely, it is quite + // risky because of some assumptions about small chunks === number[] and everything else === Uint8Array. + + const defaultChunkSize = 256; + + class Bl { + /** + * @param {number} [chunkSize] + */ + constructor (chunkSize = defaultChunkSize) { + this.chunkSize = chunkSize; + /** @type {number} */ + this.cursor = 0; + /** @type {number} */ + this.maxCursor = -1; + /** @type {(Uint8Array|number[])[]} */ + this.chunks = []; + // keep the first chunk around if we can to save allocations for future encodes + /** @type {Uint8Array|number[]|null} */ + this._initReuseChunk = null; + } + + reset () { + this.cursor = 0; + this.maxCursor = -1; + if (this.chunks.length) { + this.chunks = []; + } + if (this._initReuseChunk !== null) { + this.chunks.push(this._initReuseChunk); + this.maxCursor = this._initReuseChunk.length - 1; + } + } + + /** + * @param {Uint8Array|number[]} bytes + */ + push (bytes) { + let topChunk = this.chunks[this.chunks.length - 1]; + const newMax = this.cursor + bytes.length; + if (newMax <= this.maxCursor + 1) { + // we have at least one chunk and we can fit these bytes into that chunk + const chunkPos = topChunk.length - (this.maxCursor - this.cursor) - 1; + // @ts-ignore + topChunk.set(bytes, chunkPos); + } else { + // can't fit it in + if (topChunk) { + // trip the last chunk to `cursor` if we need to + const chunkPos = topChunk.length - (this.maxCursor - this.cursor) - 1; + if (chunkPos < topChunk.length) { + // @ts-ignore + this.chunks[this.chunks.length - 1] = topChunk.subarray(0, chunkPos); + this.maxCursor = this.cursor - 1; + } + } + if (bytes.length < 64 && bytes.length < this.chunkSize) { + // make a new chunk and copy the new one into it + topChunk = alloc(this.chunkSize); + this.chunks.push(topChunk); + this.maxCursor += topChunk.length; + if (this._initReuseChunk === null) { + this._initReuseChunk = topChunk; + } + // @ts-ignore + topChunk.set(bytes, 0); + } else { + // push the new bytes in as its own chunk + this.chunks.push(bytes); + this.maxCursor += bytes.length; + } + } + this.cursor += bytes.length; + } + + /** + * @param {boolean} [reset] + * @returns {Uint8Array} + */ + toBytes (reset = false) { + let byts; + if (this.chunks.length === 1) { + const chunk = this.chunks[0]; + if (reset && this.cursor > chunk.length / 2) { + /* c8 ignore next 2 */ + // @ts-ignore + byts = this.cursor === chunk.length ? chunk : chunk.subarray(0, this.cursor); + this._initReuseChunk = null; + this.chunks = []; + } else { + // @ts-ignore + byts = slice(chunk, 0, this.cursor); + } + } else { + // @ts-ignore + byts = concat(this.chunks, this.cursor); + } + if (reset) { + this.reset(); + } + return byts + } + } + + const decodeErrPrefix = 'CBOR decode error:'; + const encodeErrPrefix = 'CBOR encode error:'; + + /* globals BigInt */ + + + const uintBoundaries = [24, 256, 65536, 4294967296, BigInt('18446744073709551616')]; + + /** + * @param {Bl} buf + * @param {Token} token + */ + function encodeUint (buf, token) { + return encodeUintValue(buf, 0, token.value) + } + + /** + * @param {Bl} buf + * @param {number} major + * @param {number|bigint} uint + */ + function encodeUintValue (buf, major, uint) { + if (uint < uintBoundaries[0]) { + const nuint = Number(uint); + // pack into one byte, minor=0, additional=value + buf.push([major | nuint]); + } else if (uint < uintBoundaries[1]) { + const nuint = Number(uint); + // pack into two byte, minor=0, additional=24 + buf.push([major | 24, nuint]); + } else if (uint < uintBoundaries[2]) { + const nuint = Number(uint); + // pack into three byte, minor=0, additional=25 + buf.push([major | 25, nuint >>> 8, nuint & 0xff]); + } else if (uint < uintBoundaries[3]) { + const nuint = Number(uint); + // pack into five byte, minor=0, additional=26 + buf.push([major | 26, (nuint >>> 24) & 0xff, (nuint >>> 16) & 0xff, (nuint >>> 8) & 0xff, nuint & 0xff]); + } else { + const buint = BigInt(uint); + if (buint < uintBoundaries[4]) { + // pack into nine byte, minor=0, additional=27 + const set = [major | 27, 0, 0, 0, 0, 0, 0, 0]; + // simulate bitwise above 32 bits + let lo = Number(buint & BigInt(0xffffffff)); + let hi = Number(buint >> BigInt(32) & BigInt(0xffffffff)); + set[8] = lo & 0xff; + lo = lo >> 8; + set[7] = lo & 0xff; + lo = lo >> 8; + set[6] = lo & 0xff; + lo = lo >> 8; + set[5] = lo & 0xff; + set[4] = hi & 0xff; + hi = hi >> 8; + set[3] = hi & 0xff; + hi = hi >> 8; + set[2] = hi & 0xff; + hi = hi >> 8; + set[1] = hi & 0xff; + buf.push(set); + } else { + throw new Error(`${decodeErrPrefix} encountered BigInt larger than allowable range`) + } + } + } + + /** + * @param {Token} token + * @returns {number} + */ + encodeUint.encodedSize = function encodedSize (token) { + return encodeUintValue.encodedSize(token.value) + }; + + /** + * @param {number} uint + * @returns {number} + */ + encodeUintValue.encodedSize = function encodedSize (uint) { + if (uint < uintBoundaries[0]) { + return 1 + } + if (uint < uintBoundaries[1]) { + return 2 + } + if (uint < uintBoundaries[2]) { + return 3 + } + if (uint < uintBoundaries[3]) { + return 5 + } + return 9 + }; + + /** + * @param {Token} tok1 + * @param {Token} tok2 + * @returns {number} + */ + encodeUint.compareTokens = function compareTokens (tok1, tok2) { + return tok1.value < tok2.value ? -1 : tok1.value > tok2.value ? 1 : /* c8 ignore next */ 0 + }; + + /* eslint-env es2020 */ + + + const neg1b = BigInt(-1); + const pos1b = BigInt(1); + + /** + * @param {Bl} buf + * @param {Token} token + */ + function encodeNegint (buf, token) { + const negint = token.value; + const unsigned = (typeof negint === 'bigint' ? (negint * neg1b - pos1b) : (negint * -1 - 1)); + encodeUintValue(buf, token.type.majorEncoded, unsigned); + } + + /** + * @param {Token} token + * @returns {number} + */ + encodeNegint.encodedSize = function encodedSize (token) { + const negint = token.value; + const unsigned = (typeof negint === 'bigint' ? (negint * neg1b - pos1b) : (negint * -1 - 1)); + /* c8 ignore next 4 */ + // handled by quickEncode, we shouldn't get here but it's included for completeness + if (unsigned < uintBoundaries[0]) { + return 1 + } + if (unsigned < uintBoundaries[1]) { + return 2 + } + if (unsigned < uintBoundaries[2]) { + return 3 + } + if (unsigned < uintBoundaries[3]) { + return 5 + } + return 9 + }; + + /** + * @param {Token} tok1 + * @param {Token} tok2 + * @returns {number} + */ + encodeNegint.compareTokens = function compareTokens (tok1, tok2) { + // opposite of the uint comparison since we store the uint version in bytes + return tok1.value < tok2.value ? 1 : tok1.value > tok2.value ? -1 : /* c8 ignore next */ 0 + }; + + /** + * `encodedBytes` allows for caching when we do a byte version of a string + * for key sorting purposes + * @param {Token} token + * @returns {Uint8Array} + */ + function tokenBytes (token) { + if (token.encodedBytes === undefined) { + token.encodedBytes = token.type === Type.string ? fromString(token.value) : token.value; + } + // @ts-ignore c'mon + return token.encodedBytes + } + + /** + * @param {Bl} buf + * @param {Token} token + */ + function encodeBytes (buf, token) { + const bytes = tokenBytes(token); + encodeUintValue(buf, token.type.majorEncoded, bytes.length); + buf.push(bytes); + } + + /** + * @param {Token} token + * @returns {number} + */ + encodeBytes.encodedSize = function encodedSize (token) { + const bytes = tokenBytes(token); + return encodeUintValue.encodedSize(bytes.length) + bytes.length + }; + + /** + * @param {Token} tok1 + * @param {Token} tok2 + * @returns {number} + */ + encodeBytes.compareTokens = function compareTokens (tok1, tok2) { + return compareBytes(tokenBytes(tok1), tokenBytes(tok2)) + }; + + /** + * @param {Uint8Array} b1 + * @param {Uint8Array} b2 + * @returns {number} + */ + function compareBytes (b1, b2) { + return b1.length < b2.length ? -1 : b1.length > b2.length ? 1 : compare(b1, b2) + } + + const encodeString = encodeBytes; + + /** + * @param {Bl} buf + * @param {Token} token + */ + function encodeArray (buf, token) { + encodeUintValue(buf, Type.array.majorEncoded, token.value); + } + + // using an array as a map key, are you sure about this? we can only sort + // by map length here, it's up to the encoder to decide to look deeper + encodeArray.compareTokens = encodeUint.compareTokens; + + /** + * @param {Token} token + * @returns {number} + */ + encodeArray.encodedSize = function encodedSize (token) { + return encodeUintValue.encodedSize(token.value) + }; + + /** + * @param {Bl} buf + * @param {Token} token + */ + function encodeMap (buf, token) { + encodeUintValue(buf, Type.map.majorEncoded, token.value); + } + + // using a map as a map key, are you sure about this? we can only sort + // by map length here, it's up to the encoder to decide to look deeper + encodeMap.compareTokens = encodeUint.compareTokens; + + /** + * @param {Token} token + * @returns {number} + */ + encodeMap.encodedSize = function encodedSize (token) { + return encodeUintValue.encodedSize(token.value) + }; + + /** + * @param {Bl} buf + * @param {Token} token + */ + function encodeTag (buf, token) { + encodeUintValue(buf, Type.tag.majorEncoded, token.value); + } + + encodeTag.compareTokens = encodeUint.compareTokens; + + /** + * @param {Token} token + * @returns {number} + */ + encodeTag.encodedSize = function encodedSize (token) { + return encodeUintValue.encodedSize(token.value) + }; + + // TODO: shift some of the bytes logic to bytes-utils so we can use Buffer + // where possible + + + /** + * @typedef {import('./bl.js').Bl} Bl + * @typedef {import('../interface').DecodeOptions} DecodeOptions + * @typedef {import('../interface').EncodeOptions} EncodeOptions + */ + + const MINOR_FALSE = 20; + const MINOR_TRUE = 21; + const MINOR_NULL = 22; + const MINOR_UNDEFINED = 23; + + /** + * @param {Bl} buf + * @param {Token} token + * @param {EncodeOptions} options + */ + function encodeFloat (buf, token, options) { + const float = token.value; + + if (float === false) { + buf.push([Type.float.majorEncoded | MINOR_FALSE]); + } else if (float === true) { + buf.push([Type.float.majorEncoded | MINOR_TRUE]); + } else if (float === null) { + buf.push([Type.float.majorEncoded | MINOR_NULL]); + } else if (float === undefined) { + buf.push([Type.float.majorEncoded | MINOR_UNDEFINED]); + } else { + let decoded; + let success = false; + if (!options || options.float64 !== true) { + encodeFloat16(float); + decoded = readFloat16(ui8a, 1); + if (float === decoded || Number.isNaN(float)) { + ui8a[0] = 0xf9; + buf.push(ui8a.slice(0, 3)); + success = true; + } else { + encodeFloat32(float); + decoded = readFloat32(ui8a, 1); + if (float === decoded) { + ui8a[0] = 0xfa; + buf.push(ui8a.slice(0, 5)); + success = true; + } + } + } + if (!success) { + encodeFloat64(float); + decoded = readFloat64(ui8a, 1); + ui8a[0] = 0xfb; + buf.push(ui8a.slice(0, 9)); + } + } + } + + /** + * @param {Token} token + * @param {EncodeOptions} options + * @returns {number} + */ + encodeFloat.encodedSize = function encodedSize (token, options) { + const float = token.value; + + if (float === false || float === true || float === null || float === undefined) { + return 1 + } + + if (!options || options.float64 !== true) { + encodeFloat16(float); + let decoded = readFloat16(ui8a, 1); + if (float === decoded || Number.isNaN(float)) { + return 3 + } + encodeFloat32(float); + decoded = readFloat32(ui8a, 1); + if (float === decoded) { + return 5 + } + } + return 9 + }; + + const buffer = new ArrayBuffer(9); + const dataView = new DataView(buffer, 1); + const ui8a = new Uint8Array(buffer, 0); + + /** + * @param {number} inp + */ + function encodeFloat16 (inp) { + if (inp === Infinity) { + dataView.setUint16(0, 0x7c00, false); + } else if (inp === -Infinity) { + dataView.setUint16(0, 0xfc00, false); + } else if (Number.isNaN(inp)) { + dataView.setUint16(0, 0x7e00, false); + } else { + dataView.setFloat32(0, inp); + const valu32 = dataView.getUint32(0); + const exponent = (valu32 & 0x7f800000) >> 23; + const mantissa = valu32 & 0x7fffff; + + /* c8 ignore next 6 */ + if (exponent === 0xff) { + // too big, Infinity, but this should be hard (impossible?) to trigger + dataView.setUint16(0, 0x7c00, false); + } else if (exponent === 0x00) { + // 0.0, -0.0 and subnormals, shouldn't be possible to get here because 0.0 should be counted as an int + dataView.setUint16(0, ((inp & 0x80000000) >> 16) | (mantissa >> 13), false); + } else { // standard numbers + // chunks of logic here borrowed from https://github.com/PJK/libcbor/blob/c78f437182533e3efa8d963ff4b945bb635c2284/src/cbor/encoding.c#L127 + const logicalExponent = exponent - 127; + // Now we know that 2^exponent <= 0 logically + /* c8 ignore next 6 */ + if (logicalExponent < -24) { + /* No unambiguous representation exists, this float is not a half float + and is too small to be represented using a half, round off to zero. + Consistent with the reference implementation. */ + // should be difficult (impossible?) to get here in JS + dataView.setUint16(0, 0); + } else if (logicalExponent < -14) { + /* Offset the remaining decimal places by shifting the significand, the + value is lost. This is an implementation decision that works around the + absence of standard half-float in the language. */ + dataView.setUint16(0, ((valu32 & 0x80000000) >> 16) | /* sign bit */ (1 << (24 + logicalExponent)), false); + } else { + dataView.setUint16(0, ((valu32 & 0x80000000) >> 16) | ((logicalExponent + 15) << 10) | (mantissa >> 13), false); + } + } + } + } + + /** + * @param {Uint8Array} ui8a + * @param {number} pos + * @returns {number} + */ + function readFloat16 (ui8a, pos) { + if (ui8a.length - pos < 2) { + throw new Error(`${decodeErrPrefix} not enough data for float16`) + } + + const half = (ui8a[pos] << 8) + ui8a[pos + 1]; + if (half === 0x7c00) { + return Infinity + } + if (half === 0xfc00) { + return -Infinity + } + if (half === 0x7e00) { + return NaN + } + const exp = (half >> 10) & 0x1f; + const mant = half & 0x3ff; + let val; + if (exp === 0) { + val = mant * (2 ** -24); + } else if (exp !== 31) { + val = (mant + 1024) * (2 ** (exp - 25)); + /* c8 ignore next 4 */ + } else { + // may not be possible to get here + val = mant === 0 ? Infinity : NaN; + } + return (half & 0x8000) ? -val : val + } + + /** + * @param {number} inp + */ + function encodeFloat32 (inp) { + dataView.setFloat32(0, inp, false); + } + + /** + * @param {Uint8Array} ui8a + * @param {number} pos + * @returns {number} + */ + function readFloat32 (ui8a, pos) { + if (ui8a.length - pos < 4) { + throw new Error(`${decodeErrPrefix} not enough data for float32`) + } + const offset = (ui8a.byteOffset || 0) + pos; + return new DataView(ui8a.buffer, offset, 4).getFloat32(0, false) + } + + /** + * @param {number} inp + */ + function encodeFloat64 (inp) { + dataView.setFloat64(0, inp, false); + } + + /** + * @param {Uint8Array} ui8a + * @param {number} pos + * @returns {number} + */ + function readFloat64 (ui8a, pos) { + if (ui8a.length - pos < 8) { + throw new Error(`${decodeErrPrefix} not enough data for float64`) + } + const offset = (ui8a.byteOffset || 0) + pos; + return new DataView(ui8a.buffer, offset, 8).getFloat64(0, false) + } + + /** + * @param {Token} _tok1 + * @param {Token} _tok2 + * @returns {number} + */ + encodeFloat.compareTokens = encodeUint.compareTokens; + /* + encodeFloat.compareTokens = function compareTokens (_tok1, _tok2) { + return _tok1 + throw new Error(`${encodeErrPrefix} cannot use floats as map keys`) + } + */ + + /** + * @param {Token} token + * @returns {Uint8Array|undefined} + */ + function quickEncodeToken (token) { + switch (token.type) { + case Type.false: + return fromArray([0xf4]) + case Type.true: + return fromArray([0xf5]) + case Type.null: + return fromArray([0xf6]) + case Type.bytes: + if (!token.value.length) { + return fromArray([0x40]) + } + return + case Type.string: + if (token.value === '') { + return fromArray([0x60]) + } + return + case Type.array: + if (token.value === 0) { + return fromArray([0x80]) + } + /* c8 ignore next 2 */ + // shouldn't be possible if this were called when there was only one token + return + case Type.map: + if (token.value === 0) { + return fromArray([0xa0]) + } + /* c8 ignore next 2 */ + // shouldn't be possible if this were called when there was only one token + return + case Type.uint: + if (token.value < 24) { + return fromArray([Number(token.value)]) + } + return + case Type.negint: + if (token.value >= -24) { + return fromArray([31 - Number(token.value)]) + } + } + } + + /** + * @typedef {import('../interface').EncodeOptions} EncodeOptions + * @typedef {import('../interface').OptionalTypeEncoder} OptionalTypeEncoder + * @typedef {import('../interface').Reference} Reference + * @typedef {import('../interface').StrictTypeEncoder} StrictTypeEncoder + * @typedef {import('../interface').TokenTypeEncoder} TokenTypeEncoder + * @typedef {import('../interface').TokenOrNestedTokens} TokenOrNestedTokens + */ + + /** @type {EncodeOptions} */ + const defaultEncodeOptions = { + float64: false, + mapSorter, + quickEncodeToken + }; + + /** @returns {TokenTypeEncoder[]} */ + function makeCborEncoders () { + const encoders = []; + encoders[Type.uint.major] = encodeUint; + encoders[Type.negint.major] = encodeNegint; + encoders[Type.bytes.major] = encodeBytes; + encoders[Type.string.major] = encodeString; + encoders[Type.array.major] = encodeArray; + encoders[Type.map.major] = encodeMap; + encoders[Type.tag.major] = encodeTag; + encoders[Type.float.major] = encodeFloat; + return encoders + } + + const cborEncoders = makeCborEncoders(); + + const buf = new Bl(); + + /** @implements {Reference} */ + class Ref { + /** + * @param {object|any[]} obj + * @param {Reference|undefined} parent + */ + constructor (obj, parent) { + this.obj = obj; + this.parent = parent; + } + + /** + * @param {object|any[]} obj + * @returns {boolean} + */ + includes (obj) { + /** @type {Reference|undefined} */ + let p = this; + do { + if (p.obj === obj) { + return true + } + } while (p = p.parent) // eslint-disable-line + return false + } + + /** + * @param {Reference|undefined} stack + * @param {object|any[]} obj + * @returns {Reference} + */ + static createCheck (stack, obj) { + if (stack && stack.includes(obj)) { + throw new Error(`${encodeErrPrefix} object contains circular references`) + } + return new Ref(obj, stack) + } + } + + const simpleTokens = { + null: new Token(Type.null, null), + undefined: new Token(Type.undefined, undefined), + true: new Token(Type.true, true), + false: new Token(Type.false, false), + emptyArray: new Token(Type.array, 0), + emptyMap: new Token(Type.map, 0) + }; + + /** @type {{[typeName: string]: StrictTypeEncoder}} */ + const typeEncoders$1 = { + /** + * @param {any} obj + * @param {string} _typ + * @param {EncodeOptions} _options + * @param {Reference} [_refStack] + * @returns {TokenOrNestedTokens} + */ + number (obj, _typ, _options, _refStack) { + if (!Number.isInteger(obj) || !Number.isSafeInteger(obj)) { + return new Token(Type.float, obj) + } else if (obj >= 0) { + return new Token(Type.uint, obj) + } else { + return new Token(Type.negint, obj) + } + }, + + /** + * @param {any} obj + * @param {string} _typ + * @param {EncodeOptions} _options + * @param {Reference} [_refStack] + * @returns {TokenOrNestedTokens} + */ + bigint (obj, _typ, _options, _refStack) { + if (obj >= BigInt(0)) { + return new Token(Type.uint, obj) + } else { + return new Token(Type.negint, obj) + } + }, + + /** + * @param {any} obj + * @param {string} _typ + * @param {EncodeOptions} _options + * @param {Reference} [_refStack] + * @returns {TokenOrNestedTokens} + */ + Uint8Array (obj, _typ, _options, _refStack) { + return new Token(Type.bytes, obj) + }, + + /** + * @param {any} obj + * @param {string} _typ + * @param {EncodeOptions} _options + * @param {Reference} [_refStack] + * @returns {TokenOrNestedTokens} + */ + string (obj, _typ, _options, _refStack) { + return new Token(Type.string, obj) + }, + + /** + * @param {any} obj + * @param {string} _typ + * @param {EncodeOptions} _options + * @param {Reference} [_refStack] + * @returns {TokenOrNestedTokens} + */ + boolean (obj, _typ, _options, _refStack) { + return obj ? simpleTokens.true : simpleTokens.false + }, + + /** + * @param {any} _obj + * @param {string} _typ + * @param {EncodeOptions} _options + * @param {Reference} [_refStack] + * @returns {TokenOrNestedTokens} + */ + null (_obj, _typ, _options, _refStack) { + return simpleTokens.null + }, + + /** + * @param {any} _obj + * @param {string} _typ + * @param {EncodeOptions} _options + * @param {Reference} [_refStack] + * @returns {TokenOrNestedTokens} + */ + undefined (_obj, _typ, _options, _refStack) { + return simpleTokens.undefined + }, + + /** + * @param {any} obj + * @param {string} _typ + * @param {EncodeOptions} _options + * @param {Reference} [_refStack] + * @returns {TokenOrNestedTokens} + */ + ArrayBuffer (obj, _typ, _options, _refStack) { + return new Token(Type.bytes, new Uint8Array(obj)) + }, + + /** + * @param {any} obj + * @param {string} _typ + * @param {EncodeOptions} _options + * @param {Reference} [_refStack] + * @returns {TokenOrNestedTokens} + */ + DataView (obj, _typ, _options, _refStack) { + return new Token(Type.bytes, new Uint8Array(obj.buffer, obj.byteOffset, obj.byteLength)) + }, + + /** + * @param {any} obj + * @param {string} _typ + * @param {EncodeOptions} options + * @param {Reference} [refStack] + * @returns {TokenOrNestedTokens} + */ + Array (obj, _typ, options, refStack) { + if (!obj.length) { + if (options.addBreakTokens === true) { + return [simpleTokens.emptyArray, new Token(Type.break)] + } + return simpleTokens.emptyArray + } + refStack = Ref.createCheck(refStack, obj); + const entries = []; + let i = 0; + for (const e of obj) { + entries[i++] = objectToTokens(e, options, refStack); + } + if (options.addBreakTokens) { + return [new Token(Type.array, obj.length), entries, new Token(Type.break)] + } + return [new Token(Type.array, obj.length), entries] + }, + + /** + * @param {any} obj + * @param {string} typ + * @param {EncodeOptions} options + * @param {Reference} [refStack] + * @returns {TokenOrNestedTokens} + */ + Object (obj, typ, options, refStack) { + // could be an Object or a Map + const isMap = typ !== 'Object'; + // it's slightly quicker to use Object.keys() than Object.entries() + const keys = isMap ? obj.keys() : Object.keys(obj); + const length = isMap ? obj.size : keys.length; + if (!length) { + if (options.addBreakTokens === true) { + return [simpleTokens.emptyMap, new Token(Type.break)] + } + return simpleTokens.emptyMap + } + refStack = Ref.createCheck(refStack, obj); + /** @type {TokenOrNestedTokens[]} */ + const entries = []; + let i = 0; + for (const key of keys) { + entries[i++] = [ + objectToTokens(key, options, refStack), + objectToTokens(isMap ? obj.get(key) : obj[key], options, refStack) + ]; + } + sortMapEntries(entries, options); + if (options.addBreakTokens) { + return [new Token(Type.map, length), entries, new Token(Type.break)] + } + return [new Token(Type.map, length), entries] + } + }; + + typeEncoders$1.Map = typeEncoders$1.Object; + typeEncoders$1.Buffer = typeEncoders$1.Uint8Array; + for (const typ of 'Uint8Clamped Uint16 Uint32 Int8 Int16 Int32 BigUint64 BigInt64 Float32 Float64'.split(' ')) { + typeEncoders$1[`${typ}Array`] = typeEncoders$1.DataView; + } + + /** + * @param {any} obj + * @param {EncodeOptions} [options] + * @param {Reference} [refStack] + * @returns {TokenOrNestedTokens} + */ + function objectToTokens (obj, options = {}, refStack) { + const typ = is(obj); + const customTypeEncoder = (options && options.typeEncoders && /** @type {OptionalTypeEncoder} */ options.typeEncoders[typ]) || typeEncoders$1[typ]; + if (typeof customTypeEncoder === 'function') { + const tokens = customTypeEncoder(obj, typ, options, refStack); + if (tokens != null) { + return tokens + } + } + const typeEncoder = typeEncoders$1[typ]; + if (!typeEncoder) { + throw new Error(`${encodeErrPrefix} unsupported type: ${typ}`) + } + return typeEncoder(obj, typ, options, refStack) + } + + /* + CBOR key sorting is a mess. + + The canonicalisation recommendation from https://tools.ietf.org/html/rfc7049#section-3.9 + includes the wording: + + > The keys in every map must be sorted lowest value to highest. + > Sorting is performed on the bytes of the representation of the key + > data items without paying attention to the 3/5 bit splitting for + > major types. + > ... + > * If two keys have different lengths, the shorter one sorts + earlier; + > * If two keys have the same length, the one with the lower value + in (byte-wise) lexical order sorts earlier. + + 1. It is not clear what "bytes of the representation of the key" means: is it + the CBOR representation, or the binary representation of the object itself? + Consider the int and uint difference here. + 2. It is not clear what "without paying attention to" means: do we include it + and compare on that? Or do we omit the special prefix byte, (mostly) treating + the key in its plain binary representation form. + + The FIDO 2.0: Client To Authenticator Protocol spec takes the original CBOR + wording and clarifies it according to their understanding. + https://fidoalliance.org/specs/fido-v2.0-rd-20170927/fido-client-to-authenticator-protocol-v2.0-rd-20170927.html#message-encoding + + > The keys in every map must be sorted lowest value to highest. Sorting is + > performed on the bytes of the representation of the key data items without + > paying attention to the 3/5 bit splitting for major types. The sorting rules + > are: + > * If the major types are different, the one with the lower value in numerical + > order sorts earlier. + > * If two keys have different lengths, the shorter one sorts earlier; + > * If two keys have the same length, the one with the lower value in + > (byte-wise) lexical order sorts earlier. + + Some other implementations, such as borc, do a full encode then do a + length-first, byte-wise-second comparison: + https://github.com/dignifiedquire/borc/blob/b6bae8b0bcde7c3976b0f0f0957208095c392a36/src/encoder.js#L358 + https://github.com/dignifiedquire/borc/blob/b6bae8b0bcde7c3976b0f0f0957208095c392a36/src/utils.js#L143-L151 + + This has the benefit of being able to easily handle arbitrary keys, including + complex types (maps and arrays). + + We'll opt for the FIDO approach, since it affords some efficies since we don't + need a full encode of each key to determine order and can defer to the types + to determine how to most efficiently order their values (i.e. int and uint + ordering can be done on the numbers, no need for byte-wise, for example). + + Recommendation: stick to single key types or you'll get into trouble, and prefer + string keys because it's much simpler that way. + */ + + /* + (UPDATE, Dec 2020) + https://tools.ietf.org/html/rfc8949 is the updated CBOR spec and clarifies some + of the questions above with a new recommendation for sorting order being much + closer to what would be expected in other environments (i.e. no length-first + weirdness). + This new sorting order is not yet implemented here but could be added as an + option. "Determinism" (canonicity) is system dependent and it's difficult to + change existing systems that are built with existing expectations. So if a new + ordering is introduced here, the old needs to be kept as well with the user + having the option. + */ + + /** + * @param {TokenOrNestedTokens[]} entries + * @param {EncodeOptions} options + */ + function sortMapEntries (entries, options) { + if (options.mapSorter) { + entries.sort(options.mapSorter); + } + } + + /** + * @param {(Token|Token[])[]} e1 + * @param {(Token|Token[])[]} e2 + * @returns {number} + */ + function mapSorter (e1, e2) { + // the key position ([0]) could have a single token or an array + // almost always it'll be a single token but complex key might get involved + /* c8 ignore next 2 */ + const keyToken1 = Array.isArray(e1[0]) ? e1[0][0] : e1[0]; + const keyToken2 = Array.isArray(e2[0]) ? e2[0][0] : e2[0]; + + // different key types + if (keyToken1.type !== keyToken2.type) { + return keyToken1.type.compare(keyToken2.type) + } + + const major = keyToken1.type.major; + // TODO: handle case where cmp === 0 but there are more keyToken e. complex type) + const tcmp = cborEncoders[major].compareTokens(keyToken1, keyToken2); + /* c8 ignore next 5 */ + if (tcmp === 0) { + // duplicate key or complex type where the first token matched, + // i.e. a map or array and we're only comparing the opening token + console.warn('WARNING: complex key types used, CBOR key sorting guarantees are gone'); + } + return tcmp + } + + /** + * @param {Bl} buf + * @param {TokenOrNestedTokens} tokens + * @param {TokenTypeEncoder[]} encoders + * @param {EncodeOptions} options + */ + function tokensToEncoded (buf, tokens, encoders, options) { + if (Array.isArray(tokens)) { + for (const token of tokens) { + tokensToEncoded(buf, token, encoders, options); + } + } else { + encoders[tokens.type.major](buf, tokens, options); + } + } + + /** + * @param {any} data + * @param {TokenTypeEncoder[]} encoders + * @param {EncodeOptions} options + * @returns {Uint8Array} + */ + function encodeCustom (data, encoders, options) { + const tokens = objectToTokens(data, options); + if (!Array.isArray(tokens) && options.quickEncodeToken) { + const quickBytes = options.quickEncodeToken(tokens); + if (quickBytes) { + return quickBytes + } + const encoder = encoders[tokens.type.major]; + if (encoder.encodedSize) { + const size = encoder.encodedSize(tokens, options); + const buf = new Bl(size); + encoder(buf, tokens, options); + /* c8 ignore next 4 */ + // this would be a problem with encodedSize() functions + if (buf.chunks.length !== 1) { + throw new Error(`Unexpected error: pre-calculated length for ${tokens} was wrong`) + } + return asU8A(buf.chunks[0]) + } + } + buf.reset(); + tokensToEncoded(buf, tokens, encoders, options); + return buf.toBytes(true) + } + + /** + * @param {any} data + * @param {EncodeOptions} [options] + * @returns {Uint8Array} + */ + function encode$1 (data, options) { + options = Object.assign({}, defaultEncodeOptions, options); + return encodeCustom(data, cborEncoders, options) + } + + var encode_1; + var hasRequiredEncode; + + function requireEncode () { + if (hasRequiredEncode) return encode_1; + hasRequiredEncode = 1; + encode_1 = encode; + + var MSB = 0x80 + , MSBALL = -128 + , INT = Math.pow(2, 31); + + function encode(num, out, offset) { + if (Number.MAX_SAFE_INTEGER && num > Number.MAX_SAFE_INTEGER) { + encode.bytes = 0; + throw new RangeError('Could not encode varint') + } + out = out || []; + offset = offset || 0; + var oldOffset = offset; + + while(num >= INT) { + out[offset++] = (num & 0xFF) | MSB; + num /= 128; + } + while(num & MSBALL) { + out[offset++] = (num & 0xFF) | MSB; + num >>>= 7; + } + out[offset] = num | 0; + + encode.bytes = offset - oldOffset + 1; + + return out + } + return encode_1; + } + + var decode; + var hasRequiredDecode; + + function requireDecode () { + if (hasRequiredDecode) return decode; + hasRequiredDecode = 1; + decode = read; + + var MSB = 0x80 + , REST = 0x7F; + + function read(buf, offset) { + var res = 0 + , offset = offset || 0 + , shift = 0 + , counter = offset + , b + , l = buf.length; + + do { + if (counter >= l || shift > 49) { + read.bytes = 0; + throw new RangeError('Could not decode varint') + } + b = buf[counter++]; + res += shift < 28 + ? (b & REST) << shift + : (b & REST) * Math.pow(2, shift); + shift += 7; + } while (b >= MSB) + + read.bytes = counter - offset; + + return res + } + return decode; + } + + var length; + var hasRequiredLength; + + function requireLength () { + if (hasRequiredLength) return length; + hasRequiredLength = 1; + var N1 = Math.pow(2, 7); + var N2 = Math.pow(2, 14); + var N3 = Math.pow(2, 21); + var N4 = Math.pow(2, 28); + var N5 = Math.pow(2, 35); + var N6 = Math.pow(2, 42); + var N7 = Math.pow(2, 49); + var N8 = Math.pow(2, 56); + var N9 = Math.pow(2, 63); + + length = function (value) { + return ( + value < N1 ? 1 + : value < N2 ? 2 + : value < N3 ? 3 + : value < N4 ? 4 + : value < N5 ? 5 + : value < N6 ? 6 + : value < N7 ? 7 + : value < N8 ? 8 + : value < N9 ? 9 + : 10 + ) + }; + return length; + } + + var varint; + var hasRequiredVarint; + + function requireVarint () { + if (hasRequiredVarint) return varint; + hasRequiredVarint = 1; + varint = { + encode: requireEncode() + , decode: requireDecode() + , encodingLength: requireLength() + }; + return varint; + } + + var varintExports = requireVarint(); + + /*! + * Copyright (c) 2021-2024 Digital Bazaar, Inc. All rights reserved. + */ + class CborldEncoder { + encode() { + throw new Error('Must be implemented by derived class.'); + } + } + + /*! + * Copyright (c) 2021-2024 Digital Bazaar, Inc. All rights reserved. + */ + + class ContextEncoder extends CborldEncoder { + constructor({context, contextTable} = {}) { + super(); + this.context = context; + this.contextTable = contextTable; + } + + encode() { + const {context, contextTable} = this; + const id = contextTable.get(context); + if(id === undefined) { + return new Token(Type.string, context); + } + return new Token(Type.uint, id); + } + + static createEncoder({value, typeTable} = {}) { + if(typeof value !== 'string') { + return; + } + const contextTable = typeTable.get('context'); + return new ContextEncoder({context: value, contextTable}); + } + } + + /*! + * Copyright (c) 2021-2024 Digital Bazaar, Inc. All rights reserved. + */ + + // this class is used to encode a multibase encoded value in CBOR-LD, which + // actually means transforming a multibase-encoded string to bytes + class MultibaseEncoder extends CborldEncoder { + constructor({value} = {}) { + super(); + this.value = value; + } + + encode() { + const {value} = this; + + let prefix; + let suffix; + if(value[0] === 'z') { + // 0x7a === 'z' (multibase code for base58btc) + prefix = 0x7a; + suffix = decode$1(value.slice(1)); + } else if(value[0] === 'u') { + // 0x75 === 'u' (multibase code for base64url) + prefix = 0x75; + const buffer = decode$4(value.slice(1)); + suffix = new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.length); + } else if(value[0] === 'M') { + // 0x4d === 'M' (multibase code for base64pad) + prefix = 0x4d; + suffix = gBase64.toUint8Array(value.slice(1)); + } + + const bytes = new Uint8Array(1 + suffix.length); + bytes[0] = prefix; + bytes.set(suffix, 1); + return new Token(Type.bytes, bytes); + } + + static createEncoder({value} = {}) { + if(typeof value !== 'string') { + return; + } + // supported multibase encodings: + // 0x7a === 'z' (multibase code for base58btc) + // 0x75 === 'u' (multibase code for base64url) + // 0x4d === 'M' (multibase code for base64pad) + if(value[0] === 'z' || value[0] === 'u' || value[0] === 'M') { + return new MultibaseEncoder({value}); + } + } + } + + /*! + * Copyright (c) 2021-2024 Digital Bazaar, Inc. All rights reserved. + */ + + const SCHEME_TO_ID = new Map([ + ['did:v1:nym:', 1024], + ['did:key:', 1025] + ]); + + class Base58DidUrlEncoder extends CborldEncoder { + constructor({value, scheme, schemeCompressed} = {}) { + super(); + this.value = value; + this.scheme = scheme; + this.schemeCompressed = schemeCompressed; + } + + encode() { + const {value, scheme, schemeCompressed} = this; + const suffix = value.slice(scheme.length); + const [authority, fragment] = suffix.split('#'); + const entries = [ + new Token(Type.uint, schemeCompressed), + _multibase58ToToken(authority) + ]; + if(fragment !== undefined) { + entries.push(_multibase58ToToken(fragment)); + } + return [new Token(Type.array, entries.length), entries]; + } + + static createEncoder({value} = {}) { + for(const [key, schemeCompressed] of SCHEME_TO_ID) { + if(value.startsWith(key)) { + return new Base58DidUrlEncoder({ + value, + scheme: key, + schemeCompressed + }); + } + } + } + } + + function _multibase58ToToken(str) { + if(str.startsWith('z')) { + const decoded = decode$1(str.slice(1)); + if(decoded) { + return new Token(Type.bytes, decoded); + } + } + // cannot compress suffix + return new Token(Type.string, str); + } + + /*! + * Copyright (c) 2024 Digital Bazaar, Inc. All rights reserved. + */ + + /* + Data URL codec for base64 data. + + References: + https://www.rfc-editor.org/rfc/rfc2397 + https://fetch.spec.whatwg.org/#data-urls + https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URLs + + Data URL format: + data:[][;base64], + + This codec must be able to round-trip the data. The parsing is minimal and + designed to only binary encode well-formed base64 data that can be decoded to + the same input. The mediatype (with optional parameters) is stored as-is, with + the exception of ";base64" if correct base64 encoding is detected. In the case + of a non-base64 data URI, all but the "data:" prefix is encoded as a string. + + base64 encoding: [string, data] + non-base64 encoding: [string] + + TODO: An optimization could use a registry of well-known base mediatypes (ie, + image/png, text/plain, etc). + */ + + // base64 data uri regex + // when using this, use round trip code to ensure decoder will work + const DATA_BASE64_REGEX = /^data:(?.*);base64,(?.*)$/; + + class DataUrlEncoder extends CborldEncoder { + constructor({value, base64} = {}) { + super(); + this.value = value; + this.base64 = base64; + } + + encode() { + const {value, base64} = this; + + const entries = [new Token(Type.uint, URL_SCHEME_TABLE.get('data:'))]; + + if(base64) { + // base64 mode + // [string, bytes] + const parsed = DATA_BASE64_REGEX.exec(value); + entries.push( + new Token(Type.string, parsed.groups.mediatype)); + entries.push( + new Token(Type.bytes, gBase64.toUint8Array(parsed.groups.data))); + } else { + // non-base64 mode + // [string] + entries.push( + new Token(Type.string, value.slice('data:'.length))); + } + + return [new Token(Type.array, entries.length), entries]; + } + + static createEncoder({value} = {}) { + // quick check + if(!value.startsWith('data:')) { + return; + } + // attempt to parse as a base64 + const parsed = DATA_BASE64_REGEX.exec(value); + if(parsed) { + // check to ensure data can be restored + // this avoids issues with variations in encoding + const data = parsed.groups.data; + if(data === gBase64.fromUint8Array(gBase64.toUint8Array(data))) { + return new DataUrlEncoder({value, base64: true}); + } + } + return new DataUrlEncoder({value, base64: false}); + } + } + + /*! + * Copyright (c) 2021-2024 Digital Bazaar, Inc. All rights reserved. + */ + + class HttpUrlEncoder extends CborldEncoder { + constructor({value, scheme} = {}) { + super(); + this.value = value; + this.scheme = scheme; + } + + encode() { + const {value, scheme} = this; + const entries = [ + new Token(Type.uint, URL_SCHEME_TABLE.get(scheme)), + new Token(Type.string, value.slice(scheme.length)) + ]; + return [new Token(Type.array, entries.length), entries]; + } + + static createEncoder({value} = {}) { + // presume HTTPS is more common, check for it first + if(value.startsWith('https://')) { + return new HttpUrlEncoder({value, scheme: 'https://'}); + } + if(value.startsWith('http://')) { + return new HttpUrlEncoder({value, scheme: 'http://'}); + } + } + } + + /*! + * Copyright (c) 2021-2024 Digital Bazaar, Inc. All rights reserved. + */ + + class UuidUrnEncoder extends CborldEncoder { + constructor({value} = {}) { + super(); + this.value = value; + } + + encode() { + const {value} = this; + const rest = value.slice('urn:uuid:'.length); + const entries = [new Token( + Type.uint, + URL_SCHEME_TABLE.get('urn:uuid:'))]; + if(rest.toLowerCase() === rest) { + const uuidBytes = parse(rest); + entries.push(new Token(Type.bytes, uuidBytes)); + } else { + // cannot compress UUID value + entries.push(new Token(Type.string, rest)); + } + return [new Token(Type.array, entries.length), entries]; + } + + static createEncoder({value} = {}) { + if(!value.startsWith('urn:uuid:')) { + return; + } + return new UuidUrnEncoder({value}); + } + } + + /*! + * Copyright (c) 2021-2024 Digital Bazaar, Inc. All rights reserved. + */ + + // an encoded URL is an array with the first element being an integer that + // signals which encoder was used: + // `0` reserved + // `1` http + // `2` https + // `3` urn:uuid + // `4` data (RFC 2397) + // `1024` did:v1:nym + // `1025` did:key + const SCHEME_TO_ENCODER = new Map([ + ['http', HttpUrlEncoder], + ['https', HttpUrlEncoder], + ['urn:uuid', UuidUrnEncoder], + ['data', DataUrlEncoder], + ['did:v1:nym', Base58DidUrlEncoder], + ['did:key', Base58DidUrlEncoder] + ]); + + class UrlEncoder extends CborldEncoder { + constructor({termId} = {}) { + super(); + this.termId = termId; + } + + encode() { + return new Token(Type.uint, this.termId); + } + + static createEncoder({value, converter} = {}) { + // see if a term ID exists that matches the value first + const termId = converter.contextLoader.getIdForTerm({term: value}); + if(typeof termId !== 'string') { + return new UrlEncoder({termId}); + } + + // check URI prefix codecs + // get full colon-delimited prefix + let scheme; + try { + // this handles URIs both with authority followed by `//` and without + const {protocol, pathname} = new URL(value); + scheme = protocol; + if(pathname.includes(':')) { + scheme += pathname; + } + const split = value.split(':'); + split.pop(); + scheme = split.join(':'); + } catch(e) { + return; + } + + const EncoderClass = SCHEME_TO_ENCODER.get(scheme); + return EncoderClass && EncoderClass.createEncoder({value, converter}); + } + } + + /*! + * Copyright (c) 2021-2024 Digital Bazaar, Inc. All rights reserved. + */ + + class XsdDateEncoder extends CborldEncoder { + constructor({value, parsed} = {}) { + super(); + this.value = value; + this.parsed = parsed; + } + + encode() { + const {value, parsed} = this; + + const secondsSinceEpoch = Math.floor(parsed / 1000); + const dateString = new Date(secondsSinceEpoch * 1000).toISOString(); + const expectedDate = dateString.slice(0, dateString.indexOf('T')); + if(value !== expectedDate) { + // compression would be lossy, do not compress + return new Token(Type.string, value); + } + return new Token(Type.uint, secondsSinceEpoch); + } + + static createEncoder({value} = {}) { + if(value.includes('T')) { + // time included, cannot compress + return; + } + const parsed = Date.parse(value); + if(isNaN(parsed)) { + // no date parsed, cannot compress + return; + } + return new XsdDateEncoder({value, parsed}); + } + } + + /*! + * Copyright (c) 2021-2024 Digital Bazaar, Inc. All rights reserved. + */ + + class XsdDateTimeEncoder extends CborldEncoder { + constructor({value, parsed} = {}) { + super(); + this.value = value; + this.parsed = parsed; + } + + encode() { + const {value, parsed} = this; + const secondsSinceEpoch = Math.floor(parsed / 1000); + const secondsToken = new Token(Type.uint, secondsSinceEpoch); + const millisecondIndex = value.indexOf('.'); + if(millisecondIndex === -1) { + const expectedDate = new Date( + secondsSinceEpoch * 1000).toISOString().replace('.000Z', 'Z'); + if(value !== expectedDate) { + // compression would be lossy, do not compress + return new Token(Type.string, value); + } + // compress with second precision + return secondsToken; + } + + const milliseconds = parseInt(value.slice(millisecondIndex + 1), 10); + const expectedDate = new Date( + secondsSinceEpoch * 1000 + milliseconds).toISOString(); + if(value !== expectedDate) { + // compress would be lossy, do not compress + return new Token(Type.string, value); + } + + // compress with subsecond precision + const entries = [ + secondsToken, + new Token(Type.uint, milliseconds) + ]; + return [new Token(Type.array, entries.length), entries]; + } + + static createEncoder({value} = {}) { + if(!value.includes('T')) { + // no time included, cannot compress + return; + } + const parsed = Date.parse(value); + if(isNaN(parsed)) { + // no date parsed, cannot compress + return; + } + return new XsdDateTimeEncoder({value, parsed}); + } + } + + /*! + * Copyright (c) 2024 Digital Bazaar, Inc. All rights reserved. + */ + + // constants based on "default" processing mode + const PROCESSING_MODE_TYPE_ENCODERS = new Map([ + ['url', UrlEncoder], + ['https://w3id.org/security#multibase', MultibaseEncoder], + ['http://www.w3.org/2001/XMLSchema#date', XsdDateEncoder], + ['http://www.w3.org/2001/XMLSchema#dateTime', XsdDateTimeEncoder] + ]); + + class ValueEncoder extends CborldEncoder { + constructor({intValue, convertToBytes, includeSign} = {}) { + super(); + this.intValue = intValue; + this.convertToBytes = convertToBytes; + this.includeSign = includeSign; + } + + encode() { + const {intValue, convertToBytes, includeSign} = this; + if(convertToBytes) { + const toBytes = includeSign ? bytesFromInt : bytesFromUint; + const bytes = toBytes({intValue}); + return new Token(Type.bytes, bytes); + } + return new Token(Type.uint, intValue); + } + + static createEncoder({value, converter, termInfo, termType} = {}) { + const tableType = getTableType({termInfo, termType}); + if(tableType === 'url' && typeof value !== 'string') { + throw new CborldError( + 'ERR_UNSUPPORTED_JSON_TYPE', + `Invalid value type "${typeof value}" for URL; expected "string".`); + } + + // if a subtable exists for `tableType`... + const subTable = converter.strategy.typeTable.get(tableType); + if(subTable) { + let intValue = subTable.get(value); + let convertToBytes; + let includeSign; + if(intValue !== undefined) { + // determine if ID from table must be expressed as bytes for `tableType` + const {typeTableEncodedAsBytesSet} = converter; + convertToBytes = typeTableEncodedAsBytesSet.has(tableType); + includeSign = false; + } else if(tableType !== 'none' && Number.isInteger(value)) { + /* Note: Here is an unusual case that a type subtable has been defined + for a custom type, but the user data still includes a native JSON + integer. This integer has to be encoded to CBOR as bytes to ensure no + conflicts with any subtable values which are encoded as CBOR integers. + Despite being an unusual case, it is supported here by encoding the + integer as a signed integer expressed in bytes (two's complement). */ + intValue = value; + convertToBytes = includeSign = true; + } + if(intValue !== undefined) { + return new ValueEncoder({intValue, convertToBytes, includeSign}); + } + } + + // lastly, try to get a processing-mode-specific type encoder + const encoder = PROCESSING_MODE_TYPE_ENCODERS.get(tableType)?.createEncoder( + {value, converter, termInfo}); + if(encoder) { + return encoder; + } + + // return passthrough value + return value; + } + } + + /*! + * Copyright (c) 2021-2024 Digital Bazaar, Inc. All rights reserved. + */ + + const CONTEXT_TERM_ID = KEYWORDS_TABLE.get('@context'); + const CONTEXT_TERM_ID_PLURAL = CONTEXT_TERM_ID + 1; + + class Compressor { + /** + * Creates a new Compressor for generating compressed CBOR-LD from a + * JSON-LD document. The created instance may only be used on a single + * JSON-LD document at a time. + * + * @param {object} options - The options to use. + * @param {Map} options.typeTable - A map of possible value types, including + * `context`, `url`, `none`, and any JSON-LD type, each of which maps to + * another map of values of that type to their associated CBOR-LD integer + * values. + */ + constructor({typeTable} = {}) { + this.typeTable = typeTable; + } + + addOutputEntry({termInfo, values, output}) { + output.set(termInfo.termId, values); + } + + async convertContexts({activeCtx, input: obj, output}) { + activeCtx = activeCtx.applyEmbeddedContexts({obj}); + + // if no `@context` is present, return early + const context = obj['@context']; + if(!context) { + return activeCtx; + } + + // encode `@context`... + const {typeTable} = this; + const encodedContexts = []; + const isArray = Array.isArray(context); + const contexts = isArray ? context : [context]; + for(const value of contexts) { + const encoder = ContextEncoder.createEncoder({value, typeTable}); + encodedContexts.push(encoder || value); + } + const id = isArray ? CONTEXT_TERM_ID_PLURAL : CONTEXT_TERM_ID; + output.set(id, isArray ? encodedContexts : encodedContexts[0]); + + return activeCtx; + } + + convertValue({termType, value, termInfo, converter}) { + if(typeof value === 'object') { + return; + } + return ValueEncoder.createEncoder({value, converter, termInfo, termType}); + } + + createNewOutput() { + return new Map(); + } + + getInputEntries({activeCtx, input}) { + // get input entries to be converted and sort by *term* to ensure term + // IDs will be assigned in the same order that the decompressor will + const entries = []; + const keys = Object.keys(input).sort(); + for(const key of keys) { + // skip `@context`; context already converted early + if(key === '@context') { + continue; + } + // create term info + const value = input[key]; + const plural = Array.isArray(value); + const termId = activeCtx.getIdForTerm({term: key, plural}); + const def = activeCtx.getTermDefinition({term: key}); + entries.push([{term: key, termId, plural, def}, value]); + } + return entries; + } + + getObjectTypes({activeCtx, input}) { + const objectTypes = new Set(); + const typeTerms = activeCtx.getTypeTerms(); + for(const term of typeTerms) { + const types = input[term]; + if(types !== undefined) { + if(Array.isArray(types)) { + types.forEach(objectTypes.add, objectTypes); + } else { + objectTypes.add(types); + } + } + } + return objectTypes; + } + } + + /*! + * Copyright (c) 2020-2025 Digital Bazaar, Inc. All rights reserved. + */ + + // override cborg object encoder to use cborld encoders + const typeEncoders = { + Object(obj) { + if(obj instanceof CborldEncoder) { + return obj.encode({obj}); + } + } + }; + + /** + * Encodes a given JSON-LD document into a CBOR-LD byte array. + * + * @param {object} options - The options to use when encoding to CBOR-LD. + * @param {object} options.jsonldDocument - The JSON-LD Document to convert to + * CBOR-LD bytes. + * @param {documentLoaderFunction} options.documentLoader - The document loader + * to use when resolving JSON-LD Context URLs. + * @param {string} [options.format='cbor-ld-1.0'] - The CBOR-LD output format + * to use; this will default to `cbor-ld-1.0` to use CBOR-LD 1.0 tag + * `0xcb1d` (51997); to create output with a pre-1.0 CBOR-LD tag, then + * 'legacy-range' can be passed to use tags `0x0600-0x06ff` (1526-1791) and + * 'legacy-singleton' can be passed to use tags `0x0500-0x0501`. + * @param {number|string} [options.registryEntryId] - The registry + * entry ID for the registry entry associated with the resulting CBOR-LD + * payload. + * @param {Function} [options.typeTableLoader] - The `typeTable` loader to use + * to resolve `registryEntryId` to a `typeTable`. A `typeTable` is a Map of + * possible value types, including `context`, `url`, `none`, and any JSON-LD + * type, each of which maps to another Map of values of that type to their + * associated CBOR-LD integer values. + * @param {diagnosticFunction} [options.diagnose] - A function that, if + * provided, is called with diagnostic information. + * @param {Map} [options.appContextMap] - Only for use with the + * 'legacy-singleton' format. + * @param {number} [options.compressionMode] - Only for use with the + * 'legacy-singleton' format. + * @param {*} [options.typeTable] - NOT permitted; use `typeTableLoader`. + * + * @returns {Promise} - The encoded CBOR-LD bytes. + */ + async function encode({ + jsonldDocument, + documentLoader, + format = 'cbor-ld-1.0', + registryEntryId, + typeTableLoader, + diagnose, + // for "legacy-singleton" format only + appContextMap, + compressionMode, + // no longer permitted + typeTable + } = {}) { + // validate `format` + if(!(format === 'cbor-ld-1.0' || format === 'legacy-range' || + format === 'legacy-singleton')) { + throw new TypeError( + `Invalid "format" "${format}"; "format" must be ` + + '"cbor-ld-1.0" (tag 0xcb1d = 51997) or ' + + '"legacy-range" (tags 0x0600-0x06ff = 1536-1791) or ' + + '"legacy-singleton" (tags 0x0500-0x0501 = 1280-1281).'); + } + // throw on `typeTable` param which is no longer permitted + if(typeTable !== undefined) { + throw new TypeError('"typeTable" is not allowed; use "typeTableLoader".'); + } + + // validate parameter combinations + let compressPayload; + if(format === 'legacy-singleton') { + if(registryEntryId !== undefined) { + throw new TypeError( + '"registryEntryId" must not be used with format "legacy-singleton".'); + } + if(typeTableLoader !== undefined) { + throw new TypeError( + '"typeTableLoader" must not be used with format "legacy-singleton".'); + } + + // default compression mode to `1` + compressionMode = compressionMode ?? 1; + if(!(compressionMode === 0 || compressionMode === 1)) { + throw new TypeError( + '"compressionMode" must be "0" (no compression) or "1" ' + + 'for compression mode version 1.'); + } + compressPayload = compressionMode === 1; + + // generate legacy type table + typeTable = createLegacyTypeTable({typeTable, appContextMap}); + } else { + // validate that an acceptable value for `registryEntryId` was passed + if(!(typeof registryEntryId === 'number' && registryEntryId >= 0)) { + throw new TypeError('"registryEntryId" must be a non-negative integer.'); + } + + if(appContextMap !== undefined) { + throw new TypeError( + '"appContextMap" must only be used with format "legacy-singleton".'); + } + if(compressionMode !== undefined) { + throw new TypeError( + '"compressionMode" must only be used with format "legacy-singleton".'); + } + if(registryEntryId !== 0) { + if(registryEntryId === 1) { + // use default table (empty) for registry entry ID `1` + typeTable = createTypeTable({typeTable}); + } else { + if(typeof typeTableLoader !== 'function') { + throw new TypeError('"typeTableLoader" must be a function.'); + } + typeTable = await typeTableLoader({registryEntryId}); + if(!(typeTable instanceof Map)) { + throw new CborldError( + 'ERR_NO_TYPETABLE', + '"typeTable" not found for "registryEntryId" ' + + `"${registryEntryId}".`); + } + // check to make sure unsupported types not present in `typeTable` + if(typeTable.has('http://www.w3.org/2001/XMLSchema#integer') || + typeTable.has('http://www.w3.org/2001/XMLSchema#double') || + typeTable.has('http://www.w3.org/2001/XMLSchema#boolean')) { + throw new CborldError( + 'ERR_UNSUPPORTED_LITERAL_TYPE', + '"typeTable" must not contain XSD integers, doubles, or booleans.'); + } + // normalize type table + typeTable = createTypeTable({typeTable}); + } + } + // any registry entry ID other than zero uses compression by default + compressPayload = registryEntryId !== 0; + } + + // compute CBOR-LD suffix + let suffix; + if(!compressPayload) { + // output uncompressed CBOR-LD + suffix = encode$1(jsonldDocument); + } else { + const converter = new Converter({ + // compress JSON-LD => CBOR-LD + strategy: new Compressor({typeTable}), + documentLoader, + legacy: format === 'legacy-singleton' + }); + const output = await converter.convert({input: jsonldDocument}); + if(diagnose) { + diagnose('Diagnostic CBOR-LD compression transform map(s):'); + diagnose(inspect(output)); + } + suffix = encode$1(output, {typeEncoders}); + } + + // concatenate prefix and suffix + const prefix = _getPrefix({format, compressionMode, registryEntryId}); + const length = prefix.length + suffix.length; + const bytes = new Uint8Array(length); + bytes.set(prefix); + bytes.set(suffix, prefix.length); + + if(diagnose) { + diagnose('Diagnostic CBOR-LD result:'); + diagnose(inspect(bytes)); + } + + return bytes; + } + + function _getPrefix({format, compressionMode, registryEntryId}) { + if(format === 'legacy-singleton') { + return new Uint8Array([ + 0xd9, // CBOR major type 6 + 2 byte tag size + 0x05, // legacy CBOR-LD tag + compressionMode // compression flag + ]); + } + + if(format === 'legacy-range') { + if(registryEntryId < 128) { + return new Uint8Array([ + 0xd9, // CBOR major type 6 + 2 byte tag size + 0x06, // non-legacy CBOR-LD tag + registryEntryId // low-value type table id + // encoded document appended in caller + ]); + } + const idVarint = varintExports.encode(registryEntryId); + + return new Uint8Array([ + 0xd9, // CBOR major type 6 + 2 byte tag size + 0x06, // non-legacy CBOR-LD tag + idVarint[0], + ...[ + 0x82, // 2 element array + ...encode$1(Uint8Array.from(idVarint.slice(1))) + // encoded document appended as second element in caller + ] + ]); + } + + // otherwise, use current tag system + return new Uint8Array([ + 0xd9, // CBOR major type 6 + 2 byte tag size + 0xcb, 0x1d, // CBOR-LD 1.0 tag + ...[ + 0x82, // 2 element array + ...encode$1(registryEntryId) + // encoded document appended as second element in caller + ] + ]); + } + + /** + * A diagnostic function that is called with diagnostic information. Typically + * set to `console.log` when debugging. + * + * @callback diagnosticFunction + * @param {string} message - The diagnostic message. + */ + + /** + * Fetches a resource given a URL and returns it as a string. + * + * @callback documentLoaderFunction + * @param {string} url - The URL to retrieve. + + * @returns {Promise} The resource associated with the URL as a string. + */ + + const f$4={POS_INT:0,NEG_INT:1,BYTE_STRING:2,UTF8_STRING:3,ARRAY:4,MAP:5,TAG:6,SIMPLE_FLOAT:7},I={DATE_STRING:0,DATE_EPOCH:1,POS_BIGINT:2,NEG_BIGINT:3,CBOR:24,URI:32,BASE64URL:33,BASE64:34,SET:258,JSON:262,WTF8:273,REGEXP:21066,SELF_DESCRIBED:55799,INVALID_16:65535,INVALID_32:4294967295,INVALID_64:0xffffffffffffffffn},o$2={ZERO:0,ONE:24,TWO:25,FOUR:26,EIGHT:27,INDEFINITE:31},T$2={FALSE:20,TRUE:21,NULL:22,UNDEFINED:23};let N$2 = class N{static BREAK=Symbol.for("github.com/hildjj/cbor2/break");static ENCODED=Symbol.for("github.com/hildjj/cbor2/cbor-encoded");static LENGTH=Symbol.for("github.com/hildjj/cbor2/length")};const S$1={MIN:-(2n**63n),MAX:2n**64n-1n}; + + let i$1 = class i{static#e=new Map;tag;contents;constructor(e,t=void 0){this.tag=e,this.contents=t;}get noChildren(){return !!i.#e.get(this.tag)?.noChildren}static registerDecoder(e,t,n){const o=this.#e.get(e);return this.#e.set(e,t),o&&("comment"in t||(t.comment=o.comment),"noChildren"in t||(t.noChildren=o.noChildren)),n&&!t.comment&&(t.comment=()=>`(${n})`),o}static clearDecoder(e){const t=this.#e.get(e);return this.#e.delete(e),t}static getDecoder(e){return this.#e.get(e)}static getAllDecoders(){return new Map(this.#e)}*[Symbol.iterator](){yield this.contents;}push(e){return this.contents=e,1}decode(e){const t=e?.tags?.get(this.tag)??i.#e.get(this.tag);return t?t(this,e):this}comment(e,t){const n=e?.tags?.get(this.tag)??i.#e.get(this.tag);if(n?.comment)return n.comment(this,e,t)}toCBOR(){return [this.tag,this.contents]}[Symbol.for("nodejs.util.inspect.custom")](e,t,n){return `${this.tag}(${n(this.contents,t)})`}}; + + function f$3(n){if(n!=null&&typeof n=="object")return n[N$2.ENCODED]}function s$2(n){if(n!=null&&typeof n=="object")return n[N$2.LENGTH]}function u(n,e){Object.defineProperty(n,N$2.ENCODED,{configurable:true,enumerable:false,value:e});}function d$2(n,e){const r=Object(n);return u(r,e),r} + + const g$2=Symbol("CBOR_RANGES");function c$1(r,n){Object.defineProperty(r,g$2,{configurable:false,enumerable:false,writable:false,value:n});}function f$2(r){return r[g$2]}function l$2(r){return f$2(r)!==void 0}function R$1(r,n=0,t=r.length-1){const o=r.subarray(n,t),a=f$2(r);if(a){const s=[];for(const e of a)if(e[0]>=n&&e[0]+e[1]<=t){const i=[...e];i[0]-=n,s.push(i);}s.length&&c$1(o,s);}return o}function b$1(r){let n=Math.ceil(r.length/2);const t=new Uint8Array(n);n--;for(let o=r.length,a=o-2;o>=0;o=a,a-=2,n--)t[n]=parseInt(r.substring(a,o),16);return t}function A$2(r){return r.reduce((n,t)=>n+t.toString(16).padStart(2,"0"),"")}function d$1(r){const n=r.reduce((e,i)=>e+i.length,0),t=r.some(e=>l$2(e)),o=[],a=new Uint8Array(n);let s=0;for(const e of r){if(!(e instanceof Uint8Array))throw new TypeError(`Invalid array: ${e}`);if(a.set(e,s),t){const i=e[g$2]??[[0,e.length]];for(const u of i)u[0]+=s;o.push(...i);}s+=e.length;}return t&&c$1(a,o),a}function y$4(r){const n=atob(r);return Uint8Array.from(n,t=>t.codePointAt(0))}const p$2={"-":"+",_:"/"};function x$2(r){const n=r.replace(/[_-]/g,t=>p$2[t]);return y$4(n.padEnd(Math.ceil(n.length/4)*4,"="))}function h$2(){const r=new Uint8Array(4),n=new Uint32Array(r.buffer);return !((n[0]=1)&r[0])}function U$2(r){let n="";for(const t of r){const o=t.codePointAt(0)?.toString(16).padStart(4,"0");n&&(n+=", "),n+=`U+${o}`;}return n} + + let s$1 = class s{#e=new Map;registerEncoder(e,t){const n=this.#e.get(e);return this.#e.set(e,t),n}get(e){return this.#e.get(e)}delete(e){return this.#e.delete(e)}clear(){this.#e.clear();}}; + + function f$1(c,d){const[u,a,n]=c,[l,s,t]=d,r=Math.min(n.length,t.length);for(let o=0;o= 8, got ${this.#r.chunkSize}`);this.#n();}get length(){return this.#a}read(){this.#o();const t=new Uint8Array(this.#a);let i=0;for(const s of this.#i)t.set(s,i),i+=s.length;return this.#n(),t}write(t){const i=t.length;i>this.#l()?(this.#o(),i>this.#r.chunkSize?(this.#i.push(t),this.#n()):(this.#n(),this.#i[this.#i.length-1].set(t),this.#t=i)):(this.#i[this.#i.length-1].set(t,this.#t),this.#t+=i),this.#a+=i;}writeUint8(t){this.#e(1),this.#s.setUint8(this.#t,t),this.#h(1);}writeUint16(t,i=false){this.#e(2),this.#s.setUint16(this.#t,t,i),this.#h(2);}writeUint32(t,i=false){this.#e(4),this.#s.setUint32(this.#t,t,i),this.#h(4);}writeBigUint64(t,i=false){this.#e(8),this.#s.setBigUint64(this.#t,t,i),this.#h(8);}writeInt16(t,i=false){this.#e(2),this.#s.setInt16(this.#t,t,i),this.#h(2);}writeInt32(t,i=false){this.#e(4),this.#s.setInt32(this.#t,t,i),this.#h(4);}writeBigInt64(t,i=false){this.#e(8),this.#s.setBigInt64(this.#t,t,i),this.#h(8);}writeFloat32(t,i=false){this.#e(4),this.#s.setFloat32(this.#t,t,i),this.#h(4);}writeFloat64(t,i=false){this.#e(8),this.#s.setFloat64(this.#t,t,i),this.#h(8);}clear(){this.#a=0,this.#i=[],this.#n();}#n(){const t=new Uint8Array(this.#r.chunkSize);this.#i.push(t),this.#t=0,this.#s=new DataView(t.buffer,t.byteOffset,t.byteLength);}#o(){if(this.#t===0){this.#i.pop();return}const t=this.#i.length-1;this.#i[t]=this.#i[t].subarray(0,this.#t),this.#t=0,this.#s=null;}#l(){const t=this.#i.length-1;return this.#i[t].length-this.#t}#e(t){this.#l()>2,a=(e[n]&3)<<8|e[n+1];if(f===0){if(t&&a!==0)throw new Error(`Unwanted subnormal: ${r*5960464477539063e-23*a}`);return r*5960464477539063e-23*a}else if(f===31)return a?NaN:r*(1/0);return r*2**(f-25)*(1024+a)}function s(e){const n=new DataView(new ArrayBuffer(4));n.setFloat32(0,e,false);const t=n.getUint32(0,false);if((t&8191)!==0)return null;let r=t>>16&32768;const f=t>>23&255,a=t&8388607;if(!(f===0&&a===0))if(f>=113&&f<=142)r+=(f-112<<10)+(a>>13);else if(f>=103&&f<113){if(a&(1<<126-f)-1)return null;r+=a+8388608>>126-f;}else if(f===255)r|=31744,r|=a>>13;else return null;return r}function i(e){if(e!==0){const n=new ArrayBuffer(8),t=new DataView(n);t.setFloat64(0,e,false);const r=t.getBigUint64(0,false);if((r&0x7ff0000000000000n)===0n)return r&0x8000000000000000n?-0:0}return e}function l$1(e){switch(e.length){case 2:o$1(e,0,true);break;case 4:{const n=new DataView(e.buffer,e.byteOffset,e.byteLength),t=n.getUint32(0,false);if((t&2139095040)===0&&t&8388607)throw new Error(`Unwanted subnormal: ${n.getFloat32(0,false)}`);break}case 8:{const n=new DataView(e.buffer,e.byteOffset,e.byteLength),t=n.getBigUint64(0,false);if((t&0x7ff0000000000000n)===0n&&t&0x000fffffffffffn)throw new Error(`Unwanted subnormal: ${n.getFloat64(0,false)}`);break}default:throw new TypeError(`Bad input to isSubnormal: ${e}`)}} + + class DecodeError extends TypeError { + code = 'ERR_ENCODING_INVALID_ENCODED_DATA'; + constructor() { + super('The encoded data was not valid for encoding wtf-8'); + } + } + class InvalidEncodingError extends RangeError { + code = 'ERR_ENCODING_NOT_SUPPORTED'; + constructor(label) { + super(`Invalid encoding: "${label}"`); + } + } + + const BOM = 0xfeff; + const EMPTY = new Uint8Array(0); + const MIN_HIGH_SURROGATE = 0xd800; + const MIN_LOW_SURROGATE = 0xdc00; + const REPLACEMENT = 0xfffd; + const WTF8 = 'wtf-8'; + + function isArrayBufferView(input) { + return (input && + !(input instanceof ArrayBuffer) && + input.buffer instanceof ArrayBuffer); + } + function getUint8(input) { + if (!input) { + return EMPTY; + } + if (input instanceof Uint8Array) { + return input; + } + if (isArrayBufferView(input)) { + return new Uint8Array(input.buffer, input.byteOffset, input.byteLength); + } + return new Uint8Array(input); + } + const REMAINDER = [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -1, + -1, + -1, + -1, + 1, + 1, + 2, + 3, + ]; + class Wtf8Decoder { + static DEFAULT_BUFFERSIZE = 0x1000; + encoding = WTF8; + fatal; + ignoreBOM; + bufferSize; + #left = 0; + #cur = 0; + #pending = 0; + #first = true; + #buf; + constructor(label = 'wtf8', options = undefined) { + if (label.toLowerCase().replace('-', '') !== 'wtf8') { + throw new InvalidEncodingError(label); + } + this.fatal = Boolean(options?.fatal); + this.ignoreBOM = Boolean(options?.ignoreBOM); + this.bufferSize = Math.floor(options?.bufferSize ?? Wtf8Decoder.DEFAULT_BUFFERSIZE); + if (isNaN(this.bufferSize) || (this.bufferSize < 1)) { + throw new RangeError(`Invalid buffer size: ${options?.bufferSize}`); + } + this.#buf = new Uint16Array(this.bufferSize); + } + decode(input, options) { + const streaming = Boolean(options?.stream); + const bytes = getUint8(input); + const res = []; + const out = this.#buf; + const maxSize = this.bufferSize - 3; + let pos = 0; + const fatal = () => { + this.#cur = 0; + this.#left = 0; + this.#pending = 0; + if (this.fatal) { + throw new DecodeError(); + } + out[pos++] = REPLACEMENT; + }; + const fatals = () => { + const p = this.#pending; + for (let i = 0; i < p; i++) { + fatal(); + } + }; + const oneByte = (b) => { + if (this.#left === 0) { + const n = REMAINDER[b >> 4]; + switch (n) { + case -1: + fatal(); + break; + case 0: + out[pos++] = b; + break; + case 1: + this.#cur = b & 0x1f; + if ((this.#cur & 0x1e) === 0) { + fatal(); + } + else { + this.#left = 1; + this.#pending = 1; + } + break; + case 2: + this.#cur = b & 0x0f; + this.#left = 2; + this.#pending = 1; + break; + case 3: + if (b & 0x08) { + fatal(); + } + else { + this.#cur = b & 0x07; + this.#left = 3; + this.#pending = 1; + } + break; + } + } + else { + if ((b & 0xc0) !== 0x80) { + fatals(); + return oneByte(b); + } + if ((this.#pending === 1) && + (this.#left === 2) && + (this.#cur === 0) && + ((b & 0x20) === 0)) { + fatals(); + return oneByte(b); + } + if ((this.#left === 3) && (this.#cur === 0) && ((b & 0x30) === 0)) { + fatals(); + return oneByte(b); + } + this.#cur = (this.#cur << 6) | (b & 0x3f); + this.#pending++; + if (--this.#left === 0) { + if (this.ignoreBOM || !this.#first || (this.#cur !== BOM)) { + if (this.#cur < 0x10000) { + out[pos++] = this.#cur; + } + else { + const cp = this.#cur - 0x10000; + out[pos++] = ((cp >>> 10) & 0x3ff) | MIN_HIGH_SURROGATE; + out[pos++] = (cp & 0x3ff) | MIN_LOW_SURROGATE; + } + } + this.#cur = 0; + this.#pending = 0; + this.#first = false; + } + } + }; + for (const b of bytes) { + if (pos >= maxSize) { + res.push(String.fromCharCode.apply(null, out.subarray(0, pos))); + pos = 0; + } + oneByte(b); + } + if (!streaming) { + this.#first = true; + if (this.#cur || this.#left) { + fatals(); + } + } + if (pos > 0) { + res.push(String.fromCharCode.apply(null, out.subarray(0, pos))); + } + return res.join(''); + } + } + + function utf8length(str) { + let len = 0; + for (const s of str) { + const cp = s.codePointAt(0); + if (cp < 0x80) { + len++; + } + else if (cp < 0x800) { + len += 2; + } + else if (cp < 0x10000) { + len += 3; + } + else { + len += 4; + } + } + return len; + } + class Wtf8Encoder { + encoding = WTF8; + encode(input) { + if (!input) { + return EMPTY; + } + const buf = new Uint8Array(utf8length(String(input))); + this.encodeInto(input, buf); + return buf; + } + encodeInto(source, destination) { + const str = String(source); + const len = str.length; + const outLen = destination.length; + let written = 0; + let read = 0; + for (read = 0; read < len; read++) { + const c = str.codePointAt(read); + if (c < 0x80) { + if (written >= outLen) { + break; + } + destination[written++] = c; + } + else if (c < 0x800) { + if (written >= outLen - 1) { + break; + } + destination[written++] = 0xc0 | (c >> 6); + destination[written++] = 0x80 | (c & 0x3f); + } + else if (c < 0x10000) { + if (written >= outLen - 2) { + break; + } + destination[written++] = 0xe0 | (c >> 12); + destination[written++] = 0x80 | ((c >> 6) & 0x3f); + destination[written++] = 0x80 | (c & 0x3f); + } + else { + if (written >= outLen - 3) { + break; + } + destination[written++] = 0xf0 | (c >> 18); + destination[written++] = 0x80 | ((c >> 12) & 0x3f); + destination[written++] = 0x80 | ((c >> 6) & 0x3f); + destination[written++] = 0x80 | (c & 0x3f); + read++; + } + } + return { + read, + written, + }; + } + } + + const U$1=f$4.SIMPLE_FLOAT<<5|o$2.TWO,h$1=f$4.SIMPLE_FLOAT<<5|o$2.FOUR,B=f$4.SIMPLE_FLOAT<<5|o$2.EIGHT,j=f$4.SIMPLE_FLOAT<<5|T$2.TRUE,P=f$4.SIMPLE_FLOAT<<5|T$2.FALSE,$$1=f$4.SIMPLE_FLOAT<<5|T$2.UNDEFINED,q$1=f$4.SIMPLE_FLOAT<<5|T$2.NULL,z=new TextEncoder,K=new Wtf8Encoder,k$2={...e.defaultOptions,avoidInts:false,cde:false,collapseBigInts:true,dcbor:false,float64:false,flushToZero:false,forceEndian:null,ignoreOriginalEncoding:false,largeNegativeAsBigInt:false,reduceUnsafeNumbers:false,rejectBigInts:false,rejectCustomSimples:false,rejectDuplicateKeys:false,rejectFloats:false,rejectUndefined:false,simplifyNegativeZero:false,sortKeys:null,stringNormalization:null,types:null,wtf8:false},F={cde:true,ignoreOriginalEncoding:true,sortKeys:f$1},H$1={...F,dcbor:true,largeNegativeAsBigInt:true,reduceUnsafeNumbers:true,rejectCustomSimples:true,rejectDuplicateKeys:true,rejectUndefined:true,simplifyNegativeZero:true,stringNormalization:"NFC"};function y$3(e){const n=e<0;return typeof e=="bigint"?[n?-e-1n:e,n]:[n?-e-1:e,n]}function T$1(e,n,t){if(t.rejectFloats)throw new Error(`Attempt to encode an unwanted floating point number: ${e}`);if(isNaN(e))n.writeUint8(U$1),n.writeUint16(32256);else if(!t.float64&&Math.fround(e)===e){const r=s(e);r===null?(n.writeUint8(h$1),n.writeFloat32(e)):(n.writeUint8(U$1),n.writeUint16(r));}else n.writeUint8(B),n.writeFloat64(e);}function a$1(e,n,t){const[r,i]=y$3(e);if(i&&t)throw new TypeError(`Negative size: ${e}`);t??=i?f$4.NEG_INT:f$4.POS_INT,t<<=5,r<24?n.writeUint8(t|r):r<=255?(n.writeUint8(t|o$2.ONE),n.writeUint8(r)):r<=65535?(n.writeUint8(t|o$2.TWO),n.writeUint16(r)):r<=4294967295?(n.writeUint8(t|o$2.FOUR),n.writeUint32(r)):(n.writeUint8(t|o$2.EIGHT),n.writeBigUint64(BigInt(r)));}function p$1(e,n,t){typeof e=="number"?a$1(e,n,f$4.TAG):typeof e=="object"&&!t.ignoreOriginalEncoding&&N$2.ENCODED in e?n.write(e[N$2.ENCODED]):e<=Number.MAX_SAFE_INTEGER?a$1(Number(e),n,f$4.TAG):(n.writeUint8(f$4.TAG<<5|o$2.EIGHT),n.writeBigUint64(BigInt(e)));}function N$1(e,n,t){const[r,i]=y$3(e);if(t.collapseBigInts&&(!t.largeNegativeAsBigInt||e>=-0x8000000000000000n)){if(r<=0xffffffffn){a$1(Number(e),n);return}if(r<=0xffffffffffffffffn){const E=(i?f$4.NEG_INT:f$4.POS_INT)<<5;n.writeUint8(E|o$2.EIGHT),n.writeBigUint64(r);return}}if(t.rejectBigInts)throw new Error(`Attempt to encode unwanted bigint: ${e}`);const o=i?I.NEG_BIGINT:I.POS_BIGINT,c=r.toString(16),s=c.length%2?"0":"";p$1(o,n,t);const u=b$1(s+c);a$1(u.length,n,f$4.BYTE_STRING),n.write(u);}function Y(e,n,t){t.flushToZero&&(e=i(e)),Object.is(e,-0)?t.simplifyNegativeZero?t.avoidInts?T$1(0,n,t):a$1(0,n):T$1(e,n,t):!t.avoidInts&&Number.isSafeInteger(e)?a$1(e,n):t.reduceUnsafeNumbers&&Math.floor(e)===e&&e>=S$1.MIN&&e<=S$1.MAX?N$1(BigInt(e),n,t):T$1(e,n,t);}function Z(e,n,t){const r=t.stringNormalization?e.normalize(t.stringNormalization):e;if(t.wtf8&&!e.isWellFormed()){const i=K.encode(r);p$1(I.WTF8,n,t),a$1(i.length,n,f$4.BYTE_STRING),n.write(i);}else {const i=z.encode(r);a$1(i.length,n,f$4.UTF8_STRING),n.write(i);}}function J(e,n,t){const r=e;R(r,r.length,f$4.ARRAY,n,t);for(const i of r)g$1(i,n,t);}function V(e,n){a$1(e.length,n,f$4.BYTE_STRING),n.write(e);}const b=new s$1;b.registerEncoder(Array,J),b.registerEncoder(Uint8Array,V);function ce(e,n){return b.registerEncoder(e,n)}function R(e,n,t,r,i){const o=s$2(e);o&&!i.ignoreOriginalEncoding?r.write(o):a$1(n,r,t);}function X(e,n,t){if(e===null){n.writeUint8(q$1);return}if(!t.ignoreOriginalEncoding&&N$2.ENCODED in e){n.write(e[N$2.ENCODED]);return}const r=e.constructor;if(r){const o=t.types?.get(r)??b.get(r);if(o){const c=o(e,n,t);if(c!==void 0){if(!Array.isArray(c)||c.length!==2)throw new Error("Invalid encoder return value");(typeof c[0]=="bigint"||isFinite(Number(c[0])))&&p$1(c[0],n,t),g$1(c[1],n,t);}return}}if(typeof e.toCBOR=="function"){const o=e.toCBOR(n,t);o&&((typeof o[0]=="bigint"||isFinite(Number(o[0])))&&p$1(o[0],n,t),g$1(o[1],n,t));return}if(typeof e.toJSON=="function"){g$1(e.toJSON(),n,t);return}const i=Object.entries(e).map(o=>[o[0],o[1],Q(o[0],t)]);t.sortKeys&&i.sort(t.sortKeys),R(e,i.length,f$4.MAP,n,t);for(const[o,c,s]of i)n.write(s),g$1(c,n,t);}function g$1(e,n,t){switch(typeof e){case "number":Y(e,n,t);break;case "bigint":N$1(e,n,t);break;case "string":Z(e,n,t);break;case "boolean":n.writeUint8(e?j:P);break;case "undefined":if(t.rejectUndefined)throw new Error("Attempt to encode unwanted undefined.");n.writeUint8($$1);break;case "object":X(e,n,t);break;case "symbol":throw new TypeError(`Unknown symbol: ${e.toString()}`);default:throw new TypeError(`Unknown type: ${typeof e}, ${String(e)}`)}}function Q(e$1,n={}){const t={...k$2};n.dcbor?Object.assign(t,H$1):n.cde&&Object.assign(t,F),Object.assign(t,n);const r=new e(t);return g$1(e$1,r,t),r.read()} + + var o=(e=>(e[e.NEVER=-1]="NEVER",e[e.PREFERRED=0]="PREFERRED",e[e.ALWAYS=1]="ALWAYS",e))(o||{}); + + class t{static KnownSimple=new Map([[T$2.FALSE,false],[T$2.TRUE,true],[T$2.NULL,null],[T$2.UNDEFINED,void 0]]);value;constructor(e){this.value=e;}static create(e){return t.KnownSimple.has(e)?t.KnownSimple.get(e):new t(e)}toCBOR(e,i){if(i.rejectCustomSimples)throw new Error(`Cannot encode non-standard Simple value: ${this.value}`);a$1(this.value,e,f$4.SIMPLE_FLOAT);}toString(){return `simple(${this.value})`}decode(){return t.KnownSimple.has(this.value)?t.KnownSimple.get(this.value):this}[Symbol.for("nodejs.util.inspect.custom")](e,i,r){return `simple(${r(this.value,i)})`}} + + const p=new TextDecoder("utf8",{fatal:true,ignoreBOM:true});let y$2 = class y{static defaultOptions={maxDepth:1024,encoding:"hex",requirePreferred:false};#t;#r;#e=0;#i;constructor(t,r){if(this.#i={...y.defaultOptions,...r},typeof t=="string")switch(this.#i.encoding){case "hex":this.#t=b$1(t);break;case "base64":this.#t=y$4(t);break;default:throw new TypeError(`Encoding not implemented: "${this.#i.encoding}"`)}else this.#t=t;this.#r=new DataView(this.#t.buffer,this.#t.byteOffset,this.#t.byteLength);}toHere(t){return R$1(this.#t,t,this.#e)}*[Symbol.iterator](){if(yield*this.#n(0),this.#e!==this.#t.length)throw new Error("Extra data in input")}*seq(){for(;this.#ethis.#i.maxDepth)throw new Error(`Maximum depth ${this.#i.maxDepth} exceeded`);const r=this.#e,c=this.#r.getUint8(this.#e++),i=c>>5,n=c&31;let e=n,f=false,a=0;switch(n){case o$2.ONE:if(a=1,e=this.#r.getUint8(this.#e),i===f$4.SIMPLE_FLOAT){if(e<32)throw new Error(`Invalid simple encoding in extra byte: ${e}`);f=true;}else if(this.#i.requirePreferred&&e<24)throw new Error(`Unexpectedly long integer encoding (1) for ${e}`);break;case o$2.TWO:if(a=2,i===f$4.SIMPLE_FLOAT)e=o$1(this.#t,this.#e);else if(e=this.#r.getUint16(this.#e,false),this.#i.requirePreferred&&e<=255)throw new Error(`Unexpectedly long integer encoding (2) for ${e}`);break;case o$2.FOUR:if(a=4,i===f$4.SIMPLE_FLOAT)e=this.#r.getFloat32(this.#e,false);else if(e=this.#r.getUint32(this.#e,false),this.#i.requirePreferred&&e<=65535)throw new Error(`Unexpectedly long integer encoding (4) for ${e}`);break;case o$2.EIGHT:{if(a=8,i===f$4.SIMPLE_FLOAT)e=this.#r.getFloat64(this.#e,false);else if(e=this.#r.getBigUint64(this.#e,false),e<=Number.MAX_SAFE_INTEGER&&(e=Number(e)),this.#i.requirePreferred&&e<=4294967295)throw new Error(`Unexpectedly long integer encoding (8) for ${e}`);break}case 28:case 29:case 30:throw new Error(`Additional info not implemented: ${n}`);case o$2.INDEFINITE:switch(i){case f$4.POS_INT:case f$4.NEG_INT:case f$4.TAG:throw new Error(`Invalid indefinite encoding for MT ${i}`);case f$4.SIMPLE_FLOAT:yield [i,n,N$2.BREAK,r,0];return}e=1/0;break;default:f=true;}switch(this.#e+=a,i){case f$4.POS_INT:yield [i,n,e,r,a];break;case f$4.NEG_INT:yield [i,n,typeof e=="bigint"?-1n-e:-1-Number(e),r,a];break;case f$4.BYTE_STRING:e===1/0?yield*this.#s(i,t$1,r):yield [i,n,this.#a(e),r,e];break;case f$4.UTF8_STRING:e===1/0?yield*this.#s(i,t$1,r):yield [i,n,p.decode(this.#a(e)),r,e];break;case f$4.ARRAY:if(e===1/0)yield*this.#s(i,t$1,r,false);else {const o=Number(e);yield [i,n,o,r,a];for(let h=0;htypeof i=="string")?Object.fromEntries(d):new Map(d)}let w$1 = class w{static defaultDecodeOptions={...y$2.defaultOptions,ParentType:w,boxed:false,cde:false,dcbor:false,diagnosticSizes:o.PREFERRED,convertUnsafeIntsToFloat:false,createObject:k$1,pretty:false,preferMap:false,rejectLargeNegatives:false,rejectBigInts:false,rejectDuplicateKeys:false,rejectFloats:false,rejectInts:false,rejectLongLoundNaN:false,rejectLongFloats:false,rejectNegativeZero:false,rejectSimple:false,rejectStreaming:false,rejectStringsNotNormalizedAs:null,rejectSubnormals:false,rejectUndefined:false,rejectUnsafeFloatInts:false,saveOriginal:false,sortKeys:null,tags:null};static cdeDecodeOptions={cde:true,rejectStreaming:true,requirePreferred:true,sortKeys:f$1};static dcborDecodeOptions={...this.cdeDecodeOptions,dcbor:true,convertUnsafeIntsToFloat:true,rejectDuplicateKeys:true,rejectLargeNegatives:true,rejectLongLoundNaN:true,rejectLongFloats:true,rejectNegativeZero:true,rejectSimple:true,rejectUndefined:true,rejectUnsafeFloatInts:true,rejectStringsNotNormalizedAs:"NFC"};parent;mt;ai;left;offset;count=0;children=[];depth=0;#e;#t=null;constructor(r,i,e,t){if([this.mt,this.ai,,this.offset]=r,this.left=i,this.parent=e,this.#e=t,e&&(this.depth=e.depth+1),this.mt===f$4.MAP&&(this.#e.sortKeys||this.#e.rejectDuplicateKeys)&&(this.#t=[]),this.#e.rejectStreaming&&this.ai===o$2.INDEFINITE)throw new Error("Streaming not supported")}get isStreaming(){return this.left===1/0}get done(){return this.left===0}static create(r,i,e,t$1){const[s,l,n,c]=r;switch(s){case f$4.POS_INT:case f$4.NEG_INT:{if(e.rejectInts)throw new Error(`Unexpected integer: ${n}`);if(e.rejectLargeNegatives&&n<-0x8000000000000000n)throw new Error(`Invalid 65bit negative number: ${n}`);let o=n;return e.convertUnsafeIntsToFloat&&o>=S$1.MIN&&o<=S$1.MAX&&(o=Number(n)),e.boxed?d$2(o,t$1.toHere(c)):o}case f$4.SIMPLE_FLOAT:if(l>o$2.ONE){if(e.rejectFloats)throw new Error(`Decoding unwanted floating point number: ${n}`);if(e.rejectNegativeZero&&Object.is(n,-0))throw new Error("Decoding negative zero");if(e.rejectLongLoundNaN&&isNaN(n)){const o=t$1.toHere(c);if(o.length!==3||o[1]!==126||o[2]!==0)throw new Error(`Invalid NaN encoding: "${A$2(o)}"`)}if(e.rejectSubnormals&&l$1(t$1.toHere(c+1)),e.rejectLongFloats){const o=Q(n,{chunkSize:9,reduceUnsafeNumbers:e.rejectUnsafeFloatInts});if(o[0]>>5!==s)throw new Error(`Should have been encoded as int, not float: ${n}`);if(o.length=0)throw new Error(`Duplicate or out of order key: "0x${s[2]}"`);t=s;}}else if(this.#e.rejectDuplicateKeys){const t=new Set;for(const[s,l,n]of e){const c=A$2(n);if(t.has(c))throw new Error(`Duplicate key: "0x${c}"`);t.add(c);}}i=this.#e.createObject(e,this.#e);break}case f$4.BYTE_STRING:return d$1(this.children);case f$4.UTF8_STRING:{const e=this.children.join("");i=this.#e.boxed?d$2(e,r.toHere(this.offset)):e;break}case f$4.TAG:i=this.children.decode(this.#e);break;default:throw new TypeError(`Invalid mt on convert: ${this.mt}`)}return this.#e.saveOriginal&&i&&typeof i=="object"&&u(i,r.toHere(this.offset)),i}#r(){const r=this.children,i=r.length;if(i%2)throw new Error("Missing map value");const e=new Array(i/2);if(this.#t)for(let t=0;t>1]=[r[t],r[t+1],this.#t[t]];else for(let t=0;t>1]=[r[t],r[t+1],A$1];return e}}; + + const O$2=" ",y$1=new TextEncoder;class g extends w$1{close="";quote='"';get isEmptyStream(){return (this.mt===f$4.UTF8_STRING||this.mt===f$4.BYTE_STRING)&&this.count===0}}function a(m,l,n,p){let t="";if(l===o$2.INDEFINITE)t+="_";else {if(p.diagnosticSizes===o.NEVER)return "";{let r=p.diagnosticSizes===o.ALWAYS;if(!r){let e=o$2.ZERO;if(Object.is(n,-0))e=o$2.TWO;else if(m===f$4.POS_INT||m===f$4.NEG_INT){const T=n<0,u=typeof n=="bigint"?1n:1,o=T?-n-u:n;o<=23?e=Number(o):o<=255?e=o$2.ONE:o<=65535?e=o$2.TWO:o<=4294967295?e=o$2.FOUR:e=o$2.EIGHT;}else isFinite(n)?Math.fround(n)===n?s(n)==null?e=o$2.FOUR:e=o$2.TWO:e=o$2.EIGHT:e=o$2.TWO;r=e!==l;}r&&(t+="_",l0&&i!==N$2.BREAK&&(t$1.mt===f$4.MAP&&t$1.count%2?e+=": ":(e+=",",n.pretty||(e+=" "))),n.pretty&&(t$1.mt!==f$4.MAP||t$1.count%2===0)&&(e+=` +${O$2.repeat(t$1.depth+1)}`)),r=w$1.create(T,t$1,n,p),u){case f$4.POS_INT:case f$4.NEG_INT:e+=String(i),e+=a(u,o,i,n);break;case f$4.SIMPLE_FLOAT:if(i!==N$2.BREAK)if(typeof i=="number"){const c=Object.is(i,-0)?"-0.0":String(i);e+=c,isFinite(i)&&!/[.e]/.test(c)&&(e+=".0"),e+=a(u,o,i,n);}else i instanceof t?(e+="simple(",e+=String(i.value),e+=a(f$4.POS_INT,o,i.value,n),e+=")"):e+=String(i);break;case f$4.BYTE_STRING:i===1/0?(e+="(_ ",r.close=")",r.quote="'"):(e+="h'",e+=A$2(i),e+="'",e+=a(f$4.POS_INT,o,i.length,n));break;case f$4.UTF8_STRING:i===1/0?(e+="(_ ",r.close=")"):(e+=JSON.stringify(i),e+=a(f$4.POS_INT,o,y$1.encode(i).length,n));break;case f$4.ARRAY:{e+="[";const c=a(f$4.POS_INT,o,i,n);e+=c,c&&(e+=" "),n.pretty&&i?r.close=` +${O$2.repeat(r.depth)}]`:r.close="]";break}case f$4.MAP:{e+="{";const c=a(f$4.POS_INT,o,i,n);e+=c,c&&(e+=" "),n.pretty&&i?r.close=` +${O$2.repeat(r.depth)}}`:r.close="}";break}case f$4.TAG:e+=String(i),e+=a(f$4.POS_INT,o,i,n),e+="(",r.close=")";break}if(r===N$2.BREAK)if(t$1?.isStreaming)t$1.left=0;else throw new Error("Unexpected BREAK");else t$1&&(t$1.count++,t$1.left--);for(r instanceof g&&(t$1=r);t$1?.done;){if(t$1.isEmptyStream)e=e.slice(0,-3),e+=`${t$1.quote}${t$1.quote}_`;else {if(t$1.mt===f$4.MAP&&t$1.count%2!==0)throw new Error(`Odd streaming map size: ${t$1.count}`);e+=t$1.close;}t$1=t$1.parent;}}return e} + + const H=new TextDecoder;class A extends w$1{depth=0;leaf=false;value;length;[N$2.ENCODED];constructor(a,f,e,n){super(a,f,e,n),this.parent?this.depth=this.parent.depth+1:this.depth=n.initialDepth,[,,this.value,,this.length]=a;}numBytes(){switch(this.ai){case o$2.ONE:return 1;case o$2.TWO:return 2;case o$2.FOUR:return 4;case o$2.EIGHT:return 8}return 0}}function k(t){return t instanceof A}function O$1(t,a){return t===1/0?"Indefinite":a?`${t} ${a}${t!==1&&t!==1n?"s":""}`:String(t)}function y(t){return "".padStart(t," ")}function x$1(t$1,a,f){let e="";e+=y(t$1.depth*2);const n=f$3(t$1);e+=A$2(n.subarray(0,1));const r=t$1.numBytes();r&&(e+=" ",e+=A$2(n.subarray(1,r+1))),e=e.padEnd(a.minCol+1," "),e+="-- ",f!==void 0&&(e+=y(t$1.depth*2),f!==""&&(e+=`[${f}] `));let p=false;const[s]=t$1.children;switch(t$1.mt){case f$4.POS_INT:e+=`Unsigned: ${s}`,typeof s=="bigint"&&(e+="n");break;case f$4.NEG_INT:e+=`Negative: ${s}`,typeof s=="bigint"&&(e+="n");break;case f$4.BYTE_STRING:e+=`Bytes (Length: ${O$1(t$1.length)})`;break;case f$4.UTF8_STRING:e+=`UTF8 (Length: ${O$1(t$1.length)})`,t$1.length!==1/0&&(e+=`: ${JSON.stringify(s)}`);break;case f$4.ARRAY:e+=`Array (Length: ${O$1(t$1.value,"item")})`;break;case f$4.MAP:e+=`Map (Length: ${O$1(t$1.value,"pair")})`;break;case f$4.TAG:{e+=`Tag #${t$1.value}`;const o=t$1.children,[m]=o.contents.children,i=new i$1(o.tag,m);u(i,n);const l=i.comment(a,t$1.depth);l&&(e+=": ",e+=l),p||=i.noChildren;break}case f$4.SIMPLE_FLOAT:s===N$2.BREAK?e+="BREAK":t$1.ai>o$2.ONE?Object.is(s,-0)?e+="Float: -0":e+=`Float: ${s}`:(e+="Simple: ",s instanceof t?e+=s.value:e+=s);break}if(!p)if(t$1.leaf){if(e+=` +`,n.length>r+1){const o=y((t$1.depth+1)*2),m=f$2(n);if(m?.length){m.sort((l,c)=>{const g=l[0]-c[0];return g||c[1]-l[1]});let i=0;for(const[l,c,g]of m)if(!(lw===0&&D===c&&v==="<<");$>=0&&h.splice($,1);}e+=M(d),e+=` >> +`,e+=L(d,{initialDepth:t$1.depth+1,minCol:a.minCol,noPrefixHex:true});continue}else g==="'"&&(e+=y(a.minCol+1),e+="--",e+=o,e+="'",e+=H.decode(n.subarray(l,l+c)),e+=`' +`);if(l>r)for(let d=l;d{const t=[...e.entries()].map(o=>[o[0],o[1],Q(o[0],n)]);if(n.rejectDuplicateKeys){const o=new Set;for(const[d,u,y]of t){const g=A$2(y);if(o.has(g))throw new Error(`Duplicate map key: 0x${g}`);o.add(g);}}n.sortKeys&&t.sort(n.sortKeys),R(e,e.size,f$4.MAP,r,n);for(const[o,d,u]of t)r.write(u),g$1(d,r,n);});function h(e){return E(e.contents),new Date(e.contents)}h.comment=e=>(E(e.contents),`(String Date) ${new Date(e.contents).toISOString()}`),i$1.registerDecoder(I.DATE_STRING,h);function N(e){return O(e.contents),new Date(e.contents*1e3)}N.comment=e=>(O(e.contents),`(Epoch Date) ${new Date(e.contents*1e3).toISOString()}`),i$1.registerDecoder(I.DATE_EPOCH,N),ce(Date,e=>[I.DATE_EPOCH,e.valueOf()/1e3]);function T(e,r,n){if(f(r.contents),n.rejectBigInts)throw new Error(`Decoding unwanted big integer: ${r}(h'${A$2(r.contents)}')`);if(n.requirePreferred&&r.contents[0]===0)throw new Error(`Decoding overly-large bigint: ${r.tag}(h'${A$2(r.contents)})`);let t=r.contents.reduce((o,d)=>o<<8n|BigInt(d),0n);if(e&&(t=-1n-t),n.requirePreferred&&t>=Number.MIN_SAFE_INTEGER&&t<=Number.MAX_SAFE_INTEGER)throw new Error(`Decoding bigint that could have been int: ${t}n`);return n.boxed?d$2(t,r.contents):t}const _=T.bind(null,false),$=T.bind(null,true);_.comment=(e,r)=>`(Positive BigInt) ${T(false,e,r)}n`,$.comment=(e,r)=>`(Negative BigInt) ${T(true,e,r)}n`,i$1.registerDecoder(I.POS_BIGINT,_),i$1.registerDecoder(I.NEG_BIGINT,$);function D(e,r){return f(e.contents),e}D.comment=(e,r,n)=>{f(e.contents);const t={...r,initialDepth:n+2,noPrefixHex:true},o=f$3(e);let u=2**((o[0]&31)-24)+1;const y=o[u]&31;let g=A$2(o.subarray(u,++u));y>=24&&(g+=" ",g+=A$2(o.subarray(u,u+2**(y-24)))),t.minCol=Math.max(t.minCol,(n+1)*2+g.length);const p=L(e.contents,t);let I=`Embedded CBOR +`;return I+=`${"".padStart((n+1)*2," ")}${g}`.padEnd(t.minCol+1," "),I+=`-- Bytes (Length: ${e.contents.length}) +`,I+=p,I},D.noChildren=true,i$1.registerDecoder(I.CBOR,D),i$1.registerDecoder(I.URI,e=>(E(e.contents),new URL(e.contents)),"URI"),ce(URL,e=>[I.URI,e.toString()]),i$1.registerDecoder(I.BASE64URL,e=>(E(e.contents),x$2(e.contents)),"Base64url-encoded"),i$1.registerDecoder(I.BASE64,e=>(E(e.contents),y$4(e.contents)),"Base64-encoded"),i$1.registerDecoder(35,e=>(E(e.contents),new RegExp(e.contents)),"RegExp"),i$1.registerDecoder(21065,e=>{E(e.contents);const r=`^(?:${e.contents})$`;return new RegExp(r,"u")},"I-RegExp"),i$1.registerDecoder(I.REGEXP,e=>{if(U(e.contents),e.contents.length<1||e.contents.length>2)throw new Error(`Invalid RegExp Array: ${e.contents}`);return new RegExp(e.contents[0],e.contents[1])},"RegExp"),ce(RegExp,e=>[I.REGEXP,[e.source,e.flags]]),i$1.registerDecoder(64,e=>(f(e.contents),e.contents),"uint8 Typed Array");function c(e,r,n){f(e.contents);let t=e.contents.length;if(t%r.BYTES_PER_ELEMENT!==0)throw new Error(`Number of bytes must be divisible by ${r.BYTES_PER_ELEMENT}, got: ${t}`);t/=r.BYTES_PER_ELEMENT;const o=new r(t),d=new DataView(e.contents.buffer,e.contents.byteOffset,e.contents.byteLength),u=d[`get${r.name.replace(/Array/,"")}`].bind(d);for(let y=0;yc(e,Uint16Array,false),"uint16, big endian, Typed Array"),i$1.registerDecoder(66,e=>c(e,Uint32Array,false),"uint32, big endian, Typed Array"),i$1.registerDecoder(67,e=>c(e,BigUint64Array,false),"uint64, big endian, Typed Array"),i$1.registerDecoder(68,e=>(f(e.contents),new Uint8ClampedArray(e.contents)),"uint8 Typed Array, clamped arithmetic"),ce(Uint8ClampedArray,e=>[68,new Uint8Array(e.buffer,e.byteOffset,e.byteLength)]),i$1.registerDecoder(69,e=>c(e,Uint16Array,true),"uint16, little endian, Typed Array"),ce(Uint16Array,(e,r,n)=>l(r,69,65,e,n)),i$1.registerDecoder(70,e=>c(e,Uint32Array,true),"uint32, little endian, Typed Array"),ce(Uint32Array,(e,r,n)=>l(r,70,66,e,n)),i$1.registerDecoder(71,e=>c(e,BigUint64Array,true),"uint64, little endian, Typed Array"),ce(BigUint64Array,(e,r,n)=>l(r,71,67,e,n)),i$1.registerDecoder(72,e=>(f(e.contents),new Int8Array(e.contents)),"sint8 Typed Array"),ce(Int8Array,e=>[72,new Uint8Array(e.buffer,e.byteOffset,e.byteLength)]),i$1.registerDecoder(73,e=>c(e,Int16Array,false),"sint16, big endian, Typed Array"),i$1.registerDecoder(74,e=>c(e,Int32Array,false),"sint32, big endian, Typed Array"),i$1.registerDecoder(75,e=>c(e,BigInt64Array,false),"sint64, big endian, Typed Array"),i$1.registerDecoder(77,e=>c(e,Int16Array,true),"sint16, little endian, Typed Array"),ce(Int16Array,(e,r,n)=>l(r,77,73,e,n)),i$1.registerDecoder(78,e=>c(e,Int32Array,true),"sint32, little endian, Typed Array"),ce(Int32Array,(e,r,n)=>l(r,78,74,e,n)),i$1.registerDecoder(79,e=>c(e,BigInt64Array,true),"sint64, little endian, Typed Array"),ce(BigInt64Array,(e,r,n)=>l(r,79,75,e,n)),i$1.registerDecoder(81,e=>c(e,Float32Array,false),"IEEE 754 binary32, big endian, Typed Array"),i$1.registerDecoder(82,e=>c(e,Float64Array,false),"IEEE 754 binary64, big endian, Typed Array"),i$1.registerDecoder(85,e=>c(e,Float32Array,true),"IEEE 754 binary32, little endian, Typed Array"),ce(Float32Array,(e,r,n)=>l(r,85,81,e,n)),i$1.registerDecoder(86,e=>c(e,Float64Array,true),"IEEE 754 binary64, big endian, Typed Array"),ce(Float64Array,(e,r,n)=>l(r,86,82,e,n)),i$1.registerDecoder(I.SET,(e,r)=>{if(U(e.contents),r.sortKeys){const n=w$1.decodeToEncodeOpts(r);let t=null;for(const o of e.contents){const d=[o,void 0,Q(o,n)];if(t&&r.sortKeys(t,d)>=0)throw new Error(`Set items out of order in tag #${I.SET}`);t=d;}}return new Set(e.contents)},"Set"),ce(Set,(e,r,n)=>{let t=[...e];if(n.sortKeys){const o=t.map(d=>[d,void 0,Q(d,n)]);o.sort(n.sortKeys),t=o.map(([d])=>d);}return [I.SET,t]}),i$1.registerDecoder(I.JSON,e=>(E(e.contents),JSON.parse(e.contents)),"JSON-encoded");function x(e){return f(e.contents),new Wtf8Decoder().decode(e.contents)}x.comment=e=>{f(e.contents);const r=new Wtf8Decoder;return `(WTF8 string): ${JSON.stringify(r.decode(e.contents))}`},i$1.registerDecoder(I.WTF8,x),i$1.registerDecoder(I.SELF_DESCRIBED,e=>e.contents,"Self-Described"),i$1.registerDecoder(I.INVALID_16,()=>{throw new Error(`Tag always invalid: ${I.INVALID_16}`)},"Invalid"),i$1.registerDecoder(I.INVALID_32,()=>{throw new Error(`Tag always invalid: ${I.INVALID_32}`)},"Invalid"),i$1.registerDecoder(I.INVALID_64,()=>{throw new Error(`Tag always invalid: ${I.INVALID_64}`)},"Invalid");function w(e){throw new Error(`Encoding ${e.constructor.name} intentionally unimplmented. It is not concrete enough to interoperate. Convert to Uint8Array first.`)}ce(ArrayBuffer,w),ce(DataView,w),typeof SharedArrayBuffer<"u"&&ce(SharedArrayBuffer,w);function m(e){return [NaN,e.valueOf()]}ce(Boolean,m),ce(Number,m),ce(String,m),ce(BigInt,m); + + const{cdeDecodeOptions:r,dcborDecodeOptions:n,defaultDecodeOptions:d}=w$1; + + /* globals: jsonld */ + + + // Setup JSON-LD documentLoader + const xhrDocumentLoader = jsonld.documentLoaders.xhr(); + // FIXME: add UI to let users control and set context mapping + jsonld.documentLoader = function(url) { + // rewrite URLs that we know have secure JSON-LD Contexts + if(url === 'http://schema.org/' || url === 'http://schema.org') { + url = 'https://schema.org/'; + } + + // if a non-HTTPS URL, use the proxy since we run in HTTPS only mode + if(!url.startsWith('https://')) { + url = [ + location.protocol, + '//', + location.host, + // NOTE: using hard-coded path so file can be shared with dev page + //location.pathname, + '/playground/', + 'proxy?url=', + url + ].join(''); + } + + return xhrDocumentLoader(url); + }; + + const jsonLdAtTerms = [ + { label: "@context", type: "keyword", info: "Defines the JSON-LD context" }, + { label: "@id", type: "keyword", info: "Specifies the unique identifier of an entity" }, + { label: "@type", type: "keyword", info: "Defines the type of an entity" }, + { label: "@value", type: "keyword", info: "Represents the value of a node" }, + { label: "@language", type: "keyword", info: "Specifies the language of a string value" }, + { label: "@graph", type: "keyword", info: "Represents a named graph" }, + { label: "@list", type: "keyword", info: "Denotes an ordered list" }, + { label: "@set", type: "keyword", info: "Denotes an unordered set" }, + { label: "@reverse", type: "keyword", info: "Defines reverse properties" }, + { label: "@index", type: "keyword", info: "Specifies an index for ordered data" }, + { label: "@base", type: "keyword", info: "Defines the base IRI" }, + { label: "@vocab", type: "keyword", info: "Defines the default vocabulary" }, + { label: "@container", type: "keyword", info: "Specifies container types for properties" }, + { label: "@nest", type: "keyword", info: "Allows nesting of properties" }, + { label: "@prefix", type: "keyword", info: "Defines a prefix mapping" }, + { label: "@propagate", type: "keyword", info: "Controls context propagation" }, + { label: "@protected", type: "keyword", info: "Prevents term overrides" }, + { label: "@version", type: "keyword", info: "Specifies the JSON-LD version" } + ]; + + // TODO: the next two functions could probably become a petite-vue component + function editorListener(docName) { + let changes = []; // keep the changes as a list; then pick the last one + let timer; // we only want one timer, once the last one is done, use the result + function debounce(fn, delay) { + return (...args) => { + clearTimeout(timer); + timer = setTimeout(() => fn(...args), delay); + }; + } + + return EditorView.updateListener.of((update) => { + if (update.docChanged) { + changes.push(update.state.doc.toString()); + debounce((docName) => { + // set the global `doc` to the latest string from the editor + try { + const parsed = JSON.parse(changes[changes.length-1]); + this[docName] = parsed; + this.parseError = ''; + } catch (err) { + this.parseError = err.message; + } }, 1000).call(this, docName); + } + }); + } + function initEditor(id, content, varName) { + return new EditorView({ + parent: document.getElementById(id), + doc: JSON.stringify(content, null, 2), + extensions: [ + basicSetup, + keymap.of([indentWithTab]), + json(), + linter(jsonParseLinter()), + autocompletion({override: [completeFromList(jsonLdAtTerms)]}), + editorListener.call(this, varName) + ] + }); + } + + const language = new Compartment(); + + const readOnlyEditor = new EditorView({ + parent: document.getElementById('read-only-editor'), + doc: `{}`, + extensions: [ + basicSetup, + language.of(json()), + EditorState.readOnly.of(true), + EditorView.editable.of(false), + EditorView.contentAttributes.of({tabindex: '0'}) + ] + }); + + function setEditorValue(_editor, doc, lang) { + // TODO: this runs more often than it should (because v-effect); maybe debounce + if (_editor) { + // set the correct language + language.reconfigure(json()); + if (lang === 'yaml') language.reconfigure(yaml()); + else language.reconfigure(StreamLanguage.define(ntriples)); + + _editor.dispatch({ + changes: { + from: 0, + to: _editor.state.doc.length, + insert: typeof(doc) === 'object' + ? JSON.stringify(doc, null, 2) + : doc + }, + // set the correct language + effects: language.reconfigure(typeof(doc) === 'object' + ? json() + : StreamLanguage.define(ntriples)) + }); + } + } + + window.app = Qe({ + doc: {}, + contextDoc: {}, + frameDoc: {}, + tableQuads: {}, + yamlLD: '', + cborLD: { + bytes: {}, + hex: '', + jsonldSize: 0, + size: 0, + percentage: 0 + }, + remoteDocURL: '', + remoteSideDocURL: '', + parseError: '', + inputTab: 'json-ld', + outputTab: 'expanded', + options: { + processingMode: '', + base: '', + baseUrl: '', + compactArrays: true, + compactToRelative: true, + rdfDirection: '', + safe: '' + }, + // computed + get editorColumns() { + if (['compacted', 'flattened', 'framed'].indexOf(this.outputTab) > -1) { + return 'two column'; + } + return ''; + }, + get sideDoc() { + if (this.outputTab === 'framed') { + return 'frameDoc'; + } else { + return 'contextDoc'; + } + }, + get sideEditor() { + if (this.outputTab === 'framed') { + return this.frameEditor; + } else { + return this.contextEditor; + } + }, + get sideEditorURLFieldPlaceholderText() { + if (this.outputTab === 'framed') { + return 'Frame URL'; + } else { + return 'Context URL'; + } + }, + // methods + async retrieveDoc(_editor, docVar, url) { + try { + const rv = await fetch(url); + if (!rv.ok) { + throw new Error(`HTTP error status: ${rv.status}`); + } + this[docVar] = await rv.json(); + setEditorValue(_editor, this[docVar]); + // clear the remoteDocURL to avoid confusion around state + this.remoteDocURL = ''; + this.remoteSideDocURL = ''; + } catch (err) { + this.parseError = err.message; + } + }, + async loadExample(file) { + const rv = await fetch(`/examples/playground/${file}`); + this.doc = await rv.json(); + setEditorValue(this.mainEditor, this.doc); + // TODO: make this less of a hack...so we can provide other frames + if (file === 'library.jsonld') { + const frame = await fetch(`/examples/playground/library-frame.jsonld`); + this.frameDoc = await frame.json(); + setEditorValue(this.frameEditor, this.frameDoc); + } else { + this.frameDoc = {}; + setEditorValue(this.frameEditor, this.frameDoc); + } + this.setOutputTab(this.outputTab); + }, + async setOutputTab(value) { + if (value) this.outputTab = value; + let context = this.contextDoc; + switch (this.outputTab) { + case 'expanded': + // TODO: this should happen elsewhere...like a watcher + try { + const expanded = await jsonld.expand(this.doc, this.options); + setEditorValue(readOnlyEditor, expanded); + this.parseError = ''; + } catch(err) { + this.parseError = err.message; + } + break; + case 'compacted': + if (JSON.stringify(context) === '{}' && '@context' in this.doc) { + // no context set yet, so copy in the main document's + context = { + '@context': this.doc['@context'] + }; + this.contextDoc = context; + } + try { + const compacted = await jsonld.compact(this.doc, {'@context': context['@context'] || {}}, this.options); + setEditorValue(readOnlyEditor, compacted); + this.parseError = ''; + } catch(err) { + this.parseError = err.message; + } + break; + case 'flattened': + if (JSON.stringify(context) === '{}' && '@context' in this.doc) { + // no context set yet, so copy in the main document's + context = { + '@context': this.doc['@context'] + }; + this.contextDoc = context; + } + try { + const flattened = await jsonld.flatten(this.doc, {'@context': context['@context'] || {}}, this.options); + setEditorValue(readOnlyEditor, flattened); + this.parseError = ''; + } catch(err) { + this.parseError = err.message; + } + break; + case 'framed': + try { + const framed = await jsonld.frame(this.doc, this.frameDoc, this.options); + setEditorValue(readOnlyEditor, framed); + this.parseError = ''; + } catch(err) { + this.parseError = err.message; + } + break; + case 'nquads': + // TODO: this should happen elsewhere...like a watcher + try { + const output = await jsonld.toRDF(this.doc, { + format: 'application/n-quads', + ...this.options + }); + setEditorValue(readOnlyEditor, output); + this.parseError = ''; + } catch(err) { + this.parseError = err.message; + } + break; + case 'canonized': + // TODO: this should happen elsewhere...like a watcher + try { + const output = await jsonld.canonize(this.doc, { + format: 'application/n-quads', ...this.options + }); + setEditorValue(readOnlyEditor, output); + this.parseError = ''; + } catch(err) { + this.parseError = err.message; + } + break; + case 'table': + // TODO: this should happen elsewhere...like a watcher + try { + const output = await jsonld.toRDF(this.doc, this.options); + this.tableQuads = output; + this.parseError = ''; + } catch(err) { + this.parseError = err.message; + } + break; + case 'yamlld': + this.yamlLD = YAML.stringify(this.doc); + setEditorValue(readOnlyEditor, this.yamlLD, 'yaml'); + break; + case 'cborld': + try { + this.cborLD.jsonldSize = JSON.stringify(this.doc).length; + this.cborLD.bytes = await encode({ + jsonldDocument: this.doc, + documentLoader: jsonld.documentLoader, + // use standard compression (set to `0` to use no compression) + registryEntryId: 1 + }); + this.cborLD.size = this.cborLD.bytes.length; + this.cborLD.hex = Array.from(this.cborLD.bytes, byte => + byte.toString(16).padStart(2, '0')).join(''); + this.cborLD.percentage = + Math.floor(((this.cborLD.jsonldSize - this.cborLD.size) / this.cborLD.jsonldSize) * 100); + this.cborLD.diagnostics = M(this.cborLD.bytes, { + pretty: true}); + setEditorValue(readOnlyEditor, this.cborLD.diagnostics, 'cbor'); + this.parseError = ''; + } catch (err) { + // TODO: currently, the editor keeps it's old value...unupdated... + this.parseError = err.message; + console.error(err); } - break; - case 'yamlld': - this.yamlLD = YAML.stringify(this.doc); - setEditorValue(readOnlyEditor, this.yamlLD, 'yaml'); break; default: setEditorValue(readOnlyEditor, {}); diff --git a/playground/next/editor.mjs b/playground/next/editor.mjs index d38737785..d99b6232d 100644 --- a/playground/next/editor.mjs +++ b/playground/next/editor.mjs @@ -13,6 +13,7 @@ import {linter} from '@codemirror/lint'; import YAML from 'yaml'; import {yaml} from '@codemirror/lang-yaml'; import * as cborld from '@digitalbazaar/cborld'; +import * as cbor2 from 'cbor2'; // Setup JSON-LD documentLoader const xhrDocumentLoader = jsonld.documentLoaders.xhr(); @@ -335,7 +336,9 @@ window.app = createApp({ byte.toString(16).padStart(2, '0')).join(''); this.cborLD.percentage = Math.floor(((this.cborLD.jsonldSize - this.cborLD.size) / this.cborLD.jsonldSize) * 100); - setEditorValue(readOnlyEditor, this.cborLD.bytes, 'cbor'); + this.cborLD.diagnostics = cbor2.diagnose(this.cborLD.bytes, { + pretty: true}) + setEditorValue(readOnlyEditor, this.cborLD.diagnostics, 'cbor'); this.parseError = ''; } catch (err) { // TODO: currently, the editor keeps it's old value...unupdated... diff --git a/playground/next/index.html b/playground/next/index.html index f78f09ea4..c52533973 100644 --- a/playground/next/index.html +++ b/playground/next/index.html @@ -279,7 +279,7 @@

JSON-LD Playground

-
+
@@ -321,7 +321,7 @@

JSON-LD Playground

-
+
From c77a16bb0058c94e8814ae41a3f79157884dd1fd Mon Sep 17 00:00:00 2001 From: Benjamin Young Date: Thu, 22 May 2025 10:18:49 -0400 Subject: [PATCH 48/54] Use logos for YAML-LD and CBOR-LD tabs. --- playground/next/index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/playground/next/index.html b/playground/next/index.html index c52533973..9a09bb1d2 100644 --- a/playground/next/index.html +++ b/playground/next/index.html @@ -274,8 +274,8 @@

JSON-LD Playground

N-Quads
Canonized
Table
-
YAML-LD
-
CBOR-LD
+
YAML-LD
+
CBOR-LD
From 74d811275226d9c9e2c76feb24dc9d8f99f22cc0 Mon Sep 17 00:00:00 2001 From: Benjamin Young Date: Thu, 22 May 2025 10:46:31 -0400 Subject: [PATCH 49/54] Refactor tabs into a data object. Makes the markup shorter and the tabs object can now more easily be mutated (if/when needed) for future conditions where they may not all be available for a certain use case (or hidden optionally to improve developer focus). --- playground/next/editor.bundle.js | 11 +++++++++++ playground/next/editor.mjs | 11 +++++++++++ playground/next/index.html | 10 +--------- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/playground/next/editor.bundle.js b/playground/next/editor.bundle.js index 6a552ccda..21c283c13 100644 --- a/playground/next/editor.bundle.js +++ b/playground/next/editor.bundle.js @@ -39494,6 +39494,17 @@ ${O$2.repeat(r.depth)}}`:r.close="}";break}case f$4.TAG:e+=String(i),e+=a(f$4.PO rdfDirection: '', safe: '' }, + tabs: { + expanded: {icon: 'expanded alternate', label: 'Expanded'}, + compacted: {icon: 'compress alternate', label: 'Compacted'}, + flattened: {icon: 'bars', label: 'Flattened'}, + framed: {icon: 'crop alternate', label: 'Framed'}, + nquads: {icon: 'rdf-icon-rdf', label: 'N-Quads'}, + canonized: {icon: 'archive', label: 'Canonized'}, + table: {icon: 'th', label: 'Table'}, + yamlld: {icon: 'stream', label: 'YAML-LD'}, + cborld: {icon: 'robot', label: 'CBOR-LD'} + }, // computed get editorColumns() { if (['compacted', 'flattened', 'framed'].indexOf(this.outputTab) > -1) { diff --git a/playground/next/editor.mjs b/playground/next/editor.mjs index d99b6232d..5787804c8 100644 --- a/playground/next/editor.mjs +++ b/playground/next/editor.mjs @@ -169,6 +169,17 @@ window.app = createApp({ rdfDirection: '', safe: '' }, + tabs: { + expanded: {icon: 'expanded alternate', label: 'Expanded'}, + compacted: {icon: 'compress alternate', label: 'Compacted'}, + flattened: {icon: 'bars', label: 'Flattened'}, + framed: {icon: 'crop alternate', label: 'Framed'}, + nquads: {icon: 'rdf-icon-rdf', label: 'N-Quads'}, + canonized: {icon: 'archive', label: 'Canonized'}, + table: {icon: 'th', label: 'Table'}, + yamlld: {icon: 'stream', label: 'YAML-LD'}, + cborld: {icon: 'robot', label: 'CBOR-LD'} + }, // computed get editorColumns() { if (['compacted', 'flattened', 'framed'].indexOf(this.outputTab) > -1) { diff --git a/playground/next/index.html b/playground/next/index.html index 9a09bb1d2..fbc3ed696 100644 --- a/playground/next/index.html +++ b/playground/next/index.html @@ -267,15 +267,7 @@

JSON-LD Playground

From 470d980639dbf001780e83ac1172d1dbf928bcd5 Mon Sep 17 00:00:00 2001 From: Benjamin Young Date: Thu, 22 May 2025 10:55:00 -0400 Subject: [PATCH 50/54] Fix header item border. --- _layouts/fomantic.liquid | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_layouts/fomantic.liquid b/_layouts/fomantic.liquid index bc5510e8f..9a190936b 100644 --- a/_layouts/fomantic.liquid +++ b/_layouts/fomantic.liquid @@ -22,7 +22,7 @@ /* fomantic overrides */ html { scroll-padding-top: 5em; } .ui.main.container { margin-top: 7em; } - .ui.top.menu .header.item { border: none; padding-left: 0; } + .ui.top.menu .header.item { border: none !important; padding-left: 0; } .ui.top.menu .header.item > span { font-size: 150%; } .ui.top.menu .header.item > img { margin-right: 1rem; } .ui.fitted.tab.segment > .ui.table { border-left: 0; border-top: 0; border-right: 0; margin-top: 0; } From 29f13e74c3609efa9cf706748c6b8afb1a8e7b0b Mon Sep 17 00:00:00 2001 From: Benjamin Young Date: Tue, 27 May 2025 16:57:37 -0400 Subject: [PATCH 51/54] Add tweak to reduce jank. Sadly, there is still a bit remaining, but this is a bit less jarring above the fold. --- playground/next/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playground/next/index.html b/playground/next/index.html index fbc3ed696..9814eb2f2 100644 --- a/playground/next/index.html +++ b/playground/next/index.html @@ -94,7 +94,7 @@

JSON-LD Playground

JSON-LD Size bytes
-
+