diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 39615b4e..d7509f0b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,16 +11,9 @@ jobs: runs-on: ubuntu-latest strategy: fail-fast: false - matrix: + matrix: exist-version: [release, latest] node-version: ['22'] - # TODO: see #563 could still be usefull for gulp builds - # services: - # # Label used to access the service container - # exist: - # image: existdb/existdb:${{ matrix.exist-version}} - # ports: - # - 8080:8080 steps: - uses: actions/checkout@v6 @@ -36,18 +29,40 @@ jobs: - name: Run npm CI run: npm ci - + + - name: Run unit tests + run: npm test + + - name: Validate OpenAPI schema + run: npx --yes @redocly/cli lint modules/api.json + - name: Build frontend Using node.js ${{ matrix.node-version }} and build EXPath Package run: npm run build + - name: Download Roaster dependency + run: gh release download v1.12.0 --repo eeditiones/roaster --pattern "roaster-1.12.0.xar" --output build/roaster-1.12.0.xar + env: + GH_TOKEN: ${{ github.token }} + - name: Start eXist-db Docker Container run: docker run --rm --name exist --volume $(pwd)/build:/exist/autodeploy:ro --publish 8080:8080 --detach existdb/existdb:${{ matrix.exist-version }} - - name: Wait for eXist-db Startup - run: timeout 90 sh -c 'until nc -z $0 $1; do sleep 3; done' localhost 8080 + - name: Wait for eXist-db Startup and Package Deployment + run: | + for i in $(seq 1 24); do + HTTP_CODE=$(curl -s -o /dev/null -w '%{http_code}' http://localhost:8080/exist/apps/eXide/index.html || true) + echo "Attempt $i: HTTP $HTTP_CODE" + if [ "$HTTP_CODE" = "200" ]; then + echo "eXide is ready" + exit 0 + fi + sleep 5 + done + echo "Timed out waiting for eXide" + exit 1 - name: Run Cypress Integration Tests - run: npx cypress run + run: npx cypress run --spec 'cypress/e2e/*.cy.js' - name: Upload Cypress Screenshots (on failure) uses: actions/upload-artifact@v7 @@ -64,7 +79,7 @@ jobs: name: cypress-videos-${{ matrix.exist-version }}-${{ matrix.node-version }} path: cypress/videos - # TODO: Add upload to dockerhub + # TODO: Add upload to dockerhub # release: # name: Release # runs-on: ubuntu-latest @@ -87,4 +102,4 @@ jobs: # - name: Release # env: # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # run: npx semantic-release \ No newline at end of file + # run: npx semantic-release diff --git a/.gitignore b/.gitignore index 974e6367..1a742e5a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,16 +1,19 @@ .DS_Store +.env build/* expath-pkg.xml resources/scripts/xqlint.min.js -resources/scripts/ace +resources/scripts/prettier-bundle.js +resources/scripts/cm6-bundle.js +resources/scripts/ag-grid-bundle.js resources/scripts/eXide.min.* resources/scripts/jquery/jquery.plugins.min.* resources/css/ag-grid-community/ tools/r.js - tools/js.jar +tools/REx.class index.html node_modules/ @@ -18,8 +21,12 @@ node_modules/ cypress/videos/*.mp4 cypress/screenshots/**/*.png +CLAUDE.md + .* !.gitignore !.existdb.json !.gitmodules !.github +src-tauri/target/ +dist-desktop/ diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 005182b2..00000000 --- a/.gitmodules +++ /dev/null @@ -1,6 +0,0 @@ -[submodule "support/xqlint"] - path = support/xqlint -url=https://github.com/eXist-db/xqlint.git -[submodule "support/ace"] - path = support/ace - url = https://github.com/ajaxorg/ace.git diff --git a/README.md b/README.md index 2f0b7b66..ee925832 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ # eXide - a web-based XQuery IDE -eXide is a web-based XQuery IDE built around the [ace editor](https://ace.c9.io/). It is tightly integrated with the [eXist-db native XML database](https://exist-db.org). +eXide is a web-based XQuery IDE built around [CodeMirror 6](https://codemirror.net/). It is tightly integrated with the [eXist-db native XML database](https://exist-db.org). ## Features @@ -52,7 +52,7 @@ To build eXide from scratch: ```bash git clone git://github.com/eXist-db/eXide.git cd eXide -git submodule update --init --recursive + ``` Next, call `npm install` once: @@ -73,7 +73,7 @@ You should now find a `.xar` file in the `build/` directory: `build/eXide-*.*.*. We welcome contributions to help us improve both unit and integration tests. Current tests can be found in the `cypress/integration` folder. -eXide's GitHub repository is configured to run tests automatically on each PR via TravisCI (see `.travis.yml`). +eXide's GitHub repository is configured to run tests automatically on each PR via GitHub Actions. To run tests locally, build and install eXide on localhost, and start the tests: @@ -81,7 +81,7 @@ To run tests locally, build and install eXide on localhost, and start the tests: # clone the repo git clone git://github.com/eXist-db/eXide.git cd eXide -git submodule update --init --recursive + # build exide npm install # at this point if you are planning to build another branch change it now diff --git a/build.js b/build.js deleted file mode 100644 index d06e8cf8..00000000 --- a/build.js +++ /dev/null @@ -1,10 +0,0 @@ -{ - appDir: "./js", - baseUrl: "." - name: "main.js", - out: "./xqlint.js", - //dir: "build/xqlint", - paths: { - "xqlint": "support/xqlint/lib" - } -} \ No newline at end of file diff --git a/controller.xq b/controller.xq index 5d990115..37cb4e19 100644 --- a/controller.xq +++ b/controller.xq @@ -20,7 +20,7 @@ declare variable $local:forwarded-for := request:get-header("X-Forwarded-URI"); declare variable $local:wants-json := tokenize(request:get-header('Accept'), ', ?') = 'application/json'; declare function local:get-user () as xs:string? { - let $login := login:set-user($local:login-domain, "P7D", false()) + let $login := login:set-user($local:login-domain, xs:dayTimeDuration("P7D"), false()) let $user-id := request:get-attribute($local:login-domain || ".user") return $user-id }; @@ -105,6 +105,14 @@ else if ($local:method = 'get' and $exist:resource = "backdrop.svg") then +(: REST API — all /api/* requests are handled by Roaster. + : Placed before the unauthorized check so that Roaster can handle + : its own auth via OpenAPI security schemes and x-constraints. :) +else if (starts-with($exist:path, "/api/")) then + + + + (: handle unauthorized request :) else if (not($user-allowed)) @@ -145,24 +153,6 @@ then ( ) -else if (starts-with($exist:path, "/store/")) then - let $resource := substring-after($exist:path, "/store") - return - - - - - - -else if (starts-with($exist:path, "/check/")) then - let $resource := substring-after($exist:path, "/validate") - return - - - - - - else if ($local:method = 'get' and $exist:resource = "index.html") then @@ -240,25 +230,20 @@ else if ($local:method = 'get' and starts-with($exist:path, '/results/')) then -else if ($local:method = 'post' and $exist:resource eq "outline") then - let $query := request:get-parameter("qu", ()) - let $base := request:get-parameter("base", ()) - return - - - - - - - - +(: Disabled: debuger.xq is abandoned/non-functional else if ($exist:resource eq "debug") then - +:) + +(: Block abandoned/non-functional modules :) +else if ($exist:resource = ("debuger.xq", "git.xq")) then ( + response:set-status-code(410), + { $exist:resource } is no longer available. +) else if (ends-with($exist:path, ".xq")) then diff --git a/cypress/e2e/adaptive_serialization_spec.cy.js b/cypress/e2e/adaptive_serialization_spec.cy.js new file mode 100644 index 00000000..47f5f2b0 --- /dev/null +++ b/cypress/e2e/adaptive_serialization_spec.cy.js @@ -0,0 +1,141 @@ +/** + * Adaptive serialization: verifies that eXide's results panel displays each + * XDM atomic type using the W3C adaptive serialization form, as-is, with no + * additional escaping or decoding by the client. + * + * The rendering path is: + * lsp:fetch() → AdaptiveWriter → eXide /api/query/{id}/results (items[].value) + * → contentDiv.textContent = item.value (eXide.js fetchCursorPage) + * + * Test values are drawn from the XQTS ser/method-adaptive.xml test cases. + */ +describe('Adaptive serialization of atomic types', () => { + /** + * Set the active editor document content directly via the eXide JS API. + * Avoids slow keyboard simulation. + */ + function setEditorContent(text) { + cy.window().then((win) => { + const doc = win.eXide.app.getEditor().getActiveDocument(); + doc.setText(text); + }); + } + + /** + * Run the current editor content and return a Cypress chain that asserts on + * the text of the first result item in the results panel. + */ + function runAndGetFirstResult(timeout = 10000) { + cy.get('#run').click(); + return cy.get('.panel-south .results .content', { timeout }) + .first() + .invoke('text'); + } + + beforeEach(() => { + cy.loginXHR('admin', ''); + cy.visit('/eXide/index.html'); + cy.reload(true); + cy.get('.path', { timeout: 10000 }).should('contain', 'untitled-1'); + cy.get('#user', { timeout: 10000 }).should('not.have.text', 'Login'); + // Serialization mode defaults to "adaptive" (first option); no need to set it. + }); + + // ── Numerics with XQuery literal syntax ────────────────────────────────── + + it('xs:integer — bare number, no quoting (XQTS adaptive-46)', () => { + setEditorContent('xs:integer(1)'); + runAndGetFirstResult().should('eq', '1'); + }); + + it('xs:decimal — bare decimal, no quoting (XQTS adaptive-45)', () => { + setEditorContent('xs:decimal(1.2)'); + runAndGetFirstResult().should('eq', '1.2'); + }); + + it('xs:double — exponential notation, no quoting (XQTS adaptive-44)', () => { + setEditorContent('xs:double(1e0)'); + runAndGetFirstResult().should('match', /^1\.0e0$/); + }); + + it('xs:float — constructor syntax xs:float("…") (XQTS adaptive-43)', () => { + // xs:float has no XQuery literal form; serialized as xs:float("value"). + setEditorContent('xs:float("1e0")'); + runAndGetFirstResult().should('match', /^xs:float\("/); + }); + + it('xs:boolean true — true() function-call syntax (XQTS adaptive-74)', () => { + setEditorContent('xs:boolean(true())'); + runAndGetFirstResult().should('eq', 'true()'); + }); + + it('xs:boolean false — false() function-call syntax (XQTS adaptive-74)', () => { + setEditorContent('xs:boolean(false())'); + runAndGetFirstResult().should('eq', 'false()'); + }); + + // ── String-like types: double-quoted, no client escaping needed ────────── + + it('xs:string — double-quoted, displayed with quotes (XQTS adaptive-05)', () => { + setEditorContent('"simple string"'); + runAndGetFirstResult().should('eq', '"simple string"'); + }); + + it('xs:string — internal double-quotes are doubled, not backslash-escaped (XQTS adaptive-80)', () => { + // XQuery "hello ""world""" → string value: hello "world" + // Adaptive output: "hello ""world""" (outer quotes + doubled inner quotes) + setEditorContent('"hello ""world"""'); + runAndGetFirstResult().should('eq', '"hello ""world"""'); + }); + + it('xs:anyURI — double-quoted like xs:string (XQTS adaptive-77)', () => { + setEditorContent('xs:anyURI("http://www.example.org/")'); + runAndGetFirstResult().should('eq', '"http://www.example.org/"'); + }); + + it('xs:untypedAtomic — double-quoted (XQTS adaptive-35)', () => { + setEditorContent('xs:untypedAtomic("untypedAtomic")'); + runAndGetFirstResult().should('eq', '"untypedAtomic"'); + }); + + // ── Date/time types: constructor syntax typename("lexical-value") ───────── + + it('xs:dateTime — constructor syntax (XQTS adaptive-36)', () => { + setEditorContent('xs:dateTime("1999-05-31T13:20:00-05:00")'); + runAndGetFirstResult().should('eq', 'xs:dateTime("1999-05-31T13:20:00-05:00")'); + }); + + it('xs:date — constructor syntax (XQTS adaptive-38)', () => { + setEditorContent('xs:date("1999-05-31")'); + runAndGetFirstResult().should('eq', 'xs:date("1999-05-31")'); + }); + + it('xs:time — constructor syntax (XQTS adaptive-39)', () => { + setEditorContent('xs:time("12:00:00")'); + runAndGetFirstResult().should('eq', 'xs:time("12:00:00")'); + }); + + it('xs:duration — constructor syntax (XQTS adaptive-40)', () => { + setEditorContent('xs:duration("P1Y2M3DT10H30M23S")'); + runAndGetFirstResult().should('eq', 'xs:duration("P1Y2M3DT10H30M23S")'); + }); + + // ── Binary types: constructor syntax ────────────────────────────────────── + + it('xs:base64Binary — constructor syntax (XQTS adaptive-75)', () => { + setEditorContent('xs:base64Binary("01001010")'); + runAndGetFirstResult().should('eq', 'xs:base64Binary("01001010")'); + }); + + it('xs:hexBinary — constructor syntax, uppercase hex (XQTS adaptive-76)', () => { + setEditorContent('xs:hexBinary("D74D35D35D35")'); + runAndGetFirstResult().should('eq', 'xs:hexBinary("D74D35D35D35")'); + }); + + // ── QName: Q{namespace}localname syntax ─────────────────────────────────── + + it('xs:QName — Q{namespace}localname syntax (XQTS adaptive-78)', () => { + setEditorContent('xs:QName("xs:integer")'); + runAndGetFirstResult().should('eq', 'Q{http://www.w3.org/2001/XMLSchema}integer'); + }); +}); diff --git a/cypress/e2e/autocomplete_spec.cy.js b/cypress/e2e/autocomplete_spec.cy.js new file mode 100644 index 00000000..6436e0e6 --- /dev/null +++ b/cypress/e2e/autocomplete_spec.cy.js @@ -0,0 +1,228 @@ +describe('Autocomplete', () => { + // TODO: remove this skip once lsp:* module is available on CI's eXist-db + var lspAvailable = false + + before(() => { + cy.loginXHR('admin', '') + cy.request({ + method: 'POST', + url: '/eXide/api/query/completions', + body: { query: 'util:', prefix: 'util:', base: 'xmldb:exist:///db' }, + headers: { 'Content-Type': 'application/json' }, + failOnStatusCode: false + }).then((resp) => { + lspAvailable = resp.status === 200 && Array.isArray(resp.body) && resp.body.length > 0 + }) + }) + + beforeEach(() => { + cy.loginXHR('admin', '') + cy.visit('/eXide/index.html') + cy.reload(true) + cy.get('.path', { timeout: 10000 }).should('contain', 'untitled-1') + // Wait longer for login to propagate + cy.get('#user', { timeout: 15000 }).should('not.have.text', 'Login') + }) + + /** + * Set editor content, force AST parse, position cursor, trigger autocomplete. + * Disables validator to prevent race conditions with async validation. + */ + function setContentAndComplete(text, cursorPos) { + cy.window().then((win) => { + var editor = win.eXide.app.getEditor() + var view = editor.editor + var doc = editor.getActiveDocument() + var anchor = cursorPos !== undefined ? cursorPos : text.length + + editor.validator.setEnabled(false) + view.dispatch({ + changes: { from: 0, to: view.state.doc.length, insert: text }, + selection: { anchor: anchor } + }) + doc.lastValidation = 0 + doc.getModeHelper().parseXQuery(doc) + view.focus() + win.CM6.startCompletion(view) + editor.validator.setEnabled(true) + }) + } + + function setEditorContent(text, cursorPos) { + cy.window().then((win) => { + var editor = win.eXide.app.getEditor() + var view = editor.editor + var doc = editor.getActiveDocument() + var anchor = cursorPos !== undefined ? cursorPos : text.length + editor.validator.setEnabled(false) + view.dispatch({ + changes: { from: 0, to: view.state.doc.length, insert: text }, + selection: { anchor: anchor } + }) + doc.lastValidation = 0 + doc.getModeHelper().parseXQuery(doc) + view.focus() + editor.validator.setEnabled(true) + }) + } + + function getEditorContent() { + return cy.window().then((win) => { + return win.eXide.app.getEditor().editor.state.doc.toString() + }) + } + + it('shows completion popup for function prefix', function () { + if (!lspAvailable) this.skip() + setContentAndComplete('util:wai') + + cy.get('.cm-tooltip-autocomplete', { timeout: 5000 }) + .should('be.visible') + .invoke('text') + .should('contain', 'util:wait') + }) + + it('inserts snippet without backslash on selection', function () { + if (!lspAvailable) this.skip() + cy.intercept('POST', '**/api/query/completions').as('completions') + setContentAndComplete('util:wai') + + cy.wait('@completions', { timeout: 10000 }) + cy.get('.cm-tooltip-autocomplete', { timeout: 5000 }) + .should('be.visible') + .invoke('text') + .should('contain', 'util:wait') + cy.get('.cm-tooltip-autocomplete li[aria-selected="true"]', { timeout: 3000 }).click() + + getEditorContent().then((text) => { + expect(text).to.contain('util:wait(') + expect(text).to.not.contain('\\$') + }) + }) + + it('shows variable completions', () => { + // Use valid XQuery with cursor mid-word so the parser produces a usable AST + // "let $myVar := 42\nreturn $myVar" — cursor at pos 27 = after "$my" + setContentAndComplete('let $myVar := 42\nreturn $myVar', 27) + + cy.get('.cm-tooltip-autocomplete', { timeout: 5000 }) + .should('be.visible') + .invoke('text') + .should('contain', '$myVar') + }) + + it('filters completions as user types', function () { + if (!lspAvailable) this.skip() + cy.intercept('POST', '**/api/query/completions').as('completions') + setContentAndComplete('util:') + + cy.wait('@completions', { timeout: 10000 }) + cy.get('.cm-tooltip-autocomplete', { timeout: 5000 }) + .should('be.visible') + .invoke('text') + .should('contain', 'util:') + + cy.get('.cm-tooltip-autocomplete li[role="option"]') + .should('have.length.greaterThan', 5) + .then(($items) => { + const initialCount = $items.length + + cy.intercept('POST', '**/api/query/completions').as('completions2') + // Type via DOM to trigger CM6 input handling + cy.get('.cm-content').type('w', { force: true, delay: 0 }) + + cy.wait('@completions2', { timeout: 10000 }) + cy.get('.cm-tooltip-autocomplete li[role="option"]') + .should('have.length.lessThan', initialCount) + }) + }) + + it('shows default namespace functions without prefix', function () { + if (!lspAvailable) this.skip() + // Typing "conc" without "fn:" should suggest fn:concat via default namespace lookup + setContentAndComplete('conc') + + cy.get('.cm-tooltip-autocomplete', { timeout: 5000 }) + .should('be.visible') + .invoke('text') + .should('contain', 'concat') + }) + + it('inserts unprefixed function without namespace prefix', function () { + if (!lspAvailable) this.skip() + cy.intercept('POST', '**/api/query/completions').as('completions') + setContentAndComplete('conc') + + cy.wait('@completions', { timeout: 10000 }) + cy.get('.cm-tooltip-autocomplete', { timeout: 5000 }) + .should('be.visible') + .invoke('text') + .should('contain', 'concat') + cy.get('.cm-tooltip-autocomplete li[aria-selected="true"]', { timeout: 3000 }).click() + + getEditorContent().then((text) => { + expect(text).to.contain('concat(') + // Should NOT have fn: prefix since user typed without prefix + expect(text).to.not.match(/fn:concat/) + }) + }) + + it('shows completions for fn: prefixed input', function () { + if (!lspAvailable) this.skip() + setContentAndComplete('fn:conc') + + cy.get('.cm-tooltip-autocomplete', { timeout: 5000 }) + .should('be.visible') + .invoke('text') + .should('contain', 'fn:concat') + }) + + it('preserves fn: prefix when accepting prefixed completion', function () { + if (!lspAvailable) this.skip() + cy.intercept('POST', '**/api/query/completions').as('completions') + setContentAndComplete('fn:conc') + + cy.wait('@completions', { timeout: 10000 }) + cy.get('.cm-tooltip-autocomplete', { timeout: 5000 }) + .should('be.visible') + .invoke('text') + .should('contain', 'fn:concat') + cy.get('.cm-tooltip-autocomplete li[aria-selected="true"]', { timeout: 3000 }).click() + + getEditorContent().then((text) => { + expect(text).to.contain('fn:concat(') + }) + }) + + it('shows prefixed completions without duplicating namespace for repo:', function () { + if (!lspAvailable) this.skip() + setContentAndComplete('repo:') + + cy.get('.cm-tooltip-autocomplete', { timeout: 5000 }) + .should('be.visible') + .invoke('text') + .should('contain', 'repo:deploy') + .and('not.contain', 'repo:repo:') + }) + + it('shows repo: completions when typing repo without colon', function () { + if (!lspAvailable) this.skip() + setContentAndComplete('repo') + + cy.get('.cm-tooltip-autocomplete', { timeout: 5000 }) + .should('be.visible') + .invoke('text') + .should('contain', 'repo:') + }) + + it('shows function documentation tooltip via Navigate menu', () => { + setEditorContent('util:wait()', 7) + + cy.get('#menu-navigate-info').click({ force: true }) + + cy.get('.cm-funcdoc-tooltip', { timeout: 5000 }) + .should('be.visible') + .invoke('text') + .should('contain', 'util:wait') + }) +}) diff --git a/cypress/e2e/binary_preview_spec.cy.js b/cypress/e2e/binary_preview_spec.cy.js new file mode 100644 index 00000000..64a29def --- /dev/null +++ b/cypress/e2e/binary_preview_spec.cy.js @@ -0,0 +1,80 @@ +describe('Binary file preview', () => { + beforeEach(() => { + cy.loginXHR('admin', '') + cy.visit('/eXide/index.html') + cy.reload(true) + cy.get('.path', { timeout: 10000 }).should('contain', 'untitled-1') + }) + + function openBinaryFile(path) { + var name = path.split('/').pop() + cy.window().then((win) => { + win.eXide.app.$doOpenDocument({ path: path, name: name }) + }) + } + + it('shows preview overlay when opening an image', () => { + openBinaryFile('/db/apps/eXide/resources/images/logo.png') + + // Preview overlay should appear + cy.get('#binary-preview-overlay', { timeout: 5000 }).should('exist') + cy.get('.binary-preview-panel').should('exist') + }) + + it('displays image element for image files', () => { + openBinaryFile('/db/apps/eXide/resources/images/logo.png') + + cy.get('#binary-preview-overlay', { timeout: 5000 }).should('exist') + cy.get('.binary-preview-content img').should('exist') + }) + + it('shows filename in preview header', () => { + openBinaryFile('/db/apps/eXide/resources/images/logo.png') + + cy.get('.binary-preview-title', { timeout: 5000 }) + .should('contain', 'logo.png') + }) + + it('has Open in New Tab, Download, and Close buttons', () => { + openBinaryFile('/db/apps/eXide/resources/images/logo.png') + + cy.get('.binary-preview-footer', { timeout: 5000 }).within(() => { + cy.contains('button', 'Open in New Tab').should('exist') + cy.contains('button', 'Download').should('exist') + cy.contains('button', 'Close').should('exist') + }) + }) + + it('closes preview with close button (x)', () => { + openBinaryFile('/db/apps/eXide/resources/images/logo.png') + + cy.get('#binary-preview-overlay', { timeout: 5000 }).should('exist') + cy.get('.binary-preview-close').click() + cy.get('#binary-preview-overlay').should('not.exist') + }) + + it('closes preview with footer Close button', () => { + openBinaryFile('/db/apps/eXide/resources/images/logo.png') + + cy.get('#binary-preview-overlay', { timeout: 5000 }).should('exist') + cy.get('.binary-preview-footer').contains('button', 'Close').click() + cy.get('#binary-preview-overlay').should('not.exist') + }) + + it('closes preview with Escape key', () => { + openBinaryFile('/db/apps/eXide/resources/images/logo.png') + + cy.get('#binary-preview-overlay', { timeout: 5000 }).should('exist') + cy.get('body').type('{esc}') + cy.get('#binary-preview-overlay').should('not.exist') + }) + + it('closes preview when clicking outside the panel', () => { + openBinaryFile('/db/apps/eXide/resources/images/logo.png') + + cy.get('#binary-preview-overlay', { timeout: 5000 }).should('exist') + // Click on the overlay backdrop (not the panel) + cy.get('#binary-preview-overlay').click('topLeft') + cy.get('#binary-preview-overlay').should('not.exist') + }) +}) diff --git a/cypress/e2e/command_palette_spec.cy.js b/cypress/e2e/command_palette_spec.cy.js new file mode 100644 index 00000000..c7eb00ee --- /dev/null +++ b/cypress/e2e/command_palette_spec.cy.js @@ -0,0 +1,40 @@ +describe('Command Palette', () => { + beforeEach(() => { + cy.loginXHR('admin', '') + cy.visit('/eXide/index.html') + cy.get('.path', { timeout: 10000 }).should('contain', 'untitled') + }) + + function openCommandPalette() { + cy.get('#menu-navigate-commands').click({ force: true }) + cy.get('dialog.quick-picker[open]', { timeout: 5000 }).should('exist') + } + + it('opens and shows filter input', () => { + openCommandPalette() + cy.get('dialog.quick-picker .quick-picker-filter').should('be.visible') + cy.get('dialog.quick-picker .quick-picker-title').should('have.text', 'Command Palette') + }) + + it('closes on Escape key', () => { + openCommandPalette() + cy.get('dialog.quick-picker .quick-picker-filter').type('{esc}') + cy.get('dialog.quick-picker[open]').should('not.exist') + }) + + it('closes on close button click', () => { + openCommandPalette() + cy.get('dialog.quick-picker .quick-picker-close').click() + cy.get('dialog.quick-picker[open]').should('not.exist') + }) + + it('filters commands by typing', () => { + openCommandPalette() + cy.get('dialog.quick-picker .quick-picker-filter').type('save') + cy.get('dialog.quick-picker .quick-picker-list li').should('have.length.greaterThan', 0) + cy.get('dialog.quick-picker .quick-picker-list li .quick-picker-label') + .first().invoke('text').should('match', /save/i) + // Clean up + cy.get('dialog.quick-picker .quick-picker-filter').type('{esc}') + }) +}) diff --git a/cypress/e2e/dbmanager_spec.cy.js b/cypress/e2e/dbmanager_spec.cy.js index 12a448c5..b12e3d9d 100644 --- a/cypress/e2e/dbmanager_spec.cy.js +++ b/cypress/e2e/dbmanager_spec.cy.js @@ -5,7 +5,23 @@ function openDbManager() { cy.get('#fullscreen > div.editor-header > div > ul > li:nth-child(1) > ul').find('#menu-file-manager').click() } +function cleanupTestCollections() { + cy.loginXHR('admin', '') + cy.request({ + method: 'POST', + url: '/exist/rest/db', + headers: { 'Content-Type': 'application/xquery' }, + body: `for $col in ("${collectionName}", "def", "AéB") + where xmldb:collection-available("/db/" || $col) + return xmldb:remove("/db/" || $col)`, + failOnStatusCode: false + }) +} + context('DB Manager', () => { + before(() => { cleanupTestCollections() }) + after(() => { cleanupTestCollections() }) + describe('DB Manager operations', () => { beforeEach(() => { cy.loginXHR('admin', '') @@ -14,41 +30,40 @@ context('DB Manager', () => { win.localStorage.setItem('eXide.firstTime', '0') } }) - cy.get('div.ui-dialog div.ui-dialog-buttonset button').filter(':visible').click({ multiple: true, force: true }) + cy.dismissDialog() openDbManager() }) it('should open the db manager', () => { - cy.get('#ui-id-11').should('be.visible') - cy.get('#ui-id-11').invoke('text').should('eq', 'DB Manager') + cy.get('#open-dialog').closest('.eXide-dialog').should('be.visible') + cy.get('#open-dialog').closest('.eXide-dialog').find('.eXide-dialog-title').invoke('text').should('eq', 'DB Manager') }) it('should select the clicked document', () => { cy.get('div.eXide-browse-main').within(() => { - cy.get('div[role=row][row-id=0]').find('div').first().click() - cy.get('div[role=row][row-id=0]').should('have.attr', 'aria-selected', 'true') + cy.get('.browse-table tbody tr').first().click() + cy.get('.browse-table tbody tr').first().should('have.attr', 'aria-selected', 'true') }) }) describe('collection creation', () => { it('should create a new collection', () => { cy.get('#eXide-browse-toolbar-create').click() cy.get('#eXide-browse-collection-name').type(collectionName) - cy.wait(500) - cy.get('body > div:nth-child(4) > div.ui-dialog-buttonpane.ui-widget-content.ui-helper-clearfix > div > button:nth-child(1)').click() + cy.get('dialog.eXide-dialog[open] .eXide-dialog-buttons button:first-of-type').click() cy.get('div.eXide-browse-main').within(() => { - cy.contains('div[role=gridcell][col-id=name]', collectionName).should('exist') + cy.contains('.browse-table tbody td.col-name', collectionName).should('exist') }) }) it('should delete the created collection', () => { cy.get('div.eXide-browse-main').within(() => { - cy.contains('div[role=gridcell][col-id=name]', collectionName).click() + cy.contains('.browse-table tbody td.col-name', collectionName).click() }) cy.get('#eXide-browse-toolbar-delete-resource').click() - cy.get('body > div:nth-child(4) > div.ui-dialog-buttonpane.ui-widget-content.ui-helper-clearfix > div > button:nth-child(1)').click() + cy.get('dialog.eXide-dialog[open] .eXide-dialog-buttons button:first-of-type').click() cy.get('div.eXide-browse-main').within(() => { - cy.contains('div[role=gridcell][col-id=name]', collectionName).should('not.exist') + cy.contains('.browse-table tbody td.col-name', collectionName).should('not.exist') }) }) }) @@ -57,38 +72,36 @@ context('DB Manager', () => { it('should create a new collection', () => { cy.get('#eXide-browse-toolbar-create').click() cy.get('#eXide-browse-collection-name').type('toBeRenamed') - cy.wait(500) - cy.get('body > div:nth-child(4) > div.ui-dialog-buttonpane.ui-widget-content.ui-helper-clearfix > div > button:nth-child(1)').click() + cy.get('dialog.eXide-dialog[open] .eXide-dialog-buttons button:first-of-type').click() cy.get('div.eXide-browse-main').within(() => { - cy.contains('div[role=gridcell][col-id=name]', 'toBeRenamed').should('exist') + cy.contains('.browse-table tbody td.col-name', 'toBeRenamed').should('exist') }) }) it('should rename selected', () => { //click on file by name cy.get('div.eXide-browse-main').within(() => { - cy.contains('div[role=gridcell][col-id=name]', 'toBeRenamed').click() + cy.contains('.browse-table tbody td.col-name', 'toBeRenamed').click() }) //click on toolbar action cy.get('#eXide-browse-toolbar-rename').click() cy.focused().type('AéB{enter}') - cy.wait(1000) //check for modification cy.get('div.eXide-browse-main').within(() => { - cy.contains('div[role=gridcell][col-id=name]', 'AéB').should('exist') + cy.contains('.browse-table tbody td.col-name', 'AéB', { timeout: 5000 }).should('exist') }) }) it('should delete the created collection', () => { cy.get('div.eXide-browse-main').within(() => { - cy.contains('div[role=gridcell][col-id=name]', 'AéB').click() + cy.contains('.browse-table tbody td.col-name', 'AéB').click() }) cy.get('#eXide-browse-toolbar-delete-resource').click() - cy.get('body > div:nth-child(4) > div.ui-dialog-buttonpane.ui-widget-content.ui-helper-clearfix > div > button:nth-child(1)').click() + cy.get('dialog.eXide-dialog[open] .eXide-dialog-buttons button:first-of-type').click() cy.get('div.eXide-browse-main').within(() => { - cy.contains('div[role=gridcell][col-id=name]', 'AéB').should('not.exist') + cy.contains('.browse-table tbody td.col-name', 'AéB').should('not.exist') }) }) }) @@ -97,17 +110,16 @@ context('DB Manager', () => { it('should create a new collection', () => { cy.get('#eXide-browse-toolbar-create').click() cy.get('#eXide-browse-collection-name').type('AéB') - cy.wait(500) - cy.get('body > div:nth-child(4) > div.ui-dialog-buttonpane.ui-widget-content.ui-helper-clearfix > div > button:nth-child(1)').click() + cy.get('dialog.eXide-dialog[open] .eXide-dialog-buttons button:first-of-type').click() cy.get('div.eXide-browse-main').within(() => { - cy.contains('div[role=gridcell][col-id=name]', 'AéB').should('exist') + cy.contains('.browse-table tbody td.col-name', 'AéB').should('exist') }) }) it('should check for properties', () => { cy.get('div.eXide-browse-main').within(() => { - cy.contains('div[role=gridcell][col-id=name]', 'AéB').click() + cy.contains('.browse-table tbody td.col-name', 'AéB').click() }) cy.get('#eXide-browse-toolbar-properties').click() @@ -117,12 +129,59 @@ context('DB Manager', () => { it('should delete the created collection', () => { cy.get('div.eXide-browse-main').within(() => { - cy.contains('div[role=gridcell][col-id=name]', 'AéB').click() + cy.contains('.browse-table tbody td.col-name', 'AéB').click() }) cy.get('#eXide-browse-toolbar-delete-resource').click() - cy.get('body > div:nth-child(4) > div.ui-dialog-buttonpane.ui-widget-content.ui-helper-clearfix > div > button:nth-child(1)').click() + cy.get('dialog.eXide-dialog[open] .eXide-dialog-buttons button:first-of-type').click() + cy.get('div.eXide-browse-main').within(() => { + cy.contains('.browse-table tbody td.col-name', 'AéB').should('not.exist') + }) + }) + }) + + describe('resource properties for README.md', () => { + it('should navigate to /db/apps/eXide and open properties for README.md', () => { + // Navigate into apps + cy.get('div.eXide-browse-main').within(() => { + cy.contains('.browse-table tbody td.col-name', 'apps').dblclick() + }) + // Navigate into eXide + cy.get('div.eXide-browse-main').within(() => { + cy.contains('.browse-table tbody td.col-name', 'eXide').dblclick() + }) + // Select README.md cy.get('div.eXide-browse-main').within(() => { - cy.contains('div[role=gridcell][col-id=name]', 'AéB').should('not.exist') + cy.contains('.browse-table tbody td.col-name', 'README.md').click() + }) + // Open properties + cy.get('#eXide-browse-toolbar-properties').click() + + // Confirm properties dialog is visible with correct title + cy.get('#resource-properties-dialog').closest('dialog.eXide-dialog[open]').should('be.visible') + cy.get('#resource-properties-dialog').closest('dialog.eXide-dialog[open]') + .find('.eXide-dialog-title').should('contain', 'Resource/collection properties') + + // Confirm the permissions fieldset with legend exists + cy.get('#resource-properties-content').within(() => { + cy.contains('legend', 'Permissions').should('be.visible') + + // Confirm the permissions table has header row with User/Group/Other + cy.get('table th').should('have.length', 3) + cy.get('table th').eq(0).should('have.text', 'User') + cy.get('table th').eq(1).should('have.text', 'Group') + cy.get('table th').eq(2).should('have.text', 'Other') + + // Confirm checkboxes exist (4 rows x 3 columns = 12 checkboxes) + cy.get('table input[type="checkbox"]').should('have.length', 12) + + // Confirm labels exist next to checkboxes + cy.contains('table label', 'read').should('exist') + cy.contains('table label', 'write').should('exist') + cy.contains('table label', 'execute').should('exist') + + // Confirm owner/group selects exist + cy.get('select[name="owner"]').should('exist') + cy.get('select[name="group"]').should('exist') }) }) }) @@ -131,37 +190,34 @@ context('DB Manager', () => { it('should create collection to be copied', () => { cy.get('#eXide-browse-toolbar-create').click() cy.get('#eXide-browse-collection-name').type('toBeCopiedAéB') - cy.wait(500) - cy.get('body > div:nth-child(4) > div.ui-dialog-buttonpane.ui-widget-content.ui-helper-clearfix > div > button:nth-child(1)').click() + cy.get('dialog.eXide-dialog[open] .eXide-dialog-buttons button:first-of-type').click() cy.get('div.eXide-browse-main').within(() => { - cy.contains('div[role=gridcell][col-id=name]', 'toBeCopiedAéB').should('exist') + cy.contains('.browse-table tbody td.col-name', 'toBeCopiedAéB').should('exist') }) }) it('should create collection to be copied in', () => { cy.get('#eXide-browse-toolbar-create').click() cy.get('#eXide-browse-collection-name').type('toBeCopiedInAéB') - cy.wait(500) - cy.get('body > div:nth-child(4) > div.ui-dialog-buttonpane.ui-widget-content.ui-helper-clearfix > div > button:nth-child(1)').click() + cy.get('dialog.eXide-dialog[open] .eXide-dialog-buttons button:first-of-type').click() cy.get('div.eXide-browse-main').within(() => { - cy.contains('div[role=gridcell][col-id=name]', 'toBeCopiedInAéB').should('exist') + cy.contains('.browse-table tbody td.col-name', 'toBeCopiedInAéB').should('exist') }) }) it('should copy the collection', () => { //click on file by name cy.get('div.eXide-browse-main').within(() => { - cy.contains('div[role=gridcell][col-id=name]', 'toBeCopiedAéB').click() + cy.contains('.browse-table tbody td.col-name', 'toBeCopiedAéB').click() }) - cy.wait(500) //click on toolbar action cy.get('#eXide-browse-toolbar-copy').click() //navigate into collection cy.get('div.eXide-browse-main').within(() => { - cy.contains('div[role=gridcell][col-id=name]', 'toBeCopiedInAéB').dblclick() + cy.contains('.browse-table tbody td.col-name', 'toBeCopiedInAéB').dblclick() }) //paste the collection @@ -169,33 +225,32 @@ context('DB Manager', () => { //check for modification cy.get('div.eXide-browse-main').within(() => { - cy.contains('div[role=gridcell][col-id=name]', 'toBeCopiedAéB').should('exist') + cy.contains('.browse-table tbody td.col-name', 'toBeCopiedAéB').should('exist') }) }) it('should delete the created collection', () => { cy.get('div.eXide-browse-main').within(() => { - cy.wait(500) - cy.contains('div[role=gridcell][col-id=name]', 'toBeCopiedInAéB').click() + cy.contains('.browse-table tbody td.col-name', 'toBeCopiedInAéB').click() }) - cy.wait(500) cy.get('#eXide-browse-toolbar-delete-resource').click() - cy.get('body > div:nth-child(4) > div.ui-dialog-buttonpane.ui-widget-content.ui-helper-clearfix > div > button:nth-child(1)').click() - cy.wait(500) + cy.get('dialog.eXide-dialog[open] .eXide-dialog-buttons button:first-of-type').click() + + cy.get('div.eXide-browse-main').within(() => { + cy.contains('.browse-table tbody td.col-name', 'toBeCopiedInAéB', { timeout: 5000 }).should('not.exist') + }) cy.get('div.eXide-browse-main').within(() => { - cy.wait(500) - cy.contains('div[role=gridcell][col-id=name]', 'toBeCopiedAéB').click() + cy.contains('.browse-table tbody td.col-name', 'toBeCopiedAéB').click() }) - cy.wait(500) cy.get('#eXide-browse-toolbar-delete-resource').click() - cy.get('body > div:nth-child(4) > div.ui-dialog-buttonpane.ui-widget-content.ui-helper-clearfix > div > button:nth-child(1)').click() + cy.get('dialog.eXide-dialog[open] .eXide-dialog-buttons button:first-of-type').click() cy.get('div.eXide-browse-main').within(() => { - cy.contains('div[role=gridcell][col-id=name]', 'toBeCopiedInAéB').should('not.exist') + cy.contains('.browse-table tbody td.col-name', 'toBeCopiedInAéB').should('not.exist') }) cy.get('div.eXide-browse-main').within(() => { - cy.contains('div[role=gridcell][col-id=name]', 'toBeCopiedAéB').should('not.exist') + cy.contains('.browse-table tbody td.col-name', 'toBeCopiedAéB').should('not.exist') }) }) }) @@ -204,36 +259,34 @@ context('DB Manager', () => { it('should create collection to be copied', () => { cy.get('#eXide-browse-toolbar-create').click() cy.get('#eXide-browse-collection-name').type('toBeCopiedAéB') - cy.wait(500) - cy.get('body > div:nth-child(4) > div.ui-dialog-buttonpane.ui-widget-content.ui-helper-clearfix > div > button:nth-child(1)').click() + cy.get('dialog.eXide-dialog[open] .eXide-dialog-buttons button:first-of-type').click() cy.get('div.eXide-browse-main').within(() => { - cy.contains('div[role=gridcell][col-id=name]', 'toBeCopiedAéB').should('exist') + cy.contains('.browse-table tbody td.col-name', 'toBeCopiedAéB').should('exist') }) }) it('should create collection to be copied in', () => { cy.get('#eXide-browse-toolbar-create').click() cy.get('#eXide-browse-collection-name').type('toBeCopiedInAéB') - cy.wait(500) - cy.get('body > div:nth-child(4) > div.ui-dialog-buttonpane.ui-widget-content.ui-helper-clearfix > div > button:nth-child(1)').click() + cy.get('dialog.eXide-dialog[open] .eXide-dialog-buttons button:first-of-type').click() cy.get('div.eXide-browse-main').within(() => { - cy.contains('div[role=gridcell][col-id=name]', 'toBeCopiedInAéB').should('exist') + cy.contains('.browse-table tbody td.col-name', 'toBeCopiedInAéB').should('exist') }) }) it('should cut the collection', () => { //click on file by name cy.get('div.eXide-browse-main').within(() => { - cy.contains('div[role=gridcell][col-id=name]', 'toBeCopiedAéB').click() + cy.contains('.browse-table tbody td.col-name', 'toBeCopiedAéB').click() }) //click on toolbar action cy.get('#eXide-browse-toolbar-cut').click() //navigate into collection cy.get('div.eXide-browse-main').within(() => { - cy.contains('div[role=gridcell][col-id=name]', 'toBeCopiedInAéB').dblclick() + cy.contains('.browse-table tbody td.col-name', 'toBeCopiedInAéB').dblclick() }) //paste the collection @@ -241,23 +294,23 @@ context('DB Manager', () => { //check for modification cy.get('div.eXide-browse-main').within(() => { - cy.contains('div[role=gridcell][col-id=name]', 'toBeCopiedAéB').should('exist') + cy.contains('.browse-table tbody td.col-name', 'toBeCopiedAéB').should('exist') }) }) it('should delete the created collection', () => { cy.get('div.eXide-browse-main').within(() => { - cy.contains('div[role=gridcell][col-id=name]', 'toBeCopiedInAéB').click() + cy.contains('.browse-table tbody td.col-name', 'toBeCopiedInAéB').click() }) cy.get('#eXide-browse-toolbar-delete-resource').click() - cy.get('body > div:nth-child(4) > div.ui-dialog-buttonpane.ui-widget-content.ui-helper-clearfix > div > button:nth-child(1)').click() + cy.get('dialog.eXide-dialog[open] .eXide-dialog-buttons button:first-of-type').click() cy.get('div.eXide-browse-main').within(() => { - cy.contains('div[role=gridcell][col-id=name]', 'toBeCopiedInAéB').should('not.exist') + cy.contains('.browse-table tbody td.col-name', 'toBeCopiedInAéB').should('not.exist') }) // check the collection is removed by cutting it cy.get('div.eXide-browse-main').within(() => { - cy.contains('div[role=gridcell][col-id=name]', 'toBeCopiedAéB').should('not.exist') + cy.contains('.browse-table tbody td.col-name', 'toBeCopiedAéB').should('not.exist') }) }) }) @@ -281,12 +334,12 @@ context('DB Manager', () => { it('should open resource', () => { cy.visit(`/eXide/index.html`) - cy.get('div.ui-dialog div.ui-dialog-buttonset button').filter(':visible').click() + cy.dismissDialog() cy.get('#fullscreen > div.editor-header > div > ul > li:nth-child(1) > a').click() cy.get('#fullscreen > div.editor-header > div > ul > li:nth-child(1) > ul').find('#menu-file-manager').click() cy.get('div.eXide-browse-main').within(() => { - cy.contains('div[role=gridcell][col-id=name]', 'AéB').dblclick() - cy.contains('div[role=gridcell][col-id=name]', 'AéB.xml').dblclick() + cy.contains('.browse-table tbody td.col-name', 'AéB').dblclick() + cy.contains('.browse-table tbody td.col-name', 'AéB.xml').dblclick() }) cy.contains('/db/AéB/AéB.xml').should('exist') @@ -297,19 +350,19 @@ context('DB Manager', () => { it('should delete the created collection', () => { cy.visit(`/eXide/index.html`) - cy.get('div.ui-dialog div.ui-dialog-buttonset button').filter(':visible').click() + cy.dismissDialog() cy.get('#fullscreen > div.editor-header > div > ul > li:nth-child(1) > a').click() cy.get('#fullscreen > div.editor-header > div > ul > li:nth-child(1) > ul').find('#menu-file-manager').click() cy.get('div.eXide-browse-main').within(() => { - cy.contains('div[role=gridcell][col-id=name]', 'AéB').click() + cy.contains('.browse-table tbody td.col-name', 'AéB').click() }) cy.get('#eXide-browse-toolbar-delete-resource').click() - cy.get('body > div:nth-child(4) > div.ui-dialog-buttonpane.ui-widget-content.ui-helper-clearfix > div > button:nth-child(1)').click() + cy.get('dialog.eXide-dialog[open] .eXide-dialog-buttons button:first-of-type').click() cy.get('div.eXide-browse-main').within(() => { - cy.contains('div[role=gridcell][col-id=name]', 'AéB').should('not.exist') + cy.contains('.browse-table tbody td.col-name', 'AéB').should('not.exist') }) }) }) }) -}) \ No newline at end of file +}) diff --git a/cypress/e2e/exide_spec.cy.js b/cypress/e2e/exide_spec.cy.js index 89bdf5c3..27647a40 100644 --- a/cypress/e2e/exide_spec.cy.js +++ b/cypress/e2e/exide_spec.cy.js @@ -11,7 +11,7 @@ describe('eXide', () => { }) it('displays the editor with default document', () => { - cy.get('.path').should('contain', '__new__1'); + cy.get('.path').should('contain', 'untitled-1'); }) // Add more tests here diff --git a/cypress/e2e/file_save_spec.cy.js b/cypress/e2e/file_save_spec.cy.js new file mode 100644 index 00000000..3166f064 --- /dev/null +++ b/cypress/e2e/file_save_spec.cy.js @@ -0,0 +1,126 @@ +describe('File save', () => { + // Use /db which always exists; clean up test files after + var testCollection = '/db' + var testFile = 'cypress-test-' + Date.now() + '.xq' + + before(() => { + cy.cleanupTestFiles() + }) + + beforeEach(() => { + cy.loginXHR('admin', '') + cy.visit('/eXide/index.html') + cy.reload(true) + cy.get('.path', { timeout: 10000 }).should('contain', 'untitled-1') + cy.get('#user', { timeout: 10000 }).should('not.have.text', 'Login') + }) + + function setEditorContent(text) { + cy.window().then((win) => { + var doc = win.eXide.app.getEditor().getActiveDocument() + doc.setText(text) + }) + } + + /** + * Wait for the save/open dialog to appear. + * The open-dialog is non-modal (wrapped in a
). + */ + function waitForSaveDialog() { + cy.get('#open-dialog').closest('.eXide-dialog', { timeout: 5000 }) + .should('have.attr', 'open') + } + + function saveDialogShouldBeClosed() { + cy.get('#open-dialog').closest('.eXide-dialog') + .should('not.have.attr', 'open') + } + + function clickDialogButton(label) { + cy.get('#open-dialog').closest('.eXide-dialog') + .find('.eXide-dialog-buttons button').contains(label).click() + } + + function openFileDirectly(path) { + var name = path.split('/').pop() + cy.window().then((win) => { + win.eXide.app.$doOpenDocument({ path: path, name: name }) + }) + } + + after(() => { + cy.cleanupTestFiles() + }) + + it('opens the save dialog for a new document', () => { + setEditorContent('1 + 1') + cy.get('#save').click() + + waitForSaveDialog() + cy.get('#open-dialog input[name="resource"]').should('exist') + + // Close without saving + clickDialogButton('Cancel') + saveDialogShouldBeClosed() + }) + + it('saves a new XQuery file to the database', () => { + setEditorContent('(: test file :)\n1 + 1') + cy.get('#save').click() + + waitForSaveDialog() + + // The dialog starts in /db, type filename and save + cy.get('#open-dialog input[name="resource"]').clear().type(testFile) + clickDialogButton('Save') + + // Dialog should close and path should update + saveDialogShouldBeClosed() + cy.get('.path', { timeout: 5000 }).should('contain', testFile) + }) + + it('saves an existing document without opening dialog', () => { + // Open the file we saved in the previous test directly + openFileDirectly(testCollection + '/' + testFile) + cy.get('.path', { timeout: 10000 }).should('contain', testFile) + + // Modify the content + setEditorContent('(: modified :)\n2 + 2') + + // Save — should NOT open dialog since file already exists + cy.get('#save').click() + + // Toast should confirm save + cy.get('.eXide-toast', { timeout: 5000 }).should('contain', 'stored') + + // Dialog should not have opened + saveDialogShouldBeClosed() + }) + + it('warns when saving XQuery with non-XQuery extension', () => { + setEditorContent('1 + 1') + cy.get('#save').click() + + waitForSaveDialog() + + // Try to save with .txt extension + cy.get('#open-dialog input[name="resource"]').clear().type('bad-name.txt') + clickDialogButton('Save') + + // Warning dialog should appear about wrong extension + cy.get('.eXide-dialog[open]', { timeout: 5000 }) + .should('contain', 'non-XQuery file extension') + }) + + it('can open a saved file and verify content', () => { + // Open the file we saved and modified earlier + openFileDirectly(testCollection + '/' + testFile) + cy.get('.path', { timeout: 10000 }).should('contain', testFile) + + // Verify the document was loaded with the modified content + cy.window().then((win) => { + var text = win.eXide.app.getEditor().getActiveDocument().getText() + expect(text).to.contain('modified') + }) + }) +}) diff --git a/cypress/e2e/find_replace_spec.cy.js b/cypress/e2e/find_replace_spec.cy.js new file mode 100644 index 00000000..5d2453b1 --- /dev/null +++ b/cypress/e2e/find_replace_spec.cy.js @@ -0,0 +1,89 @@ +describe('Find and Replace', () => { + beforeEach(() => { + cy.visit('/eXide/index.html') + cy.reload(true) + cy.get('.path', { timeout: 10000 }).should('contain', 'untitled-1') + }) + + function setEditorContent(text) { + cy.window().then((win) => { + var doc = win.eXide.app.getEditor().getActiveDocument() + doc.setText(text) + }) + } + + function getEditorContent() { + return cy.window().then((win) => { + return win.eXide.app.getEditor().getActiveDocument().getText() + }) + } + + it('opens find panel via menu', () => { + cy.get('#menu-edit-find').click({ force: true }) + // CM6 search panel should appear + cy.get('.cm-search').should('be.visible') + }) + + it('finds text in document', () => { + setEditorContent('let $x := 1\nlet $y := 2\nreturn $x + $y') + cy.get('#menu-edit-find').click({ force: true }) + cy.get('.cm-search').should('be.visible') + + // Type search query + cy.get('.cm-search input[name="search"]').clear().type('$x') + + // Should show match count or highlight matches + cy.get('.cm-editor .cm-searchMatch').should('have.length.at.least', 1) + }) + + it('navigates between matches', () => { + setEditorContent('apple banana apple cherry apple') + cy.get('#menu-edit-find').click({ force: true }) + cy.get('.cm-search input[name="search"]').clear().type('apple') + + // Multiple matches should be highlighted + cy.get('.cm-editor .cm-searchMatch').should('have.length.at.least', 2) + + // Click next to navigate + cy.get('.cm-search button[name="next"]').click() + }) + + it('opens find-replace panel via menu', () => { + cy.get('#menu-edit-find-replace').click({ force: true }) + cy.get('.cm-search').should('be.visible') + // Replace input should be visible + cy.get('.cm-search input[name="replace"]').should('be.visible') + }) + + it('replaces text', () => { + setEditorContent('hello world hello') + cy.get('#menu-edit-find-replace').click({ force: true }) + + cy.get('.cm-search input[name="search"]').clear().type('hello') + cy.get('.cm-search input[name="replace"]').clear().type('goodbye') + + // Click replace all + cy.get('.cm-search button[name="replaceAll"]').click() + + getEditorContent().should('contain', 'goodbye').and('not.contain', 'hello') + }) + + it('closes search panel with Escape', () => { + cy.get('#menu-edit-find').click({ force: true }) + cy.get('.cm-search').should('be.visible') + cy.get('.cm-search input[name="search"]').type('{esc}') + cy.get('.cm-search').should('not.exist') + }) + + it('case-sensitive search', () => { + setEditorContent('Hello hello HELLO') + cy.get('#menu-edit-find').click({ force: true }) + cy.get('.cm-search input[name="search"]').clear().type('Hello') + + // Toggle case sensitivity (checkbox labeled "match case") + cy.get('.cm-search input[name="case"]').check({ force: true }) + + // Should match only exact case + cy.get('.cm-editor .cm-searchMatch').should('have.length', 1) + }) +}) diff --git a/cypress/e2e/goto_references_spec.cy.js b/cypress/e2e/goto_references_spec.cy.js new file mode 100644 index 00000000..39c66b80 --- /dev/null +++ b/cypress/e2e/goto_references_spec.cy.js @@ -0,0 +1,88 @@ +describe('Go-to-definition and Find References', () => { + beforeEach(() => { + cy.loginXHR('admin', '') + cy.visit('/eXide/index.html') + cy.reload(true) + cy.get('.path', { timeout: 10000 }).should('contain', 'untitled-1') + cy.get('#user', { timeout: 15000 }).should('not.have.text', 'Login') + }) + + function setEditorContent(text, cursorPos) { + cy.window().then((win) => { + var editor = win.eXide.app.getEditor() + var view = editor.editor + var doc = editor.getActiveDocument() + var anchor = cursorPos !== undefined ? cursorPos : 0 + editor.validator.setEnabled(false) + view.dispatch({ + changes: { from: 0, to: view.state.doc.length, insert: text }, + selection: { anchor: anchor } + }) + doc.lastValidation = 0 + doc.getModeHelper().parseXQuery(doc) + view.focus() + editor.validator.setEnabled(true) + }) + } + + describe('Go-to-definition', () => { + it('jumps to function declaration via F3', () => { + var code = 'declare function local:greet($name) {\n "Hello " || $name\n};\n\nlocal:greet("world")' + // Place cursor on "local:greet" call at line 5 (offset ~62) + setEditorContent(code, 62) + + cy.window().then((win) => { + win.eXide.app.getEditor().exec('gotoDefinition') + }) + + // Cursor should move to the function declaration (line 1) + cy.get('#status-cursor', { timeout: 5000 }) + .invoke('text') + .should('contain', 'Ln 1') + }) + + it('adds cm-goto-clickable class when Cmd/Ctrl is held', () => { + setEditorContent('let $x := 1 return $x') + + // The cm-content should get clickable class on modifier key + cy.get('.cm-content').trigger('keydown', { metaKey: true, key: 'Meta' }) + cy.get('.cm-content.cm-goto-clickable, .cm-editor .cm-goto-clickable') + .should('exist') + + cy.get('.cm-content').trigger('keyup', { key: 'Meta' }) + }) + }) + + describe('Find All References', () => { + it('opens QuickPicker with references via findReferences command', () => { + // Use a function call — references work best on function names + var code = 'declare function local:greet($name) {\n "Hello " || $name\n};\n\nlocal:greet("a"),\nlocal:greet("b")' + // Place cursor on "local:greet" in the call at line 5 (after the newlines) + setEditorContent(code, 56) + + cy.window().then((win) => { + win.eXide.app.getEditor().exec('findReferences') + }) + + cy.get('.quick-picker', { timeout: 10000 }) + .should('be.visible') + + // Should show at least one reference + cy.get('.quick-picker-list li') + .should('have.length.at.least', 1) + }) + + it('closes QuickPicker on Escape', () => { + var code = 'declare function local:greet($name) {\n "Hello " || $name\n};\n\nlocal:greet("a"),\nlocal:greet("b")' + setEditorContent(code, 56) + + cy.window().then((win) => { + win.eXide.app.getEditor().exec('findReferences') + }) + + cy.get('.quick-picker', { timeout: 10000 }).should('be.visible') + cy.get('.quick-picker-filter').type('{esc}') + cy.get('.quick-picker').should('not.be.visible') + }) + }) +}) diff --git a/cypress/e2e/layout_spec.cy.js b/cypress/e2e/layout_spec.cy.js new file mode 100644 index 00000000..ee0b9578 --- /dev/null +++ b/cypress/e2e/layout_spec.cy.js @@ -0,0 +1,56 @@ +describe('Layout fills viewport', () => { + beforeEach(() => { + cy.visit('/eXide/index.html') + cy.reload(true) + cy.get('.path', { timeout: 10000 }).should('contain', 'untitled-1') + }) + + it('layout-horizontal extends to the bottom of the viewport', () => { + cy.window().then((win) => { + const viewportHeight = win.innerHeight + cy.get('.layout-horizontal').then(($el) => { + const rect = $el[0].getBoundingClientRect() + expect(rect.bottom).to.be.closeTo(viewportHeight, 2) + }) + }) + }) + + it('west panel tabs align with editor tabs in light mode', () => { + cy.get('#tabs-outline-container').then(($west) => { + cy.get('#tabs-container').then(($editor) => { + const westTop = $west[0].getBoundingClientRect().top + const editorTop = $editor[0].getBoundingClientRect().top + expect(westTop).to.be.closeTo(editorTop, 1) + }) + }) + }) + + it('west panel tabs align with editor tabs in dark mode', () => { + // Switch to dark mode + cy.get('#toggle-dark-mode').click() + // Wait for theme to apply + cy.get('body').should('have.class', 'dark') + // Small wait for any reflow + + cy.get('#tabs-outline-container').then(($west) => { + cy.get('#tabs-container').then(($editor) => { + const westTop = $west[0].getBoundingClientRect().top + const editorTop = $editor[0].getBoundingClientRect().top + expect(westTop).to.be.closeTo(editorTop, 1) + }) + }) + }) + + it('layout fills viewport in dark mode', () => { + cy.get('#toggle-dark-mode').click() + cy.get('body').should('have.class', 'dark') + + cy.window().then((win) => { + const viewportHeight = win.innerHeight + cy.get('.layout-horizontal').then(($el) => { + const rect = $el[0].getBoundingClientRect() + expect(rect.bottom).to.be.closeTo(viewportHeight, 2) + }) + }) + }) +}) diff --git a/cypress/e2e/local_files_spec.cy.js b/cypress/e2e/local_files_spec.cy.js new file mode 100644 index 00000000..9d18a924 --- /dev/null +++ b/cypress/e2e/local_files_spec.cy.js @@ -0,0 +1,161 @@ +describe('Local files pane (desktop simulation)', () => { + beforeEach(() => { + cy.loginXHR('admin', '') + cy.visit('/eXide/index.html') + cy.reload(true) + cy.get('.cm-editor', { timeout: 10000 }).should('exist') + cy.get('#user', { timeout: 15000 }).should('not.have.text', 'Login') + }) + + function injectLocalFiles() { + return cy.readFile('src-tauri/local-files.js').then((script) => { + cy.window().then((win) => { + win.eval(script) + }) + }) + } + + function injectWithFolder(folderPath, entries) { + return cy.readFile('src-tauri/local-files.js').then((script) => { + cy.window().then((win) => { + win.eval(script) + if (win.__exideLocalFiles) { + win.__exideLocalFiles.openFolderWithData(folderPath, entries) + } + }) + }) + } + + var mockEntries = [ + { name: 'src', path: '/mock/project/src', is_dir: true, children: [ + { name: 'app.xq', path: '/mock/project/src/app.xq', is_dir: false, children: null }, + { name: 'lib.xqm', path: '/mock/project/src/lib.xqm', is_dir: false, children: null } + ]}, + { name: 'test', path: '/mock/project/test', is_dir: true, children: [] }, + { name: 'README.md', path: '/mock/project/README.md', is_dir: false, children: null }, + { name: 'pom.xml', path: '/mock/project/pom.xml', is_dir: false, children: null } + ] + + it('injects LOCAL tab into the tab bar', () => { + injectLocalFiles() + + cy.get('#tabs-outline li').should('have.length.at.least', 3) + cy.get('#tabs-outline').invoke('text').should('contain', 'local') + }) + + it('shows local pane when LOCAL tab is clicked', () => { + injectWithFolder('/mock/project', mockEntries) + + cy.get('#local-files-body').should('be.visible') + }) + + it('renders directory tree with folders and files', () => { + injectWithFolder('/mock/project', mockEntries) + + // Check top-level entries only (direct children of #local-directory) + cy.get('#local-directory > li.collection').should('have.length', 2) + cy.get('#local-directory > li.resource').should('have.length', 2) + cy.get('#local-directory > li.collection span').first().should('contain', 'src') + cy.get('#local-directory > li.resource span').first().should('contain', 'README.md') + }) + + it('expands and collapses folders on click', () => { + injectWithFolder('/mock/project', mockEntries) + + // src folder's children should be hidden initially + cy.get('#local-directory > li.collection').first().find('> ul').should('not.be.visible') + + // Click the folder's label span to expand + cy.get('#local-directory > li.collection').first().find('> span').click() + cy.get('#local-directory > li.collection').first().find('> ul').should('be.visible') + cy.get('#local-directory > li.collection').first().find('> ul > li.resource').should('have.length', 2) + + // Click the folder's label span again to collapse + cy.get('#local-directory > li.collection').first().find('> span').click() + cy.get('#local-directory > li.collection').first().find('> ul').should('not.be.visible') + }) + + it('switches between COLLECTIONS and LOCAL tabs', () => { + injectWithFolder('/mock/project', mockEntries) + + // LOCAL should be visible + cy.get('#local-files-body').should('be.visible') + + // Click COLLECTIONS + cy.get('#tabs-outline li').first().click() + cy.get('#local-files-body').should('not.be.visible') + + // Click LOCAL again + cy.get('#tabs-outline').contains('local').click() + cy.get('#local-files-body').should('be.visible') + }) + + it('filters local files', () => { + injectWithFolder('/mock/project', mockEntries) + + cy.get('#local-filter').type('README') + + // Only README.md should be visible at root level + cy.get('#local-directory > li.resource').filter(':visible').should('have.length', 1) + cy.get('#local-directory > li.resource').filter(':visible').invoke('text').should('contain', 'README') + }) + + it('LOCAL tab shows active state when selected', () => { + injectWithFolder('/mock/project', mockEntries) + + // LOCAL tab should be active + cy.get('#tabs-outline').contains('a.tab', 'local') + .should('have.class', 'active') + + // Other tabs should not be active + cy.get('#tabs-outline').contains('a.tab', 'collections') + .should('not.have.class', 'active') + }) + + it('LOCAL tab loses active when switching to COLLECTIONS', () => { + injectWithFolder('/mock/project', mockEntries) + + // Click COLLECTIONS + cy.get('#tabs-outline').contains('a.tab', 'collections').click() + + // LOCAL should no longer be active + cy.get('#tabs-outline').contains('a.tab', 'local') + .should('not.have.class', 'active') + + // COLLECTIONS should be active + cy.get('#tabs-outline').contains('a.tab', 'collections') + .should('have.class', 'active') + }) + + it('all three tabs cycle active state correctly', () => { + injectWithFolder('/mock/project', mockEntries) + + // LOCAL is active + cy.get('#tabs-outline').contains('a.tab', 'local').should('have.class', 'active') + + // Switch to COLLECTIONS + cy.get('#tabs-outline').contains('a.tab', 'collections').click() + cy.get('#tabs-outline').contains('a.tab', 'collections').should('have.class', 'active') + cy.get('#tabs-outline').contains('a.tab', 'local').should('not.have.class', 'active') + cy.get('#tabs-outline').contains('a.tab', 'outline').should('not.have.class', 'active') + + // Switch to OUTLINE + cy.get('#tabs-outline').contains('a.tab', 'outline').click() + cy.get('#tabs-outline').contains('a.tab', 'outline').should('have.class', 'active') + cy.get('#tabs-outline').contains('a.tab', 'collections').should('not.have.class', 'active') + cy.get('#tabs-outline').contains('a.tab', 'local').should('not.have.class', 'active') + + // Switch back to LOCAL + cy.get('#tabs-outline').contains('a.tab', 'local').click() + cy.get('#tabs-outline').contains('a.tab', 'local').should('have.class', 'active') + cy.get('#tabs-outline').contains('a.tab', 'outline').should('not.have.class', 'active') + cy.get('#tabs-outline').contains('a.tab', 'collections').should('not.have.class', 'active') + }) + + it('has correct folder icon classes', () => { + injectWithFolder('/mock/project', mockEntries) + + cy.get('#local-directory li.collection i').first().should('have.class', 'fa-folder') + cy.get('#local-directory li.resource i').first().should('have.class', 'fa-file-o') + }) +}) diff --git a/cypress/e2e/lsp/diagnostics_panel_spec.cy.js b/cypress/e2e/lsp/diagnostics_panel_spec.cy.js new file mode 100644 index 00000000..b1255605 --- /dev/null +++ b/cypress/e2e/lsp/diagnostics_panel_spec.cy.js @@ -0,0 +1,88 @@ +describe('Diagnostics panel', () => { + beforeEach(() => { + cy.intercept('POST', '**/api/query/compile').as('compile') + cy.loginXHR('admin', '') + cy.visit('/eXide/index.html') + cy.reload(true) + cy.get('.path', { timeout: 10000 }).should('contain', 'untitled-1') + cy.get('#user', { timeout: 15000 }).should('not.have.text', 'Login') + // Wait for initial compile to settle + cy.wait('@compile', { timeout: 10000 }) + }) + + function setEditorContent(text) { + cy.window().then((win) => { + var view = win.eXide.app.getEditor().editor + view.dispatch({ + changes: { from: 0, to: view.state.doc.length, insert: text } + }) + view.focus() + }) + } + + it('opens lint panel via Navigate menu', () => { + cy.get('.cm-panel-lint').should('not.exist') + + cy.get('#menu-navigate-diagnostics').click({ force: true }) + + cy.get('.cm-panel-lint', { timeout: 3000 }).should('be.visible') + }) + + it('toggles panel closed on second click', () => { + cy.get('#menu-navigate-diagnostics').click({ force: true }) + cy.get('.cm-panel-lint', { timeout: 3000 }).should('be.visible') + + cy.get('#menu-navigate-diagnostics').click({ force: true }) + cy.get('.cm-panel-lint').should('not.exist') + }) + + it('shows diagnostics for invalid XQuery', () => { + setEditorContent('declare function local:test() {\n $undefined\n};') + // Wait for validation + cy.wait('@compile', { timeout: 10000 }) + cy.wait(500) + + cy.get('#menu-navigate-diagnostics').click({ force: true }) + cy.get('.cm-panel-lint', { timeout: 3000 }).should('be.visible') + + // Lint panel should list at least one diagnostic + cy.get('.cm-panel-lint li').should('have.length.at.least', 1) + }) + + it('clears diagnostics when code is fixed', () => { + // First introduce an error + setEditorContent('declare function local:test() {\n $undefined\n};') + cy.wait('@compile', { timeout: 10000 }) + cy.wait(500) + + // Open panel and verify error exists + cy.get('#menu-navigate-diagnostics').click({ force: true }) + cy.get('.cm-panel-lint', { timeout: 3000 }).should('be.visible') + cy.get('.cm-panel-lint li').should('have.length.at.least', 1) + + // Fix the code + setEditorContent('1 + 1') + cy.wait('@compile', { timeout: 10000 }) + cy.wait(500) + + // Diagnostics should be cleared — panel shows "No diagnostics" text + cy.get('.cm-panel-lint').invoke('text').should('contain', 'No diagnostics') + }) + + it('error clearing: error pill disappears when code is fixed', () => { + // Trigger an error by setting invalid content + setEditorContent('declare function local:broken() { $x };\nlocal:broken()') + // Wait for compile to return the error + cy.wait('@compile', { timeout: 10000 }) + cy.wait(500) + + // Verify error appears + cy.get('#exide-err-pill', { timeout: 5000 }).should('have.class', 'has-error') + + // Fix the error with valid code + setEditorContent('1 + 1') + + // The error pill should eventually clear after re-validation + cy.get('#exide-err-pill', { timeout: 15000 }).should('not.have.class', 'has-error') + }) +}) diff --git a/cypress/e2e/lsp/error_status_spec.cy.js b/cypress/e2e/lsp/error_status_spec.cy.js new file mode 100644 index 00000000..50f9147c --- /dev/null +++ b/cypress/e2e/lsp/error_status_spec.cy.js @@ -0,0 +1,134 @@ +describe('Error status UI', () => { + /** + * Simulate eXide's editor.updateStatus() by writing to #error-status. + * Uses the same textContent approach the real code uses, then waits + * a tick for the MutationObserver callback to fire. + */ + function setError(msg) { + cy.window().then((win) => { + const el = win.document.getElementById('error-status') + el.textContent = msg + }) + // Give the MutationObserver microtask a chance to run + cy.wait(50) + } + + /** + * Wait for eXide to finish its initial compilation cycle. + * The editor compiles the default document on load, which writes to + * #error-status. We need to wait for that to settle before testing. + */ + function waitForInitialLoad() { + cy.get('.path', { timeout: 10000 }).should('contain', 'untitled-1') + // Wait for the initial compilation cycle to finish + cy.wait('@compile', { timeout: 10000 }) + // Clear any existing error state before each test + cy.window().then((win) => { + const el = win.document.getElementById('error-status') + el.textContent = '' + }) + cy.wait(50) + } + + beforeEach(() => { + cy.intercept('POST', '**/api/query/compile').as('compile') + cy.visit('/eXide/index.html') + cy.reload(true) + waitForInitialLoad() + }) + + it('pill and panel are hidden when there is no error', () => { + cy.get('#exide-err-pill').should('not.have.class', 'has-error') + cy.get('#exide-err-panel').should('not.have.class', 'ep-open') + }) + + it('shows pill and panel when an error is written to #error-status', () => { + setError('err:XPST0017 Call to undeclared function [at line 5, column 12]') + + cy.get('#exide-err-pill') + .should('have.class', 'has-error') + .and('be.visible') + + cy.get('#exide-err-pill-label') + .invoke('text') + .should('have.length.greaterThan', 0) + + cy.get('#exide-err-panel') + .should('have.class', 'ep-open') + .and('be.visible') + + cy.get('#exide-err-panel-body') + .should('contain', 'XPST0017') + }) + + it('toggles panel closed and open when clicking the pill', () => { + setError('err:XPST0003 Unexpected token [at line 1, column 1]') + + cy.get('#exide-err-panel').should('have.class', 'ep-open') + + cy.get('#exide-err-pill').click() + cy.get('#exide-err-panel').should('not.have.class', 'ep-open') + + cy.get('#exide-err-pill').click() + cy.get('#exide-err-panel').should('have.class', 'ep-open') + }) + + it('closes panel when clicking the close button', () => { + setError('Cannot compile xquery: syntax error') + + cy.get('#exide-err-panel').should('have.class', 'ep-open') + cy.get('#exide-err-panel-close').click() + cy.get('#exide-err-panel').should('not.have.class', 'ep-open') + }) + + it('closes panel on Escape key', () => { + setError('err:XPST0003 Unexpected end of input') + + cy.get('#exide-err-panel').should('have.class', 'ep-open') + cy.get('body').type('{esc}') + cy.get('#exide-err-panel').should('not.have.class', 'ep-open') + }) + + it('hides pill and panel when error is cleared', () => { + setError('err:XPST0003 Some error') + + cy.get('#exide-err-pill').should('have.class', 'has-error') + cy.get('#exide-err-panel').should('have.class', 'ep-open') + + setError('') + + cy.get('#exide-err-pill').should('not.have.class', 'has-error') + cy.get('#exide-err-panel').should('not.have.class', 'ep-open') + }) + + it('clears error when switching to a different tab', () => { + setError('err:XPST0003 Some error in first tab') + + cy.get('#exide-err-pill').should('have.class', 'has-error') + cy.get('#exide-err-panel').should('have.class', 'ep-open') + + // Create a new tab via the New XQuery button + cy.get('#new-xquery').click() + // Confirm we switched to a new tab + cy.get('.path').should('contain', 'untitled-2') + + // Error should be cleared + cy.get('#exide-err-pill').should('not.have.class', 'has-error') + cy.get('#exide-err-panel').should('not.have.class', 'ep-open') + }) + + it('copy button copies error text to clipboard', () => { + const errorMsg = 'err:XPST0017 Call to undeclared function: local:foo' + + // Stub clipboard before triggering the error + cy.window().then((win) => { + cy.stub(win.navigator.clipboard, 'writeText').as('clipWrite').resolves() + }) + + setError(errorMsg) + cy.get('#exide-err-panel').should('have.class', 'ep-open') + + cy.get('#exide-err-panel-copy').click() + cy.get('@clipWrite').should('have.been.calledWith', errorMsg) + }) +}) diff --git a/cypress/e2e/monitor_spec.cy.js b/cypress/e2e/monitor_spec.cy.js new file mode 100644 index 00000000..e97d2958 --- /dev/null +++ b/cypress/e2e/monitor_spec.cy.js @@ -0,0 +1,108 @@ +describe('Monitor panel', () => { + beforeEach(() => { + cy.loginXHR('admin', '') + cy.visit('/eXide/index.html') + cy.reload(true) + cy.get('.path', { timeout: 10000 }).should('contain', 'untitled-1') + cy.get('#user', { timeout: 10000 }).should('not.have.text', 'Login') + }) + + it('token endpoint returns server status for admin user', () => { + cy.window().then((win) => { + return win.fetch('api/admin/status') + .then((r) => r.json()) + }).then((data) => { + expect(data).to.have.property('version') + }) + }) + + it('app.login.isAdmin is true after login', () => { + cy.window().then((win) => { + expect(win.eXide.app.login).to.not.be.null + expect(win.eXide.app.login.isAdmin).to.be.true + }) + }) + + it('starts polling when toggled open by admin', () => { + cy.get('#toggle-monitor').click() + cy.get('.panel-east').should('be.visible') + cy.get('.mon-title').should('contain', 'MONITOR') + + cy.get('#mon-mem-nums', { timeout: 15000 }).invoke('text').should('match', /\d+ \/ \d+ MB/) + }) + + it('updates memory bar on poll', () => { + cy.get('#toggle-monitor').click() + cy.get('.panel-east').should('be.visible') + + cy.get('#mon-mem-used', { timeout: 15000 }).invoke('css', 'width').should('not.eq', '0px') + }) + + it('shows uptime after polling', () => { + cy.get('#toggle-monitor').click() + + cy.get('#mon-chip-uptime', { timeout: 15000 }).invoke('text').should('match', /Up .+/) + }) + + it('stops polling when toggled closed', () => { + cy.intercept('POST', '**/api/ws/monitor').as('wsPoll') + + cy.get('#toggle-monitor').click() + // Wait until at least one poll has fired (data is visible) + cy.get('#mon-mem-nums', { timeout: 15000 }).invoke('text').should('match', /\d+ \/ \d+ MB/) + + cy.get('#toggle-monitor').click() + cy.get('.panel-east').should('not.be.visible') + + cy.get('@wsPoll.all').then((interceptions) => { + var count = interceptions.length + // Need a real wait here — we're asserting nothing NEW happens + cy.wait(3000) + cy.get('@wsPoll.all').should('have.length', count) + }) + }) + + it('shows running query and allows killing it', () => { + cy.get('#toggle-monitor').click() + cy.get('.panel-east').should('be.visible') + // Wait for first poll to complete before running query + cy.get('#mon-mem-nums', { timeout: 15000 }).invoke('text').should('match', /\d+ \/ \d+ MB/) + + // Run a slow query in the background via the editor + cy.window().then((win) => { + var editor = win.eXide.app.getEditor() + var view = editor.editor + view.dispatch({ + changes: { from: 0, to: view.state.doc.length, insert: 'util:wait(10000)' } + }) + }) + + // Click Run to start the query + cy.get('#run').click() + + // Wait for the running query to appear in the monitor + cy.get('#mon-running-body .mon-running', { timeout: 5000 }) + .should('have.length.at.least', 1) + + // Click the kill button + cy.get('#mon-running-body .mon-kill').first().click() + + // Wait for the query to disappear after the next poll cycle + cy.get('#mon-running-body .mon-empty', { timeout: 15000 }) + .should('exist') + }) + + it('restores monitor on reload if it was open', () => { + cy.get('#toggle-monitor').click() + // Ensure monitor is actually running before reload + cy.get('#mon-mem-nums', { timeout: 15000 }).invoke('text').should('match', /\d+ \/ \d+ MB/) + + cy.reload(true) + cy.get('.path', { timeout: 10000 }).should('contain', 'untitled-1') + cy.get('#user', { timeout: 10000 }).should('not.have.text', 'Login') + + // Monitor panel should be visible again with data + cy.get('.panel-east', { timeout: 5000 }).should('be.visible') + cy.get('#mon-mem-nums', { timeout: 15000 }).invoke('text').should('match', /\d+ \/ \d+ MB/) + }) +}) diff --git a/cypress/e2e/native_autocomplete_spec.cy.js b/cypress/e2e/native_autocomplete_spec.cy.js new file mode 100644 index 00000000..e25ad796 --- /dev/null +++ b/cypress/e2e/native_autocomplete_spec.cy.js @@ -0,0 +1,119 @@ +describe('Native autocomplete for non-XQuery modes', () => { + beforeEach(() => { + cy.loginXHR('admin', '') + cy.visit('/eXide/index.html') + cy.reload(true) + cy.get('.cm-editor', { timeout: 10000 }).should('exist') + cy.get('#user', { timeout: 15000 }).should('not.have.text', 'Login') + }) + + /** + * Create a new document with the given file type and content. + * Types are: html, css, javascript, json, xml, less, markdown + */ + function newDocument(type, content) { + cy.window().then((win) => { + var editor = win.eXide.app.getEditor() + editor.newDocument(null, type) + }) + cy.wait(300) + if (content) { + cy.window().then((win) => { + var view = win.eXide.app.getEditor().editor + view.dispatch({ + changes: { from: 0, to: view.state.doc.length, insert: content }, + selection: { anchor: content.length } + }) + view.focus() + }) + } + } + + function triggerCompletion() { + cy.window().then((win) => { + var view = win.eXide.app.getEditor().editor + view.focus() + win.CM6.startCompletion(view) + }) + } + + function typeInEditor(text) { + cy.get('.cm-content').type(text, { delay: 50 }) + } + + describe('HTML completions', () => { + it('shows tag completions when typing <', () => { + newDocument('html', '') + typeInEditor(' { + newDocument('html', '
{ + it('shows property completions inside a rule', () => { + newDocument('css', 'body {\n co') + triggerCompletion() + + cy.get('.cm-tooltip-autocomplete', { timeout: 5000 }) + .should('be.visible') + .invoke('text') + .should('contain', 'color') + }) + + it('shows multiple property suggestions', () => { + newDocument('css', 'body {\n b') + triggerCompletion() + + cy.get('.cm-tooltip-autocomplete', { timeout: 5000 }) + .should('be.visible') + .invoke('text') + .should('match', /background|border|bottom/) + }) + }) + + describe('JavaScript completions', () => { + it('shows local variable completions', () => { + newDocument('javascript', 'var myLongVariable = 42;\nmy') + triggerCompletion() + + cy.get('.cm-tooltip-autocomplete', { timeout: 5000 }) + .should('be.visible') + .invoke('text') + .should('contain', 'myLongVariable') + }) + }) + + describe('JSON linting', () => { + it('shows parse error for invalid JSON', () => { + newDocument('json', '{ foo: bar }') + cy.wait(500) + + // Lint gutter should show an error marker + cy.get('.cm-lint-marker-error', { timeout: 5000 }) + .should('have.length.at.least', 1) + }) + + it('no errors for valid JSON', () => { + newDocument('json', '{\n "name": "test",\n "version": "1.0"\n}') + cy.wait(500) + + cy.get('.cm-lint-marker-error').should('have.length', 0) + }) + }) + +}) diff --git a/cypress/e2e/navbar.spec.cy.js b/cypress/e2e/navbar.spec.cy.js index 4134abe0..398dfd41 100644 --- a/cypress/e2e/navbar.spec.cy.js +++ b/cypress/e2e/navbar.spec.cy.js @@ -7,9 +7,9 @@ describe('Navbar', function () { cy.get("div.ui-dialog-buttonset button").click() }) cy.wait(500) - cy.get("div.ui-dialog div.ui-dialog-buttonset button").filter(':visible').click() + cy.dismissDialog() cy.get('#editor > div.ace_scroller > div').type("") - cy.get("#eval").click() + cy.get("#run").click() cy.wait(1300) cy.get("#copy-all-clipboard").click({ force: true }) @@ -24,9 +24,9 @@ describe('Navbar', function () { cy.get("div.ui-dialog-buttonset button").click() }) cy.wait(500) - cy.get("div.ui-dialog div.ui-dialog-buttonset button").filter(':visible').click() + cy.dismissDialog() cy.get('#editor > div.ace_scroller > div').type("") - cy.get("#eval").click() + cy.get("#run").click() cy.wait(1300) cy.get("#copy-all-clipboard").click({ force: true }) diff --git a/cypress/e2e/outline_modes_spec.cy.js b/cypress/e2e/outline_modes_spec.cy.js new file mode 100644 index 00000000..fd26c038 --- /dev/null +++ b/cypress/e2e/outline_modes_spec.cy.js @@ -0,0 +1,155 @@ +// TODO: remove LSP skip once lsp:* module is available on CI's eXist-db +describe('Outline modes and filter', () => { + var lspAvailable = false + + before(() => { + cy.loginXHR('admin', '') + cy.request({ + method: 'POST', + url: '/eXide/api/query/symbols', + body: { query: 'declare function local:test() { 1 };', base: 'xmldb:exist:///db' }, + headers: { 'Content-Type': 'application/json' }, + failOnStatusCode: false + }).then((resp) => { + try { + var body = typeof resp.body === 'string' ? JSON.parse(resp.body) : resp.body + lspAvailable = resp.status === 200 && Array.isArray(body) && body.length > 0 && body[0].name !== undefined + } catch (e) { + lspAvailable = false + } + }) + }) + + function setup() { + cy.loginXHR('admin', '') + cy.visit('/eXide/index.html') + cy.reload(true) + cy.get('.path', { timeout: 10000 }).should('contain', 'untitled-1') + cy.get('#user', { timeout: 15000 }).should('not.have.text', 'Login') + + cy.get('#directory li').contains('span', 'apps', { timeout: 5000 }).click() + cy.get('#directory li').contains('span', 'eXide', { timeout: 5000 }).click() + cy.get('#directory li').contains('span', 'modules', { timeout: 5000 }).click() + cy.get('#directory li').contains('span', 'config.xqm', { timeout: 5000 }).click() + cy.get('.path', { timeout: 10000 }).should('contain', '/db/apps/eXide/modules/config.xqm') + + cy.get('#tabs-outline').contains('outline').click() + cy.get('#outline li', { timeout: 10000 }).should('have.length.at.least', 2) + } + + it('shows toolbar with three mode buttons', function () { + if (!lspAvailable) this.skip() + setup() + cy.get('#outline-toolbar').should('exist') + cy.get('[data-outline-mode="nested-doc"]').should('exist') + cy.get('[data-outline-mode="flat-doc"]').should('exist') + cy.get('[data-outline-mode="flat-alpha"]').should('exist') + }) + + it('nested-doc mode is active by default', function () { + if (!lspAvailable) this.skip() + setup() + cy.get('[data-outline-mode="nested-doc"]').should('have.class', 'active') + cy.get('[data-outline-mode="flat-doc"]').should('not.have.class', 'active') + cy.get('[data-outline-mode="flat-alpha"]').should('not.have.class', 'active') + }) + + it('switches to flat-doc mode', function () { + if (!lspAvailable) this.skip() + setup() + cy.get('[data-outline-mode="flat-doc"]').click() + cy.get('[data-outline-mode="flat-doc"]').should('have.class', 'active') + cy.get('[data-outline-mode="nested-doc"]').should('not.have.class', 'active') + + cy.get('#outline li', { timeout: 5000 }).should('have.length.at.least', 2) + }) + + it('switches to flat-alpha mode and sorts alphabetically', function () { + if (!lspAvailable) this.skip() + setup() + cy.get('[data-outline-mode="flat-alpha"]').click() + cy.get('[data-outline-mode="flat-alpha"]').should('have.class', 'active') + + cy.get('#outline li a .outline-name').should('have.length.at.least', 2) + .then(($names) => { + var names = [] + $names.each(function () { + names.push(Cypress.$(this).text().trim().replace(/^\$/, '').toLowerCase()) + }) + for (var i = 1; i < names.length; i++) { + expect(names[i] >= names[i - 1], names[i - 1] + ' <= ' + names[i]).to.be.true + } + }) + }) + + it('flat-doc mode preserves document order', function () { + if (!lspAvailable) this.skip() + setup() + var nestedOrder = [] + cy.get('#outline li a .outline-name').then(($names) => { + $names.each(function () { + nestedOrder.push(Cypress.$(this).text().trim()) + }) + }) + + cy.get('[data-outline-mode="flat-doc"]').click() + cy.get('#outline li', { timeout: 5000 }).should('have.length.at.least', 2) + + cy.get('#outline li a .outline-name').then(($names) => { + var flatOrder = [] + $names.each(function () { + flatOrder.push(Cypress.$(this).text().trim()) + }) + expect(flatOrder).to.deep.eq(nestedOrder) + }) + }) + + it('filter input narrows visible items', function () { + if (!lspAvailable) this.skip() + setup() + var totalCount + cy.get('#outline li').then(($items) => { + totalCount = $items.length + }) + + cy.get('#outline-filter').clear().type('access') + + cy.get('#outline li').then(($items) => { + var visible = $items.filter(function () { + return Cypress.$(this).css('display') !== 'none' + }) + expect(visible.length).to.be.below(totalCount) + expect(visible.text()).to.contain('access') + }) + }) + + it('clearing filter shows all items again', function () { + if (!lspAvailable) this.skip() + setup() + var totalCount + cy.get('#outline li').then(($items) => { + totalCount = $items.length + }) + + cy.get('#outline-filter').clear().type('access') + + cy.get('#outline-filter').clear() + + cy.get('#outline li').then(($items) => { + var visible = $items.filter(function () { + return Cypress.$(this).css('display') !== 'none' + }) + expect(visible.length).to.eq(totalCount) + }) + }) + + it('clicking an outline item navigates the editor', function () { + if (!lspAvailable) this.skip() + setup() + cy.on('uncaught:exception', () => false) + + cy.get('#outline li a').first().click() + + cy.get('#editor .cm-editor', { timeout: 5000 }).should('exist') + }) +}) diff --git a/cypress/e2e/outline_navigation_spec.cy.js b/cypress/e2e/outline_navigation_spec.cy.js new file mode 100644 index 00000000..b8940035 --- /dev/null +++ b/cypress/e2e/outline_navigation_spec.cy.js @@ -0,0 +1,209 @@ +describe('Outline navigation', () => { + beforeEach(() => { + cy.loginXHR('admin', '') + cy.visit('/eXide/index.html') + cy.reload(true) + cy.get('.path', { timeout: 10000 }).should('contain', 'untitled-1') + cy.get('#user', { timeout: 15000 }).should('not.have.text', 'Login') + }) + + function setEditorContent(text) { + cy.window().then((win) => { + var view = win.eXide.app.getEditor().editor + view.dispatch({ + changes: { from: 0, to: view.state.doc.length, insert: text } + }) + view.focus() + }) + } + + function newDocument(type, content) { + cy.window().then((win) => { + var editor = win.eXide.app.getEditor() + editor.newDocument(null, type) + }) + cy.wait(300) + if (content) { + setEditorContent(content) + } + } + + function showOutline() { + cy.get('#tabs-outline').contains('outline').click() + cy.wait(300) + } + + describe('XML outline', () => { + var xml = [ + '', + '
', + ' Test', + '
', + ' ', + '
', + ' Hello', + '
', + '
', + ' World', + '
', + ' ', + '
' + ].join('\n') + + it('shows XML elements as nested tree', () => { + newDocument('xml', xml) + showOutline() + + cy.get('#outline li', { timeout: 5000 }).should('have.length.at.least', 5) + cy.get('#outline .outline-name').first().should('contain', 'root') + }) + + it('shows XPath signatures in tooltips', () => { + newDocument('xml', xml) + showOutline() + + // Outline items should have XPath-like title attributes + cy.get('#outline li a', { timeout: 5000 }).first() + .should('have.attr', 'title') + .and('match', /\/root/) + }) + + it('shows hint attributes for elements with id', () => { + newDocument('xml', xml) + showOutline() + + cy.get('#outline .outline-hint', { timeout: 5000 }) + .should('have.length.at.least', 1) + .first() + .invoke('text') + .should('contain', 'intro') + }) + + it('uses nested
    structure for hierarchy', () => { + newDocument('xml', xml) + showOutline() + + // Nested mode should produce
      children inside
    • elements + cy.get('#outline li ul', { timeout: 5000 }) + .should('have.length.at.least', 1) + }) + + it('navigates to element and centers viewport on click', () => { + newDocument('xml', xml) + showOutline() + + // Click the last element to trigger scroll + cy.get('#outline li a .outline-name').contains('section').first().parent().click() + + // Editor should exist and have the cursor on or near the section element + cy.get('#editor .cm-editor', { timeout: 5000 }).should('exist') + }) + }) + + describe('XML gotoSymbol', () => { + var xml = '\n \n \n' + + it('opens QuickPicker with XPath items', () => { + newDocument('xml', xml) + // Wait for outline to populate (needs ensureSyntaxTree + requestAnimationFrame) + showOutline() + cy.get('#outline li', { timeout: 5000 }).should('have.length.at.least', 1) + + cy.window().then((win) => { + win.eXide.app.getEditor().exec('gotoSymbol') + }) + + cy.get('.quick-picker', { timeout: 5000 }) + .should('be.visible') + + // Should show XPath-style items + cy.get('.quick-picker-list li', { timeout: 3000 }) + .should('have.length.at.least', 1) + .invoke('text') + .should('contain', '/') + }) + }) + + describe('JSON outline', () => { + var json = '{\n "name": "test",\n "version": "1.0",\n "scripts": {\n "build": "npm run build"\n }\n}' + + it('shows property keys in outline', () => { + newDocument('json', json) + showOutline() + + cy.get('#outline li', { timeout: 5000 }).should('have.length.at.least', 3) + cy.get('#outline .outline-name').invoke('text').should('contain', 'name') + }) + + it('opens QuickPicker via gotoSymbol', () => { + newDocument('json', json) + showOutline() + cy.get('#outline li', { timeout: 5000 }).should('have.length.at.least', 1) + + cy.window().then((win) => { + win.eXide.app.getEditor().exec('gotoSymbol') + }) + + cy.get('.quick-picker', { timeout: 5000 }).should('be.visible') + }) + }) + + describe('CSS outline', () => { + var css = 'body {\n color: red;\n}\n\n.header {\n display: flex;\n}\n\n#main {\n padding: 10px;\n}' + + it('shows selectors in outline', () => { + newDocument('css', css) + showOutline() + + cy.get('#outline li', { timeout: 5000 }).should('have.length.at.least', 2) + cy.get('#outline .outline-name').invoke('text').should('contain', 'body') + }) + }) + + describe('JavaScript outline', () => { + var js = 'function greet(name) {\n return "Hello " + name;\n}\n\nfunction add(a, b) {\n return a + b;\n}' + + it('shows functions in outline', () => { + newDocument('javascript', js) + showOutline() + + cy.get('#outline li', { timeout: 5000 }).should('have.length.at.least', 2) + cy.get('#outline .outline-name').invoke('text').should('contain', 'greet') + }) + }) + + describe('Markdown outline', () => { + var md = '# Title\n\n## Section 1\n\nContent\n\n## Section 2\n\nMore content\n\n### Subsection' + + it('shows headings in outline', () => { + newDocument('markdown', md) + showOutline() + + cy.get('#outline li', { timeout: 5000 }).should('have.length.at.least', 3) + cy.get('#outline .outline-name').invoke('text').should('contain', 'Title') + }) + }) + + describe('Navigation flash', () => { + it('flashes target line on gotoLine', () => { + // Open config.xqm which has enough lines + cy.get('#directory li').contains('span', 'apps', { timeout: 5000 }).click() + cy.get('#directory li').contains('span', 'eXide', { timeout: 5000 }).click() + cy.get('#directory li').contains('span', 'modules', { timeout: 5000 }).click() + cy.get('#directory li').contains('span', 'config.xqm', { timeout: 5000 }).click() + cy.get('.path', { timeout: 10000 }).should('contain', 'config.xqm') + + // Navigate to a line programmatically + cy.window().then((win) => { + var view = win.eXide.app.getEditor().editor + win.editorUtils.gotoLine(view, 10, 0, true) + }) + + // Flash decoration should appear briefly + cy.get('.cm-goto-flash', { timeout: 1000 }).should('exist') + + // Flash should fade and be removed + cy.get('.cm-goto-flash', { timeout: 2000 }).should('not.exist') + }) + }) +}) diff --git a/cypress/e2e/outline_spec.cy.js b/cypress/e2e/outline_spec.cy.js new file mode 100644 index 00000000..757d3ba9 --- /dev/null +++ b/cypress/e2e/outline_spec.cy.js @@ -0,0 +1,48 @@ +// TODO: remove LSP skip once lsp:* module is available on CI's eXist-db +describe('Outline view', () => { + var lspAvailable = false + + before(() => { + cy.loginXHR('admin', '') + cy.request({ + method: 'POST', + url: '/eXide/api/query/symbols', + body: { query: 'declare function local:test() { 1 };', base: 'xmldb:exist:///db' }, + headers: { 'Content-Type': 'application/json' }, + failOnStatusCode: false + }).then((resp) => { + try { + var body = typeof resp.body === 'string' ? JSON.parse(resp.body) : resp.body + lspAvailable = resp.status === 200 && Array.isArray(body) && body.length > 0 && body[0].name !== undefined + } catch (e) { + lspAvailable = false + } + }) + }) + + it('should show XQuery functions after opening a library module via collections pane', function () { + if (!lspAvailable) this.skip() + + cy.loginXHR('admin', '') + cy.visit('/eXide/index.html') + cy.reload(true) + cy.get('.path', { timeout: 10000 }).should('contain', 'untitled-1') + cy.get('#user', { timeout: 15000 }).should('not.have.text', 'Login') + + cy.get('#directory li').contains('span', 'apps', { timeout: 5000 }).click() + cy.get('#directory li').contains('span', 'eXide', { timeout: 5000 }).click() + cy.get('#directory li').contains('span', 'modules', { timeout: 5000 }).click() + cy.get('#directory li').contains('span', 'config.xqm', { timeout: 5000 }).click() + + cy.get('.path', { timeout: 10000 }).should('contain', '/db/apps/eXide/modules/config.xqm') + + cy.get('#tabs-outline').contains('outline').click() + + cy.get('#outline li', { timeout: 10000 }).should('have.length.at.least', 1) + + cy.get('#outline li a .outline-name').contains('config:access-allowed').should('exist') + cy.get('#outline li a .outline-name').contains('config:repo-descriptor').should('exist') + cy.get('#outline li a .outline-name').contains('config:expath-descriptor').should('exist') + cy.get('#outline li a .outline-name').contains('config:app-info').should('exist') + }) +}) diff --git a/cypress/e2e/panels_spec.cy.js b/cypress/e2e/panels_spec.cy.js new file mode 100644 index 00000000..6a35ef45 --- /dev/null +++ b/cypress/e2e/panels_spec.cy.js @@ -0,0 +1,119 @@ +describe('Panel resize handles', () => { + beforeEach(() => { + cy.visit('/eXide/index.html') + cy.reload(true) + // Wait for eXide to fully initialize + cy.get('.path', { timeout: 10000 }).should('contain', 'untitled-1') + }) + + function runQuery() { + // Select all content and replace with a valid query + cy.get('#editor .cm-content').click() + cy.get('#editor .cm-content').type('{ctrl+a}{backspace}1 + 1', { delay: 0 }) + cy.get('#run').click() + // Wait for results to appear + cy.get('.panel-south .results', { timeout: 5000 }).should('not.be.empty') + } + + describe('West panel (outline)', () => { + it('toggles closed and open by clicking the resize handle', () => { + cy.get('.panel-west').should('be.visible') + cy.get('.panel-west').invoke('outerWidth').should('be.gt', 50) + + // Click the resize handle to collapse + cy.get('.panel-west .resize-handle').click() + cy.get('.panel-west').invoke('outerWidth').should('be.lte', 12) + + // Click again to expand + cy.get('.panel-west .minimized').click() + cy.get('.panel-west').invoke('outerWidth').should('be.gt', 50) + }) + }) + + describe('South panel (results)', () => { + it('shows results after running a query', () => { + runQuery() + cy.get('.panel-south').invoke('outerHeight').should('be.gt', 50) + }) + + it('toggles closed and open by clicking the resize handle', () => { + runQuery() + cy.get('.panel-south').invoke('outerHeight').should('be.gt', 50) + + // Click the resize handle to collapse + cy.get('.panel-south .resize-handle').click() + cy.get('.panel-south').invoke('outerHeight').should('be.lte', 12) + + // Click again to expand + cy.get('.panel-south .minimized').click() + cy.get('.panel-south').invoke('outerHeight').should('be.gt', 50) + }) + }) + + describe('Drag to resize', () => { + it('resizes the west panel by dragging its handle', () => { + cy.get('.panel-west').invoke('outerWidth').then((initialWidth) => { + // Drag the west handle 80px to the right to widen + cy.get('.panel-west .resize-handle').then(($handle) => { + const rect = $handle[0].getBoundingClientRect() + const startX = rect.left + rect.width / 2 + const startY = rect.top + rect.height / 2 + + cy.get('.panel-west .resize-handle') + .trigger('mousedown', { pageX: startX, pageY: startY, which: 1 }) + cy.get('.layout') + .trigger('mousemove', { pageX: startX + 80, pageY: startY, which: 1 }) + cy.document().trigger('mouseup') + + cy.get('.panel-west').invoke('outerWidth').should('be.gt', initialWidth + 40) + }) + }) + }) + + it('resizes the south panel by dragging its handle', () => { + runQuery() + cy.get('.panel-south').invoke('outerHeight').then((initialHeight) => { + // Drag the south handle 100px up to make it taller + cy.get('.panel-south .resize-handle').then(($handle) => { + const rect = $handle[0].getBoundingClientRect() + const startX = rect.left + rect.width / 2 + const startY = rect.top + rect.height / 2 + + cy.get('.panel-south .resize-handle') + .trigger('mousedown', { pageX: startX, pageY: startY, which: 1 }) + cy.get('.layout') + .trigger('mousemove', { pageX: startX, pageY: startY - 100, which: 1 }) + cy.document().trigger('mouseup') + + cy.get('.panel-south').invoke('outerHeight').should('be.gt', initialHeight + 50) + }) + }) + }) + }) + + describe('East panel (monitor)', () => { + it('toggles open and closed via the monitor toolbar button', () => { + // Monitor panel should be hidden initially + cy.get('.panel-east').should('not.be.visible') + + // Click the monitor toggle button + cy.get('#toggle-monitor').click() + + // Monitor content should be present and visible + cy.get('.mon-title', { timeout: 5000 }).should('be.visible').and('contain', 'MONITOR') + cy.get('.panel-east').invoke('outerWidth').should('be.gt', 50) + + // Click again to close + cy.get('#toggle-monitor').click() + cy.get('.panel-east').should('not.be.visible') + }) + + it('closes via the close button in the monitor bar', () => { + cy.get('#toggle-monitor').click() + cy.get('.mon-title', { timeout: 5000 }).should('be.visible') + + cy.get('#monitor-close').click() + cy.get('.panel-east').should('not.be.visible') + }) + }) +}) diff --git a/cypress/e2e/preferences_spec.cy.js b/cypress/e2e/preferences_spec.cy.js new file mode 100644 index 00000000..2f8d5ffe --- /dev/null +++ b/cypress/e2e/preferences_spec.cy.js @@ -0,0 +1,88 @@ +describe('Preferences dialog', () => { + beforeEach(() => { + cy.visit('/eXide/index.html') + cy.reload(true) + cy.get('.path', { timeout: 10000 }).should('contain', 'untitled-1') + }) + + function openPreferences() { + cy.get('#menu-edit-preferences').click({ force: true }) + cy.get('#preferences-dialog').should('be.visible') + } + + it('opens via Edit menu', () => { + openPreferences() + cy.get('#preferences-dialog form').should('exist') + // Check fieldsets are present + cy.get('#preferences-dialog legend').should('have.length.at.least', 3) + }) + + it('shows current theme setting', () => { + openPreferences() + cy.get('#theme').should('have.value', 'light') + }) + + it('changes theme to dark', () => { + openPreferences() + cy.get('#theme').select('dark') + // Body should get dark class + cy.get('body').should('have.class', 'dark') + }) + + it('changes font size', () => { + openPreferences() + cy.get('#pref-font-size').select('16') + // CM6 editor should reflect the new font size + cy.get('.cm-editor .cm-content').should('have.css', 'font-size', '16px') + }) + + it('toggles show invisibles', () => { + openPreferences() + // Check current state + cy.get('#pref-show-invisibles').then(($cb) => { + var wasChecked = $cb.prop('checked') + // Toggle it + cy.get('#pref-show-invisibles').click() + // Verify it changed + cy.get('#pref-show-invisibles').should(wasChecked ? 'not.be.checked' : 'be.checked') + }) + }) + + it('persists settings across reload', () => { + openPreferences() + cy.get('#pref-font-size').select('16') + // Close dialog + cy.get('#preferences-dialog').closest('.eXide-dialog').find('.eXide-dialog-buttons button').contains('Close').click() + + // Reload and check persistence + cy.reload(true) + cy.get('.path', { timeout: 10000 }).should('contain', 'untitled-1') + openPreferences() + cy.get('#pref-font-size').should('have.value', '16') + + // Reset to default + cy.get('#pref-font-size').select('14') + }) + + it('changes indent mode', () => { + openPreferences() + cy.get('#pref-indent').select('Tabs') + cy.get('#pref-indent').should('have.value', 'Tabs') + }) + + it('changes prettier print width', () => { + openPreferences() + cy.get('#pref-prettier-print-width').select('120') + cy.get('#pref-prettier-print-width').should('have.value', '120') + // Reset + cy.get('#pref-prettier-print-width').select('80') + }) + + it('reverts theme on close when changed to dark then back', () => { + openPreferences() + cy.get('#theme').select('dark') + cy.get('body').should('have.class', 'dark') + cy.get('#theme').select('light') + cy.get('body').should('not.have.class', 'dark') + }) +}) diff --git a/cypress/e2e/prettier_format_spec.cy.js b/cypress/e2e/prettier_format_spec.cy.js new file mode 100644 index 00000000..07ad3808 --- /dev/null +++ b/cypress/e2e/prettier_format_spec.cy.js @@ -0,0 +1,83 @@ +describe('Prettier formatting', () => { + beforeEach(() => { + cy.visit('/eXide/index.html') + cy.reload(true) + cy.get('.path', { timeout: 10000 }).should('contain', 'untitled-1') + }) + + function setEditorContent(text) { + cy.window().then((win) => { + var doc = win.eXide.app.getEditor().getActiveDocument() + doc.setText(text) + }) + } + + function getEditorContent() { + return cy.window().then((win) => { + return win.eXide.app.getEditor().getActiveDocument().getText() + }) + } + + it('formats XQuery code via menu', () => { + // Badly formatted XQuery + var ugly = 'for $x in (1,2,3) let $y:=$x*2 return {$y}' + setEditorContent(ugly) + + // Trigger format via the Edit > Format Code menu item + cy.get('#menu-edit-format').click({ force: true }) + + // Content should change (Prettier reformats it) + cy.wait(500) + getEditorContent().should('not.eq', ugly) + }) + + it('formats XML code', () => { + // Create a new XML document + cy.window().then((win) => { + win.eXide.app.newDocument('text', 'xml') + }) + cy.get('.path', { timeout: 5000 }).should('contain', 'untitled-') + + var before + getEditorContent().then((text) => { + before = text + }) + + cy.get('#menu-edit-format').click({ force: true }) + cy.wait(500) + + // XML should be indented/reformatted + getEditorContent().then((text) => { + expect(text).to.not.eq(before) + // Formatted XML should have newlines (indentation) + expect(text).to.contain('\n') + }) + }) + + it('formats CSS code', () => { + cy.window().then((win) => { + win.eXide.app.newDocument('body{color:red;margin:0;padding:0}h1{font-size:2em}', 'css') + }) + cy.get('.path', { timeout: 5000 }).should('contain', 'untitled-') + + cy.get('#menu-edit-format').click({ force: true }) + cy.wait(500) + + getEditorContent().then((text) => { + // Formatted CSS should have newlines and proper structure + expect(text).to.contain('\n') + expect(text).to.match(/color:\s*red/) + }) + }) + + it('shows error toast for unparseable code', () => { + // Completely broken XQuery that Prettier can't parse + setEditorContent('{{{{{') + + cy.get('#menu-edit-format').click({ force: true }) + + // Error toast should appear + cy.get('.eXide-toast', { timeout: 5000 }) + .should('contain', 'could not be formatted') + }) +}) diff --git a/cypress/e2e/query_execution_spec.cy.js b/cypress/e2e/query_execution_spec.cy.js new file mode 100644 index 00000000..df787592 --- /dev/null +++ b/cypress/e2e/query_execution_spec.cy.js @@ -0,0 +1,264 @@ +describe('Query execution', () => { + beforeEach(() => { + cy.loginXHR('admin', '') + cy.visit('/eXide/index.html') + cy.reload(true) + cy.get('.path', { timeout: 10000 }).should('contain', 'untitled-1') + cy.get('#user', { timeout: 10000 }).should('not.have.text', 'Login') + }) + + /** + * Set the editor content by calling the eXide API directly. + */ + function setEditorContent(text) { + cy.window().then((win) => { + var doc = win.eXide.app.getEditor().getActiveDocument() + doc.setText(text) + }) + } + + it('runs a simple XQuery and shows results', () => { + setEditorContent('for $i in 1 to 3 return {$i}') + cy.get('#run').click() + + // Results panel should appear with content + cy.get('.panel-south .results', { timeout: 10000 }) + .should('not.be.empty') + cy.get('.panel-south .results .content', { timeout: 10000 }) + .should('have.length.at.least', 1) + + // Status message should mention results + cy.get('.panel-south .current') + .invoke('text') + .should('match', /Showing results/) + }) + + it('shows an error for invalid XQuery', () => { + setEditorContent('for $x in return') + cy.get('#run').click() + + // Error should appear in the error status area + cy.get('#error-status', { timeout: 10000 }) + .invoke('text') + .should('have.length.greaterThan', 0) + }) + + it('displays correct result count', () => { + setEditorContent('for $i in 1 to 5 return $i') + cy.get('#run').click() + + cy.get('.panel-south .current', { timeout: 10000 }) + .invoke('text') + .should('contain', '5') + }) + + it('evaluates a string expression', () => { + setEditorContent('"Hello, eXide!"') + cy.get('#run').click() + + cy.get('.panel-south .results .content', { timeout: 10000 }) + .first() + .should('contain', 'Hello, eXide!') + }) + + it('clears previous results before running new query', () => { + setEditorContent('1 + 1') + cy.get('#run').click() + cy.get('.panel-south .results .content', { timeout: 10000 }) + .should('have.length.at.least', 1) + + // Run a different query + setEditorContent('"second query"') + cy.get('#run').click() + cy.get('.panel-south .results .content', { timeout: 10000 }) + .first() + .should('contain', 'second query') + }) + + it('shows a toast notification with result count', () => { + setEditorContent('1') + cy.get('#run').click() + + // util.message() shows a toast with "Query returned N item(s) in Xs" + cy.get('.eXide-toast', { timeout: 10000 }) + .should('contain', 'returned') + .and('contain', 'item') + }) + + it('shows timing info after query completes', () => { + setEditorContent('for $i in 1 to 3 return $i') + cy.get('#run').click() + + cy.get('#query-timing', { timeout: 10000 }) + .should('be.visible') + .invoke('text') + .should('contain', 'Items:') + // Timing bar shows either detailed (Compile/Eval/Total) or simple (Elapsed) + .and('match', /Compile:|Elapsed:/) + }) + + it('shows cancel button during execution and hides after', () => { + // Cancel button should be hidden initially + cy.get('#cancel-query').should('not.be.visible') + + // Run a query — cancel button should appear briefly then hide + setEditorContent('1 + 1') + cy.get('#run').click() + + // After completion, cancel button should be hidden + cy.get('.panel-south .results .content', { timeout: 10000 }) + .should('have.length.at.least', 1) + cy.get('#cancel-query').should('not.be.visible') + }) + + it('cancel button exists and is hidden when no query is running', () => { + cy.get('#cancel-query').should('exist').and('not.be.visible') + }) + + it('falls back to HTTP when WebSocket unavailable', () => { + // Disconnect the ws-eval WebSocket + cy.window().then((win) => { + win.eXide.wsEval.disconnect() + }) + + setEditorContent('1 + 1') + cy.get('#run').click() + + // Should still produce results via HTTP fallback + cy.get('.panel-south .results .content', { timeout: 10000 }) + .should('have.length.at.least', 1) + }) + + it('supports adaptive serialization mode', () => { + // First run a simple query to make results panel visible + setEditorContent('1') + cy.get('#run').click() + cy.get('.panel-south .results .content', { timeout: 10000 }) + .should('have.length.at.least', 1) + + // Now switch serialization mode and run again + cy.get('.panel-south #serialization-mode').select('adaptive') + setEditorContent('"adaptive output"') + cy.get('#run').click() + + cy.get('.panel-south .results .content', { timeout: 10000 }) + .should('have.length.at.least', 1) + }) + + it('paginates results with next/previous buttons', () => { + // Query with 25 results, default page size 10 + setEditorContent('for $i in 1 to 25 return $i') + cy.get('#run').click() + + // First page: 1 to 10 + cy.get('.panel-south .current', { timeout: 10000 }) + .invoke('text') + .should('contain', '1 to 10 of 25') + cy.get('.panel-south .results .content') + .should('have.length', 10) + + // Click next page + cy.get('.panel-south .next').click() + cy.get('.panel-south .current') + .invoke('text') + .should('contain', '11 to 20 of 25') + + // Click next again — partial page + cy.get('.panel-south .next').click() + cy.get('.panel-south .current') + .invoke('text') + .should('contain', '21 to 25 of 25') + + // Click previous + cy.get('.panel-south .previous').click() + cy.get('.panel-south .current') + .invoke('text') + .should('contain', '11 to 20 of 25') + + // Click first + cy.get('.panel-south .first-page').click() + cy.get('.panel-south .current') + .invoke('text') + .should('contain', '1 to 10 of 25') + + // Click last + cy.get('.panel-south .last-page').click() + cy.get('.panel-south .current') + .invoke('text') + .should('contain', '21 to 25 of 25') + }) + + it('handles large result sets without browser memory issues', () => { + // 10,000 items — should return quickly with cursor, only first page rendered + setEditorContent('for $i in 1 to 10000 return $i') + cy.get('#run').click() + + cy.get('.panel-south .current', { timeout: 30000 }) + .invoke('text') + .should('contain', '10000') + // Only 10 items rendered in DOM (not 10,000) + cy.get('.panel-south .results .content') + .should('have.length', 10) + }) + + it('re-fetches page when serialization mode changes without re-executing', () => { + setEditorContent('') + cy.get('#run').click() + cy.get('.panel-south .results .content', { timeout: 10000 }) + .should('have.length.at.least', 1) + + // Switch to XML mode — should re-fetch current page, not re-run query + cy.intercept('GET', '**/api/query/*/results*').as('fetchPage') + cy.intercept('POST', '**/api/query').as('execQuery') + + cy.get('.panel-south #serialization-mode').select('xml') + + // Should fetch results but NOT post a new query + cy.wait('@fetchPage') + cy.get('@execQuery.all').should('have.length', 0) + }) + + it('adaptive serialization quotes xs:string values', () => { + // Run any query first so the results panel becomes visible + setEditorContent('1') + cy.get('#run').click() + cy.get('.panel-south .results .content', { timeout: 10000 }).should('have.length.at.least', 1) + + cy.get('.panel-south #serialization-mode').select('adaptive') + setEditorContent('"hello"') + cy.get('#run').click() + + cy.get('.panel-south .results .content', { timeout: 10000 }) + .first() + .invoke('text') + .should('eq', '"hello"') + }) + + it('adaptive serialization does not quote xs:integer values', () => { + setEditorContent('1') + cy.get('#run').click() + cy.get('.panel-south .results .content', { timeout: 10000 }).should('have.length.at.least', 1) + + cy.get('.panel-south #serialization-mode').select('adaptive') + setEditorContent('42') + cy.get('#run').click() + + cy.get('.panel-south .results .content', { timeout: 10000 }) + .first() + .invoke('text') + .should('eq', '42') + }) + + it('re-fetches page when indent toggle changes', () => { + setEditorContent('') + cy.get('#run').click() + cy.get('.panel-south .results .content', { timeout: 10000 }) + .should('have.length.at.least', 1) + + // Toggle indent off + cy.intercept('GET', '**/api/query/*/results*').as('fetchPage') + cy.get('#indent-results-btn').click() + + cy.wait('@fetchPage') + }) +}) diff --git a/cypress/e2e/rename_spec.cy.js b/cypress/e2e/rename_spec.cy.js new file mode 100644 index 00000000..72259548 --- /dev/null +++ b/cypress/e2e/rename_spec.cy.js @@ -0,0 +1,75 @@ +describe('Rename (multi-cursor)', () => { + beforeEach(() => { + cy.visit('/eXide/index.html') + cy.reload(true) + cy.get('.path', { timeout: 10000 }).should('contain', 'untitled-1') + // Dismiss any startup toasts + cy.wait(500) + }) + + function setEditorContent(text) { + cy.window().then((win) => { + var doc = win.eXide.app.getEditor().getActiveDocument() + doc.setText(text) + }) + } + + function placeCursorAndRename(line, col) { + // Place cursor and trigger rename in a single window call + // to avoid focus-change side effects between Cypress commands + cy.window().then((win) => { + var editor = win.eXide.app.getEditor() + var view = editor.editor + var editorUtils = win.editorUtils + var offset = editorUtils.rowColToOffset(view.state, line, col) + view.dispatch({ selection: { anchor: offset } }) + view.focus() + var doc = editor.getActiveDocument() + doc.helper.rename(doc) + }) + } + + it('creates multi-cursor selection for variable rename', () => { + setEditorContent('let $count := 1\nlet $other := $count + 1\nreturn $count') + // Place cursor on 'count' (EQName inside VarName) — line 2, col 9 + placeCursorAndRename(2, 9) + + cy.get('.eXide-toast', { timeout: 5000 }) + .should('contain', 'Editing 3 occurrence') + }) + + it('renames variable from $ sign position', () => { + setEditorContent('let $x := 1 return $x') + // Place cursor on $ of $x in 'return $x' — line 0, col 20 + placeCursorAndRename(0, 20) + + cy.get('.eXide-toast', { timeout: 5000 }) + .should('contain', 'Editing 2 occurrence') + }) + + it('creates multi-cursor selection for function rename', () => { + setEditorContent('declare function local:greet($name) { "Hello " || $name };\nlocal:greet("world")') + // Place cursor on 'greet' in the function call — line 1, col 8 + placeCursorAndRename(1, 8) + + cy.get('.eXide-toast', { timeout: 5000 }) + .should('contain', 'Editing 2 occurrence') + }) + + it('creates multi-cursor selection for XML element tag rename', () => { + setEditorContent('
      hello
      ') + // Place cursor on 'div' in opening tag — line 0, col 2 + placeCursorAndRename(0, 2) + + cy.get('.eXide-toast', { timeout: 5000 }) + .should('contain', 'Editing 2 occurrence') + }) + + it('shows message when cursor is not on a renameable symbol', () => { + setEditorContent('1 + 2') + placeCursorAndRename(0, 2) + + cy.get('.eXide-toast', { timeout: 5000 }) + .should('contain', 'cursor') + }) +}) diff --git a/cypress/e2e/search_spec.cy.js b/cypress/e2e/search_spec.cy.js new file mode 100644 index 00000000..b29a8e21 --- /dev/null +++ b/cypress/e2e/search_spec.cy.js @@ -0,0 +1,162 @@ +describe('Find in Files (search API)', () => { + beforeEach(() => { + cy.loginXHR('admin', '') + cy.visit('/eXide/index.html') + cy.reload(true) + cy.get('.path', { timeout: 10000 }).should('contain', 'untitled-1') + cy.get('#user', { timeout: 10000 }).should('not.have.text', 'Login') + }) + + it('POST /api/search returns JSON results', () => { + cy.request({ + method: 'POST', + url: '/eXide/api/search', + headers: { 'Content-Type': 'application/json' }, + body: { + query: 'function', + collection: '/db/apps/eXide/modules', + type: 'xquery' + } + }).then((response) => { + expect(response.status).to.eq(200) + expect(response.body).to.have.property('query', 'function') + expect(response.body).to.have.property('collection', '/db/apps/eXide/modules') + expect(response.body).to.have.property('hits') + expect(response.body.hits).to.be.an('array') + expect(response.body.hits.length).to.be.greaterThan(0) + + const hit = response.body.hits[0] + expect(hit).to.have.property('resource') + expect(hit).to.have.property('name') + expect(hit).to.have.property('line') + expect(hit).to.have.property('text') + }) + }) + + it('returns 400 for empty query', () => { + cy.request({ + method: 'POST', + url: '/eXide/api/search', + headers: { 'Content-Type': 'application/json' }, + body: { query: '', collection: '/db' }, + failOnStatusCode: false + }).then((response) => { + expect(response.status).to.eq(400) + expect(response.body).to.have.property('error') + }) + }) + + it('filters by type', () => { + cy.request({ + method: 'POST', + url: '/eXide/api/search', + headers: { 'Content-Type': 'application/json' }, + body: { + query: 'import', + collection: '/db/apps/eXide/modules', + type: 'xquery' + } + }).then((response) => { + expect(response.status).to.eq(200) + response.body.hits.forEach((hit) => { + expect(hit.resource).to.match(/\.(xq|xqm|xql|xquery)$/) + }) + }) + }) + + it('supports case-sensitive search', () => { + cy.request({ + method: 'POST', + url: '/eXide/api/search', + headers: { 'Content-Type': 'application/json' }, + body: { + query: 'FUNCTION', + collection: '/db/apps/eXide/modules', + type: 'xquery', + caseSensitive: true + } + }).then((response) => { + expect(response.status).to.eq(200) + // XQuery uses lowercase 'function', so case-sensitive 'FUNCTION' should find fewer/no hits + expect(response.body.hits.length).to.eq(0) + }) + }) + + it('supports regex search', () => { + cy.request({ + method: 'POST', + url: '/eXide/api/search', + headers: { 'Content-Type': 'application/json' }, + body: { + query: 'declare\\s+function', + collection: '/db/apps/eXide/modules', + type: 'xquery', + regex: true + } + }).then((response) => { + expect(response.status).to.eq(200) + expect(response.body.hits.length).to.be.greaterThan(0) + response.body.hits.forEach((hit) => { + expect(hit.text).to.match(/declare\s+function/) + }) + }) + }) + + it('opens Find in Files dialog and renders results in iframe', () => { + // Intercept the API call before any UI interaction + cy.intercept('POST', '**/api/search').as('searchApi') + + // Open the dialog via menu + cy.get('#menu-edit-find-files').click({ force: true }) + cy.get('#find-dialog', { timeout: 5000 }).should('be.visible') + + // Fill in search form + cy.get('#find-dialog input[name="search"]').clear().type('function') + cy.get('#find-dialog select[name="type"]').select('xquery') + cy.get('#find-dialog input[name="target"][value="all"]').check() + + // Click Search button (in the dialog's button bar, sibling of #find-dialog) + cy.get('#find-dialog').closest('.eXide-dialog').find('.eXide-dialog-buttons button').contains('Search').click() + + // Verify API was called and results rendered + cy.wait('@searchApi').then((interception) => { + expect(interception.response.statusCode).to.eq(200) + expect(interception.response.body.hits.length).to.be.greaterThan(0) + }) + + // Results should appear in the iframe (re-query body to avoid detached DOM) + cy.get('#results-iframe', { timeout: 10000 }).should('be.visible') + cy.get('#results-iframe').its('0.contentDocument.body', { timeout: 10000 }) + .should('not.be.empty') + cy.get('#results-iframe').its('0.contentDocument.body') + .find('.sourceinfo') + .should('have.length.greaterThan', 0) + }) + + it('clicking a search result opens the document', () => { + // Intercept before UI interaction + cy.intercept('POST', '**/api/search').as('searchApi') + + // Open dialog and search + cy.get('#menu-edit-find-files').click({ force: true }) + cy.get('#find-dialog', { timeout: 5000 }).should('be.visible') + cy.get('#find-dialog input[name="search"]').clear().type('declare function') + cy.get('#find-dialog select[name="type"]').select('xquery') + cy.get('#find-dialog input[name="target"][value="all"]').check() + + cy.get('#find-dialog').closest('.eXide-dialog').find('.eXide-dialog-buttons button').contains('Search').click() + cy.wait('@searchApi') + + // Click the first result link inside the iframe + cy.get('#results-iframe', { timeout: 10000 }) + .its('0.contentDocument.body') + .find('.resource') + .first() + .click() + + // A new tab should open with the file + cy.get('.path', { timeout: 10000 }) + .invoke('text') + .should('not.eq', 'untitled-1') + }) +}) diff --git a/cypress/e2e/semantic_highlight_spec.cy.js b/cypress/e2e/semantic_highlight_spec.cy.js new file mode 100644 index 00000000..35fcc00e --- /dev/null +++ b/cypress/e2e/semantic_highlight_spec.cy.js @@ -0,0 +1,74 @@ +describe('Semantic highlighting', () => { + beforeEach(() => { + cy.visit('/eXide/index.html') + cy.reload(true) + cy.get('.path', { timeout: 10000 }).should('contain', 'untitled-1') + }) + + function setEditorContent(text) { + cy.window().then((win) => { + var doc = win.eXide.app.getEditor().getActiveDocument() + doc.setText(text) + }) + } + + function triggerParse() { + // Force a parse by calling the helper's validate method + cy.window().then((win) => { + var editor = win.eXide.app.getEditor() + var doc = editor.getActiveDocument() + var helper = editor.getActiveDocument().helper + if (helper && helper.parseXQuery) { + helper.parseXQuery(doc) + } + }) + } + + function semSpans(cls) { + return cy.get('.cm-editor .cm-content').find('.' + cls, { timeout: 5000 }) + } + + it('highlights function names in declarations and calls', () => { + setEditorContent('declare function local:test() { local:test() };\nlocal:test()') + triggerParse() + semSpans('sem-function-name').should('have.length.at.least', 2) + }) + + it('highlights variables', () => { + setEditorContent('let $x := 1 return $x') + triggerParse() + semSpans('sem-variable').should('have.length.at.least', 1) + }) + + it('highlights namespace prefixes in declarations', () => { + setEditorContent('module namespace demo = "http://example.com/demo";\ndeclare function demo:f() { 1 };') + triggerParse() + semSpans('sem-namespace-prefix').should('have.length.at.least', 1) + }) + + it('highlights annotations', () => { + setEditorContent('declare %rest:GET %rest:path("/api") function local:f() { 1 };') + triggerParse() + semSpans('sem-annotation').should('have.length.at.least', 2) + }) + + it('highlights type names', () => { + setEditorContent('let $x as xs:string := "hello" return $x') + triggerParse() + semSpans('sem-type-name').should('have.length.at.least', 1) + }) + + it('updates highlights when content changes', () => { + setEditorContent('let $x := 1 return $x') + triggerParse() + semSpans('sem-variable').should('have.length.at.least', 1) + + // Change content to have no variables + setEditorContent('1 + 2') + triggerParse() + + // Give a moment for decorations to clear + cy.wait(200) + cy.get('.cm-editor .cm-content').find('.sem-variable').should('not.exist') + }) +}) diff --git a/cypress/e2e/split_pane_spec.cy.js b/cypress/e2e/split_pane_spec.cy.js new file mode 100644 index 00000000..1f9aafcb --- /dev/null +++ b/cypress/e2e/split_pane_spec.cy.js @@ -0,0 +1,70 @@ +describe('West panel split/tab toggle', () => { + beforeEach(() => { + cy.visit('/eXide/index.html') + cy.reload(true) + cy.get('.path', { timeout: 10000 }).should('contain', 'untitled-1') + // Ensure we start in tabbed mode (default) + cy.get('.panel-west').should('not.have.class', 'split-pane') + }) + + it('switches from tabbed to split when collections tab is active', () => { + // Collections tab is active by default (index 0) + cy.get('#tabs-outline a.tab').first().should('have.class', 'active') + cy.get('#directory-body').should('have.css', 'visibility', 'visible') + cy.get('#outline-body').should('have.css', 'visibility', 'hidden') + + // Toggle to split + cy.get('#toggle-split-pane').click() + cy.get('.panel-west').should('have.class', 'split-pane') + + // Both panels should be visible + cy.get('#directory-body').should('have.css', 'visibility', 'visible') + cy.get('#outline-body').should('have.css', 'visibility', 'visible') + }) + + it('switches from tabbed to split when outline tab is active', () => { + // Click the outline tab + cy.get('#tabs-outline a.tab').last().click() + cy.get('#tabs-outline a.tab').last().should('have.class', 'active') + cy.get('#outline-body').should('have.css', 'visibility', 'visible') + cy.get('#directory-body').should('have.css', 'visibility', 'hidden') + + // Toggle to split + cy.get('#toggle-split-pane').click() + cy.get('.panel-west').should('have.class', 'split-pane') + + // Both panels should be visible + cy.get('#directory-body').should('have.css', 'visibility', 'visible') + cy.get('#outline-body').should('have.css', 'visibility', 'visible') + }) + + it('switches from split back to tabbed, restoring the active tab', () => { + // Click the outline tab, then toggle to split + cy.get('#tabs-outline a.tab').last().click() + cy.get('#toggle-split-pane').click() + cy.get('.panel-west').should('have.class', 'split-pane') + + // Toggle back to tabbed + cy.get('#toggle-split-pane').click() + cy.get('.panel-west').should('not.have.class', 'split-pane') + + // Outline tab was active, so outline should be visible and collections hidden + cy.get('#tabs-outline a.tab').last().should('have.class', 'active') + cy.get('#outline-body').should('have.css', 'visibility', 'visible') + cy.get('#directory-body').should('have.css', 'visibility', 'hidden') + }) + + it('switches from split back to tabbed with collections tab active', () => { + // Collections tab is active by default, toggle to split then back + cy.get('#toggle-split-pane').click() + cy.get('.panel-west').should('have.class', 'split-pane') + + cy.get('#toggle-split-pane').click() + cy.get('.panel-west').should('not.have.class', 'split-pane') + + // Collections tab was active, so collections should be visible and outline hidden + cy.get('#tabs-outline a.tab').first().should('have.class', 'active') + cy.get('#directory-body').should('have.css', 'visibility', 'visible') + cy.get('#outline-body').should('have.css', 'visibility', 'hidden') + }) +}) diff --git a/cypress/e2e/tab_overflow_spec.cy.js b/cypress/e2e/tab_overflow_spec.cy.js new file mode 100644 index 00000000..12b59a59 --- /dev/null +++ b/cypress/e2e/tab_overflow_spec.cy.js @@ -0,0 +1,149 @@ +describe('Tab bar overflow controls', () => { + beforeEach(() => { + cy.visit('/eXide/index.html') + cy.reload(true) + cy.get('.path', { timeout: 10000 }).should('contain', 'untitled-1') + }) + + describe('control buttons exist', () => { + it('should render scroll-left, scroll-right, and list buttons', () => { + cy.get('#tab-scroll-left').should('exist').and('be.visible') + cy.get('#tab-scroll-right').should('exist').and('be.visible') + cy.get('#tab-list-btn').should('exist').and('be.visible') + }) + + it('scroll-left should be disabled initially (single tab)', () => { + cy.get('#tab-scroll-left').should('be.disabled') + }) + }) + + describe('dropdown menu', () => { + it('tab-list-menu element should exist in DOM', () => { + cy.get('#tab-list-menu').should('exist') + }) + + it('tab-list-menu should be hidden initially', () => { + cy.get('#tab-list-menu').should('not.have.class', 'open') + cy.get('#tab-list-menu').should('have.css', 'display', 'none') + }) + + it('clicking the ▾ button should add .open class to menu', () => { + cy.get('#tab-list-btn').click() + cy.get('#tab-list-menu').should('have.class', 'open') + }) + + it('clicking the ▾ button should make menu visible', () => { + cy.get('#tab-list-btn').click() + cy.get('#tab-list-menu') + .should('have.class', 'open') + .and('have.css', 'display', 'block') + }) + + it('menu should contain at least one tab entry', () => { + cy.get('#tab-list-btn').click() + cy.get('#tab-list-menu li').should('have.length.at.least', 1) + }) + + it('active tab should be marked in the dropdown', () => { + cy.get('#tab-list-btn').click() + cy.get('#tab-list-menu li.active').should('have.length', 1) + }) + + it('menu should have non-zero dimensions when open', () => { + cy.get('#tab-list-btn').click() + cy.get('#tab-list-menu').should('have.class', 'open') + cy.get('#tab-list-menu').then(($menu) => { + const rect = $menu[0].getBoundingClientRect() + expect(rect.width, 'menu width').to.be.gt(50) + expect(rect.height, 'menu height').to.be.gt(10) + }) + }) + + it('menu should be within the viewport when open', () => { + cy.get('#tab-list-btn').click() + cy.get('#tab-list-menu').should('have.class', 'open') + cy.window().then((win) => { + cy.get('#tab-list-menu').then(($menu) => { + const rect = $menu[0].getBoundingClientRect() + expect(rect.top, 'top within viewport').to.be.at.least(0) + expect(rect.bottom, 'bottom within viewport').to.be.at.most(win.innerHeight) + expect(rect.left, 'left within viewport').to.be.at.least(0) + expect(rect.right, 'right within viewport').to.be.at.most(win.innerWidth) + }) + }) + }) + + it('menu should not be clipped by parent overflow', () => { + cy.get('#tab-list-btn').click() + cy.get('#tab-list-menu').should('have.class', 'open') + // Walk up the DOM checking no ancestor clips the menu + cy.get('#tab-list-menu').then(($menu) => { + let el = $menu[0].parentElement + const menuRect = $menu[0].getBoundingClientRect() + const ancestors = [] + while (el) { + const style = window.getComputedStyle(el) + const overflow = style.overflow + ' ' + style.overflowX + ' ' + style.overflowY + if (/hidden|clip/.test(overflow)) { + const parentRect = el.getBoundingClientRect() + ancestors.push({ + tag: el.tagName, + id: el.id, + class: el.className, + overflow, + clips: menuRect.bottom > parentRect.bottom || + menuRect.top < parentRect.top || + menuRect.right > parentRect.right || + menuRect.left < parentRect.left + }) + } + el = el.parentElement + } + // Log all ancestors with overflow hidden/clip + ancestors.forEach(a => { + cy.log(`${a.tag}#${a.id}.${a.class} overflow="${a.overflow}" clips=${a.clips}`) + }) + // Fail if any ancestor actually clips the menu + const clippers = ancestors.filter(a => a.clips) + const desc = clippers.map(c => `${c.tag}#${c.id}.${c.class} overflow="${c.overflow}"`).join('; ') + expect(clippers, 'no ancestor should clip the menu [' + desc + ']').to.have.length(0) + }) + }) + + it('clicking ▾ again should close the menu (toggle)', () => { + cy.get('#tab-list-btn').click() + cy.get('#tab-list-menu').should('have.class', 'open') + cy.get('#tab-list-btn').click() + cy.get('#tab-list-menu').should('not.have.class', 'open') + }) + + it('pressing Escape should close the menu', () => { + cy.get('#tab-list-btn').click() + cy.get('#tab-list-menu').should('have.class', 'open') + cy.get('body').type('{esc}') + cy.get('#tab-list-menu').should('not.have.class', 'open') + }) + + it('clicking outside should close the menu', () => { + cy.get('#tab-list-btn').click() + cy.get('#tab-list-menu').should('have.class', 'open') + cy.get('#editor').click({ force: true }) + cy.get('#tab-list-menu').should('not.have.class', 'open') + }) + + it('clicking a tab entry should switch to that tab and close menu', () => { + cy.get('#tab-list-btn').click() + cy.get('#tab-list-menu li').first().click() + cy.get('#tab-list-menu').should('not.have.class', 'open') + }) + }) + + describe('ui-init.js script loading', () => { + it('should have loaded ui-init.js (button has click handler)', () => { + // Verify the script attached handlers by checking aria-expanded toggles + cy.get('#tab-list-btn').should('have.attr', 'aria-expanded', 'false') + cy.get('#tab-list-btn').click() + cy.get('#tab-list-btn').should('have.attr', 'aria-expanded', 'true') + }) + }) +}) diff --git a/cypress/e2e/tag_scope_selection_spec.cy.js b/cypress/e2e/tag_scope_selection_spec.cy.js new file mode 100644 index 00000000..29ea6235 --- /dev/null +++ b/cypress/e2e/tag_scope_selection_spec.cy.js @@ -0,0 +1,158 @@ +describe('Tag matching, scope breadcrumb, and selection match', () => { + beforeEach(() => { + cy.loginXHR('admin', '') + cy.visit('/eXide/index.html') + cy.reload(true) + cy.get('.path', { timeout: 10000 }).should('contain', 'untitled-1') + cy.get('#user', { timeout: 15000 }).should('not.have.text', 'Login') + }) + + function newDocument(type, content) { + cy.window().then((win) => { + var editor = win.eXide.app.getEditor() + editor.newDocument(null, type) + }) + cy.wait(300) + if (content) { + cy.window().then((win) => { + var view = win.eXide.app.getEditor().editor + view.dispatch({ + changes: { from: 0, to: view.state.doc.length, insert: content } + }) + view.focus() + }) + } + } + + function setCursorAt(line, col) { + cy.window().then((win) => { + var view = win.eXide.app.getEditor().editor + var lineInfo = view.state.doc.line(line) + var offset = lineInfo.from + col + view.dispatch({ selection: { anchor: offset } }) + view.focus() + }) + } + + describe('Tag matching (XML/HTML)', () => { + var xml = '\n text\n \n' + + it('has tag matching CSS styles defined', () => { + // Verify the cm-matchingTag CSS class is defined in the stylesheet + // (Tag matching works visually but is difficult to test in Cypress because + // CM6's syntax tree parsing and ViewPlugin update timing is unpredictable + // in headless browsers) + cy.document().then((doc) => { + var sheets = doc.styleSheets + var found = false + for (var i = 0; i < sheets.length; i++) { + try { + var rules = sheets[i].cssRules + for (var j = 0; j < rules.length; j++) { + if (rules[j].selectorText && rules[j].selectorText.includes('cm-matchingTag')) { + found = true + break + } + } + } catch (e) { /* cross-origin stylesheet */ } + if (found) break + } + expect(found).to.be.true + }) + }) + }) + + describe('Scope breadcrumb', () => { + it('shows XPath scope for XML documents', () => { + var xml = '\n
      \n Test\n
      \n
      ' + newDocument('xml', xml) + + // Place cursor inside content + setCursorAt(3, 12) + cy.wait(300) + + cy.get('#status-scope', { timeout: 3000 }) + .should('be.visible') + .invoke('text') + .should('contain', 'root') + .and('contain', 'title') + }) + + it('shows scope for XQuery function body', () => { + var code = 'declare function local:test() {\n let $x := 1\n return $x\n};' + cy.window().then((win) => { + var editor = win.eXide.app.getEditor() + var view = editor.editor + var doc = editor.getActiveDocument() + editor.validator.setEnabled(false) + view.dispatch({ + changes: { from: 0, to: view.state.doc.length, insert: code } + }) + doc.lastValidation = 0 + doc.getModeHelper().parseXQuery(doc) + view.focus() + editor.validator.setEnabled(true) + }) + + // Place cursor inside the function body (line 2, col 5 = inside "let") + setCursorAt(2, 5) + cy.wait(500) + + // Scope should show the function context + cy.get('#status-scope', { timeout: 5000 }) + .should('be.visible') + .invoke('text') + .should('have.length.greaterThan', 0) + }) + + it('hides scope when not inside a named context', () => { + // Default untitled document with version declaration + cy.get('#status-scope') + .should('not.be.visible') + }) + }) + + describe('Selection match highlighting', () => { + it('highlights matching text when a word is selected', () => { + var code = 'let $total := 42\nlet $other := $total\nreturn $total' + cy.window().then((win) => { + var view = win.eXide.app.getEditor().editor + view.dispatch({ + changes: { from: 0, to: view.state.doc.length, insert: code } + }) + // Select "$total" at the first occurrence (offset 4-10) + view.dispatch({ selection: { anchor: 4, head: 10 } }) + view.focus() + }) + + cy.wait(300) + + // CM6 highlightSelectionMatches should mark other occurrences + cy.get('.cm-selectionMatch', { timeout: 3000 }) + .should('have.length.at.least', 1) + }) + + it('removes highlights when selection is cleared', () => { + var code = 'let $total := 42\nreturn $total' + cy.window().then((win) => { + var view = win.eXide.app.getEditor().editor + view.dispatch({ + changes: { from: 0, to: view.state.doc.length, insert: code } + }) + // Select "$total" + view.dispatch({ selection: { anchor: 4, head: 10 } }) + view.focus() + }) + cy.wait(300) + + // Clear selection by clicking + cy.window().then((win) => { + var view = win.eXide.app.getEditor().editor + view.dispatch({ selection: { anchor: 0 } }) + }) + cy.wait(300) + + cy.get('.cm-selectionMatch').should('not.exist') + }) + }) +}) diff --git a/cypress/e2e/websocket_spec.cy.js b/cypress/e2e/websocket_spec.cy.js new file mode 100644 index 00000000..73cefd51 --- /dev/null +++ b/cypress/e2e/websocket_spec.cy.js @@ -0,0 +1,115 @@ +describe('WebSocket transport', () => { + beforeEach(() => { + cy.loginXHR('admin', '') + cy.visit('/eXide/index.html') + cy.reload(true) + cy.get('.cm-editor', { timeout: 10000 }).should('exist') + cy.get('#user', { timeout: 15000 }).should('not.have.text', 'Login') + }) + + /** Retry until ws.isConnected() returns true, or skip the test. */ + function waitForWs(skipCtx) { + return cy.window().should((win) => { + if (!win.eXide.ws || !win.eXide.ws.isConnected()) { + throw new Error('WebSocket not yet connected') + } + }).then(() => { + return cy.window() + }, () => { + // If it never connected, skip + skipCtx.skip() + }) + } + + it('auto-connects to WebSocket on startup', () => { + cy.window().then((win) => { + expect(win.eXide.ws).to.exist + expect(win.eXide.ws.isConnected).to.be.a('function') + }) + }) + + it('connects to /exist/ws endpoint', function () { + waitForWs(this) + cy.window().then((win) => { + expect(win.eXide.ws.isConnected()).to.be.true + }) + }) + + it('handles ping messages without errors', function () { + waitForWs(this) + cy.window().then((win) => { + expect(win.eXide.ws.isConnected()).to.be.true + }) + }) + + it('exposes send and notify methods', () => { + cy.window().then((win) => { + expect(win.eXide.ws.send).to.be.a('function') + expect(win.eXide.ws.notify).to.be.a('function') + expect(win.eXide.ws.on).to.be.a('function') + expect(win.eXide.ws.off).to.be.a('function') + }) + }) + + it('receives monitoring data via WebSocket push', function () { + waitForWs(this) + cy.window().then((win) => { + var received = null + win.eXide.ws.on("exist/metrics", function (data) { + received = data + }) + + // Trigger the monitoring push via HTTP (which pushes to WebSocket) + cy.request({ + method: 'POST', + url: '/eXide/api/ws/monitor', + headers: { 'Content-Type': 'application/json' }, + failOnStatusCode: false + }).then(() => { + // Retry until WebSocket delivers the message + cy.wrap(null, { timeout: 5000 }).should(() => { + expect(received).to.not.be.null + expect(received.type).to.eq("exist/metrics") + expect(received.version).to.be.a("string") + }) + }) + }) + }) + + it('receives diagnostics push after compilation', function () { + waitForWs(this) + cy.window().then((win) => { + var received = null + win.eXide.ws.on("textDocument/publishDiagnostics", function (data) { + received = data + }) + + // Trigger compilation with invalid code + cy.request({ + method: 'POST', + url: '/eXide/api/query/compile', + headers: { 'Content-Type': 'application/json' }, + body: { query: 'let $x := retrun $x', base: 'xmldb:exist:///db', uri: 'test.xq' }, + failOnStatusCode: false + }).then(() => { + cy.wrap(null, { timeout: 5000 }).should(() => { + expect(received).to.not.be.null + expect(received.type).to.eq("textDocument/publishDiagnostics") + expect(received.uri).to.eq("test.xq") + expect(received.diagnostics).to.be.an("array") + expect(received.diagnostics.length).to.be.greaterThan(0) + }) + }) + }) + }) + + it('falls back gracefully when WebSocket unavailable', () => { + cy.get('.cm-editor').should('exist') + cy.get('#status-bar').should('exist') + cy.window().then((win) => { + var editor = win.eXide.app.getEditor() + expect(editor).to.exist + expect(editor.editor).to.exist + }) + }) +}) diff --git a/cypress/support/commands.js b/cypress/support/commands.js index 882eda0d..d336273d 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -28,7 +28,7 @@ Cypress.Commands.add("loginXHR", (user, password) => { cy.session(['xhr', user, password], () => { cy.request({ method: 'POST', - url: '/eXide/login', + url: '/eXide/api/auth/session', form: true, body: { user, password }, headers: { 'Accept': 'application/json' } @@ -36,6 +36,33 @@ Cypress.Commands.add("loginXHR", (user, password) => { }) }) +// cy.dismissDialog() -- wait for editor to load and login to complete, then dismiss any visible dialog +Cypress.Commands.add("dismissDialog", () => { + // Wait for the editor to be initialized + cy.get('#editor .cm-editor', { timeout: 10000 }).should('exist') + // Wait for the login check to complete (user text changes from default) + cy.get('#user', { timeout: 10000 }).should('not.have.text', 'Login') + // Dismiss any startup dialogs + cy.get('body').then(($body) => { + const btn = $body.find('.eXide-dialog[open] .eXide-dialog-buttons button') + if (btn.length) { + btn[0].click() + } + }) +}) + +// cy.cleanupTestFiles() -- removes cypress-test-* files from /db +Cypress.Commands.add("cleanupTestFiles", () => { + cy.loginXHR('admin', '') + cy.request({ + method: 'POST', + url: '/exist/rest/db', + headers: { 'Content-Type': 'application/xquery' }, + body: 'for $r in xmldb:get-child-resources("/db") where starts-with($r, "cypress-test-") return xmldb:remove("/db", $r)', + failOnStatusCode: false + }) +}) + // cy.logout() -- does not work reliably Cypress.Commands.add("logout", () => cy.request('/eXide/index.html', {logout: true})) @@ -52,18 +79,15 @@ Cypress.Commands.add("setConf", function (executeQuery, restrictAccess) { const body = getConf(executeQuery, restrictAccess); const confFilePath = "/apps/eXide/configuration.xml" cy.request({ - method: 'POST', - url: `/eXide/store/db${confFilePath}`, + method: 'PUT', + url: `/eXide/api/storage${confFilePath}`, headers: { - 'Content-Type': 'application/xml', - 'Content-length': body.length + 'Content-Type': 'application/xml' }, body }) .then((response) => { - const parsed = JSON.parse(response.body) - expect(parsed).to.have.property('status', 'ok') - expect(parsed).to.have.property('externalLink', `/exist${confFilePath}`) + expect(response.body).to.have.property('status', 'ok') }) }) diff --git a/expath-pkg.xml b/expath-pkg.xml new file mode 100644 index 00000000..c2db0535 --- /dev/null +++ b/expath-pkg.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<package xmlns="http://expath.org/ns/pkg" name="http://exist-db.org/apps/eXide" abbrev="eXide" version="4.0.0" spec="1.0"> + <title>eXide - XQuery IDE + + + + diff --git a/expath-pkg.xml.tmpl b/expath-pkg.xml.tmpl index 6a8f39eb..a65ae974 100644 --- a/expath-pkg.xml.tmpl +++ b/expath-pkg.xml.tmpl @@ -2,4 +2,6 @@ eXide - XQuery IDE + + diff --git a/grammars/README.md b/grammars/README.md new file mode 100644 index 00000000..f4f12633 --- /dev/null +++ b/grammars/README.md @@ -0,0 +1,133 @@ +# XQuery Grammars for eXide + +eXide uses a [REx](https://www.bottlecaps.de/rex/)-generated parser built from W3C EBNF grammars. This directory contains the source grammars and their combined variants. + +## Grammar Files + +### Reference grammars (individual) + +| File | Description | +|------|-------------| +| `XQuery-31.ebnf` | Base XQuery 3.1 | +| `XQuery-40.ebnf` | Base XQuery 4.0 (from REx repo) | +| `XQuery-Update-30.ebnf` | W3C XQuery Update Facility 3.0 | +| `XQuery-FullText-10.ebnf` | XQuery Full Text 1.0 | +| `XQuery-Update-eXist-Legacy.ebnf` | XQUFEL (eXist-db proprietary update syntax) | + +### Combined grammars + +| File | Description | +|------|-------------| +| `XQuery-31-Family-XQUFEL.ebnf` | **Current parser source** — XQ 3.1 + Update 3.0 + Full Text 1.0 + XQUFEL | +| `XQuery-40-Family-XQUFEL.ebnf` | Future upgrade — XQ 4.0 + Update 3.0 + Full Text 1.0 + XQUFEL | + +The "Family" grammars are created by merging the individual grammars. See [Merging Extension Grammars](#merging-extension-grammars) below. + +## Generating the Parser + +Parser generation is automated via `tools/generate-parser.js`: + +```bash +npm run generate-parser +``` + +This runs REx with the correct options, applies post-generation patches (Nonterminal constructor fix for REx v6.1), and appends export boilerplate. The output is written to `src/parser/XQueryParser.js`. + +To generate from a different grammar: + +```bash +node tools/generate-parser.js --grammar grammars/XQuery-40-Family-XQUFEL.ebnf +``` + +### REx Options + +eXide uses REx in **LL(3) mode** with these flags: + +``` +-ll 3 -backtrack -tree -javascript -name XQueryParser +``` + +| Flag | Purpose | +|------|---------| +| `-ll 3` | LL parser with lookahead depth 3 | +| `-backtrack` | Enable backtracking for ambiguous constructs in the XQuery grammar | +| `-tree` | Generate `TopDownTreeBuilder` for AST access | +| `-javascript` | JavaScript output | +| `-name XQueryParser` | Constructor/class name | + +**Why LL mode?** The `-tree` flag (TopDownTreeBuilder) requires LL mode, and LL parsers produce top-down parse trees that map directly to eXide's expected AST node shape. + +### Testing a Grammar + +To verify a grammar generates without errors before running the full pipeline: + +```bash +java -cp tools REx grammars/.ebnf -ll 3 -backtrack -tree -javascript -name XQueryParser +``` + +Exit code 0 with no output means success. REx prints errors to stderr if the grammar has issues. + +## Preparing New Grammars + +The base grammars from the [REx repository](https://github.com/GuntherRademworker/rex-parser-generator) are **reference grammars** written in standard W3C EBNF notation. They may use left-recursion, which is valid EBNF but **incompatible with LL parsing**. Before a new grammar can be used with eXide, two steps are typically needed: + +### 1. Eliminate Left-Recursion + +LL parsers cannot handle left-recursive productions. The pattern to fix is: + +``` +(* Left-recursive — REx will reject this in LL mode *) +A ::= B + | A Suffix + +(* Iterative equivalent — LL-compatible *) +A ::= B Suffix* +``` + +**Example: XQuery 4.0 PostfixExpr** + +The XQ 4.0 grammar defines `PostfixExpr` via five separate left-recursive productions: + +``` +PostfixExpr ::= PrimaryExpr | FilterExpr | DynamicFunctionCall + | LookupExpr | MethodCall | FilterExprAM +FilterExpr ::= PostfixExpr Predicate +DynamicFunctionCall ::= PostfixExpr PositionalArgumentList +LookupExpr ::= PostfixExpr Lookup +MethodCall ::= PostfixExpr '=?>' NCName PositionalArgumentList +FilterExprAM ::= PostfixExpr '?[' Expr ']' +``` + +The fix folds all suffixes into an iterative loop on `PostfixExpr` and replaces the left-recursive productions with suffix-only variants: + +``` +PostfixExpr ::= PrimaryExpr ( Predicate | PositionalArgumentList + | Lookup | MethodCallSuffix | FilterExprAMSuffix )* +MethodCallSuffix ::= '=?>' NCName PositionalArgumentList +FilterExprAMSuffix ::= '?[' Expr ']' +``` + +`FilterExpr`, `DynamicFunctionCall`, and `LookupExpr` are deleted entirely (their suffixes — `Predicate`, `PositionalArgumentList`, `Lookup` — are already defined elsewhere). + +Similarly, `PositionalArguments` needed the same treatment: + +``` +(* Left-recursive *) +PositionalArguments ::= Argument | PositionalArguments ',' Argument + +(* Iterative *) +PositionalArguments ::= Argument ( ',' Argument )* +``` + +### 2. Merge Extension Grammars + +To build a combined grammar (e.g., XQuery + Update + Full Text + XQUFEL): + +1. Start with the base grammar (e.g., `XQuery-40.ebnf`) +2. Add productions from each extension grammar, inserting new alternatives into existing productions where the extension spec indicates +3. Resolve any naming conflicts between extensions +4. Test the combined grammar with REx + +### Post-Generation Patches + +REx v6.1 generates a `Nonterminal` constructor that doesn't expose `name` and `children` as object properties (they're only closure variables). `tools/generate-parser.js` patches this automatically — see the `patches` array in its CONFIG object. diff --git a/grammars/XQuery-31-Family-XQUFEL.ebnf b/grammars/XQuery-31-Family-XQUFEL.ebnf new file mode 100644 index 00000000..1adfb066 --- /dev/null +++ b/grammars/XQuery-31-Family-XQUFEL.ebnf @@ -0,0 +1,1045 @@ +/* Combined grammar: XQuery 3.1 + XQUF 3.0 + XQFT 1.0 + XQUFEL (XQuery Update Facility eXist Legacy) */ + +XQuery ::= Module EOF +Module ::= VersionDecl? ( LibraryModule | MainModule ) +VersionDecl + ::= 'xquery' ( 'encoding' StringLiteral | 'version' StringLiteral ( 'encoding' StringLiteral )? ) Separator +MainModule + ::= Prolog QueryBody +LibraryModule + ::= ModuleDecl Prolog +ModuleDecl + ::= 'module' 'namespace' NCName '=' URILiteral Separator +Prolog ::= ( ( DefaultNamespaceDecl | Setter | NamespaceDecl | Import | FTOptionDecl ) Separator )* ( ( ContextItemDecl | AnnotatedDecl | OptionDecl ) Separator )* +Separator + ::= ';' +Setter ::= BoundarySpaceDecl + | DefaultCollationDecl + | BaseURIDecl + | ConstructionDecl + | OrderingModeDecl + | EmptyOrderDecl + | CopyNamespacesDecl + | DecimalFormatDecl + | RevalidationDecl +RevalidationDecl + ::= 'declare' 'revalidation' ( 'strict' | 'lax' | 'skip' ) +BoundarySpaceDecl + ::= 'declare' 'boundary-space' ( 'preserve' | 'strip' ) +DefaultCollationDecl + ::= 'declare' 'default' 'collation' URILiteral +BaseURIDecl + ::= 'declare' 'base-uri' URILiteral +ConstructionDecl + ::= 'declare' 'construction' ( 'strip' | 'preserve' ) +OrderingModeDecl + ::= 'declare' 'ordering' ( 'ordered' | 'unordered' ) +EmptyOrderDecl + ::= 'declare' 'default' 'order' 'empty' ( 'greatest' | 'least' ) +CopyNamespacesDecl + ::= 'declare' 'copy-namespaces' PreserveMode ',' InheritMode +PreserveMode + ::= 'preserve' + | 'no-preserve' +InheritMode + ::= 'inherit' + | 'no-inherit' +DecimalFormatDecl + ::= 'declare' ( 'decimal-format' EQName | 'default' 'decimal-format' ) ( DFPropertyName '=' StringLiteral )* +DFPropertyName + ::= 'decimal-separator' + | 'grouping-separator' + | 'infinity' + | 'minus-sign' + | 'NaN' + | 'percent' + | 'per-mille' + | 'zero-digit' + | 'digit' + | 'pattern-separator' + | 'exponent-separator' +Import ::= SchemaImport + | ModuleImport +SchemaImport + ::= 'import' 'schema' SchemaPrefix? URILiteral ( 'at' URILiteral ( ',' URILiteral )* )? +SchemaPrefix + ::= 'namespace' NCName '=' + | 'default' 'element' 'namespace' +ModuleImport + ::= 'import' 'module' ( 'namespace' NCName '=' )? URILiteral ( 'at' URILiteral ( ',' URILiteral )* )? +NamespaceDecl + ::= 'declare' 'namespace' NCName '=' URILiteral +DefaultNamespaceDecl + ::= 'declare' 'default' ( 'element' | 'function' ) 'namespace' URILiteral +AnnotatedDecl + ::= 'declare' Annotation* ( VarDecl | FunctionDecl ) +Annotation + ::= '%' EQName ( '(' Literal ( ',' Literal )* ')' )? +VarDecl ::= 'variable' '$' VarName TypeDeclaration? ( ':=' VarValue | 'external' ( ':=' VarDefaultValue )? ) +VarValue ::= ExprSingle +VarDefaultValue + ::= ExprSingle +ContextItemDecl + ::= 'declare' 'context' 'item' ( 'as' ItemType )? ( ':=' VarValue | 'external' ( ':=' VarDefaultValue )? ) +FunctionDecl + ::= 'function' EQName '(' ParamList? ')' ( 'as' SequenceType )? ( FunctionBody | 'external' ) +ParamList + ::= Param ( ',' Param )* +Param ::= '$' EQName TypeDeclaration? +FunctionBody + ::= EnclosedExpr +EnclosedExpr + ::= '{' Expr? '}' +OptionDecl + ::= 'declare' 'option' EQName StringLiteral +QueryBody + ::= Expr +Expr ::= ExprSingle ( ',' ExprSingle )* +ExprSingle + ::= FLWORExpr + | QuantifiedExpr + | SwitchExpr + | TypeswitchExpr + | IfExpr + | TryCatchExpr + | InsertExpr + | DeleteExpr + | RenameExpr + | ReplaceExpr + | TransformExpr + | ExistUpdateExpr + | OrExpr +FLWORExpr + ::= InitialClause IntermediateClause* ReturnClause +InitialClause + ::= ForClause + | LetClause + | WindowClause +IntermediateClause + ::= InitialClause + | WhereClause + | GroupByClause + | OrderByClause + | CountClause +ForClause + ::= 'for' ForBinding ( ',' ForBinding )* +ForBinding + ::= '$' VarName TypeDeclaration? AllowingEmpty? PositionalVar? FTScoreVar? 'in' ExprSingle +AllowingEmpty + ::= 'allowing' 'empty' +PositionalVar + ::= 'at' '$' VarName +LetClause + ::= 'let' LetBinding ( ',' LetBinding )* +LetBinding + ::= ( '$' VarName TypeDeclaration? | FTScoreVar ) ':=' ExprSingle +WindowClause + ::= 'for' ( TumblingWindowClause | SlidingWindowClause ) +TumblingWindowClause + ::= 'tumbling' 'window' '$' VarName TypeDeclaration? 'in' ExprSingle WindowStartCondition WindowEndCondition? +SlidingWindowClause + ::= 'sliding' 'window' '$' VarName TypeDeclaration? 'in' ExprSingle WindowStartCondition WindowEndCondition +WindowStartCondition + ::= 'start' WindowVars 'when' ExprSingle +WindowEndCondition + ::= 'only'? 'end' WindowVars 'when' ExprSingle +WindowVars + ::= ( '$' CurrentItem )? PositionalVar? ( 'previous' '$' PreviousItem )? ( 'next' '$' NextItem )? +CurrentItem + ::= EQName +PreviousItem + ::= EQName +NextItem ::= EQName +CountClause + ::= 'count' '$' VarName +WhereClause + ::= 'where' ExprSingle +GroupByClause + ::= 'group' 'by' GroupingSpecList +GroupingSpecList + ::= GroupingSpec ( ',' GroupingSpec )* +GroupingSpec + ::= GroupingVariable ( TypeDeclaration? ':=' ExprSingle )? ( 'collation' URILiteral )? +GroupingVariable + ::= '$' VarName +OrderByClause + ::= ( 'order' 'by' | 'stable' 'order' 'by' ) OrderSpecList +OrderSpecList + ::= OrderSpec ( ',' OrderSpec )* +OrderSpec + ::= ExprSingle OrderModifier +OrderModifier + ::= ( 'ascending' | 'descending' )? ( 'empty' ( 'greatest' | 'least' ) )? ( 'collation' URILiteral )? +ReturnClause + ::= 'return' ExprSingle +QuantifiedExpr + ::= ( 'some' | 'every' ) '$' VarName TypeDeclaration? 'in' ExprSingle ( ',' '$' VarName TypeDeclaration? 'in' ExprSingle )* 'satisfies' ExprSingle +SwitchExpr + ::= 'switch' '(' Expr ')' SwitchCaseClause+ 'default' 'return' ExprSingle +SwitchCaseClause + ::= ( 'case' SwitchCaseOperand )+ 'return' ExprSingle +SwitchCaseOperand + ::= ExprSingle +TypeswitchExpr + ::= 'typeswitch' '(' Expr ')' CaseClause+ 'default' ( '$' VarName )? 'return' ExprSingle +CaseClause + ::= 'case' ( '$' VarName 'as' )? SequenceTypeUnion 'return' ExprSingle +SequenceTypeUnion + ::= SequenceType ( '|' SequenceType )* +IfExpr ::= 'if' '(' Expr ')' 'then' ExprSingle 'else' ExprSingle +TryCatchExpr + ::= TryClause CatchClause+ +TryClause + ::= 'try' EnclosedTryTargetExpr +EnclosedTryTargetExpr + ::= EnclosedExpr +CatchClause + ::= 'catch' CatchErrorList EnclosedExpr +CatchErrorList + ::= NameTest ( '|' NameTest )* +InsertExpr + ::= 'insert' ( 'node' | 'nodes' ) SourceExpr InsertExprTargetChoice TargetExpr +InsertExprTargetChoice + ::= ( 'as' ( 'first' | 'last' ) )? 'into' + | 'after' + | 'before' +SourceExpr + ::= ExprSingle +TargetExpr + ::= ExprSingle +DeleteExpr + ::= 'delete' ( 'node' | 'nodes' ) TargetExpr +ReplaceExpr + ::= 'replace' ( 'value' 'of' )? 'node' TargetExpr 'with' ExprSingle +RenameExpr + ::= 'rename' 'node' TargetExpr 'as' NewNameExpr +NewNameExpr + ::= ExprSingle +TransformExpr + ::= 'copy' '$' VarName ':=' ExprSingle ( ',' '$' VarName ':=' ExprSingle )* 'modify' ExprSingle 'return' ExprSingle +ExistUpdateExpr + ::= 'update' ( ExistInsertExpr | ExistReplaceExpr | ExistValueExpr | ExistDeleteExpr | ExistRenameExpr ) +ExistInsertExpr + ::= 'insert' ExprSingle ( 'into' | 'following' | 'preceding' ) ExprSingle +ExistReplaceExpr + ::= 'replace' ExprSingle 'with' ExprSingle +ExistValueExpr + ::= 'value' ExprSingle 'with' ExprSingle +ExistDeleteExpr + ::= 'delete' ExprSingle +ExistRenameExpr + ::= 'rename' ExprSingle 'as' ExprSingle +OrExpr ::= AndExpr ( 'or' AndExpr )* +AndExpr ::= ComparisonExpr ( 'and' ComparisonExpr )* +ComparisonExpr + ::= FTContainsExpr ( ( ValueComp | GeneralComp | NodeComp ) FTContainsExpr )? +FTContainsExpr + ::= StringConcatExpr ( 'contains' 'text' FTSelection FTIgnoreOption? )? +StringConcatExpr + ::= RangeExpr ( '||' RangeExpr )* +RangeExpr + ::= AdditiveExpr ( 'to' AdditiveExpr )? +AdditiveExpr + ::= MultiplicativeExpr ( ( '+' | '-' ) MultiplicativeExpr )* +MultiplicativeExpr + ::= UnionExpr ( ( '*' | 'div' | 'idiv' | 'mod' ) UnionExpr )* +UnionExpr + ::= IntersectExceptExpr ( ( 'union' | '|' ) IntersectExceptExpr )* +IntersectExceptExpr + ::= InstanceofExpr ( ( 'intersect' | 'except' ) InstanceofExpr )* +InstanceofExpr + ::= TreatExpr ( 'instance' 'of' SequenceType )? +TreatExpr + ::= CastableExpr ( 'treat' 'as' SequenceType )? +CastableExpr + ::= CastExpr ( 'castable' 'as' SingleType )? +CastExpr ::= ArrowExpr ( 'cast' 'as' SingleType )? +ArrowExpr + ::= UnaryExpr ( '=>' ArrowFunctionSpecifier ArgumentList )* +UnaryExpr + ::= ( '-' | '+' )* ValueExpr +ValueExpr + ::= ValidateExpr + | ExtensionExpr + | SimpleMapExpr +GeneralComp + ::= '=' + | '!=' + | '<' + | '<=' + | '>' + | '>=' +ValueComp + ::= 'eq' + | 'ne' + | 'lt' + | 'le' + | 'gt' + | 'ge' +NodeComp ::= 'is' + | '<<' + | '>>' +ValidateExpr + ::= 'validate' ( ValidationMode | 'type' TypeName )? '{' Expr '}' +ValidationMode + ::= 'lax' + | 'strict' +ExtensionExpr + ::= Pragma+ '{' Expr? '}' +Pragma ::= '(#' S? EQName ( S PragmaContents )? '#)' + /* ws: explicit */ +SimpleMapExpr + ::= PathExpr ( '!' PathExpr )* +PathExpr ::= '/' ( RelativePathExpr / ) + | '//' RelativePathExpr + | RelativePathExpr +RelativePathExpr + ::= StepExpr ( ( '/' | '//' ) StepExpr )* +StepExpr ::= PostfixExpr + | AxisStep +AxisStep ::= ( ReverseStep | ForwardStep ) PredicateList +ForwardStep + ::= ForwardAxis NodeTest + | AbbrevForwardStep +ForwardAxis + ::= 'child' '::' + | 'descendant' '::' + | 'attribute' '::' + | 'self' '::' + | 'descendant-or-self' '::' + | 'following-sibling' '::' + | 'following' '::' +AbbrevForwardStep + ::= '@'? NodeTest +ReverseStep + ::= ReverseAxis NodeTest + | AbbrevReverseStep +ReverseAxis + ::= 'parent' '::' + | 'ancestor' '::' + | 'preceding-sibling' '::' + | 'preceding' '::' + | 'ancestor-or-self' '::' +AbbrevReverseStep + ::= '..' +NodeTest ::= KindTest + | NameTest +NameTest ::= EQName + | Wildcard +PostfixExpr + ::= PrimaryExpr ( Predicate | ArgumentList | Lookup )* +ArgumentList + ::= '(' ( Argument ( ',' Argument )* )? ')' +PredicateList + ::= Predicate* +Predicate + ::= '[' Expr ']' +Lookup ::= '?' KeySpecifier +KeySpecifier + ::= NCName + | IntegerLiteral + | ParenthesizedExpr + | '*' +ArrowFunctionSpecifier + ::= EQName + | VarRef + | ParenthesizedExpr +PrimaryExpr + ::= Literal + | VarRef + | ParenthesizedExpr + | ContextItemExpr + | FunctionCall + | OrderedExpr + | UnorderedExpr + | NodeConstructor + | FunctionItemExpr + | MapConstructor + | ArrayConstructor + | StringConstructor + | UnaryLookup +Literal ::= NumericLiteral + | StringLiteral +NumericLiteral + ::= IntegerLiteral + | DecimalLiteral + | DoubleLiteral +VarRef ::= '$' VarName +VarName ::= EQName +ParenthesizedExpr + ::= '(' Expr? ')' +ContextItemExpr + ::= '.' +OrderedExpr + ::= 'ordered' EnclosedExpr +UnorderedExpr + ::= 'unordered' EnclosedExpr +FunctionCall + ::= FunctionEQName ArgumentList +Argument ::= ExprSingle + | ArgumentPlaceholder +ArgumentPlaceholder + ::= '?' +NodeConstructor + ::= DirectConstructor + | ComputedConstructor +DirectConstructor + ::= DirElemConstructor + | DirCommentConstructor + | DirPIConstructor +DirElemConstructor + ::= '<' QName DirAttributeList ( '/>' | '>' DirElemContent* '' ) + /* ws: explicit */ +DirAttributeList + ::= ( S ( QName S? '=' S? DirAttributeValue )? )* + /* ws: explicit */ +DirAttributeValue + ::= '"' ( EscapeQuot | QuotAttrValueContent )* '"' + | "'" ( EscapeApos | AposAttrValueContent )* "'" + /* ws: explicit */ +QuotAttrValueContent + ::= QuotAttrContentChar + | CommonContent +AposAttrValueContent + ::= AposAttrContentChar + | CommonContent +DirElemContent + ::= DirectConstructor + | CDataSection + | CommonContent + | ElementContentChar +CommonContent + ::= PredefinedEntityRef + | CharRef + | '{{' + | '}}' + | EnclosedExpr +DirCommentConstructor + ::= '' + /* ws: explicit */ +DirPIConstructor + ::= '' + /* ws: explicit */ +CDataSection + ::= '' + /* ws: explicit */ +ComputedConstructor + ::= CompDocConstructor + | CompElemConstructor + | CompAttrConstructor + | CompNamespaceConstructor + | CompTextConstructor + | CompCommentConstructor + | CompPIConstructor +CompDocConstructor + ::= 'document' EnclosedExpr +CompElemConstructor + ::= 'element' ( EQName | '{' Expr '}' ) EnclosedContentExpr +EnclosedContentExpr + ::= EnclosedExpr +CompAttrConstructor + ::= 'attribute' ( EQName | '{' Expr '}' ) EnclosedExpr +CompNamespaceConstructor + ::= 'namespace' ( Prefix | EnclosedPrefixExpr ) EnclosedURIExpr +Prefix ::= NCName +EnclosedPrefixExpr + ::= EnclosedExpr +EnclosedURIExpr + ::= EnclosedExpr +CompTextConstructor + ::= 'text' EnclosedExpr +CompCommentConstructor + ::= 'comment' EnclosedExpr +CompPIConstructor + ::= 'processing-instruction' ( NCName | '{' Expr '}' ) EnclosedExpr +FunctionItemExpr + ::= NamedFunctionRef + | InlineFunctionExpr +NamedFunctionRef + ::= EQName '#' IntegerLiteral +InlineFunctionExpr + ::= Annotation* 'function' '(' ParamList? ')' ( 'as' SequenceType )? FunctionBody +MapConstructor + ::= 'map' '{' ( MapConstructorEntry ( ',' MapConstructorEntry )* )? '}' +MapConstructorEntry + ::= MapKeyExpr ':' MapValueExpr +MapKeyExpr + ::= ExprSingle +MapValueExpr + ::= ExprSingle +ArrayConstructor + ::= SquareArrayConstructor + | CurlyArrayConstructor +SquareArrayConstructor + ::= '[' ( ExprSingle ( ',' ExprSingle )* )? ']' +CurlyArrayConstructor + ::= 'array' EnclosedExpr +StringConstructor + ::= '``[' StringConstructorContent ']``' + /* ws: explicit */ +StringConstructorContent + ::= StringConstructorChars ( StringConstructorInterpolation StringConstructorChars )* + /* ws: explicit */ +StringConstructorInterpolation + ::= '`{' Expr? '}`' +UnaryLookup + ::= '?' KeySpecifier +SingleType + ::= SimpleTypeName '?'? +TypeDeclaration + ::= 'as' SequenceType +SequenceType + ::= 'empty-sequence' '(' ')' + | ItemType ( OccurrenceIndicator / ) +OccurrenceIndicator + ::= '?' + | '*' + | '+' +ItemType ::= KindTest + | 'item' '(' ')' + | FunctionTest + | MapTest + | ArrayTest + | AtomicOrUnionType + | ParenthesizedItemType +AtomicOrUnionType + ::= EQName +KindTest ::= DocumentTest + | ElementTest + | AttributeTest + | SchemaElementTest + | SchemaAttributeTest + | PITest + | CommentTest + | TextTest + | NamespaceNodeTest + | AnyKindTest +AnyKindTest + ::= 'node' '(' ')' +DocumentTest + ::= 'document-node' '(' ( ElementTest | SchemaElementTest )? ')' +TextTest ::= 'text' '(' ')' +CommentTest + ::= 'comment' '(' ')' +NamespaceNodeTest + ::= 'namespace-node' '(' ')' +PITest ::= 'processing-instruction' '(' ( NCName | StringLiteral )? ')' +AttributeTest + ::= 'attribute' '(' ( AttribNameOrWildcard ( ',' TypeName )? )? ')' +AttribNameOrWildcard + ::= AttributeName + | '*' +SchemaAttributeTest + ::= 'schema-attribute' '(' AttributeDeclaration ')' +AttributeDeclaration + ::= AttributeName +ElementTest + ::= 'element' '(' ( ElementNameOrWildcard ( ',' TypeName '?'? )? )? ')' +ElementNameOrWildcard + ::= ElementName + | '*' +SchemaElementTest + ::= 'schema-element' '(' ElementDeclaration ')' +ElementDeclaration + ::= ElementName +AttributeName + ::= EQName +ElementName + ::= EQName +SimpleTypeName + ::= TypeName +TypeName ::= EQName +FunctionTest + ::= Annotation* ( AnyFunctionTest | TypedFunctionTest ) +AnyFunctionTest + ::= 'function' '(' '*' ')' +TypedFunctionTest + ::= 'function' '(' ( SequenceType ( ',' SequenceType )* )? ')' 'as' SequenceType +MapTest ::= AnyMapTest + | TypedMapTest +AnyMapTest + ::= 'map' '(' '*' ')' +TypedMapTest + ::= 'map' '(' AtomicOrUnionType ',' SequenceType ')' +ArrayTest + ::= AnyArrayTest + | TypedArrayTest +AnyArrayTest + ::= 'array' '(' '*' ')' +TypedArrayTest + ::= 'array' '(' SequenceType ')' +ParenthesizedItemType + ::= '(' ItemType ')' +FTOptionDecl + ::= 'declare' 'ft-option' FTMatchOptions +FTScoreVar + ::= 'score' '$' VarName +FTSelection + ::= FTOr FTPosFilter* +FTWeight ::= 'weight' '{' Expr '}' +FTOr ::= FTAnd ( 'ftor' FTAnd )* +FTAnd ::= FTMildNot ( 'ftand' FTMildNot )* +FTMildNot + ::= FTUnaryNot ( 'not' 'in' FTUnaryNot )* +FTUnaryNot + ::= 'ftnot'? FTPrimaryWithOptions +FTPrimaryWithOptions + ::= FTPrimary FTMatchOptions? FTWeight? +FTPrimary + ::= FTWords FTTimes? + | '(' FTSelection ')' + | FTExtensionSelection +FTWords ::= FTWordsValue FTAnyallOption? +FTWordsValue + ::= StringLiteral + | '{' Expr '}' +FTExtensionSelection + ::= Pragma+ '{' FTSelection? '}' +FTAnyallOption + ::= 'any' 'word'? + | 'all' 'words'? + | 'phrase' +FTTimes ::= 'occurs' FTRange 'times' +FTRange ::= 'exactly' AdditiveExpr + | 'at' 'least' AdditiveExpr + | 'at' 'most' AdditiveExpr + | 'from' AdditiveExpr 'to' AdditiveExpr +FTPosFilter + ::= FTOrder + | FTWindow + | FTDistance + | FTScope + | FTContent +FTOrder ::= 'ordered' +FTWindow ::= 'window' AdditiveExpr FTUnit +FTDistance + ::= 'distance' FTRange FTUnit +FTUnit ::= 'words' + | 'sentences' + | 'paragraphs' +FTScope ::= ( 'same' | 'different' ) FTBigUnit +FTBigUnit + ::= 'sentence' + | 'paragraph' +FTContent + ::= 'at' 'start' + | 'at' 'end' + | 'entire' 'content' +FTMatchOptions + ::= ( 'using' FTMatchOption )+ +FTMatchOption + ::= FTLanguageOption + | FTWildCardOption + | FTThesaurusOption + | FTStemOption + | FTCaseOption + | FTDiacriticsOption + | FTStopWordOption + | FTExtensionOption +FTCaseOption + ::= 'case' 'insensitive' + | 'case' 'sensitive' + | 'lowercase' + | 'uppercase' +FTDiacriticsOption + ::= 'diacritics' 'insensitive' + | 'diacritics' 'sensitive' +FTStemOption + ::= 'stemming' + | 'no' 'stemming' +FTThesaurusOption + ::= 'thesaurus' ( FTThesaurusID | 'default' ) + | 'thesaurus' '(' ( FTThesaurusID | 'default' ) ( ',' FTThesaurusID )* ')' + | 'no' 'thesaurus' +FTThesaurusID + ::= 'at' URILiteral ( 'relationship' StringLiteral )? ( FTLiteralRange 'levels' )? +FTLiteralRange + ::= 'exactly' IntegerLiteral + | 'at' 'least' IntegerLiteral + | 'at' 'most' IntegerLiteral + | 'from' IntegerLiteral 'to' IntegerLiteral +FTStopWordOption + ::= 'stop' 'words' FTStopWords FTStopWordsInclExcl* + | 'stop' 'words' 'default' FTStopWordsInclExcl* + | 'no' 'stop' 'words' +FTStopWords + ::= 'at' URILiteral + | '(' StringLiteral ( ',' StringLiteral )* ')' +FTStopWordsInclExcl + ::= ( 'union' | 'except' ) FTStopWords +FTLanguageOption + ::= 'language' StringLiteral +FTWildCardOption + ::= 'wildcards' + | 'no' 'wildcards' +FTExtensionOption + ::= 'option' EQName StringLiteral +FTIgnoreOption + ::= 'without' 'content' UnionExpr +URILiteral + ::= StringLiteral +EQName ::= QName + | URIQualifiedName +FunctionEQName + ::= FunctionName + | URIQualifiedName +QName ::= FunctionName + | 'array' + | 'attribute' + | 'comment' + | 'document-node' + | 'element' + | 'empty-sequence' + | 'function' + | 'if' + | 'item' + | 'map' + | 'namespace-node' + | 'node' + | 'processing-instruction' + | 'schema-attribute' + | 'schema-element' + | 'switch' + | 'text' + | 'typeswitch' +FunctionName + ::= QName^Token + | 'after' + | 'ancestor' + | 'ancestor-or-self' + | 'and' + | 'ascending' + | 'before' + | 'case' + | 'cast' + | 'castable' + | 'child' + | 'collation' + | 'contains' + | 'content' + | 'copy' + | 'count' + | 'declare' + | 'default' + | 'delete' + | 'descendant' + | 'descendant-or-self' + | 'descending' + | 'diacritics' + | 'different' + | 'distance' + | 'div' + | 'document' + | 'else' + | 'empty' + | 'end' + | 'entire' + | 'eq' + | 'every' + | 'exactly' + | 'except' + | 'first' + | 'following' + | 'following-sibling' + | 'for' + | 'ft-option' + | 'ftand' + | 'ftnot' + | 'ftor' + | 'ge' + | 'group' + | 'gt' + | 'idiv' + | 'import' + | 'insensitive' + | 'insert' + | 'instance' + | 'intersect' + | 'into' + | 'is' + | 'language' + | 'last' + | 'le' + | 'least' + | 'let' + | 'levels' + | 'lowercase' + | 'lt' + | 'mod' + | 'modify' + | 'module' + | 'most' + | 'namespace' + | 'ne' + | 'no' + | 'nodes' + | 'not' + | 'occurs' + | 'only' + | 'or' + | 'order' + | 'ordered' + | 'paragraph' + | 'paragraphs' + | 'parent' + | 'phrase' + | 'preceding' + | 'preceding-sibling' + | 'relationship' + | 'rename' + | 'replace' + | 'return' + | 'revalidation' + | 'same' + | 'satisfies' + | 'score' + | 'self' + | 'sensitive' + | 'sentence' + | 'sentences' + | 'skip' + | 'some' + | 'stable' + | 'start' + | 'stemming' + | 'stop' + | 'thesaurus' + | 'times' + | 'to' + | 'treat' + | 'try' + | 'union' + | 'unordered' + | 'update' + | 'uppercase' + | 'using' + | 'validate' + | 'value' + | 'weight' + | 'where' + | 'wildcards' + | 'window' + | 'with' + | 'without' + | 'word' + | 'words' + | 'xquery' +NCName ::= NCName^Token + | 'after' + | 'and' + | 'ascending' + | 'before' + | 'case' + | 'cast' + | 'castable' + | 'collation' + | 'contains' + | 'content' + | 'copy' + | 'count' + | 'default' + | 'delete' + | 'descending' + | 'diacritics' + | 'different' + | 'distance' + | 'div' + | 'else' + | 'empty' + | 'end' + | 'entire' + | 'eq' + | 'exactly' + | 'except' + | 'first' + | 'for' + | 'ftand' + | 'ftnot' + | 'ftor' + | 'ge' + | 'group' + | 'gt' + | 'idiv' + | 'insensitive' + | 'insert' + | 'instance' + | 'intersect' + | 'into' + | 'is' + | 'language' + | 'last' + | 'le' + | 'least' + | 'let' + | 'levels' + | 'lowercase' + | 'lt' + | 'mod' + | 'modify' + | 'most' + | 'ne' + | 'no' + | 'nodes' + | 'not' + | 'occurs' + | 'only' + | 'or' + | 'order' + | 'paragraph' + | 'paragraphs' + | 'phrase' + | 'relationship' + | 'rename' + | 'replace' + | 'return' + | 'revalidation' + | 'same' + | 'satisfies' + | 'score' + | 'sensitive' + | 'sentence' + | 'sentences' + | 'skip' + | 'stable' + | 'start' + | 'stemming' + | 'stop' + | 'thesaurus' + | 'times' + | 'to' + | 'treat' + | 'union' + | 'update' + | 'uppercase' + | 'using' + | 'value' + | 'weight' + | 'where' + | 'wildcards' + | 'with' + | 'without' + | 'word' + | 'words' +Whitespace + ::= S^WS + | Comment + /* ws: definition */ +Comment ::= '(:' ( CommentContents | Comment )* ':)' + /* ws: explicit */ + + + +IntegerLiteral + ::= Digits +DecimalLiteral + ::= '.' Digits + | Digits '.' [0-9]* + /* ws: explicit */ +DoubleLiteral + ::= ( '.' Digits | Digits ( '.' [0-9]* )? ) [eE] [+#x002D]? Digits + /* ws: explicit */ +StringLiteral + ::= '"' ( PredefinedEntityRef | CharRef | EscapeQuot | [^"&] )* '"' + | "'" ( PredefinedEntityRef | CharRef | EscapeApos | [^'&] )* "'" + /* ws: explicit */ +URIQualifiedName + ::= BracedURILiteral NCName + /* ws: explicit */ +BracedURILiteral + ::= 'Q' '{' ( PredefinedEntityRef | CharRef | [^&{}] )* '}' + /* ws: explicit */ +PredefinedEntityRef + ::= '&' ( 'lt' | 'gt' | 'amp' | 'quot' | 'apos' ) ';' + /* ws: explicit */ +EscapeQuot + ::= '""' +EscapeApos + ::= "''" +ElementContentChar + ::= Char - [{}<&] +QuotAttrContentChar + ::= Char - ["{}<&] +AposAttrContentChar + ::= Char - ['{}<&] +PITarget ::= NCName - ( ( 'X' | 'x' ) ( 'M' | 'm' ) ( 'L' | 'l' ) ) +NameStartChar + ::= ':' + | [A-Z] + | '_' + | [a-z] + | [#x00C0-#x00D6] + | [#x00D8-#x00F6] + | [#x00F8-#x02FF] + | [#x0370-#x037D] + | [#x037F-#x1FFF] + | [#x200C-#x200D] + | [#x2070-#x218F] + | [#x2C00-#x2FEF] + | [#x3001-#xD7FF] + | [#xF900-#xFDCF] + | [#xFDF0-#xFFFD] + | [#x10000-#xEFFFF] +NameChar ::= NameStartChar + | '-' + | '.' + | [0-9] + | #x00B7 + | [#x0300-#x036F] + | [#x203F-#x2040] +Name ::= NameStartChar NameChar* +CharRef ::= '&#' [0-9]+ ';' + | '&#x' [0-9a-fA-F]+ ';' +NCName ::= Name - ( Char* ':' Char* ) +QName ::= PrefixedName + | UnprefixedName +PrefixedName + ::= Prefix ':' LocalPart +UnprefixedName + ::= LocalPart +Prefix ::= NCName +LocalPart + ::= NCName +StringConstructorChars + ::= ( Char* - ( Char* ( '`{' | ']``' ) Char* ) ) &( '`{' | ']`' ) + /* ws: explicit */ +S ::= ( #x0020 | #x0009 | #x000D | #x000A )+ +Char ::= #x0009 + | #x000A + | #x000D + | [#x0020-#xD7FF] + | [#xE000-#xFFFD] + | [#x10000-#x10FFFF] +Digits ::= [0-9]+ +CommentContents + ::= ( ( Char+ - ( Char* ( '(:' | ':)' ) Char* ) ) - ( Char* '(' ) ) &':' + | ( Char+ - ( Char* ( '(:' | ':)' ) Char* ) ) &'(' +PragmaContents + ::= ( Char* - ( Char* '#)' Char* ) ) &'#' +Wildcard ::= '*' + | NCName ':*' + | '*:' NCName + | BracedURILiteral '*' +DirCommentContents + ::= ( Char - '-' | '-' ( Char - '-' ) )* +DirPIContents + ::= ( Char* - ( Char* '?>' Char* ) ) &'?' +CDataSectionContents + ::= ( Char* - ( Char* ']]>' Char* ) ) &']]' +EOF ::= $ +NonNCNameChar + ::= $ + | ':' + | Char - NameChar +DelimitingChar + ::= NonNCNameChar + | '-' + | '.' +DelimitingChar + \\ DecimalLiteral DoubleLiteral IntegerLiteral +NonNCNameChar + \\ URIQualifiedName NCName^Token QName^Token 'NaN' 'after' 'all' 'allowing' 'ancestor' 'ancestor-or-self' 'and' 'any' 'array' 'as' 'ascending' 'at' 'attribute' 'base-uri' 'before' 'boundary-space' 'by' 'case' 'cast' 'castable' 'catch' 'child' 'collation' 'comment' 'construction' 'contains' 'content' 'context' 'copy' 'copy-namespaces' 'count' 'decimal-format' 'decimal-separator' 'declare' 'default' 'delete' 'descendant' 'descendant-or-self' 'descending' 'diacritics' 'different' 'digit' 'distance' 'div' 'document' 'document-node' 'element' 'else' 'empty' 'empty-sequence' 'encoding' 'end' 'entire' 'eq' 'every' 'exactly' 'except' 'exponent-separator' 'external' 'first' 'following' 'following-sibling' 'for' 'from' 'ft-option' 'ftand' 'ftnot' 'ftor' 'function' 'ge' 'greatest' 'group' 'grouping-separator' 'gt' 'idiv' 'if' 'import' 'in' 'infinity' 'inherit' 'insensitive' 'insert' 'instance' 'intersect' 'into' 'is' 'item' 'language' 'last' 'lax' 'le' 'least' 'let' 'levels' 'lowercase' 'lt' 'map' 'minus-sign' 'mod' 'modify' 'module' 'most' 'namespace' 'namespace-node' 'ne' 'next' 'no' 'no-inherit' 'no-preserve' 'node' 'nodes' 'not' 'occurs' 'of' 'only' 'option' 'or' 'order' 'ordered' 'ordering' 'paragraph' 'paragraphs' 'parent' 'pattern-separator' 'per-mille' 'percent' 'phrase' 'preceding' 'preceding-sibling' 'preserve' 'previous' 'processing-instruction' 'relationship' 'rename' 'replace' 'return' 'revalidation' 'same' 'satisfies' 'schema' 'schema-attribute' 'schema-element' 'score' 'self' 'sensitive' 'sentence' 'sentences' 'skip' 'sliding' 'some' 'stable' 'start' 'stemming' 'stop' 'strict' 'strip' 'switch' 'text' 'then' 'thesaurus' 'times' 'to' 'treat' 'try' 'tumbling' 'type' 'typeswitch' 'union' 'unordered' 'update' 'uppercase' 'using' 'validate' 'value' 'variable' 'version' 'weight' 'when' 'where' 'wildcards' 'window' 'with' 'without' 'word' 'words' 'xquery' 'zero-digit' +'*' << Wildcard +NCName^Token + << 'after' 'and' 'as' 'ascending' 'before' 'case' 'cast' 'castable' 'collation' 'contains' 'content' 'copy' 'count' 'default' 'delete' 'descending' 'diacritics' 'different' 'distance' 'div' 'else' 'empty' 'end' 'entire' 'eq' 'exactly' 'except' 'first' 'following' 'for' 'ftand' 'ftnot' 'ftor' 'ge' 'group' 'gt' 'idiv' 'insensitive' 'insert' 'instance' 'intersect' 'into' 'is' 'language' 'last' 'le' 'least' 'let' 'levels' 'lowercase' 'lt' 'mod' 'modify' 'most' 'ne' 'no' 'nodes' 'not' 'occurs' 'only' 'or' 'order' 'paragraph' 'paragraphs' 'phrase' 'preceding' 'relationship' 'rename' 'replace' 'return' 'revalidation' 'same' 'satisfies' 'score' 'sensitive' 'sentence' 'sentences' 'skip' 'stable' 'start' 'stemming' 'stop' 'thesaurus' 'times' 'to' 'treat' 'union' 'update' 'uppercase' 'using' 'value' 'weight' 'where' 'wildcards' 'with' 'without' 'word' 'words' +QName^Token + << 'after' 'ancestor' 'ancestor-or-self' 'and' 'array' 'as' 'ascending' 'attribute' 'before' 'case' 'cast' 'castable' 'child' 'collation' 'comment' 'contains' 'content' 'copy' 'count' 'declare' 'default' 'delete' 'descendant' 'descendant-or-self' 'descending' 'diacritics' 'different' 'distance' 'div' 'document' 'document-node' 'element' 'else' 'empty' 'empty-sequence' 'end' 'entire' 'eq' 'every' 'exactly' 'except' 'first' 'following' 'following-sibling' 'for' 'ft-option' 'ftand' 'ftnot' 'ftor' 'function' 'ge' 'group' 'gt' 'idiv' 'if' 'import' 'insensitive' 'insert' 'instance' 'intersect' 'into' 'is' 'item' 'language' 'last' 'le' 'least' 'let' 'levels' 'lowercase' 'lt' 'map' 'mod' 'modify' 'module' 'most' 'namespace' 'namespace-node' 'ne' 'no' 'node' 'nodes' 'not' 'occurs' 'only' 'or' 'order' 'ordered' 'paragraph' 'paragraphs' 'parent' 'phrase' 'preceding' 'preceding-sibling' 'processing-instruction' 'relationship' 'rename' 'replace' 'return' 'revalidation' 'same' 'satisfies' 'schema-attribute' 'schema-element' 'score' 'self' 'sensitive' 'sentence' 'sentences' 'skip' 'some' 'stable' 'start' 'stemming' 'stop' 'switch' 'text' 'then' 'thesaurus' 'times' 'to' 'treat' 'try' 'typeswitch' 'union' 'unordered' 'update' 'uppercase' 'using' 'validate' 'value' 'weight' 'where' 'wildcards' 'window' 'with' 'without' 'word' 'words' 'xquery' diff --git a/grammars/XQuery-31-Update-FullText.ebnf b/grammars/XQuery-31-Update-FullText.ebnf new file mode 100644 index 00000000..d59fa517 --- /dev/null +++ b/grammars/XQuery-31-Update-FullText.ebnf @@ -0,0 +1,1028 @@ +XQuery ::= Module EOF +Module ::= VersionDecl? ( LibraryModule | MainModule ) +VersionDecl + ::= 'xquery' ( 'encoding' StringLiteral | 'version' StringLiteral ( 'encoding' StringLiteral )? ) Separator +MainModule + ::= Prolog QueryBody +LibraryModule + ::= ModuleDecl Prolog +ModuleDecl + ::= 'module' 'namespace' NCName '=' URILiteral Separator +Prolog ::= ( ( DefaultNamespaceDecl | Setter | NamespaceDecl | Import | FTOptionDecl ) Separator )* ( ( ContextItemDecl | AnnotatedDecl | OptionDecl ) Separator )* +Separator + ::= ';' +Setter ::= BoundarySpaceDecl + | DefaultCollationDecl + | BaseURIDecl + | ConstructionDecl + | OrderingModeDecl + | EmptyOrderDecl + | CopyNamespacesDecl + | DecimalFormatDecl + | RevalidationDecl +RevalidationDecl + ::= 'declare' 'revalidation' ( 'strict' | 'lax' | 'skip' ) +BoundarySpaceDecl + ::= 'declare' 'boundary-space' ( 'preserve' | 'strip' ) +DefaultCollationDecl + ::= 'declare' 'default' 'collation' URILiteral +BaseURIDecl + ::= 'declare' 'base-uri' URILiteral +ConstructionDecl + ::= 'declare' 'construction' ( 'strip' | 'preserve' ) +OrderingModeDecl + ::= 'declare' 'ordering' ( 'ordered' | 'unordered' ) +EmptyOrderDecl + ::= 'declare' 'default' 'order' 'empty' ( 'greatest' | 'least' ) +CopyNamespacesDecl + ::= 'declare' 'copy-namespaces' PreserveMode ',' InheritMode +PreserveMode + ::= 'preserve' + | 'no-preserve' +InheritMode + ::= 'inherit' + | 'no-inherit' +DecimalFormatDecl + ::= 'declare' ( 'decimal-format' EQName | 'default' 'decimal-format' ) ( DFPropertyName '=' StringLiteral )* +DFPropertyName + ::= 'decimal-separator' + | 'grouping-separator' + | 'infinity' + | 'minus-sign' + | 'NaN' + | 'percent' + | 'per-mille' + | 'zero-digit' + | 'digit' + | 'pattern-separator' + | 'exponent-separator' +Import ::= SchemaImport + | ModuleImport +SchemaImport + ::= 'import' 'schema' SchemaPrefix? URILiteral ( 'at' URILiteral ( ',' URILiteral )* )? +SchemaPrefix + ::= 'namespace' NCName '=' + | 'default' 'element' 'namespace' +ModuleImport + ::= 'import' 'module' ( 'namespace' NCName '=' )? URILiteral ( 'at' URILiteral ( ',' URILiteral )* )? +NamespaceDecl + ::= 'declare' 'namespace' NCName '=' URILiteral +DefaultNamespaceDecl + ::= 'declare' 'default' ( 'element' | 'function' ) 'namespace' URILiteral +AnnotatedDecl + ::= 'declare' Annotation* ( VarDecl | FunctionDecl ) +Annotation + ::= '%' EQName ( '(' Literal ( ',' Literal )* ')' )? +VarDecl ::= 'variable' '$' VarName TypeDeclaration? ( ':=' VarValue | 'external' ( ':=' VarDefaultValue )? ) +VarValue ::= ExprSingle +VarDefaultValue + ::= ExprSingle +ContextItemDecl + ::= 'declare' 'context' 'item' ( 'as' ItemType )? ( ':=' VarValue | 'external' ( ':=' VarDefaultValue )? ) +FunctionDecl + ::= 'function' EQName '(' ParamList? ')' ( 'as' SequenceType )? ( FunctionBody | 'external' ) +ParamList + ::= Param ( ',' Param )* +Param ::= '$' EQName TypeDeclaration? +FunctionBody + ::= EnclosedExpr +EnclosedExpr + ::= '{' Expr? '}' +OptionDecl + ::= 'declare' 'option' EQName StringLiteral +QueryBody + ::= Expr +Expr ::= ExprSingle ( ',' ExprSingle )* +ExprSingle + ::= FLWORExpr + | QuantifiedExpr + | SwitchExpr + | TypeswitchExpr + | IfExpr + | TryCatchExpr + | InsertExpr + | DeleteExpr + | RenameExpr + | ReplaceExpr + | TransformExpr + | OrExpr +FLWORExpr + ::= InitialClause IntermediateClause* ReturnClause +InitialClause + ::= ForClause + | LetClause + | WindowClause +IntermediateClause + ::= InitialClause + | WhereClause + | GroupByClause + | OrderByClause + | CountClause +ForClause + ::= 'for' ForBinding ( ',' ForBinding )* +ForBinding + ::= '$' VarName TypeDeclaration? AllowingEmpty? PositionalVar? FTScoreVar? 'in' ExprSingle +AllowingEmpty + ::= 'allowing' 'empty' +PositionalVar + ::= 'at' '$' VarName +LetClause + ::= 'let' LetBinding ( ',' LetBinding )* +LetBinding + ::= ( '$' VarName TypeDeclaration? | FTScoreVar ) ':=' ExprSingle +WindowClause + ::= 'for' ( TumblingWindowClause | SlidingWindowClause ) +TumblingWindowClause + ::= 'tumbling' 'window' '$' VarName TypeDeclaration? 'in' ExprSingle WindowStartCondition WindowEndCondition? +SlidingWindowClause + ::= 'sliding' 'window' '$' VarName TypeDeclaration? 'in' ExprSingle WindowStartCondition WindowEndCondition +WindowStartCondition + ::= 'start' WindowVars 'when' ExprSingle +WindowEndCondition + ::= 'only'? 'end' WindowVars 'when' ExprSingle +WindowVars + ::= ( '$' CurrentItem )? PositionalVar? ( 'previous' '$' PreviousItem )? ( 'next' '$' NextItem )? +CurrentItem + ::= EQName +PreviousItem + ::= EQName +NextItem ::= EQName +CountClause + ::= 'count' '$' VarName +WhereClause + ::= 'where' ExprSingle +GroupByClause + ::= 'group' 'by' GroupingSpecList +GroupingSpecList + ::= GroupingSpec ( ',' GroupingSpec )* +GroupingSpec + ::= GroupingVariable ( TypeDeclaration? ':=' ExprSingle )? ( 'collation' URILiteral )? +GroupingVariable + ::= '$' VarName +OrderByClause + ::= ( 'order' 'by' | 'stable' 'order' 'by' ) OrderSpecList +OrderSpecList + ::= OrderSpec ( ',' OrderSpec )* +OrderSpec + ::= ExprSingle OrderModifier +OrderModifier + ::= ( 'ascending' | 'descending' )? ( 'empty' ( 'greatest' | 'least' ) )? ( 'collation' URILiteral )? +ReturnClause + ::= 'return' ExprSingle +QuantifiedExpr + ::= ( 'some' | 'every' ) '$' VarName TypeDeclaration? 'in' ExprSingle ( ',' '$' VarName TypeDeclaration? 'in' ExprSingle )* 'satisfies' ExprSingle +SwitchExpr + ::= 'switch' '(' Expr ')' SwitchCaseClause+ 'default' 'return' ExprSingle +SwitchCaseClause + ::= ( 'case' SwitchCaseOperand )+ 'return' ExprSingle +SwitchCaseOperand + ::= ExprSingle +TypeswitchExpr + ::= 'typeswitch' '(' Expr ')' CaseClause+ 'default' ( '$' VarName )? 'return' ExprSingle +CaseClause + ::= 'case' ( '$' VarName 'as' )? SequenceTypeUnion 'return' ExprSingle +SequenceTypeUnion + ::= SequenceType ( '|' SequenceType )* +IfExpr ::= 'if' '(' Expr ')' 'then' ExprSingle 'else' ExprSingle +TryCatchExpr + ::= TryClause CatchClause+ +TryClause + ::= 'try' EnclosedTryTargetExpr +EnclosedTryTargetExpr + ::= EnclosedExpr +CatchClause + ::= 'catch' CatchErrorList EnclosedExpr +CatchErrorList + ::= NameTest ( '|' NameTest )* +InsertExpr + ::= 'insert' ( 'node' | 'nodes' ) SourceExpr InsertExprTargetChoice TargetExpr +InsertExprTargetChoice + ::= ( 'as' ( 'first' | 'last' ) )? 'into' + | 'after' + | 'before' +SourceExpr + ::= ExprSingle +TargetExpr + ::= ExprSingle +DeleteExpr + ::= 'delete' ( 'node' | 'nodes' ) TargetExpr +ReplaceExpr + ::= 'replace' ( 'value' 'of' )? 'node' TargetExpr 'with' ExprSingle +RenameExpr + ::= 'rename' 'node' TargetExpr 'as' NewNameExpr +NewNameExpr + ::= ExprSingle +TransformExpr + ::= 'copy' '$' VarName ':=' ExprSingle ( ',' '$' VarName ':=' ExprSingle )* 'modify' ExprSingle 'return' ExprSingle +OrExpr ::= AndExpr ( 'or' AndExpr )* +AndExpr ::= ComparisonExpr ( 'and' ComparisonExpr )* +ComparisonExpr + ::= FTContainsExpr ( ( ValueComp | GeneralComp | NodeComp ) FTContainsExpr )? +FTContainsExpr + ::= StringConcatExpr ( 'contains' 'text' FTSelection FTIgnoreOption? )? +StringConcatExpr + ::= RangeExpr ( '||' RangeExpr )* +RangeExpr + ::= AdditiveExpr ( 'to' AdditiveExpr )? +AdditiveExpr + ::= MultiplicativeExpr ( ( '+' | '-' ) MultiplicativeExpr )* +MultiplicativeExpr + ::= UnionExpr ( ( '*' | 'div' | 'idiv' | 'mod' ) UnionExpr )* +UnionExpr + ::= IntersectExceptExpr ( ( 'union' | '|' ) IntersectExceptExpr )* +IntersectExceptExpr + ::= InstanceofExpr ( ( 'intersect' | 'except' ) InstanceofExpr )* +InstanceofExpr + ::= TreatExpr ( 'instance' 'of' SequenceType )? +TreatExpr + ::= CastableExpr ( 'treat' 'as' SequenceType )? +CastableExpr + ::= CastExpr ( 'castable' 'as' SingleType )? +CastExpr ::= ArrowExpr ( 'cast' 'as' SingleType )? +ArrowExpr + ::= UnaryExpr ( '=>' ArrowFunctionSpecifier ArgumentList )* +UnaryExpr + ::= ( '-' | '+' )* ValueExpr +ValueExpr + ::= ValidateExpr + | ExtensionExpr + | SimpleMapExpr +GeneralComp + ::= '=' + | '!=' + | '<' + | '<=' + | '>' + | '>=' +ValueComp + ::= 'eq' + | 'ne' + | 'lt' + | 'le' + | 'gt' + | 'ge' +NodeComp ::= 'is' + | '<<' + | '>>' +ValidateExpr + ::= 'validate' ( ValidationMode | 'type' TypeName )? '{' Expr '}' +ValidationMode + ::= 'lax' + | 'strict' +ExtensionExpr + ::= Pragma+ '{' Expr? '}' +Pragma ::= '(#' S? EQName ( S PragmaContents )? '#)' + /* ws: explicit */ +SimpleMapExpr + ::= PathExpr ( '!' PathExpr )* +PathExpr ::= '/' ( RelativePathExpr / ) + | '//' RelativePathExpr + | RelativePathExpr +RelativePathExpr + ::= StepExpr ( ( '/' | '//' ) StepExpr )* +StepExpr ::= PostfixExpr + | AxisStep +AxisStep ::= ( ReverseStep | ForwardStep ) PredicateList +ForwardStep + ::= ForwardAxis NodeTest + | AbbrevForwardStep +ForwardAxis + ::= 'child' '::' + | 'descendant' '::' + | 'attribute' '::' + | 'self' '::' + | 'descendant-or-self' '::' + | 'following-sibling' '::' + | 'following' '::' +AbbrevForwardStep + ::= '@'? NodeTest +ReverseStep + ::= ReverseAxis NodeTest + | AbbrevReverseStep +ReverseAxis + ::= 'parent' '::' + | 'ancestor' '::' + | 'preceding-sibling' '::' + | 'preceding' '::' + | 'ancestor-or-self' '::' +AbbrevReverseStep + ::= '..' +NodeTest ::= KindTest + | NameTest +NameTest ::= EQName + | Wildcard +PostfixExpr + ::= PrimaryExpr ( Predicate | ArgumentList | Lookup )* +ArgumentList + ::= '(' ( Argument ( ',' Argument )* )? ')' +PredicateList + ::= Predicate* +Predicate + ::= '[' Expr ']' +Lookup ::= '?' KeySpecifier +KeySpecifier + ::= NCName + | IntegerLiteral + | ParenthesizedExpr + | '*' +ArrowFunctionSpecifier + ::= EQName + | VarRef + | ParenthesizedExpr +PrimaryExpr + ::= Literal + | VarRef + | ParenthesizedExpr + | ContextItemExpr + | FunctionCall + | OrderedExpr + | UnorderedExpr + | NodeConstructor + | FunctionItemExpr + | MapConstructor + | ArrayConstructor + | StringConstructor + | UnaryLookup +Literal ::= NumericLiteral + | StringLiteral +NumericLiteral + ::= IntegerLiteral + | DecimalLiteral + | DoubleLiteral +VarRef ::= '$' VarName +VarName ::= EQName +ParenthesizedExpr + ::= '(' Expr? ')' +ContextItemExpr + ::= '.' +OrderedExpr + ::= 'ordered' EnclosedExpr +UnorderedExpr + ::= 'unordered' EnclosedExpr +FunctionCall + ::= FunctionEQName ArgumentList +Argument ::= ExprSingle + | ArgumentPlaceholder +ArgumentPlaceholder + ::= '?' +NodeConstructor + ::= DirectConstructor + | ComputedConstructor +DirectConstructor + ::= DirElemConstructor + | DirCommentConstructor + | DirPIConstructor +DirElemConstructor + ::= '<' QName DirAttributeList ( '/>' | '>' DirElemContent* '' ) + /* ws: explicit */ +DirAttributeList + ::= ( S ( QName S? '=' S? DirAttributeValue )? )* + /* ws: explicit */ +DirAttributeValue + ::= '"' ( EscapeQuot | QuotAttrValueContent )* '"' + | "'" ( EscapeApos | AposAttrValueContent )* "'" + /* ws: explicit */ +QuotAttrValueContent + ::= QuotAttrContentChar + | CommonContent +AposAttrValueContent + ::= AposAttrContentChar + | CommonContent +DirElemContent + ::= DirectConstructor + | CDataSection + | CommonContent + | ElementContentChar +CommonContent + ::= PredefinedEntityRef + | CharRef + | '{{' + | '}}' + | EnclosedExpr +DirCommentConstructor + ::= '' + /* ws: explicit */ +DirPIConstructor + ::= '' + /* ws: explicit */ +CDataSection + ::= '' + /* ws: explicit */ +ComputedConstructor + ::= CompDocConstructor + | CompElemConstructor + | CompAttrConstructor + | CompNamespaceConstructor + | CompTextConstructor + | CompCommentConstructor + | CompPIConstructor +CompDocConstructor + ::= 'document' EnclosedExpr +CompElemConstructor + ::= 'element' ( EQName | '{' Expr '}' ) EnclosedContentExpr +EnclosedContentExpr + ::= EnclosedExpr +CompAttrConstructor + ::= 'attribute' ( EQName | '{' Expr '}' ) EnclosedExpr +CompNamespaceConstructor + ::= 'namespace' ( Prefix | EnclosedPrefixExpr ) EnclosedURIExpr +Prefix ::= NCName +EnclosedPrefixExpr + ::= EnclosedExpr +EnclosedURIExpr + ::= EnclosedExpr +CompTextConstructor + ::= 'text' EnclosedExpr +CompCommentConstructor + ::= 'comment' EnclosedExpr +CompPIConstructor + ::= 'processing-instruction' ( NCName | '{' Expr '}' ) EnclosedExpr +FunctionItemExpr + ::= NamedFunctionRef + | InlineFunctionExpr +NamedFunctionRef + ::= EQName '#' IntegerLiteral +InlineFunctionExpr + ::= Annotation* 'function' '(' ParamList? ')' ( 'as' SequenceType )? FunctionBody +MapConstructor + ::= 'map' '{' ( MapConstructorEntry ( ',' MapConstructorEntry )* )? '}' +MapConstructorEntry + ::= MapKeyExpr ':' MapValueExpr +MapKeyExpr + ::= ExprSingle +MapValueExpr + ::= ExprSingle +ArrayConstructor + ::= SquareArrayConstructor + | CurlyArrayConstructor +SquareArrayConstructor + ::= '[' ( ExprSingle ( ',' ExprSingle )* )? ']' +CurlyArrayConstructor + ::= 'array' EnclosedExpr +StringConstructor + ::= '``[' StringConstructorContent ']``' + /* ws: explicit */ +StringConstructorContent + ::= StringConstructorChars ( StringConstructorInterpolation StringConstructorChars )* + /* ws: explicit */ +StringConstructorInterpolation + ::= '`{' Expr? '}`' +UnaryLookup + ::= '?' KeySpecifier +SingleType + ::= SimpleTypeName '?'? +TypeDeclaration + ::= 'as' SequenceType +SequenceType + ::= 'empty-sequence' '(' ')' + | ItemType ( OccurrenceIndicator / ) +OccurrenceIndicator + ::= '?' + | '*' + | '+' +ItemType ::= KindTest + | 'item' '(' ')' + | FunctionTest + | MapTest + | ArrayTest + | AtomicOrUnionType + | ParenthesizedItemType +AtomicOrUnionType + ::= EQName +KindTest ::= DocumentTest + | ElementTest + | AttributeTest + | SchemaElementTest + | SchemaAttributeTest + | PITest + | CommentTest + | TextTest + | NamespaceNodeTest + | AnyKindTest +AnyKindTest + ::= 'node' '(' ')' +DocumentTest + ::= 'document-node' '(' ( ElementTest | SchemaElementTest )? ')' +TextTest ::= 'text' '(' ')' +CommentTest + ::= 'comment' '(' ')' +NamespaceNodeTest + ::= 'namespace-node' '(' ')' +PITest ::= 'processing-instruction' '(' ( NCName | StringLiteral )? ')' +AttributeTest + ::= 'attribute' '(' ( AttribNameOrWildcard ( ',' TypeName )? )? ')' +AttribNameOrWildcard + ::= AttributeName + | '*' +SchemaAttributeTest + ::= 'schema-attribute' '(' AttributeDeclaration ')' +AttributeDeclaration + ::= AttributeName +ElementTest + ::= 'element' '(' ( ElementNameOrWildcard ( ',' TypeName '?'? )? )? ')' +ElementNameOrWildcard + ::= ElementName + | '*' +SchemaElementTest + ::= 'schema-element' '(' ElementDeclaration ')' +ElementDeclaration + ::= ElementName +AttributeName + ::= EQName +ElementName + ::= EQName +SimpleTypeName + ::= TypeName +TypeName ::= EQName +FunctionTest + ::= Annotation* ( AnyFunctionTest | TypedFunctionTest ) +AnyFunctionTest + ::= 'function' '(' '*' ')' +TypedFunctionTest + ::= 'function' '(' ( SequenceType ( ',' SequenceType )* )? ')' 'as' SequenceType +MapTest ::= AnyMapTest + | TypedMapTest +AnyMapTest + ::= 'map' '(' '*' ')' +TypedMapTest + ::= 'map' '(' AtomicOrUnionType ',' SequenceType ')' +ArrayTest + ::= AnyArrayTest + | TypedArrayTest +AnyArrayTest + ::= 'array' '(' '*' ')' +TypedArrayTest + ::= 'array' '(' SequenceType ')' +ParenthesizedItemType + ::= '(' ItemType ')' +FTOptionDecl + ::= 'declare' 'ft-option' FTMatchOptions +FTScoreVar + ::= 'score' '$' VarName +FTSelection + ::= FTOr FTPosFilter* +FTWeight ::= 'weight' '{' Expr '}' +FTOr ::= FTAnd ( 'ftor' FTAnd )* +FTAnd ::= FTMildNot ( 'ftand' FTMildNot )* +FTMildNot + ::= FTUnaryNot ( 'not' 'in' FTUnaryNot )* +FTUnaryNot + ::= 'ftnot'? FTPrimaryWithOptions +FTPrimaryWithOptions + ::= FTPrimary FTMatchOptions? FTWeight? +FTPrimary + ::= FTWords FTTimes? + | '(' FTSelection ')' + | FTExtensionSelection +FTWords ::= FTWordsValue FTAnyallOption? +FTWordsValue + ::= StringLiteral + | '{' Expr '}' +FTExtensionSelection + ::= Pragma+ '{' FTSelection? '}' +FTAnyallOption + ::= 'any' 'word'? + | 'all' 'words'? + | 'phrase' +FTTimes ::= 'occurs' FTRange 'times' +FTRange ::= 'exactly' AdditiveExpr + | 'at' 'least' AdditiveExpr + | 'at' 'most' AdditiveExpr + | 'from' AdditiveExpr 'to' AdditiveExpr +FTPosFilter + ::= FTOrder + | FTWindow + | FTDistance + | FTScope + | FTContent +FTOrder ::= 'ordered' +FTWindow ::= 'window' AdditiveExpr FTUnit +FTDistance + ::= 'distance' FTRange FTUnit +FTUnit ::= 'words' + | 'sentences' + | 'paragraphs' +FTScope ::= ( 'same' | 'different' ) FTBigUnit +FTBigUnit + ::= 'sentence' + | 'paragraph' +FTContent + ::= 'at' 'start' + | 'at' 'end' + | 'entire' 'content' +FTMatchOptions + ::= ( 'using' FTMatchOption )+ +FTMatchOption + ::= FTLanguageOption + | FTWildCardOption + | FTThesaurusOption + | FTStemOption + | FTCaseOption + | FTDiacriticsOption + | FTStopWordOption + | FTExtensionOption +FTCaseOption + ::= 'case' 'insensitive' + | 'case' 'sensitive' + | 'lowercase' + | 'uppercase' +FTDiacriticsOption + ::= 'diacritics' 'insensitive' + | 'diacritics' 'sensitive' +FTStemOption + ::= 'stemming' + | 'no' 'stemming' +FTThesaurusOption + ::= 'thesaurus' ( FTThesaurusID | 'default' ) + | 'thesaurus' '(' ( FTThesaurusID | 'default' ) ( ',' FTThesaurusID )* ')' + | 'no' 'thesaurus' +FTThesaurusID + ::= 'at' URILiteral ( 'relationship' StringLiteral )? ( FTLiteralRange 'levels' )? +FTLiteralRange + ::= 'exactly' IntegerLiteral + | 'at' 'least' IntegerLiteral + | 'at' 'most' IntegerLiteral + | 'from' IntegerLiteral 'to' IntegerLiteral +FTStopWordOption + ::= 'stop' 'words' FTStopWords FTStopWordsInclExcl* + | 'stop' 'words' 'default' FTStopWordsInclExcl* + | 'no' 'stop' 'words' +FTStopWords + ::= 'at' URILiteral + | '(' StringLiteral ( ',' StringLiteral )* ')' +FTStopWordsInclExcl + ::= ( 'union' | 'except' ) FTStopWords +FTLanguageOption + ::= 'language' StringLiteral +FTWildCardOption + ::= 'wildcards' + | 'no' 'wildcards' +FTExtensionOption + ::= 'option' EQName StringLiteral +FTIgnoreOption + ::= 'without' 'content' UnionExpr +URILiteral + ::= StringLiteral +EQName ::= QName + | URIQualifiedName +FunctionEQName + ::= FunctionName + | URIQualifiedName +QName ::= FunctionName + | 'array' + | 'attribute' + | 'comment' + | 'document-node' + | 'element' + | 'empty-sequence' + | 'function' + | 'if' + | 'item' + | 'map' + | 'namespace-node' + | 'node' + | 'processing-instruction' + | 'schema-attribute' + | 'schema-element' + | 'switch' + | 'text' + | 'typeswitch' +FunctionName + ::= QName^Token + | 'after' + | 'ancestor' + | 'ancestor-or-self' + | 'and' + | 'ascending' + | 'before' + | 'case' + | 'cast' + | 'castable' + | 'child' + | 'collation' + | 'contains' + | 'content' + | 'copy' + | 'count' + | 'declare' + | 'default' + | 'delete' + | 'descendant' + | 'descendant-or-self' + | 'descending' + | 'diacritics' + | 'different' + | 'distance' + | 'div' + | 'document' + | 'else' + | 'empty' + | 'end' + | 'entire' + | 'eq' + | 'every' + | 'exactly' + | 'except' + | 'first' + | 'following' + | 'following-sibling' + | 'for' + | 'ft-option' + | 'ftand' + | 'ftnot' + | 'ftor' + | 'ge' + | 'group' + | 'gt' + | 'idiv' + | 'import' + | 'insensitive' + | 'insert' + | 'instance' + | 'intersect' + | 'into' + | 'is' + | 'language' + | 'last' + | 'le' + | 'least' + | 'let' + | 'levels' + | 'lowercase' + | 'lt' + | 'mod' + | 'modify' + | 'module' + | 'most' + | 'namespace' + | 'ne' + | 'no' + | 'nodes' + | 'not' + | 'occurs' + | 'only' + | 'or' + | 'order' + | 'ordered' + | 'paragraph' + | 'paragraphs' + | 'parent' + | 'phrase' + | 'preceding' + | 'preceding-sibling' + | 'relationship' + | 'rename' + | 'replace' + | 'return' + | 'revalidation' + | 'same' + | 'satisfies' + | 'score' + | 'self' + | 'sensitive' + | 'sentence' + | 'sentences' + | 'skip' + | 'some' + | 'stable' + | 'start' + | 'stemming' + | 'stop' + | 'thesaurus' + | 'times' + | 'to' + | 'treat' + | 'try' + | 'union' + | 'unordered' + | 'uppercase' + | 'using' + | 'validate' + | 'value' + | 'weight' + | 'where' + | 'wildcards' + | 'window' + | 'with' + | 'without' + | 'word' + | 'words' + | 'xquery' +NCName ::= NCName^Token + | 'after' + | 'and' + | 'ascending' + | 'before' + | 'case' + | 'cast' + | 'castable' + | 'collation' + | 'contains' + | 'content' + | 'copy' + | 'count' + | 'default' + | 'delete' + | 'descending' + | 'diacritics' + | 'different' + | 'distance' + | 'div' + | 'else' + | 'empty' + | 'end' + | 'entire' + | 'eq' + | 'exactly' + | 'except' + | 'first' + | 'for' + | 'ftand' + | 'ftnot' + | 'ftor' + | 'ge' + | 'group' + | 'gt' + | 'idiv' + | 'insensitive' + | 'insert' + | 'instance' + | 'intersect' + | 'into' + | 'is' + | 'language' + | 'last' + | 'le' + | 'least' + | 'let' + | 'levels' + | 'lowercase' + | 'lt' + | 'mod' + | 'modify' + | 'most' + | 'ne' + | 'no' + | 'nodes' + | 'not' + | 'occurs' + | 'only' + | 'or' + | 'order' + | 'paragraph' + | 'paragraphs' + | 'phrase' + | 'relationship' + | 'rename' + | 'replace' + | 'return' + | 'revalidation' + | 'same' + | 'satisfies' + | 'score' + | 'sensitive' + | 'sentence' + | 'sentences' + | 'skip' + | 'stable' + | 'start' + | 'stemming' + | 'stop' + | 'thesaurus' + | 'times' + | 'to' + | 'treat' + | 'union' + | 'uppercase' + | 'using' + | 'value' + | 'weight' + | 'where' + | 'wildcards' + | 'with' + | 'without' + | 'word' + | 'words' +Whitespace + ::= S^WS + | Comment + /* ws: definition */ +Comment ::= '(:' ( CommentContents | Comment )* ':)' + /* ws: explicit */ + + + +IntegerLiteral + ::= Digits +DecimalLiteral + ::= '.' Digits + | Digits '.' [0-9]* + /* ws: explicit */ +DoubleLiteral + ::= ( '.' Digits | Digits ( '.' [0-9]* )? ) [eE] [+#x002D]? Digits + /* ws: explicit */ +StringLiteral + ::= '"' ( PredefinedEntityRef | CharRef | EscapeQuot | [^"&] )* '"' + | "'" ( PredefinedEntityRef | CharRef | EscapeApos | [^'&] )* "'" + /* ws: explicit */ +URIQualifiedName + ::= BracedURILiteral NCName + /* ws: explicit */ +BracedURILiteral + ::= 'Q' '{' ( PredefinedEntityRef | CharRef | [^&{}] )* '}' + /* ws: explicit */ +PredefinedEntityRef + ::= '&' ( 'lt' | 'gt' | 'amp' | 'quot' | 'apos' ) ';' + /* ws: explicit */ +EscapeQuot + ::= '""' +EscapeApos + ::= "''" +ElementContentChar + ::= Char - [{}<&] +QuotAttrContentChar + ::= Char - ["{}<&] +AposAttrContentChar + ::= Char - ['{}<&] +PITarget ::= NCName - ( ( 'X' | 'x' ) ( 'M' | 'm' ) ( 'L' | 'l' ) ) +NameStartChar + ::= ':' + | [A-Z] + | '_' + | [a-z] + | [#x00C0-#x00D6] + | [#x00D8-#x00F6] + | [#x00F8-#x02FF] + | [#x0370-#x037D] + | [#x037F-#x1FFF] + | [#x200C-#x200D] + | [#x2070-#x218F] + | [#x2C00-#x2FEF] + | [#x3001-#xD7FF] + | [#xF900-#xFDCF] + | [#xFDF0-#xFFFD] + | [#x10000-#xEFFFF] +NameChar ::= NameStartChar + | '-' + | '.' + | [0-9] + | #x00B7 + | [#x0300-#x036F] + | [#x203F-#x2040] +Name ::= NameStartChar NameChar* +CharRef ::= '&#' [0-9]+ ';' + | '&#x' [0-9a-fA-F]+ ';' +NCName ::= Name - ( Char* ':' Char* ) +QName ::= PrefixedName + | UnprefixedName +PrefixedName + ::= Prefix ':' LocalPart +UnprefixedName + ::= LocalPart +Prefix ::= NCName +LocalPart + ::= NCName +StringConstructorChars + ::= ( Char* - ( Char* ( '`{' | ']``' ) Char* ) ) &( '`{' | ']`' ) + /* ws: explicit */ +S ::= ( #x0020 | #x0009 | #x000D | #x000A )+ +Char ::= #x0009 + | #x000A + | #x000D + | [#x0020-#xD7FF] + | [#xE000-#xFFFD] + | [#x10000-#x10FFFF] +Digits ::= [0-9]+ +CommentContents + ::= ( ( Char+ - ( Char* ( '(:' | ':)' ) Char* ) ) - ( Char* '(' ) ) &':' + | ( Char+ - ( Char* ( '(:' | ':)' ) Char* ) ) &'(' +PragmaContents + ::= ( Char* - ( Char* '#)' Char* ) ) &'#' +Wildcard ::= '*' + | NCName ':*' + | '*:' NCName + | BracedURILiteral '*' +DirCommentContents + ::= ( Char - '-' | '-' ( Char - '-' ) )* +DirPIContents + ::= ( Char* - ( Char* '?>' Char* ) ) &'?' +CDataSectionContents + ::= ( Char* - ( Char* ']]>' Char* ) ) &']]' +EOF ::= $ +NonNCNameChar + ::= $ + | ':' + | Char - NameChar +DelimitingChar + ::= NonNCNameChar + | '-' + | '.' +DelimitingChar + \\ DecimalLiteral DoubleLiteral IntegerLiteral +NonNCNameChar + \\ URIQualifiedName NCName^Token QName^Token 'NaN' 'after' 'all' 'allowing' 'ancestor' 'ancestor-or-self' 'and' 'any' 'array' 'as' 'ascending' 'at' 'attribute' 'base-uri' 'before' 'boundary-space' 'by' 'case' 'cast' 'castable' 'catch' 'child' 'collation' 'comment' 'construction' 'contains' 'content' 'context' 'copy' 'copy-namespaces' 'count' 'decimal-format' 'decimal-separator' 'declare' 'default' 'delete' 'descendant' 'descendant-or-self' 'descending' 'diacritics' 'different' 'digit' 'distance' 'div' 'document' 'document-node' 'element' 'else' 'empty' 'empty-sequence' 'encoding' 'end' 'entire' 'eq' 'every' 'exactly' 'except' 'exponent-separator' 'external' 'first' 'following' 'following-sibling' 'for' 'from' 'ft-option' 'ftand' 'ftnot' 'ftor' 'function' 'ge' 'greatest' 'group' 'grouping-separator' 'gt' 'idiv' 'if' 'import' 'in' 'infinity' 'inherit' 'insensitive' 'insert' 'instance' 'intersect' 'into' 'is' 'item' 'language' 'last' 'lax' 'le' 'least' 'let' 'levels' 'lowercase' 'lt' 'map' 'minus-sign' 'mod' 'modify' 'module' 'most' 'namespace' 'namespace-node' 'ne' 'next' 'no' 'no-inherit' 'no-preserve' 'node' 'nodes' 'not' 'occurs' 'of' 'only' 'option' 'or' 'order' 'ordered' 'ordering' 'paragraph' 'paragraphs' 'parent' 'pattern-separator' 'per-mille' 'percent' 'phrase' 'preceding' 'preceding-sibling' 'preserve' 'previous' 'processing-instruction' 'relationship' 'rename' 'replace' 'return' 'revalidation' 'same' 'satisfies' 'schema' 'schema-attribute' 'schema-element' 'score' 'self' 'sensitive' 'sentence' 'sentences' 'skip' 'sliding' 'some' 'stable' 'start' 'stemming' 'stop' 'strict' 'strip' 'switch' 'text' 'then' 'thesaurus' 'times' 'to' 'treat' 'try' 'tumbling' 'type' 'typeswitch' 'union' 'unordered' 'uppercase' 'using' 'validate' 'value' 'variable' 'version' 'weight' 'when' 'where' 'wildcards' 'window' 'with' 'without' 'word' 'words' 'xquery' 'zero-digit' +'*' << Wildcard +NCName^Token + << 'after' 'and' 'as' 'ascending' 'before' 'case' 'cast' 'castable' 'collation' 'contains' 'content' 'copy' 'count' 'default' 'delete' 'descending' 'diacritics' 'different' 'distance' 'div' 'else' 'empty' 'end' 'entire' 'eq' 'exactly' 'except' 'first' 'for' 'ftand' 'ftnot' 'ftor' 'ge' 'group' 'gt' 'idiv' 'insensitive' 'insert' 'instance' 'intersect' 'into' 'is' 'language' 'last' 'le' 'least' 'let' 'levels' 'lowercase' 'lt' 'mod' 'modify' 'most' 'ne' 'no' 'nodes' 'not' 'occurs' 'only' 'or' 'order' 'paragraph' 'paragraphs' 'phrase' 'relationship' 'rename' 'replace' 'return' 'revalidation' 'same' 'satisfies' 'score' 'sensitive' 'sentence' 'sentences' 'skip' 'stable' 'start' 'stemming' 'stop' 'thesaurus' 'times' 'to' 'treat' 'union' 'uppercase' 'using' 'value' 'weight' 'where' 'wildcards' 'with' 'without' 'word' 'words' +QName^Token + << 'after' 'ancestor' 'ancestor-or-self' 'and' 'array' 'as' 'ascending' 'attribute' 'before' 'case' 'cast' 'castable' 'child' 'collation' 'comment' 'contains' 'content' 'copy' 'count' 'declare' 'default' 'delete' 'descendant' 'descendant-or-self' 'descending' 'diacritics' 'different' 'distance' 'div' 'document' 'document-node' 'element' 'else' 'empty' 'empty-sequence' 'end' 'entire' 'eq' 'every' 'exactly' 'except' 'first' 'following' 'following-sibling' 'for' 'ft-option' 'ftand' 'ftnot' 'ftor' 'function' 'ge' 'group' 'gt' 'idiv' 'if' 'import' 'insensitive' 'insert' 'instance' 'intersect' 'into' 'is' 'item' 'language' 'last' 'le' 'least' 'let' 'levels' 'lowercase' 'lt' 'map' 'mod' 'modify' 'module' 'most' 'namespace' 'namespace-node' 'ne' 'no' 'node' 'nodes' 'not' 'occurs' 'only' 'or' 'order' 'ordered' 'paragraph' 'paragraphs' 'parent' 'phrase' 'preceding' 'preceding-sibling' 'processing-instruction' 'relationship' 'rename' 'replace' 'return' 'revalidation' 'same' 'satisfies' 'schema-attribute' 'schema-element' 'score' 'self' 'sensitive' 'sentence' 'sentences' 'skip' 'some' 'stable' 'start' 'stemming' 'stop' 'switch' 'text' 'then' 'thesaurus' 'times' 'to' 'treat' 'try' 'typeswitch' 'union' 'unordered' 'uppercase' 'using' 'validate' 'value' 'weight' 'where' 'wildcards' 'window' 'with' 'without' 'word' 'words' 'xquery' \ No newline at end of file diff --git a/grammars/XQuery-31.ebnf b/grammars/XQuery-31.ebnf new file mode 100644 index 00000000..67defc25 --- /dev/null +++ b/grammars/XQuery-31.ebnf @@ -0,0 +1,840 @@ +XQuery ::= Module EOF +Module ::= VersionDecl? ( LibraryModule | MainModule ) +VersionDecl + ::= 'xquery' ( 'encoding' StringLiteral | 'version' StringLiteral ( 'encoding' StringLiteral )? ) Separator +MainModule + ::= Prolog QueryBody +LibraryModule + ::= ModuleDecl Prolog +ModuleDecl + ::= 'module' 'namespace' NCName '=' URILiteral Separator +Prolog ::= ( ( DefaultNamespaceDecl | Setter | NamespaceDecl | Import ) Separator )* ( ( ContextItemDecl | AnnotatedDecl | OptionDecl ) Separator )* +Separator + ::= ';' +Setter ::= BoundarySpaceDecl + | DefaultCollationDecl + | BaseURIDecl + | ConstructionDecl + | OrderingModeDecl + | EmptyOrderDecl + | CopyNamespacesDecl + | DecimalFormatDecl + | RevalidationDecl +RevalidationDecl + ::= 'declare' 'revalidation' ( 'strict' | 'lax' | 'skip' ) +BoundarySpaceDecl + ::= 'declare' 'boundary-space' ( 'preserve' | 'strip' ) +DefaultCollationDecl + ::= 'declare' 'default' 'collation' URILiteral +BaseURIDecl + ::= 'declare' 'base-uri' URILiteral +ConstructionDecl + ::= 'declare' 'construction' ( 'strip' | 'preserve' ) +OrderingModeDecl + ::= 'declare' 'ordering' ( 'ordered' | 'unordered' ) +EmptyOrderDecl + ::= 'declare' 'default' 'order' 'empty' ( 'greatest' | 'least' ) +CopyNamespacesDecl + ::= 'declare' 'copy-namespaces' PreserveMode ',' InheritMode +PreserveMode + ::= 'preserve' + | 'no-preserve' +InheritMode + ::= 'inherit' + | 'no-inherit' +DecimalFormatDecl + ::= 'declare' ( 'decimal-format' EQName | 'default' 'decimal-format' ) ( DFPropertyName '=' StringLiteral )* +DFPropertyName + ::= 'decimal-separator' + | 'grouping-separator' + | 'infinity' + | 'minus-sign' + | 'NaN' + | 'percent' + | 'per-mille' + | 'zero-digit' + | 'digit' + | 'pattern-separator' + | 'exponent-separator' +Import ::= SchemaImport + | ModuleImport +SchemaImport + ::= 'import' 'schema' SchemaPrefix? URILiteral ( 'at' URILiteral ( ',' URILiteral )* )? +SchemaPrefix + ::= 'namespace' NCName '=' + | 'default' 'element' 'namespace' +ModuleImport + ::= 'import' 'module' ( 'namespace' NCName '=' )? URILiteral ( 'at' URILiteral ( ',' URILiteral )* )? +NamespaceDecl + ::= 'declare' 'namespace' NCName '=' URILiteral +DefaultNamespaceDecl + ::= 'declare' 'default' ( 'element' | 'function' ) 'namespace' URILiteral +AnnotatedDecl + ::= 'declare' Annotation* ( VarDecl | FunctionDecl ) +Annotation + ::= '%' EQName ( '(' Literal ( ',' Literal )* ')' )? +VarDecl ::= 'variable' '$' VarName TypeDeclaration? ( ':=' VarValue | 'external' ( ':=' VarDefaultValue )? ) +VarValue ::= ExprSingle +VarDefaultValue + ::= ExprSingle +ContextItemDecl + ::= 'declare' 'context' 'item' ( 'as' ItemType )? ( ':=' VarValue | 'external' ( ':=' VarDefaultValue )? ) +FunctionDecl + ::= 'function' EQName '(' ParamList? ')' ( 'as' SequenceType )? ( FunctionBody | 'external' ) +ParamList + ::= Param ( ',' Param )* +Param ::= '$' EQName TypeDeclaration? +FunctionBody + ::= EnclosedExpr +EnclosedExpr + ::= '{' Expr? '}' +OptionDecl + ::= 'declare' 'option' EQName StringLiteral +QueryBody + ::= Expr +Expr ::= ExprSingle ( ',' ExprSingle )* +ExprSingle + ::= FLWORExpr + | QuantifiedExpr + | SwitchExpr + | TypeswitchExpr + | IfExpr + | TryCatchExpr + | InsertExpr + | DeleteExpr + | RenameExpr + | ReplaceExpr + | TransformExpr + | OrExpr +FLWORExpr + ::= InitialClause IntermediateClause* ReturnClause +InitialClause + ::= ForClause + | LetClause + | WindowClause +IntermediateClause + ::= InitialClause + | WhereClause + | GroupByClause + | OrderByClause + | CountClause +ForClause + ::= 'for' ForBinding ( ',' ForBinding )* +ForBinding + ::= '$' VarName TypeDeclaration? AllowingEmpty? PositionalVar? 'in' ExprSingle +AllowingEmpty + ::= 'allowing' 'empty' +PositionalVar + ::= 'at' '$' VarName +LetClause + ::= 'let' LetBinding ( ',' LetBinding )* +LetBinding + ::= '$' VarName TypeDeclaration? ':=' ExprSingle +WindowClause + ::= 'for' ( TumblingWindowClause | SlidingWindowClause ) +TumblingWindowClause + ::= 'tumbling' 'window' '$' VarName TypeDeclaration? 'in' ExprSingle WindowStartCondition WindowEndCondition? +SlidingWindowClause + ::= 'sliding' 'window' '$' VarName TypeDeclaration? 'in' ExprSingle WindowStartCondition WindowEndCondition +WindowStartCondition + ::= 'start' WindowVars 'when' ExprSingle +WindowEndCondition + ::= 'only'? 'end' WindowVars 'when' ExprSingle +WindowVars + ::= ( '$' CurrentItem )? PositionalVar? ( 'previous' '$' PreviousItem )? ( 'next' '$' NextItem )? +CurrentItem + ::= EQName +PreviousItem + ::= EQName +NextItem ::= EQName +CountClause + ::= 'count' '$' VarName +WhereClause + ::= 'where' ExprSingle +GroupByClause + ::= 'group' 'by' GroupingSpecList +GroupingSpecList + ::= GroupingSpec ( ',' GroupingSpec )* +GroupingSpec + ::= GroupingVariable ( TypeDeclaration? ':=' ExprSingle )? ( 'collation' URILiteral )? +GroupingVariable + ::= '$' VarName +OrderByClause + ::= ( 'order' 'by' | 'stable' 'order' 'by' ) OrderSpecList +OrderSpecList + ::= OrderSpec ( ',' OrderSpec )* +OrderSpec + ::= ExprSingle OrderModifier +OrderModifier + ::= ( 'ascending' | 'descending' )? ( 'empty' ( 'greatest' | 'least' ) )? ( 'collation' URILiteral )? +ReturnClause + ::= 'return' ExprSingle +QuantifiedExpr + ::= ( 'some' | 'every' ) '$' VarName TypeDeclaration? 'in' ExprSingle ( ',' '$' VarName TypeDeclaration? 'in' ExprSingle )* 'satisfies' ExprSingle +SwitchExpr + ::= 'switch' '(' Expr ')' SwitchCaseClause+ 'default' 'return' ExprSingle +SwitchCaseClause + ::= ( 'case' SwitchCaseOperand )+ 'return' ExprSingle +SwitchCaseOperand + ::= ExprSingle +TypeswitchExpr + ::= 'typeswitch' '(' Expr ')' CaseClause+ 'default' ( '$' VarName )? 'return' ExprSingle +CaseClause + ::= 'case' ( '$' VarName 'as' )? SequenceTypeUnion 'return' ExprSingle +SequenceTypeUnion + ::= SequenceType ( '|' SequenceType )* +IfExpr ::= 'if' '(' Expr ')' 'then' ExprSingle 'else' ExprSingle +TryCatchExpr + ::= TryClause CatchClause+ +TryClause + ::= 'try' EnclosedTryTargetExpr +EnclosedTryTargetExpr + ::= EnclosedExpr +CatchClause + ::= 'catch' CatchErrorList EnclosedExpr +CatchErrorList + ::= NameTest ( '|' NameTest )* +InsertExpr + ::= 'insert' ( 'node' | 'nodes' ) SourceExpr InsertExprTargetChoice TargetExpr +InsertExprTargetChoice + ::= ( 'as' ( 'first' | 'last' ) )? 'into' + | 'after' + | 'before' +SourceExpr + ::= ExprSingle +TargetExpr + ::= ExprSingle +DeleteExpr + ::= 'delete' ( 'node' | 'nodes' ) TargetExpr +ReplaceExpr + ::= 'replace' ( 'value' 'of' )? 'node' TargetExpr 'with' ExprSingle +RenameExpr + ::= 'rename' 'node' TargetExpr 'as' NewNameExpr +NewNameExpr + ::= ExprSingle +TransformExpr + ::= 'copy' '$' VarName ':=' ExprSingle ( ',' '$' VarName ':=' ExprSingle )* 'modify' ExprSingle 'return' ExprSingle +OrExpr ::= AndExpr ( 'or' AndExpr )* +AndExpr ::= ComparisonExpr ( 'and' ComparisonExpr )* +ComparisonExpr + ::= StringConcatExpr ( ( ValueComp | GeneralComp | NodeComp ) StringConcatExpr )? +StringConcatExpr + ::= RangeExpr ( '||' RangeExpr )* +RangeExpr + ::= AdditiveExpr ( 'to' AdditiveExpr )? +AdditiveExpr + ::= MultiplicativeExpr ( ( '+' | '-' ) MultiplicativeExpr )* +MultiplicativeExpr + ::= UnionExpr ( ( '*' | 'div' | 'idiv' | 'mod' ) UnionExpr )* +UnionExpr + ::= IntersectExceptExpr ( ( 'union' | '|' ) IntersectExceptExpr )* +IntersectExceptExpr + ::= InstanceofExpr ( ( 'intersect' | 'except' ) InstanceofExpr )* +InstanceofExpr + ::= TreatExpr ( 'instance' 'of' SequenceType )? +TreatExpr + ::= CastableExpr ( 'treat' 'as' SequenceType )? +CastableExpr + ::= CastExpr ( 'castable' 'as' SingleType )? +CastExpr ::= ArrowExpr ( 'cast' 'as' SingleType )? +ArrowExpr + ::= UnaryExpr ( '=>' ArrowFunctionSpecifier ArgumentList )* +UnaryExpr + ::= ( '-' | '+' )* ValueExpr +ValueExpr + ::= ValidateExpr + | ExtensionExpr + | SimpleMapExpr +GeneralComp + ::= '=' + | '!=' + | '<' + | '<=' + | '>' + | '>=' +ValueComp + ::= 'eq' + | 'ne' + | 'lt' + | 'le' + | 'gt' + | 'ge' +NodeComp ::= 'is' + | '<<' + | '>>' +ValidateExpr + ::= 'validate' ( ValidationMode | 'type' TypeName )? '{' Expr '}' +ValidationMode + ::= 'lax' + | 'strict' +ExtensionExpr + ::= Pragma+ '{' Expr? '}' +Pragma ::= '(#' S? EQName ( S PragmaContents )? '#)' + /* ws: explicit */ +SimpleMapExpr + ::= PathExpr ( '!' PathExpr )* +PathExpr ::= '/' ( RelativePathExpr / ) + | '//' RelativePathExpr + | RelativePathExpr +RelativePathExpr + ::= StepExpr ( ( '/' | '//' ) StepExpr )* +StepExpr ::= PostfixExpr + | AxisStep +AxisStep ::= ( ReverseStep | ForwardStep ) PredicateList +ForwardStep + ::= ForwardAxis NodeTest + | AbbrevForwardStep +ForwardAxis + ::= 'child' '::' + | 'descendant' '::' + | 'attribute' '::' + | 'self' '::' + | 'descendant-or-self' '::' + | 'following-sibling' '::' + | 'following' '::' +AbbrevForwardStep + ::= '@'? NodeTest +ReverseStep + ::= ReverseAxis NodeTest + | AbbrevReverseStep +ReverseAxis + ::= 'parent' '::' + | 'ancestor' '::' + | 'preceding-sibling' '::' + | 'preceding' '::' + | 'ancestor-or-self' '::' +AbbrevReverseStep + ::= '..' +NodeTest ::= KindTest + | NameTest +NameTest ::= EQName + | Wildcard +PostfixExpr + ::= PrimaryExpr ( Predicate | ArgumentList | Lookup )* +ArgumentList + ::= '(' ( Argument ( ',' Argument )* )? ')' +PredicateList + ::= Predicate* +Predicate + ::= '[' Expr ']' +Lookup ::= '?' KeySpecifier +KeySpecifier + ::= NCName + | IntegerLiteral + | ParenthesizedExpr + | '*' +ArrowFunctionSpecifier + ::= EQName + | VarRef + | ParenthesizedExpr +PrimaryExpr + ::= Literal + | VarRef + | ParenthesizedExpr + | ContextItemExpr + | FunctionCall + | OrderedExpr + | UnorderedExpr + | NodeConstructor + | FunctionItemExpr + | MapConstructor + | ArrayConstructor + | StringConstructor + | UnaryLookup +Literal ::= NumericLiteral + | StringLiteral +NumericLiteral + ::= IntegerLiteral + | DecimalLiteral + | DoubleLiteral +VarRef ::= '$' VarName +VarName ::= EQName +ParenthesizedExpr + ::= '(' Expr? ')' +ContextItemExpr + ::= '.' +OrderedExpr + ::= 'ordered' EnclosedExpr +UnorderedExpr + ::= 'unordered' EnclosedExpr +FunctionCall + ::= FunctionEQName ArgumentList +Argument ::= ExprSingle + | ArgumentPlaceholder +ArgumentPlaceholder + ::= '?' +NodeConstructor + ::= DirectConstructor + | ComputedConstructor +DirectConstructor + ::= DirElemConstructor + | DirCommentConstructor + | DirPIConstructor +DirElemConstructor + ::= '<' QName DirAttributeList ( '/>' | '>' DirElemContent* '' ) + /* ws: explicit */ +DirAttributeList + ::= ( S ( QName S? '=' S? DirAttributeValue )? )* + /* ws: explicit */ +DirAttributeValue + ::= '"' ( EscapeQuot | QuotAttrValueContent )* '"' + | "'" ( EscapeApos | AposAttrValueContent )* "'" + /* ws: explicit */ +QuotAttrValueContent + ::= QuotAttrContentChar + | CommonContent +AposAttrValueContent + ::= AposAttrContentChar + | CommonContent +DirElemContent + ::= DirectConstructor + | CDataSection + | CommonContent + | ElementContentChar +CommonContent + ::= PredefinedEntityRef + | CharRef + | '{{' + | '}}' + | EnclosedExpr +DirCommentConstructor + ::= '' + /* ws: explicit */ +DirPIConstructor + ::= '' + /* ws: explicit */ +CDataSection + ::= '' + /* ws: explicit */ +ComputedConstructor + ::= CompDocConstructor + | CompElemConstructor + | CompAttrConstructor + | CompNamespaceConstructor + | CompTextConstructor + | CompCommentConstructor + | CompPIConstructor +CompDocConstructor + ::= 'document' EnclosedExpr +CompElemConstructor + ::= 'element' ( EQName | '{' Expr '}' ) EnclosedContentExpr +EnclosedContentExpr + ::= EnclosedExpr +CompAttrConstructor + ::= 'attribute' ( EQName | '{' Expr '}' ) EnclosedExpr +CompNamespaceConstructor + ::= 'namespace' ( Prefix | EnclosedPrefixExpr ) EnclosedURIExpr +Prefix ::= NCName +EnclosedPrefixExpr + ::= EnclosedExpr +EnclosedURIExpr + ::= EnclosedExpr +CompTextConstructor + ::= 'text' EnclosedExpr +CompCommentConstructor + ::= 'comment' EnclosedExpr +CompPIConstructor + ::= 'processing-instruction' ( NCName | '{' Expr '}' ) EnclosedExpr +FunctionItemExpr + ::= NamedFunctionRef + | InlineFunctionExpr +NamedFunctionRef + ::= EQName '#' IntegerLiteral +InlineFunctionExpr + ::= Annotation* 'function' '(' ParamList? ')' ( 'as' SequenceType )? FunctionBody +MapConstructor + ::= 'map' '{' ( MapConstructorEntry ( ',' MapConstructorEntry )* )? '}' +MapConstructorEntry + ::= MapKeyExpr ':' MapValueExpr +MapKeyExpr + ::= ExprSingle +MapValueExpr + ::= ExprSingle +ArrayConstructor + ::= SquareArrayConstructor + | CurlyArrayConstructor +SquareArrayConstructor + ::= '[' ( ExprSingle ( ',' ExprSingle )* )? ']' +CurlyArrayConstructor + ::= 'array' EnclosedExpr +StringConstructor + ::= '``[' StringConstructorContent ']``' + /* ws: explicit */ +StringConstructorContent + ::= StringConstructorChars ( StringConstructorInterpolation StringConstructorChars )* + /* ws: explicit */ +StringConstructorInterpolation + ::= '`{' Expr? '}`' +UnaryLookup + ::= '?' KeySpecifier +SingleType + ::= SimpleTypeName '?'? +TypeDeclaration + ::= 'as' SequenceType +SequenceType + ::= 'empty-sequence' '(' ')' + | ItemType ( OccurrenceIndicator / ) +OccurrenceIndicator + ::= '?' + | '*' + | '+' +ItemType ::= KindTest + | 'item' '(' ')' + | FunctionTest + | MapTest + | ArrayTest + | AtomicOrUnionType + | ParenthesizedItemType +AtomicOrUnionType + ::= EQName +KindTest ::= DocumentTest + | ElementTest + | AttributeTest + | SchemaElementTest + | SchemaAttributeTest + | PITest + | CommentTest + | TextTest + | NamespaceNodeTest + | AnyKindTest +AnyKindTest + ::= 'node' '(' ')' +DocumentTest + ::= 'document-node' '(' ( ElementTest | SchemaElementTest )? ')' +TextTest ::= 'text' '(' ')' +CommentTest + ::= 'comment' '(' ')' +NamespaceNodeTest + ::= 'namespace-node' '(' ')' +PITest ::= 'processing-instruction' '(' ( NCName | StringLiteral )? ')' +AttributeTest + ::= 'attribute' '(' ( AttribNameOrWildcard ( ',' TypeName )? )? ')' +AttribNameOrWildcard + ::= AttributeName + | '*' +SchemaAttributeTest + ::= 'schema-attribute' '(' AttributeDeclaration ')' +AttributeDeclaration + ::= AttributeName +ElementTest + ::= 'element' '(' ( ElementNameOrWildcard ( ',' TypeName '?'? )? )? ')' +ElementNameOrWildcard + ::= ElementName + | '*' +SchemaElementTest + ::= 'schema-element' '(' ElementDeclaration ')' +ElementDeclaration + ::= ElementName +AttributeName + ::= EQName +ElementName + ::= EQName +SimpleTypeName + ::= TypeName +TypeName ::= EQName +FunctionTest + ::= Annotation* ( AnyFunctionTest | TypedFunctionTest ) +AnyFunctionTest + ::= 'function' '(' '*' ')' +TypedFunctionTest + ::= 'function' '(' ( SequenceType ( ',' SequenceType )* )? ')' 'as' SequenceType +MapTest ::= AnyMapTest + | TypedMapTest +AnyMapTest + ::= 'map' '(' '*' ')' +TypedMapTest + ::= 'map' '(' AtomicOrUnionType ',' SequenceType ')' +ArrayTest + ::= AnyArrayTest + | TypedArrayTest +AnyArrayTest + ::= 'array' '(' '*' ')' +TypedArrayTest + ::= 'array' '(' SequenceType ')' +ParenthesizedItemType + ::= '(' ItemType ')' +URILiteral + ::= StringLiteral +EQName ::= QName + | URIQualifiedName +FunctionEQName + ::= FunctionName + | URIQualifiedName +QName ::= FunctionName + | 'array' + | 'attribute' + | 'comment' + | 'document-node' + | 'element' + | 'empty-sequence' + | 'function' + | 'if' + | 'item' + | 'map' + | 'namespace-node' + | 'node' + | 'processing-instruction' + | 'schema-attribute' + | 'schema-element' + | 'switch' + | 'text' + | 'typeswitch' +FunctionName + ::= QName^Token + | 'after' + | 'ancestor' + | 'ancestor-or-self' + | 'and' + | 'ascending' + | 'before' + | 'case' + | 'cast' + | 'castable' + | 'child' + | 'collation' + | 'copy' + | 'count' + | 'declare' + | 'default' + | 'delete' + | 'descendant' + | 'descendant-or-self' + | 'descending' + | 'div' + | 'document' + | 'else' + | 'empty' + | 'end' + | 'eq' + | 'every' + | 'except' + | 'first' + | 'following' + | 'following-sibling' + | 'for' + | 'ge' + | 'group' + | 'gt' + | 'idiv' + | 'import' + | 'insert' + | 'instance' + | 'intersect' + | 'into' + | 'is' + | 'last' + | 'le' + | 'let' + | 'lt' + | 'mod' + | 'modify' + | 'module' + | 'namespace' + | 'ne' + | 'nodes' + | 'only' + | 'or' + | 'order' + | 'ordered' + | 'parent' + | 'preceding' + | 'preceding-sibling' + | 'rename' + | 'replace' + | 'return' + | 'revalidation' + | 'satisfies' + | 'self' + | 'skip' + | 'some' + | 'stable' + | 'start' + | 'to' + | 'treat' + | 'try' + | 'union' + | 'unordered' + | 'validate' + | 'value' + | 'where' + | 'with' + | 'xquery' +NCName ::= NCName^Token + | 'after' + | 'and' + | 'ascending' + | 'before' + | 'case' + | 'cast' + | 'castable' + | 'collation' + | 'copy' + | 'count' + | 'default' + | 'delete' + | 'descending' + | 'div' + | 'else' + | 'empty' + | 'end' + | 'eq' + | 'except' + | 'first' + | 'for' + | 'ge' + | 'group' + | 'gt' + | 'idiv' + | 'insert' + | 'instance' + | 'intersect' + | 'into' + | 'is' + | 'last' + | 'le' + | 'let' + | 'lt' + | 'mod' + | 'modify' + | 'ne' + | 'nodes' + | 'only' + | 'or' + | 'order' + | 'rename' + | 'replace' + | 'return' + | 'revalidation' + | 'satisfies' + | 'skip' + | 'stable' + | 'start' + | 'to' + | 'treat' + | 'union' + | 'value' + | 'where' + | 'with' +Whitespace + ::= S^WS + | Comment + /* ws: definition */ +Comment ::= '(:' ( CommentContents | Comment )* ':)' + /* ws: explicit */ + + + +IntegerLiteral + ::= Digits +DecimalLiteral + ::= '.' Digits + | Digits '.' [0-9]* + /* ws: explicit */ +DoubleLiteral + ::= ( '.' Digits | Digits ( '.' [0-9]* )? ) [eE] [+#x002D]? Digits + /* ws: explicit */ +StringLiteral + ::= '"' ( PredefinedEntityRef | CharRef | EscapeQuot | [^"&] )* '"' + | "'" ( PredefinedEntityRef | CharRef | EscapeApos | [^'&] )* "'" + /* ws: explicit */ +URIQualifiedName + ::= BracedURILiteral NCName + /* ws: explicit */ +BracedURILiteral + ::= 'Q' '{' ( PredefinedEntityRef | CharRef | [^&{}] )* '}' + /* ws: explicit */ +PredefinedEntityRef + ::= '&' ( 'lt' | 'gt' | 'amp' | 'quot' | 'apos' ) ';' + /* ws: explicit */ +EscapeQuot + ::= '""' +EscapeApos + ::= "''" +ElementContentChar + ::= Char - [{}<&] +QuotAttrContentChar + ::= Char - ["{}<&] +AposAttrContentChar + ::= Char - ['{}<&] +PITarget ::= NCName - ( ( 'X' | 'x' ) ( 'M' | 'm' ) ( 'L' | 'l' ) ) +NameStartChar + ::= ':' + | [A-Z] + | '_' + | [a-z] + | [#x00C0-#x00D6] + | [#x00D8-#x00F6] + | [#x00F8-#x02FF] + | [#x0370-#x037D] + | [#x037F-#x1FFF] + | [#x200C-#x200D] + | [#x2070-#x218F] + | [#x2C00-#x2FEF] + | [#x3001-#xD7FF] + | [#xF900-#xFDCF] + | [#xFDF0-#xFFFD] + | [#x10000-#xEFFFF] +NameChar ::= NameStartChar + | '-' + | '.' + | [0-9] + | #x00B7 + | [#x0300-#x036F] + | [#x203F-#x2040] +Name ::= NameStartChar NameChar* +CharRef ::= '&#' [0-9]+ ';' + | '&#x' [0-9a-fA-F]+ ';' +NCName ::= Name - ( Char* ':' Char* ) +QName ::= PrefixedName + | UnprefixedName +PrefixedName + ::= Prefix ':' LocalPart +UnprefixedName + ::= LocalPart +Prefix ::= NCName +LocalPart + ::= NCName +StringConstructorChars + ::= ( Char* - ( Char* ( '`{' | ']``' ) Char* ) ) &( '`{' | ']`' ) + /* ws: explicit */ +S ::= ( #x0020 | #x0009 | #x000D | #x000A )+ +Char ::= #x0009 + | #x000A + | #x000D + | [#x0020-#xD7FF] + | [#xE000-#xFFFD] + | [#x10000-#x10FFFF] +Digits ::= [0-9]+ +CommentContents + ::= ( ( Char+ - ( Char* ( '(:' | ':)' ) Char* ) ) - ( Char* '(' ) ) &':' + | ( Char+ - ( Char* ( '(:' | ':)' ) Char* ) ) &'(' +PragmaContents + ::= ( Char* - ( Char* '#)' Char* ) ) &'#' +Wildcard ::= '*' + | NCName ':*' + | '*:' NCName + | BracedURILiteral '*' +DirCommentContents + ::= ( Char - '-' | '-' ( Char - '-' ) )* +DirPIContents + ::= ( Char* - ( Char* '?>' Char* ) ) &'?' +CDataSectionContents + ::= ( Char* - ( Char* ']]>' Char* ) ) &']]' +EOF ::= $ +NonNCNameChar + ::= $ + | ':' + | Char - NameChar +DelimitingChar + ::= NonNCNameChar + | '-' + | '.' +DelimitingChar + \\ DecimalLiteral DoubleLiteral IntegerLiteral +NonNCNameChar + \\ URIQualifiedName NCName^Token QName^Token 'NaN' 'after' 'allowing' 'ancestor' 'ancestor-or-self' 'and' 'array' 'as' 'ascending' 'at' 'attribute' 'base-uri' 'before' 'boundary-space' 'by' 'case' 'cast' 'castable' 'catch' 'child' 'collation' 'comment' 'construction' 'context' 'copy' 'copy-namespaces' 'count' 'decimal-format' 'decimal-separator' 'declare' 'default' 'delete' 'descendant' 'descendant-or-self' 'descending' 'digit' 'div' 'document' 'document-node' 'element' 'else' 'empty' 'empty-sequence' 'encoding' 'end' 'eq' 'every' 'except' 'exponent-separator' 'external' 'first' 'following' 'following-sibling' 'for' 'function' 'ge' 'greatest' 'group' 'grouping-separator' 'gt' 'idiv' 'if' 'import' 'in' 'infinity' 'inherit' 'insert' 'instance' 'intersect' 'into' 'is' 'item' 'last' 'lax' 'le' 'least' 'let' 'lt' 'map' 'minus-sign' 'mod' 'modify' 'module' 'namespace' 'namespace-node' 'ne' 'next' 'no-inherit' 'no-preserve' 'node' 'nodes' 'of' 'only' 'option' 'or' 'order' 'ordered' 'ordering' 'parent' 'pattern-separator' 'per-mille' 'percent' 'preceding' 'preceding-sibling' 'preserve' 'previous' 'processing-instruction' 'rename' 'replace' 'return' 'revalidation' 'satisfies' 'schema' 'schema-attribute' 'schema-element' 'self' 'skip' 'sliding' 'some' 'stable' 'start' 'strict' 'strip' 'switch' 'text' 'then' 'to' 'treat' 'try' 'tumbling' 'type' 'typeswitch' 'union' 'unordered' 'validate' 'value' 'variable' 'version' 'when' 'where' 'window' 'with' 'xquery' 'zero-digit' +'*' << Wildcard +NCName^Token + << 'after' 'and' 'as' 'ascending' 'before' 'case' 'cast' 'castable' 'collation' 'copy' 'count' 'default' 'delete' 'descending' 'div' 'else' 'empty' 'end' 'eq' 'except' 'first' 'for' 'ge' 'group' 'gt' 'idiv' 'insert' 'instance' 'intersect' 'into' 'is' 'last' 'le' 'let' 'lt' 'mod' 'modify' 'ne' 'nodes' 'only' 'or' 'order' 'rename' 'replace' 'return' 'revalidation' 'satisfies' 'skip' 'stable' 'start' 'to' 'treat' 'union' 'value' 'where' 'with' +QName^Token + << 'after' 'ancestor' 'ancestor-or-self' 'and' 'array' 'as' 'ascending' 'attribute' 'before' 'case' 'cast' 'castable' 'child' 'collation' 'comment' 'copy' 'count' 'declare' 'default' 'delete' 'descendant' 'descendant-or-self' 'descending' 'div' 'document' 'document-node' 'element' 'else' 'empty' 'empty-sequence' 'end' 'eq' 'every' 'except' 'first' 'following' 'following-sibling' 'for' 'function' 'ge' 'group' 'gt' 'idiv' 'if' 'import' 'insert' 'instance' 'intersect' 'into' 'is' 'item' 'last' 'le' 'let' 'lt' 'map' 'mod' 'modify' 'module' 'namespace' 'namespace-node' 'ne' 'node' 'nodes' 'only' 'or' 'order' 'ordered' 'parent' 'preceding' 'preceding-sibling' 'processing-instruction' 'rename' 'replace' 'return' 'revalidation' 'satisfies' 'schema-attribute' 'schema-element' 'self' 'skip' 'some' 'stable' 'start' 'switch' 'text' 'to' 'treat' 'try' 'typeswitch' 'union' 'unordered' 'validate' 'value' 'where' 'with' 'xquery' \ No newline at end of file diff --git a/grammars/XQuery-40-Family-XQUFEL.ebnf b/grammars/XQuery-40-Family-XQUFEL.ebnf new file mode 100644 index 00000000..12b599c3 --- /dev/null +++ b/grammars/XQuery-40-Family-XQUFEL.ebnf @@ -0,0 +1,1611 @@ +/* Combined grammar: XQuery 4.0 + XQUF 3.0 + XQFT 1.0 + XQUFEL (XQuery Update Facility eXist Legacy) */ + +/* XQuery 4.0: An XML Query Language + * version https://qt4cg.org/specifications/xquery-40/ + * extracted from https://qt4cg.org/specifications/xquery-40/xquery-40.html on Tue Feb 17, 2026, 21:31 (UTC+01) + * with references to https://www.w3.org/TR/REC-xml/ resolved and inlined + * with references to https://www.w3.org/TR/REC-xml-names/ resolved and inlined + * reordered into depth-first order + * adapted for REx by rexify-xquery-40.xq + */ + +XQuery ::= Module EOF +Module ::= VersionDecl? ( LibraryModule | MainModule ) +VersionDecl + ::= 'xquery' ( 'encoding' StringLiteral | 'version' StringLiteral ( 'encoding' StringLiteral )? ) Separator +Separator + ::= ';' +LibraryModule + ::= ModuleDecl Prolog +ModuleDecl + ::= 'module' 'namespace' NCName '=' URILiteral Separator +NCName ::= UnreservedNCName + | 'NaN' + | 'after' + | 'all' + | 'allowing' + | 'ancestor' + | 'ancestor-or-self' + | 'and' + | 'any' + | 'array' + | 'as' + | 'ascending' + | 'at' + | 'attribute' + | 'base-uri' + | 'before' + | 'boundary-space' + | 'by' + | 'case' + | 'cast' + | 'castable' + | 'catch' + | 'child' + | 'collation' + | 'comment' + | 'construction' + | 'contains' + | 'content' + | 'context' + | 'copy' + | 'copy-namespaces' + | 'count' + | 'decimal-format' + | 'decimal-separator' + | 'declare' + | 'default' + | 'delete' + | 'descendant' + | 'descendant-or-self' + | 'descending' + | 'diacritics' + | 'different' + | 'digit' + | 'distance' + | 'div' + | 'document' + | 'document-node' + | 'element' + | 'else' + | 'empty' + | 'empty-sequence' + | 'encoding' + | 'end' + | 'entire' + | 'enum' + | 'eq' + | 'every' + | 'exactly' + | 'except' + | 'exponent-separator' + | 'external' + | 'false' + | 'finally' + | 'first' + | 'fixed' + | 'fn' + | 'following' + | 'following-or-self' + | 'following-sibling' + | 'following-sibling-or-self' + | 'follows' + | 'follows-or-is' + | 'for' + | 'from' + | 'ft-option' + | 'ftand' + | 'ftnot' + | 'ftor' + | 'function' + | 'ge' + | 'get' + | 'gnode' + | 'greatest' + | 'group' + | 'grouping-separator' + | 'gt' + | 'idiv' + | 'if' + | 'import' + | 'in' + | 'infinity' + | 'inherit' + | 'insert' + | 'insensitive' + | 'instance' + | 'intersect' + | 'into' + | 'is' + | 'is-not' + | 'item' + | 'jnode' + | 'key' + | 'language' + | 'last' + | 'lax' + | 'le' + | 'least' + | 'let' + | 'levels' + | 'lowercase' + | 'lt' + | 'map' + | 'member' + | 'minus-sign' + | 'mod' + | 'modify' + | 'module' + | 'most' + | 'namespace' + | 'namespace-node' + | 'ne' + | 'next' + | 'no' + | 'no-inherit' + | 'no-preserve' + | 'node' + | 'nodes' + | 'not' + | 'occurs' + | 'of' + | 'only' + | 'option' + | 'or' + | 'order' + | 'ordered' + | 'ordering' + | 'otherwise' + | 'paragraph' + | 'paragraphs' + | 'parent' + | 'pattern-separator' + | 'per-mille' + | 'percent' + | 'phrase' + | 'precedes' + | 'precedes-or-is' + | 'preceding' + | 'preceding-or-self' + | 'preceding-sibling' + | 'preceding-sibling-or-self' + | 'preserve' + | 'previous' + | 'processing-instruction' + | 'record' + | 'relationship' + | 'rename' + | 'replace' + | 'return' + | 'revalidation' + | 'same' + | 'satisfies' + | 'schema' + | 'schema-attribute' + | 'schema-element' + | 'score' + | 'self' + | 'sensitive' + | 'sentence' + | 'sentences' + | 'skip' + | 'sliding' + | 'some' + | 'stable' + | 'start' + | 'stemming' + | 'stop' + | 'strict' + | 'strip' + | 'switch' + | 'text' + | 'then' + | 'thesaurus' + | 'times' + | 'to' + | 'treat' + | 'true' + | 'try' + | 'tumbling' + | 'type' + | 'typeswitch' + | 'union' + | 'unordered' + | 'update' + | 'uppercase' + | 'using' + | 'validate' + | 'value' + | 'variable' + | 'version' + | 'weight' + | 'when' + | 'where' + | 'while' + | 'wildcards' + | 'window' + | 'with' + | 'without' + | 'word' + | 'words' + | 'xquery' + | 'zero-digit' +URILiteral + ::= StringLiteral +Prolog ::= ( ( DefaultNamespaceDecl | Setter | NamespaceDecl | Import | FTOptionDecl ) Separator )* ( ( ContextValueDecl | VarDecl | FunctionDecl | ItemTypeDecl | NamedRecordTypeDecl | OptionDecl ) Separator )* +DefaultNamespaceDecl + ::= 'declare' 'fixed'? 'default' ( 'element' | 'function' ) 'namespace' URILiteral +Setter ::= BoundarySpaceDecl + | DefaultCollationDecl + | BaseURIDecl + | ConstructionDecl + | OrderingModeDecl + | EmptyOrderDecl + | CopyNamespacesDecl + | DecimalFormatDecl + | RevalidationDecl +RevalidationDecl + ::= 'declare' 'revalidation' ( 'strict' | 'lax' | 'skip' ) +BoundarySpaceDecl + ::= 'declare' 'boundary-space' ( 'preserve' | 'strip' ) +DefaultCollationDecl + ::= 'declare' 'default' 'collation' URILiteral +BaseURIDecl + ::= 'declare' 'base-uri' URILiteral +ConstructionDecl + ::= 'declare' 'construction' ( 'strip' | 'preserve' ) +OrderingModeDecl + ::= 'declare' 'ordering' ( 'ordered' | 'unordered' ) +EmptyOrderDecl + ::= 'declare' 'default' 'order' 'empty' ( 'greatest' | 'least' ) +CopyNamespacesDecl + ::= 'declare' 'copy-namespaces' PreserveMode ',' InheritMode +PreserveMode + ::= 'preserve' + | 'no-preserve' +InheritMode + ::= 'inherit' + | 'no-inherit' +DecimalFormatDecl + ::= 'declare' ( 'decimal-format' EQName | 'default' 'decimal-format' ) ( DFPropertyName '=' StringLiteral )* +EQName ::= QName + | URIQualifiedName +QName ::= UnreservedFunctionQName + | 'attribute' + | 'comment' + | 'document-node' + | 'element' + | 'namespace-node' + | 'node' + | 'processing-instruction' + | 'schema-attribute' + | 'schema-element' + | 'text' + | 'array' + | 'enum' + | 'fn' + | 'function' + | 'gnode' + | 'get' + | 'if' + | 'item' + | 'jnode' + | 'map' + | 'record' + | 'switch' + | 'type' + | 'typeswitch' +UnreservedFunctionQName + ::= UnreservedQName + | 'NaN' + | 'after' + | 'all' + | 'allowing' + | 'ancestor' + | 'ancestor-or-self' + | 'and' + | 'any' + | 'as' + | 'ascending' + | 'at' + | 'base-uri' + | 'before' + | 'boundary-space' + | 'by' + | 'case' + | 'cast' + | 'castable' + | 'catch' + | 'child' + | 'collation' + | 'construction' + | 'contains' + | 'content' + | 'context' + | 'copy' + | 'copy-namespaces' + | 'count' + | 'decimal-format' + | 'decimal-separator' + | 'declare' + | 'default' + | 'delete' + | 'descendant' + | 'descendant-or-self' + | 'descending' + | 'diacritics' + | 'different' + | 'digit' + | 'distance' + | 'div' + | 'document' + | 'else' + | 'empty' + | 'empty-sequence' + | 'encoding' + | 'end' + | 'entire' + | 'eq' + | 'every' + | 'exactly' + | 'except' + | 'exponent-separator' + | 'external' + | 'false' + | 'finally' + | 'first' + | 'fixed' + | 'following' + | 'following-or-self' + | 'following-sibling' + | 'following-sibling-or-self' + | 'follows' + | 'follows-or-is' + | 'for' + | 'from' + | 'ft-option' + | 'ftand' + | 'ftnot' + | 'ftor' + | 'ge' + | 'greatest' + | 'group' + | 'grouping-separator' + | 'gt' + | 'idiv' + | 'import' + | 'in' + | 'infinity' + | 'inherit' + | 'insert' + | 'insensitive' + | 'instance' + | 'intersect' + | 'into' + | 'is' + | 'is-not' + | 'key' + | 'language' + | 'last' + | 'lax' + | 'le' + | 'least' + | 'let' + | 'levels' + | 'lowercase' + | 'lt' + | 'member' + | 'minus-sign' + | 'mod' + | 'modify' + | 'module' + | 'most' + | 'namespace' + | 'ne' + | 'next' + | 'no' + | 'no-inherit' + | 'no-preserve' + | 'nodes' + | 'not' + | 'occurs' + | 'of' + | 'only' + | 'option' + | 'or' + | 'order' + | 'ordered' + | 'ordering' + | 'otherwise' + | 'paragraph' + | 'paragraphs' + | 'parent' + | 'pattern-separator' + | 'per-mille' + | 'percent' + | 'phrase' + | 'precedes' + | 'precedes-or-is' + | 'preceding' + | 'preceding-or-self' + | 'preceding-sibling' + | 'preceding-sibling-or-self' + | 'preserve' + | 'previous' + | 'relationship' + | 'rename' + | 'replace' + | 'return' + | 'revalidation' + | 'same' + | 'satisfies' + | 'schema' + | 'score' + | 'self' + | 'sensitive' + | 'sentence' + | 'sentences' + | 'skip' + | 'sliding' + | 'some' + | 'stable' + | 'start' + | 'stemming' + | 'stop' + | 'strict' + | 'strip' + | 'thesaurus' + | 'then' + | 'times' + | 'to' + | 'treat' + | 'true' + | 'try' + | 'tumbling' + | 'union' + | 'unordered' + | 'update' + | 'uppercase' + | 'using' + | 'validate' + | 'value' + | 'variable' + | 'version' + | 'weight' + | 'when' + | 'where' + | 'while' + | 'wildcards' + | 'window' + | 'with' + | 'without' + | 'word' + | 'words' + | 'xquery' + | 'zero-digit' +DFPropertyName + ::= 'decimal-separator' + | 'grouping-separator' + | 'infinity' + | 'minus-sign' + | 'NaN' + | 'percent' + | 'per-mille' + | 'zero-digit' + | 'digit' + | 'pattern-separator' + | 'exponent-separator' +NamespaceDecl + ::= 'declare' 'namespace' NCName '=' URILiteral +Import ::= SchemaImport + | ModuleImport +SchemaImport + ::= 'import' 'schema' SchemaPrefix? URILiteral ( 'at' URILiteral ( ',' URILiteral )* )? +SchemaPrefix + ::= 'namespace' NCName '=' + | 'fixed'? 'default' 'element' 'namespace' +ModuleImport + ::= 'import' 'module' ( 'namespace' NCName '=' )? URILiteral ( 'at' URILiteral ( ',' URILiteral )* )? +ContextValueDecl + ::= 'declare' 'context' ( 'value' ( 'as' SequenceType )? | 'item' ( 'as' ItemType )? ) ( ':=' VarValue | 'external' ( ':=' VarDefaultValue )? ) +SequenceType + ::= 'empty-sequence' '(' ')' + | ItemType ( OccurrenceIndicator / ) +ItemType ::= RegularItemType + | FunctionType + | TypeName + | ChoiceItemType +RegularItemType + ::= AnyItemTest + | NodeKindTest + | GNodeType + | JNodeType + | MapType + | ArrayType + | RecordType + | EnumerationType +AnyItemTest + ::= 'item' '(' ')' +NodeKindTest + ::= DocumentTest + | ElementTest + | AttributeTest + | SchemaElementTest + | SchemaAttributeTest + | PITest + | CommentTest + | TextTest + | NamespaceNodeTest + | AnyNodeKindTest +DocumentTest + ::= 'document-node' '(' ( ElementTest | SchemaElementTest | NameTestUnion )? ')' +ElementTest + ::= 'element' '(' ( NameTestUnion ( ',' TypeName '?'? )? )? ')' +NameTestUnion + ::= NameTest ( '|' NameTest )* +NameTest ::= EQName + | Wildcard +TypeName ::= EQName +SchemaElementTest + ::= 'schema-element' '(' ElementName ')' +ElementName + ::= EQName +AttributeTest + ::= 'attribute' '(' ( NameTestUnion ( ',' TypeName )? )? ')' +SchemaAttributeTest + ::= 'schema-attribute' '(' AttributeName ')' +AttributeName + ::= EQName +PITest ::= 'processing-instruction' '(' ( NCName | StringLiteral )? ')' +CommentTest + ::= 'comment' '(' ')' +TextTest ::= 'text' '(' ')' +NamespaceNodeTest + ::= 'namespace-node' '(' ')' +AnyNodeKindTest + ::= 'node' '(' ')' +GNodeType + ::= 'gnode' '(' ')' +JNodeType + ::= 'jnode' '(' ( ( '*' | JRootSelector | NCName | Constant ) ( ',' ( '*' | SequenceType ) )? )? ')' +JRootSelector + ::= '(' ')' +Constant ::= StringLiteral + | '-'? NumericLiteral + | QNameLiteral + | 'true' '(' ')' + | 'false' '(' ')' +NumericLiteral + ::= IntegerLiteral + | HexIntegerLiteral + | BinaryIntegerLiteral + | DecimalLiteral + | DoubleLiteral +QNameLiteral + ::= '#' EQName +MapType ::= AnyMapType + | TypedMapType +AnyMapType + ::= 'map' '(' '*' ')' +TypedMapType + ::= 'map' '(' ItemType ',' SequenceType ')' +ArrayType + ::= AnyArrayType + | TypedArrayType +AnyArrayType + ::= 'array' '(' '*' ')' +TypedArrayType + ::= 'array' '(' SequenceType ')' +RecordType + ::= 'record' '(' ( FieldDeclaration ( ',' FieldDeclaration )* )? ')' +FieldDeclaration + ::= FieldName '?'? ( 'as' SequenceType )? +FieldName + ::= NCName + | StringLiteral +EnumerationType + ::= 'enum' '(' StringLiteral ( ',' StringLiteral )* ')' +FunctionType + ::= Annotation* ( AnyFunctionType | TypedFunctionType ) +Annotation + ::= '%' EQName ( '(' Constant ( ',' Constant )* ')' )? +AnyFunctionType + ::= ( 'function' | 'fn' ) '(' '*' ')' +TypedFunctionType + ::= ( 'function' | 'fn' ) '(' ( TypedFunctionParam ( ',' TypedFunctionParam )* )? ')' 'as' SequenceType +TypedFunctionParam + ::= ( '$' EQName 'as' )? SequenceType +ChoiceItemType + ::= '(' ItemType ( '|' ItemType )* ')' +OccurrenceIndicator + ::= '?' + | '*' + | '+' +VarValue ::= ExprSingle +ExprSingle + ::= FLWORExpr + | QuantifiedExpr + | SwitchExpr + | TypeswitchExpr + | IfExpr + | TryCatchExpr + | InsertExpr + | DeleteExpr + | RenameExpr + | ReplaceExpr + | TransformExpr + | ExistUpdateExpr + | OrExpr +FLWORExpr + ::= InitialClause IntermediateClause* ReturnClause +InitialClause + ::= ForClause + | LetClause + | WindowClause +ForClause + ::= 'for' ForBinding ( ',' ForBinding )* +ForBinding + ::= ForItemBinding + | ForMemberBinding + | ForEntryBinding +ForItemBinding + ::= VarNameAndType AllowingEmpty? PositionalVar? FTScoreVar? 'in' ExprSingle +VarNameAndType + ::= '$' EQName TypeDeclaration? +TypeDeclaration + ::= 'as' SequenceType +AllowingEmpty + ::= 'allowing' 'empty' +PositionalVar + ::= 'at' VarName +VarName ::= '$' EQName +ForMemberBinding + ::= 'member' VarNameAndType PositionalVar? 'in' ExprSingle +ForEntryBinding + ::= ( ForEntryKeyBinding ForEntryValueBinding? | ForEntryValueBinding ) PositionalVar? 'in' ExprSingle +ForEntryKeyBinding + ::= 'key' VarNameAndType +ForEntryValueBinding + ::= 'value' VarNameAndType +LetClause + ::= 'let' LetBinding ( ',' LetBinding )* +LetBinding + ::= LetValueBinding + | LetSequenceBinding + | LetArrayBinding + | LetMapBinding + | FTScoreVar ':=' ExprSingle +LetValueBinding + ::= VarNameAndType ':=' ExprSingle +LetSequenceBinding + ::= '$' '(' VarNameAndType ( ',' VarNameAndType )* ')' TypeDeclaration? ':=' ExprSingle +LetArrayBinding + ::= '$' '[' VarNameAndType ( ',' VarNameAndType )* ']' TypeDeclaration? ':=' ExprSingle +LetMapBinding + ::= '$' '{' VarNameAndType ( ',' VarNameAndType )* '}' TypeDeclaration? ':=' ExprSingle +WindowClause + ::= 'for' ( TumblingWindowClause | SlidingWindowClause ) +TumblingWindowClause + ::= 'tumbling' 'window' VarNameAndType 'in' ExprSingle WindowStartCondition? WindowEndCondition? +WindowStartCondition + ::= 'start' WindowVars ( 'when' ExprSingle )? +WindowVars + ::= CurrentVar? PositionalVar? PreviousVar? NextVar? +CurrentVar + ::= VarName +PreviousVar + ::= 'previous' VarName +NextVar ::= 'next' VarName +WindowEndCondition + ::= 'only'? 'end' WindowVars ( 'when' ExprSingle )? +SlidingWindowClause + ::= 'sliding' 'window' VarNameAndType 'in' ExprSingle WindowStartCondition? WindowEndCondition +IntermediateClause + ::= InitialClause + | WhereClause + | WhileClause + | GroupByClause + | OrderByClause + | CountClause +WhereClause + ::= 'where' ExprSingle +WhileClause + ::= 'while' ExprSingle +GroupByClause + ::= 'group' 'by' GroupingSpec ( ',' GroupingSpec )* +GroupingSpec + ::= VarName ( TypeDeclaration? ':=' ExprSingle )? ( 'collation' URILiteral )? +OrderByClause + ::= 'stable'? 'order' 'by' OrderSpec ( ',' OrderSpec )* +OrderSpec + ::= ExprSingle OrderModifier +OrderModifier + ::= ( 'ascending' | 'descending' )? ( 'empty' ( 'greatest' | 'least' ) )? ( 'collation' URILiteral )? +CountClause + ::= 'count' VarName +ReturnClause + ::= 'return' ExprSingle +QuantifiedExpr + ::= ( 'some' | 'every' ) QuantifierBinding ( ',' QuantifierBinding )* 'satisfies' ExprSingle +QuantifierBinding + ::= VarNameAndType 'in' ExprSingle +SwitchExpr + ::= 'switch' SwitchComparand ( SwitchCases | BracedSwitchCases ) +SwitchComparand + ::= '(' Expr? ')' +Expr ::= ExprSingle ( ',' ExprSingle )* +SwitchCases + ::= SwitchCaseClause+ 'default' 'return' ExprSingle +SwitchCaseClause + ::= ( 'case' SwitchCaseOperand )+ 'return' ExprSingle +SwitchCaseOperand + ::= Expr +BracedSwitchCases + ::= '{' SwitchCases '}' +TypeswitchExpr + ::= 'typeswitch' '(' Expr ')' ( TypeswitchCases | BracedTypeswitchCases ) +TypeswitchCases + ::= CaseClause+ 'default' VarName? 'return' ExprSingle +CaseClause + ::= 'case' ( VarName 'as' )? SequenceTypeUnion 'return' ExprSingle +SequenceTypeUnion + ::= SequenceType ( '|' SequenceType )* +BracedTypeswitchCases + ::= '{' TypeswitchCases '}' +IfExpr ::= 'if' '(' Expr ')' ( UnbracedActions | BracedAction ) +UnbracedActions + ::= 'then' ExprSingle 'else' ExprSingle +BracedAction + ::= EnclosedExpr +EnclosedExpr + ::= '{' Expr? '}' +TryCatchExpr + ::= TryClause ( CatchClause+ FinallyClause? | FinallyClause ) +TryClause + ::= 'try' EnclosedExpr +CatchClause + ::= 'catch' NameTestUnion EnclosedExpr +FinallyClause + ::= 'finally' EnclosedExpr +InsertExpr + ::= 'insert' ( 'node' | 'nodes' ) SourceExpr InsertExprTargetChoice TargetExpr +InsertExprTargetChoice + ::= ( 'as' ( 'first' | 'last' ) )? 'into' + | 'after' + | 'before' +SourceExpr + ::= ExprSingle +TargetExpr + ::= ExprSingle +DeleteExpr + ::= 'delete' ( 'node' | 'nodes' ) TargetExpr +ReplaceExpr + ::= 'replace' ( 'value' 'of' )? 'node' TargetExpr 'with' ExprSingle +RenameExpr + ::= 'rename' 'node' TargetExpr 'as' NewNameExpr +NewNameExpr + ::= ExprSingle +TransformExpr + ::= 'copy' VarName ':=' ExprSingle ( ',' VarName ':=' ExprSingle )* 'modify' ExprSingle 'return' ExprSingle +ExistUpdateExpr + ::= 'update' ( ExistInsertExpr | ExistReplaceExpr | ExistValueExpr | ExistDeleteExpr | ExistRenameExpr ) +ExistInsertExpr + ::= 'insert' ExprSingle ( 'into' | 'following' | 'preceding' ) ExprSingle +ExistReplaceExpr + ::= 'replace' ExprSingle 'with' ExprSingle +ExistValueExpr + ::= 'value' ExprSingle 'with' ExprSingle +ExistDeleteExpr + ::= 'delete' ExprSingle +ExistRenameExpr + ::= 'rename' ExprSingle 'as' ExprSingle +OrExpr ::= AndExpr ( 'or' AndExpr )* +AndExpr ::= ComparisonExpr ( 'and' ComparisonExpr )* +ComparisonExpr + ::= FTContainsExpr ( ( ValueComp | GeneralComp | NodeComp ) FTContainsExpr )? +FTContainsExpr + ::= OtherwiseExpr ( 'contains' 'text' FTSelection FTIgnoreOption? )? +OtherwiseExpr + ::= StringConcatExpr ( 'otherwise' StringConcatExpr )* +StringConcatExpr + ::= RangeExpr ( '||' RangeExpr )* +RangeExpr + ::= AdditiveExpr ( 'to' AdditiveExpr )? +AdditiveExpr + ::= MultiplicativeExpr ( ( '+' | '-' ) MultiplicativeExpr )* +MultiplicativeExpr + ::= UnionExpr ( ( '*' | '×' | 'div' | '÷' | 'idiv' | 'mod' ) UnionExpr )* +UnionExpr + ::= IntersectExceptExpr ( ( 'union' | '|' ) IntersectExceptExpr )* +IntersectExceptExpr + ::= InstanceofExpr ( ( 'intersect' | 'except' ) InstanceofExpr )* +InstanceofExpr + ::= TreatExpr ( 'instance' 'of' SequenceType )? +TreatExpr + ::= CastableExpr ( 'treat' 'as' SequenceType )? +CastableExpr + ::= CastExpr ( 'castable' 'as' CastTarget '?'? )? +CastExpr ::= PipelineExpr ( 'cast' 'as' CastTarget '?'? )? +PipelineExpr + ::= ArrowExpr ( '->' ArrowExpr )* +ArrowExpr + ::= UnaryExpr ( SequenceArrowTarget | MappingArrowTarget )* +UnaryExpr + ::= ( '-' | '+' )* ValueExpr +ValueExpr + ::= ValidateExpr + | ExtensionExpr + | SimpleMapExpr +ValidateExpr + ::= 'validate' ( ValidationMode | 'type' TypeName )? '{' Expr '}' +ValidationMode + ::= 'lax' + | 'strict' +ExtensionExpr + ::= Pragma+ '{' Expr? '}' +Pragma ::= '(#' S EQName ( S PragmaContents )? '#)' + /* ws: explicit */ +SimpleMapExpr + ::= PathExpr ( '!' PathExpr )* +PathExpr ::= AbsolutePathExpr + | RelativePathExpr +AbsolutePathExpr + ::= '/' ( RelativePathExpr / ) + | '//' RelativePathExpr +RelativePathExpr + ::= StepExpr ( ( '/' | '//' ) StepExpr )* +StepExpr ::= PostfixExpr + | AxisStep +PostfixExpr + ::= PrimaryExpr ( Predicate | PositionalArgumentList | Lookup | MethodCallSuffix | FilterExprAMSuffix )* +PrimaryExpr + ::= Literal + | VarRef + | ParenthesizedExpr + | ContextValueRef + | FunctionCall + | OrderedExpr + | UnorderedExpr + | NodeConstructor + | FunctionItemExpr + | MapConstructor + | ArrayConstructor + | StringTemplate + | StringConstructor + | UnaryLookup +Literal ::= NumericLiteral + | StringLiteral + | QNameLiteral +VarRef ::= '$' EQName +ParenthesizedExpr + ::= '(' Expr? ')' +ContextValueRef + ::= '.' +FunctionCall + ::= UnreservedFunctionEQName ArgumentList +UnreservedFunctionEQName + ::= UnreservedFunctionQName + | URIQualifiedName +ArgumentList + ::= '(' ( PositionalArguments ( ',' KeywordArguments )? | KeywordArguments )? ')' +PositionalArguments + ::= Argument ( ',' Argument )* +Argument ::= ExprSingle + | ArgumentPlaceholder +ArgumentPlaceholder + ::= '?' +KeywordArguments + ::= KeywordArgument ( ',' KeywordArgument )* +KeywordArgument + ::= EQName ':=' Argument +OrderedExpr + ::= 'ordered' EnclosedExpr +UnorderedExpr + ::= 'unordered' EnclosedExpr +NodeConstructor + ::= DirectConstructor + | ComputedConstructor +DirectConstructor + ::= DirElemConstructor + | DirCommentConstructor + | DirPIConstructor +DirElemConstructor + ::= '<'^DirElemConstructor QName DirAttributeList ( '/>' | '>' DirElemContent* '' ) + /* ws: explicit */ +DirAttributeList + ::= ( S ( QName S? '=' S? DirAttributeValue )? )* + /* ws: explicit */ +DirAttributeValue + ::= '"' ( EscapeQuot | QuotAttrValueContent )* '"' + | "'" ( EscapeApos | AposAttrValueContent )* "'" + /* ws: explicit */ +QuotAttrValueContent + ::= QuotAttrContentChar + | CommonContent +CommonContent + ::= PredefinedEntityRef + | CharRef + | '{{' + | '}}' + | EnclosedExpr +AposAttrValueContent + ::= AposAttrContentChar + | CommonContent +DirElemContent + ::= DirectConstructor + | CDataSection + | CommonContent + | ElementContentChar +CDataSection + ::= '' + /* ws: explicit */ +DirCommentConstructor + ::= '' + /* ws: explicit */ +DirPIConstructor + ::= '' + /* ws: explicit */ +ComputedConstructor + ::= CompDocConstructor + | CompElemConstructor + | CompAttrConstructor + | CompNamespaceConstructor + | CompTextConstructor + | CompCommentConstructor + | CompPIConstructor +CompDocConstructor + ::= 'document' EnclosedExpr +CompElemConstructor + ::= 'element' CompNodeName EnclosedContentExpr +CompNodeName + ::= QNameLiteral + | UnreservedName + | '{' Expr '}' +UnreservedName + ::= UnreservedQName + | URIQualifiedName +EnclosedContentExpr + ::= EnclosedExpr +CompAttrConstructor + ::= 'attribute' CompNodeName EnclosedExpr +CompNamespaceConstructor + ::= 'namespace' CompNodeNCName EnclosedExpr +CompNodeNCName + ::= MarkedNCName + | UnreservedNCName + | '{' Expr '}' +MarkedNCName + ::= '#' NCName +CompTextConstructor + ::= 'text' EnclosedExpr +CompCommentConstructor + ::= 'comment' EnclosedExpr +CompPIConstructor + ::= 'processing-instruction' CompNodeNCName EnclosedExpr +FunctionItemExpr + ::= NamedFunctionRef + | InlineFunctionExpr +NamedFunctionRef + ::= UnreservedFunctionEQName '#' IntegerLiteral +InlineFunctionExpr + ::= Annotation* ( 'function' | 'fn' ) FunctionSignature? FunctionBody +FunctionSignature + ::= '(' ParamList ')' TypeDeclaration? +ParamList + ::= ( VarNameAndType ( ',' VarNameAndType )* )? +FunctionBody + ::= EnclosedExpr +MapConstructor + ::= 'map'? '{' ( MapConstructorEntry ( ',' MapConstructorEntry )* )? '}' +MapConstructorEntry + ::= ExprSingle ( ':' ExprSingle )? +ArrayConstructor + ::= SquareArrayConstructor + | CurlyArrayConstructor +SquareArrayConstructor + ::= '[' ( ExprSingle ( ',' ExprSingle )* )? ']' +CurlyArrayConstructor + ::= 'array' EnclosedExpr +StringTemplate + ::= '`' ( StringTemplateFixedPart | StringTemplateVariablePart )* '`' + /* ws: explicit */ +StringTemplateVariablePart + ::= EnclosedExpr +StringConstructor + ::= '``[' StringConstructorContent ']``' + /* ws: explicit */ +StringConstructorContent + ::= StringConstructorChars ( StringInterpolation StringConstructorChars )* + /* ws: explicit */ +StringInterpolation + ::= '`' EnclosedExpr '`' + /* ws: explicit */ +UnaryLookup + ::= Lookup +Lookup ::= '?' KeySpecifier +KeySpecifier + ::= NCName + | Literal + | ContextValueRef + | VarRef + | ParenthesizedExpr + | LookupWildcard +LookupWildcard + ::= '*' +Predicate + ::= '[' Expr ']' +PositionalArgumentList + ::= '(' PositionalArguments? ')' +MethodCallSuffix + ::= '=?>' NCName PositionalArgumentList +FilterExprAMSuffix + ::= '?[' Expr ']' +AxisStep ::= ( AbbreviatedStep | FullStep ) Predicate* +AbbreviatedStep + ::= '..' + | '@' NodeTest + | SimpleNodeTest +NodeTest ::= UnionNodeTest + | SimpleNodeTest +UnionNodeTest + ::= '(' SimpleNodeTest ( '|' SimpleNodeTest )* ')' +SimpleNodeTest + ::= TypeTest + | Selector +TypeTest ::= GNodeType + | NodeKindTest + | JNodeType +Selector ::= EQName + | Wildcard + | 'get' '(' ExprSingle ')' +FullStep ::= Axis NodeTest +Axis ::= ( 'ancestor' | 'ancestor-or-self' | 'attribute' | 'child' | 'descendant' | 'descendant-or-self' | 'following' | 'following-or-self' | 'following-sibling' | 'following-sibling-or-self' | 'parent' | 'preceding' | 'preceding-or-self' | 'preceding-sibling' | 'preceding-sibling-or-self' | 'self' ) '::' +SequenceArrowTarget + ::= '=>' ArrowTarget +ArrowTarget + ::= FunctionCall + | RestrictedDynamicCall +RestrictedDynamicCall + ::= ( VarRef | ParenthesizedExpr | FunctionItemExpr | MapConstructor | ArrayConstructor ) PositionalArgumentList +MappingArrowTarget + ::= '=!>' ArrowTarget +CastTarget + ::= TypeName + | ChoiceItemType + | EnumerationType +ValueComp + ::= 'eq' + | 'ne' + | 'lt' + | 'le' + | 'gt' + | 'ge' +GeneralComp + ::= '=' + | '!=' + | '<'^GeneralComp + | '<=' + | '>' + | '>=' +NodeComp ::= 'is' + | 'is-not' + | NodePrecedes + | NodeFollows + | 'precedes-or-is' + | 'follows-or-is' +NodePrecedes + ::= '<<' + | 'precedes' +NodeFollows + ::= '>>' + | 'follows' +VarDefaultValue + ::= ExprSingle +VarDecl ::= 'declare' Annotation* 'variable' VarNameAndType ( ':=' VarValue | 'external' ( ':=' VarDefaultValue )? ) +FunctionDecl + ::= 'declare' Annotation* 'function' UnreservedFunctionEQName '(' ParamListWithDefaults? ')' TypeDeclaration? ( FunctionBody | 'external' ) +ParamListWithDefaults + ::= ParamWithDefault ( ',' ParamWithDefault )* +ParamWithDefault + ::= VarNameAndType ( ':=' ExprSingle )? +ItemTypeDecl + ::= 'declare' Annotation* 'type' EQName 'as' ItemType +NamedRecordTypeDecl + ::= 'declare' Annotation* 'record' EQName '(' ( ExtendedFieldDeclaration ( ',' ExtendedFieldDeclaration )* )? ')' +ExtendedFieldDeclaration + ::= FieldDeclaration ( ':=' ExprSingle )? +OptionDecl + ::= 'declare' 'option' EQName StringLiteral +MainModule + ::= Prolog QueryBody +QueryBody + ::= Expr +FTOptionDecl + ::= 'declare' 'ft-option' FTMatchOptions +FTScoreVar + ::= 'score' VarName +FTSelection + ::= FTOr FTPosFilter* +FTWeight ::= 'weight' '{' Expr '}' +FTOr ::= FTAnd ( 'ftor' FTAnd )* +FTAnd ::= FTMildNot ( 'ftand' FTMildNot )* +FTMildNot + ::= FTUnaryNot ( 'not' 'in' FTUnaryNot )* +FTUnaryNot + ::= 'ftnot'? FTPrimaryWithOptions +FTPrimaryWithOptions + ::= FTPrimary FTMatchOptions? FTWeight? +FTPrimary + ::= FTWords FTTimes? + | '(' FTSelection ')' + | FTExtensionSelection +FTWords ::= FTWordsValue FTAnyallOption? +FTWordsValue + ::= StringLiteral + | '{' Expr '}' +FTExtensionSelection + ::= Pragma+ '{' FTSelection? '}' +FTAnyallOption + ::= 'any' 'word'? + | 'all' 'words'? + | 'phrase' +FTTimes ::= 'occurs' FTRange 'times' +FTRange ::= 'exactly' AdditiveExpr + | 'at' 'least' AdditiveExpr + | 'at' 'most' AdditiveExpr + | 'from' AdditiveExpr 'to' AdditiveExpr +FTPosFilter + ::= FTOrder + | FTWindow + | FTDistance + | FTScope + | FTContent +FTOrder ::= 'ordered' +FTWindow ::= 'window' AdditiveExpr FTUnit +FTDistance + ::= 'distance' FTRange FTUnit +FTUnit ::= 'words' + | 'sentences' + | 'paragraphs' +FTScope ::= ( 'same' | 'different' ) FTBigUnit +FTBigUnit + ::= 'sentence' + | 'paragraph' +FTContent + ::= 'at' 'start' + | 'at' 'end' + | 'entire' 'content' +FTMatchOptions + ::= ( 'using' FTMatchOption )+ +FTMatchOption + ::= FTLanguageOption + | FTWildCardOption + | FTThesaurusOption + | FTStemOption + | FTCaseOption + | FTDiacriticsOption + | FTStopWordOption + | FTExtensionOption +FTCaseOption + ::= 'case' 'insensitive' + | 'case' 'sensitive' + | 'lowercase' + | 'uppercase' +FTDiacriticsOption + ::= 'diacritics' 'insensitive' + | 'diacritics' 'sensitive' +FTStemOption + ::= 'stemming' + | 'no' 'stemming' +FTThesaurusOption + ::= 'thesaurus' ( FTThesaurusID | 'default' ) + | 'thesaurus' '(' ( FTThesaurusID | 'default' ) ( ',' FTThesaurusID )* ')' + | 'no' 'thesaurus' +FTThesaurusID + ::= 'at' URILiteral ( 'relationship' StringLiteral )? ( FTLiteralRange 'levels' )? +FTLiteralRange + ::= 'exactly' IntegerLiteral + | 'at' 'least' IntegerLiteral + | 'at' 'most' IntegerLiteral + | 'from' IntegerLiteral 'to' IntegerLiteral +FTStopWordOption + ::= 'stop' 'words' FTStopWords FTStopWordsInclExcl* + | 'stop' 'words' 'default' FTStopWordsInclExcl* + | 'no' 'stop' 'words' +FTStopWords + ::= 'at' URILiteral + | '(' StringLiteral ( ',' StringLiteral )* ')' +FTStopWordsInclExcl + ::= ( 'union' | 'except' ) FTStopWords +FTLanguageOption + ::= 'language' StringLiteral +FTWildCardOption + ::= 'wildcards' + | 'no' 'wildcards' +FTExtensionOption + ::= 'option' EQName StringLiteral +FTIgnoreOption + ::= 'without' 'content' UnionExpr +Whitespace + ::= S^WS + | Comment + /* ws: definition */ +Comment ::= '(:' ( CommentContents | Comment )* ':)' + /* ws: explicit */ + + + +EOF ::= $ +StringLiteral + ::= AposStringLiteral + | QuotStringLiteral + /* ws: explicit */ +AposStringLiteral + ::= "'" ( PredefinedEntityRef | CharRef | EscapeApos | [^'&] )* "'" + /* ws: explicit */ +PredefinedEntityRef + ::= '&' ( 'lt' | 'gt' | 'amp' | 'quot' | 'apos' ) ';' + /* ws: explicit */ +CharRef ::= '&#' [0-9]+ ';' + | '&#x' [0-9a-fA-F]+ ';' +EscapeApos + ::= "''" + /* ws: explicit */ +QuotStringLiteral + ::= '"' ( PredefinedEntityRef | CharRef | EscapeQuot | [^"&] )* '"' + /* ws: explicit */ +EscapeQuot + ::= '""' + /* ws: explicit */ +UnreservedNCName + ::= NCName - ReservedName +NCName ::= Name - ( Char* ':' Char* ) +Name ::= NameStartChar NameChar* +NameStartChar + ::= ':' + | [A-Z] + | '_' + | [a-z] + | [#xC0-#xD6] + | [#xD8-#xF6] + | [#xF8-#x2FF] + | [#x370-#x37D] + | [#x37F-#x1FFF] + | [#x200C-#x200D] + | [#x2070-#x218F] + | [#x2C00-#x2FEF] + | [#x3001-#xD7FF] + | [#xF900-#xFDCF] + | [#xFDF0-#xFFFD] + | [#x10000-#xEFFFF] +NameChar ::= NameStartChar + | '-' + | '.' + | [0-9] + | #xB7 + | [#x0300-#x036F] + | [#x203F-#x2040] +Char ::= #x9 + | #xA + | #xD + | [#x20-#xD7FF] + | [#xE000-#xFFFD] + | [#x10000-#x10FFFF] +ReservedName + ::= 'NaN' + | 'after' + | 'all' + | 'allowing' + | 'ancestor' + | 'ancestor-or-self' + | 'and' + | 'any' + | 'array' + | 'as' + | 'ascending' + | 'at' + | 'attribute' + | 'base-uri' + | 'before' + | 'boundary-space' + | 'by' + | 'case' + | 'cast' + | 'castable' + | 'catch' + | 'child' + | 'collation' + | 'comment' + | 'construction' + | 'contains' + | 'content' + | 'context' + | 'copy' + | 'copy-namespaces' + | 'count' + | 'decimal-format' + | 'decimal-separator' + | 'declare' + | 'default' + | 'delete' + | 'descendant' + | 'descendant-or-self' + | 'descending' + | 'diacritics' + | 'different' + | 'digit' + | 'distance' + | 'div' + | 'document' + | 'document-node' + | 'element' + | 'else' + | 'empty' + | 'empty-sequence' + | 'encoding' + | 'end' + | 'entire' + | 'enum' + | 'eq' + | 'every' + | 'exactly' + | 'except' + | 'exponent-separator' + | 'external' + | 'false' + | 'finally' + | 'first' + | 'fixed' + | 'fn' + | 'following' + | 'following-or-self' + | 'following-sibling' + | 'following-sibling-or-self' + | 'follows' + | 'follows-or-is' + | 'for' + | 'from' + | 'ft-option' + | 'ftand' + | 'ftnot' + | 'ftor' + | 'function' + | 'ge' + | 'get' + | 'gnode' + | 'greatest' + | 'group' + | 'grouping-separator' + | 'gt' + | 'idiv' + | 'if' + | 'import' + | 'in' + | 'infinity' + | 'inherit' + | 'insert' + | 'insensitive' + | 'instance' + | 'intersect' + | 'into' + | 'is' + | 'is-not' + | 'item' + | 'jnode' + | 'key' + | 'language' + | 'last' + | 'lax' + | 'le' + | 'least' + | 'let' + | 'levels' + | 'lowercase' + | 'lt' + | 'map' + | 'member' + | 'minus-sign' + | 'mod' + | 'modify' + | 'module' + | 'most' + | 'namespace' + | 'namespace-node' + | 'ne' + | 'next' + | 'no' + | 'no-inherit' + | 'no-preserve' + | 'node' + | 'nodes' + | 'not' + | 'occurs' + | 'of' + | 'only' + | 'option' + | 'or' + | 'order' + | 'ordered' + | 'ordering' + | 'otherwise' + | 'paragraph' + | 'paragraphs' + | 'parent' + | 'pattern-separator' + | 'per-mille' + | 'percent' + | 'phrase' + | 'precedes' + | 'precedes-or-is' + | 'preceding' + | 'preceding-or-self' + | 'preceding-sibling' + | 'preceding-sibling-or-self' + | 'preserve' + | 'previous' + | 'processing-instruction' + | 'record' + | 'relationship' + | 'rename' + | 'replace' + | 'return' + | 'revalidation' + | 'same' + | 'satisfies' + | 'schema' + | 'schema-attribute' + | 'schema-element' + | 'score' + | 'self' + | 'sensitive' + | 'sentence' + | 'sentences' + | 'skip' + | 'sliding' + | 'some' + | 'stable' + | 'start' + | 'stemming' + | 'stop' + | 'strict' + | 'strip' + | 'switch' + | 'text' + | 'then' + | 'thesaurus' + | 'times' + | 'to' + | 'treat' + | 'true' + | 'try' + | 'tumbling' + | 'type' + | 'typeswitch' + | 'union' + | 'unordered' + | 'update' + | 'uppercase' + | 'using' + | 'validate' + | 'value' + | 'variable' + | 'version' + | 'weight' + | 'when' + | 'where' + | 'while' + | 'wildcards' + | 'window' + | 'with' + | 'without' + | 'word' + | 'words' + | 'xquery' + | 'zero-digit' +URIQualifiedName + ::= BracedURILiteral ( NCName ':' )? NCName + /* ws: explicit */ +BracedURILiteral + ::= 'Q' '{' ( PredefinedEntityRef | CharRef | [^&{}] )* '}' + /* ws: explicit */ +UnreservedQName + ::= QName - ReservedName +QName ::= PrefixedName + | UnprefixedName +PrefixedName + ::= Prefix ':' LocalPart +Prefix ::= NCName +LocalPart + ::= NCName +UnprefixedName + ::= LocalPart +Wildcard ::= '*' + | NCName ':*' + | '*:' NCName + | BracedURILiteral '*' + /* ws: explicit */ +IntegerLiteral + ::= Digits + /* ws: explicit */ +Digits ::= DecDigit ( ( DecDigit | '_' )* DecDigit )? + /* ws: explicit */ +DecDigit ::= [0-9] + /* ws: explicit */ +HexIntegerLiteral + ::= '0x' HexDigits + /* ws: explicit */ +HexDigits + ::= HexDigit ( ( HexDigit | '_' )* HexDigit )? + /* ws: explicit */ +HexDigit ::= [0-9a-fA-F] + /* ws: explicit */ +BinaryIntegerLiteral + ::= '0b' BinaryDigits + /* ws: explicit */ +BinaryDigits + ::= BinaryDigit ( ( BinaryDigit | '_' )* BinaryDigit )? + /* ws: explicit */ +BinaryDigit + ::= [0-1] + /* ws: explicit */ +DecimalLiteral + ::= '.' Digits + | Digits '.' Digits? + /* ws: explicit */ +DoubleLiteral + ::= ( '.' Digits | Digits ( '.' Digits? )? ) [eE] [+#x2D]? Digits + /* ws: explicit */ +S ::= ( #x20 | #x9 | #xD | #xA )+ +PragmaContents + ::= Char* - ( Char* '#)' Char* ) & '#' +QuotAttrContentChar + ::= Char - ["{}<&] +AposAttrContentChar + ::= Char - ['{}<&] +ElementContentChar + ::= Char - [{}<&] +CDataSectionContents + ::= Char* - ( Char* ']]>' Char* ) & ']]' +DirCommentContents + ::= ( Char - '-' | '-' ( Char - '-' ) )* + /* ws: explicit */ +PITarget ::= NCName - ( ( 'X' | 'x' ) ( 'M' | 'm' ) ( 'L' | 'l' ) ) +DirPIContents + ::= Char* - ( Char* '?>' Char* ) & '?' +StringTemplateFixedPart + ::= ( Char - ( '{' | '}' | '`' ) | '{{' | '}}' | '``' )+ + /* ws: explicit */ +StringConstructorChars + ::= Char* - ( Char* ( '`{' | ']``' ) Char* ) & ( '`{' | ']`' ) +CommentContents + ::= ( Char+ - ( Char* ( '(:' | ':)' ) Char* ) ) - ( Char* '(' ) & ':' + | Char+ - ( Char* ( '(:' | ':)' ) Char* ) & '(' + /* ws: explicit */ +QNameOrKeywordDelimiter + ::= $ + | ':' + | Char - NameChar +NCNameDelimiter + ::= $ $ + | ( Char - NameChar ) ( $ | Char ) + | ':' ( Char - NameStartChar ) +NumericLiteralDelimiter + ::= QNameOrKeywordDelimiter + | '-' +GeneralCompDelimiter + ::= [^?] +DirElemConstructorDelimiter + ::= QName ( S QName S? '=' | S? [/>] ) +'*' << Wildcard +QNameOrKeywordDelimiter + \\ UnreservedQName 'NaN' 'after' 'all' 'allowing' 'ancestor' 'ancestor-or-self' 'and' 'any' 'array' 'as' 'ascending' 'at' 'attribute' 'base-uri' 'before' 'boundary-space' 'by' 'case' 'cast' 'castable' 'catch' 'child' 'collation' 'comment' 'construction' 'contains' 'content' 'context' 'copy' 'copy-namespaces' 'count' 'decimal-format' 'decimal-separator' 'declare' 'default' 'delete' 'descendant' 'descendant-or-self' 'descending' 'diacritics' 'different' 'digit' 'distance' 'div' 'document' 'document-node' 'element' 'else' 'empty' 'empty-sequence' 'encoding' 'end' 'entire' 'enum' 'eq' 'every' 'exactly' 'except' 'exponent-separator' 'external' 'false' 'finally' 'first' 'fixed' 'fn' 'following' 'following-or-self' 'following-sibling' 'following-sibling-or-self' 'follows' 'follows-or-is' 'for' 'from' 'ft-option' 'ftand' 'ftnot' 'ftor' 'function' 'ge' 'get' 'gnode' 'greatest' 'group' 'grouping-separator' 'gt' 'idiv' 'if' 'import' 'in' 'infinity' 'inherit' 'insert' 'insensitive' 'instance' 'intersect' 'into' 'is' 'is-not' 'item' 'jnode' 'key' 'language' 'last' 'lax' 'le' 'least' 'let' 'levels' 'lowercase' 'lt' 'map' 'member' 'minus-sign' 'mod' 'modify' 'module' 'most' 'namespace' 'namespace-node' 'ne' 'next' 'no' 'no-inherit' 'no-preserve' 'node' 'nodes' 'not' 'occurs' 'of' 'only' 'option' 'or' 'order' 'ordered' 'ordering' 'otherwise' 'paragraph' 'paragraphs' 'parent' 'pattern-separator' 'per-mille' 'percent' 'phrase' 'precedes' 'precedes-or-is' 'preceding' 'preceding-or-self' 'preceding-sibling' 'preceding-sibling-or-self' 'preserve' 'previous' 'processing-instruction' 'record' 'relationship' 'rename' 'replace' 'return' 'revalidation' 'same' 'satisfies' 'schema' 'schema-attribute' 'schema-element' 'score' 'self' 'sensitive' 'sentence' 'sentences' 'skip' 'sliding' 'some' 'stable' 'start' 'stemming' 'stop' 'strict' 'strip' 'switch' 'text' 'then' 'thesaurus' 'times' 'to' 'treat' 'true' 'try' 'tumbling' 'type' 'typeswitch' 'union' 'unordered' 'update' 'uppercase' 'using' 'validate' 'value' 'variable' 'version' 'weight' 'when' 'where' 'while' 'wildcards' 'window' 'with' 'without' 'word' 'words' 'xquery' 'zero-digit' +Char \\ '' ArrowExpr )* +ArrowExpr + ::= UnaryExpr ( SequenceArrowTarget | MappingArrowTarget )* +UnaryExpr + ::= ( '-' | '+' )* ValueExpr +ValueExpr + ::= ValidateExpr + | ExtensionExpr + | SimpleMapExpr +ValidateExpr + ::= 'validate' ( ValidationMode | 'type' TypeName )? '{' Expr '}' +ValidationMode + ::= 'lax' + | 'strict' +ExtensionExpr + ::= Pragma+ '{' Expr? '}' +Pragma ::= '(#' S EQName ( S PragmaContents )? '#)' + /* ws: explicit */ +SimpleMapExpr + ::= PathExpr ( '!' PathExpr )* +PathExpr ::= AbsolutePathExpr + | RelativePathExpr +AbsolutePathExpr + ::= '/' ( RelativePathExpr / ) + | '//' RelativePathExpr +RelativePathExpr + ::= StepExpr ( ( '/' | '//' ) StepExpr )* +StepExpr ::= PostfixExpr + | AxisStep +PostfixExpr + ::= PrimaryExpr ( Predicate | PositionalArgumentList | Lookup | MethodCallSuffix | FilterExprAMSuffix )* +PrimaryExpr + ::= Literal + | VarRef + | ParenthesizedExpr + | ContextValueRef + | FunctionCall + | OrderedExpr + | UnorderedExpr + | NodeConstructor + | FunctionItemExpr + | MapConstructor + | ArrayConstructor + | StringTemplate + | StringConstructor + | UnaryLookup +Literal ::= NumericLiteral + | StringLiteral + | QNameLiteral +VarRef ::= '$' EQName +ParenthesizedExpr + ::= '(' Expr? ')' +ContextValueRef + ::= '.' +FunctionCall + ::= UnreservedFunctionEQName ArgumentList +UnreservedFunctionEQName + ::= UnreservedFunctionQName + | URIQualifiedName +ArgumentList + ::= '(' ( PositionalArguments ( ',' KeywordArguments )? | KeywordArguments )? ')' +PositionalArguments + ::= Argument ( ',' Argument )* +Argument ::= ExprSingle + | ArgumentPlaceholder +ArgumentPlaceholder + ::= '?' +KeywordArguments + ::= KeywordArgument ( ',' KeywordArgument )* +KeywordArgument + ::= EQName ':=' Argument +OrderedExpr + ::= 'ordered' EnclosedExpr +UnorderedExpr + ::= 'unordered' EnclosedExpr +NodeConstructor + ::= DirectConstructor + | ComputedConstructor +DirectConstructor + ::= DirElemConstructor + | DirCommentConstructor + | DirPIConstructor +DirElemConstructor + ::= '<'^DirElemConstructor QName DirAttributeList ( '/>' | '>' DirElemContent* '' ) + /* ws: explicit */ +DirAttributeList + ::= ( S ( QName S? '=' S? DirAttributeValue )? )* + /* ws: explicit */ +DirAttributeValue + ::= '"' ( EscapeQuot | QuotAttrValueContent )* '"' + | "'" ( EscapeApos | AposAttrValueContent )* "'" + /* ws: explicit */ +QuotAttrValueContent + ::= QuotAttrContentChar + | CommonContent +CommonContent + ::= PredefinedEntityRef + | CharRef + | '{{' + | '}}' + | EnclosedExpr +AposAttrValueContent + ::= AposAttrContentChar + | CommonContent +DirElemContent + ::= DirectConstructor + | CDataSection + | CommonContent + | ElementContentChar +CDataSection + ::= '' + /* ws: explicit */ +DirCommentConstructor + ::= '' + /* ws: explicit */ +DirPIConstructor + ::= '' + /* ws: explicit */ +ComputedConstructor + ::= CompDocConstructor + | CompElemConstructor + | CompAttrConstructor + | CompNamespaceConstructor + | CompTextConstructor + | CompCommentConstructor + | CompPIConstructor +CompDocConstructor + ::= 'document' EnclosedExpr +CompElemConstructor + ::= 'element' CompNodeName EnclosedContentExpr +CompNodeName + ::= QNameLiteral + | UnreservedName + | '{' Expr '}' +UnreservedName + ::= UnreservedQName + | URIQualifiedName +EnclosedContentExpr + ::= EnclosedExpr +CompAttrConstructor + ::= 'attribute' CompNodeName EnclosedExpr +CompNamespaceConstructor + ::= 'namespace' CompNodeNCName EnclosedExpr +CompNodeNCName + ::= MarkedNCName + | UnreservedNCName + | '{' Expr '}' +MarkedNCName + ::= '#' NCName +CompTextConstructor + ::= 'text' EnclosedExpr +CompCommentConstructor + ::= 'comment' EnclosedExpr +CompPIConstructor + ::= 'processing-instruction' CompNodeNCName EnclosedExpr +FunctionItemExpr + ::= NamedFunctionRef + | InlineFunctionExpr +NamedFunctionRef + ::= UnreservedFunctionEQName '#' IntegerLiteral +InlineFunctionExpr + ::= Annotation* ( 'function' | 'fn' ) FunctionSignature? FunctionBody +FunctionSignature + ::= '(' ParamList ')' TypeDeclaration? +ParamList + ::= ( VarNameAndType ( ',' VarNameAndType )* )? +FunctionBody + ::= EnclosedExpr +MapConstructor + ::= 'map'? '{' ( MapConstructorEntry ( ',' MapConstructorEntry )* )? '}' +MapConstructorEntry + ::= ExprSingle ( ':' ExprSingle )? +ArrayConstructor + ::= SquareArrayConstructor + | CurlyArrayConstructor +SquareArrayConstructor + ::= '[' ( ExprSingle ( ',' ExprSingle )* )? ']' +CurlyArrayConstructor + ::= 'array' EnclosedExpr +StringTemplate + ::= '`' ( StringTemplateFixedPart | StringTemplateVariablePart )* '`' + /* ws: explicit */ +StringTemplateVariablePart + ::= EnclosedExpr +StringConstructor + ::= '``[' StringConstructorContent ']``' + /* ws: explicit */ +StringConstructorContent + ::= StringConstructorChars ( StringInterpolation StringConstructorChars )* + /* ws: explicit */ +StringInterpolation + ::= '`' EnclosedExpr '`' + /* ws: explicit */ +UnaryLookup + ::= Lookup +Lookup ::= '?' KeySpecifier +KeySpecifier + ::= NCName + | Literal + | ContextValueRef + | VarRef + | ParenthesizedExpr + | LookupWildcard +LookupWildcard + ::= '*' +Predicate + ::= '[' Expr ']' +PositionalArgumentList + ::= '(' PositionalArguments? ')' +MethodCallSuffix + ::= '=?>' NCName PositionalArgumentList +FilterExprAMSuffix + ::= '?[' Expr ']' +AxisStep ::= ( AbbreviatedStep | FullStep ) Predicate* +AbbreviatedStep + ::= '..' + | '@' NodeTest + | SimpleNodeTest +NodeTest ::= UnionNodeTest + | SimpleNodeTest +UnionNodeTest + ::= '(' SimpleNodeTest ( '|' SimpleNodeTest )* ')' +SimpleNodeTest + ::= TypeTest + | Selector +TypeTest ::= GNodeType + | NodeKindTest + | JNodeType +Selector ::= EQName + | Wildcard + | 'get' '(' ExprSingle ')' +FullStep ::= Axis NodeTest +Axis ::= ( 'ancestor' | 'ancestor-or-self' | 'attribute' | 'child' | 'descendant' | 'descendant-or-self' | 'following' | 'following-or-self' | 'following-sibling' | 'following-sibling-or-self' | 'parent' | 'preceding' | 'preceding-or-self' | 'preceding-sibling' | 'preceding-sibling-or-self' | 'self' ) '::' +SequenceArrowTarget + ::= '=>' ArrowTarget +ArrowTarget + ::= FunctionCall + | RestrictedDynamicCall +RestrictedDynamicCall + ::= ( VarRef | ParenthesizedExpr | FunctionItemExpr | MapConstructor | ArrayConstructor ) PositionalArgumentList +MappingArrowTarget + ::= '=!>' ArrowTarget +CastTarget + ::= TypeName + | ChoiceItemType + | EnumerationType +ValueComp + ::= 'eq' + | 'ne' + | 'lt' + | 'le' + | 'gt' + | 'ge' +GeneralComp + ::= '=' + | '!=' + | '<'^GeneralComp + | '<=' + | '>' + | '>=' +NodeComp ::= 'is' + | 'is-not' + | NodePrecedes + | NodeFollows + | 'precedes-or-is' + | 'follows-or-is' +NodePrecedes + ::= '<<' + | 'precedes' +NodeFollows + ::= '>>' + | 'follows' +VarDefaultValue + ::= ExprSingle +VarDecl ::= 'declare' Annotation* 'variable' VarNameAndType ( ':=' VarValue | 'external' ( ':=' VarDefaultValue )? ) +FunctionDecl + ::= 'declare' Annotation* 'function' UnreservedFunctionEQName '(' ParamListWithDefaults? ')' TypeDeclaration? ( FunctionBody | 'external' ) +ParamListWithDefaults + ::= ParamWithDefault ( ',' ParamWithDefault )* +ParamWithDefault + ::= VarNameAndType ( ':=' ExprSingle )? +ItemTypeDecl + ::= 'declare' Annotation* 'type' EQName 'as' ItemType +NamedRecordTypeDecl + ::= 'declare' Annotation* 'record' EQName '(' ( ExtendedFieldDeclaration ( ',' ExtendedFieldDeclaration )* )? ')' +ExtendedFieldDeclaration + ::= FieldDeclaration ( ':=' ExprSingle )? +OptionDecl + ::= 'declare' 'option' EQName StringLiteral +MainModule + ::= Prolog QueryBody +QueryBody + ::= Expr +Whitespace + ::= S^WS + | Comment + /* ws: definition */ +Comment ::= '(:' ( CommentContents | Comment )* ':)' + /* ws: explicit */ + + + +EOF ::= $ +StringLiteral + ::= AposStringLiteral + | QuotStringLiteral + /* ws: explicit */ +AposStringLiteral + ::= "'" ( PredefinedEntityRef | CharRef | EscapeApos | [^'&] )* "'" + /* ws: explicit */ +PredefinedEntityRef + ::= '&' ( 'lt' | 'gt' | 'amp' | 'quot' | 'apos' ) ';' + /* ws: explicit */ +CharRef ::= '&#' [0-9]+ ';' + | '&#x' [0-9a-fA-F]+ ';' +EscapeApos + ::= "''" + /* ws: explicit */ +QuotStringLiteral + ::= '"' ( PredefinedEntityRef | CharRef | EscapeQuot | [^"&] )* '"' + /* ws: explicit */ +EscapeQuot + ::= '""' + /* ws: explicit */ +UnreservedNCName + ::= NCName - ReservedName +NCName ::= Name - ( Char* ':' Char* ) +Name ::= NameStartChar NameChar* +NameStartChar + ::= ':' + | [A-Z] + | '_' + | [a-z] + | [#xC0-#xD6] + | [#xD8-#xF6] + | [#xF8-#x2FF] + | [#x370-#x37D] + | [#x37F-#x1FFF] + | [#x200C-#x200D] + | [#x2070-#x218F] + | [#x2C00-#x2FEF] + | [#x3001-#xD7FF] + | [#xF900-#xFDCF] + | [#xFDF0-#xFFFD] + | [#x10000-#xEFFFF] +NameChar ::= NameStartChar + | '-' + | '.' + | [0-9] + | #xB7 + | [#x0300-#x036F] + | [#x203F-#x2040] +Char ::= #x9 + | #xA + | #xD + | [#x20-#xD7FF] + | [#xE000-#xFFFD] + | [#x10000-#x10FFFF] +ReservedName + ::= 'NaN' + | 'allowing' + | 'ancestor' + | 'ancestor-or-self' + | 'and' + | 'array' + | 'as' + | 'ascending' + | 'at' + | 'attribute' + | 'base-uri' + | 'boundary-space' + | 'by' + | 'case' + | 'cast' + | 'castable' + | 'catch' + | 'child' + | 'collation' + | 'comment' + | 'construction' + | 'context' + | 'copy-namespaces' + | 'count' + | 'decimal-format' + | 'decimal-separator' + | 'declare' + | 'default' + | 'descendant' + | 'descendant-or-self' + | 'descending' + | 'digit' + | 'div' + | 'document' + | 'document-node' + | 'element' + | 'else' + | 'empty' + | 'empty-sequence' + | 'encoding' + | 'end' + | 'enum' + | 'eq' + | 'every' + | 'except' + | 'exponent-separator' + | 'external' + | 'false' + | 'finally' + | 'fixed' + | 'fn' + | 'following' + | 'following-or-self' + | 'following-sibling' + | 'following-sibling-or-self' + | 'follows' + | 'follows-or-is' + | 'for' + | 'function' + | 'ge' + | 'get' + | 'gnode' + | 'greatest' + | 'group' + | 'grouping-separator' + | 'gt' + | 'idiv' + | 'if' + | 'import' + | 'in' + | 'infinity' + | 'inherit' + | 'instance' + | 'intersect' + | 'is' + | 'is-not' + | 'item' + | 'jnode' + | 'key' + | 'lax' + | 'le' + | 'least' + | 'let' + | 'lt' + | 'map' + | 'member' + | 'minus-sign' + | 'mod' + | 'module' + | 'namespace' + | 'namespace-node' + | 'ne' + | 'next' + | 'no-inherit' + | 'no-preserve' + | 'node' + | 'of' + | 'only' + | 'option' + | 'or' + | 'order' + | 'ordered' + | 'ordering' + | 'otherwise' + | 'parent' + | 'pattern-separator' + | 'per-mille' + | 'percent' + | 'precedes' + | 'precedes-or-is' + | 'preceding' + | 'preceding-or-self' + | 'preceding-sibling' + | 'preceding-sibling-or-self' + | 'preserve' + | 'previous' + | 'processing-instruction' + | 'record' + | 'return' + | 'satisfies' + | 'schema' + | 'schema-attribute' + | 'schema-element' + | 'self' + | 'sliding' + | 'some' + | 'stable' + | 'start' + | 'strict' + | 'strip' + | 'switch' + | 'text' + | 'then' + | 'to' + | 'treat' + | 'true' + | 'try' + | 'tumbling' + | 'type' + | 'typeswitch' + | 'union' + | 'unordered' + | 'validate' + | 'value' + | 'variable' + | 'version' + | 'when' + | 'where' + | 'while' + | 'window' + | 'xquery' + | 'zero-digit' +URIQualifiedName + ::= BracedURILiteral ( NCName ':' )? NCName + /* ws: explicit */ +BracedURILiteral + ::= 'Q' '{' ( PredefinedEntityRef | CharRef | [^&{}] )* '}' + /* ws: explicit */ +UnreservedQName + ::= QName - ReservedName +QName ::= PrefixedName + | UnprefixedName +PrefixedName + ::= Prefix ':' LocalPart +Prefix ::= NCName +LocalPart + ::= NCName +UnprefixedName + ::= LocalPart +Wildcard ::= '*' + | NCName ':*' + | '*:' NCName + | BracedURILiteral '*' + /* ws: explicit */ +IntegerLiteral + ::= Digits + /* ws: explicit */ +Digits ::= DecDigit ( ( DecDigit | '_' )* DecDigit )? + /* ws: explicit */ +DecDigit ::= [0-9] + /* ws: explicit */ +HexIntegerLiteral + ::= '0x' HexDigits + /* ws: explicit */ +HexDigits + ::= HexDigit ( ( HexDigit | '_' )* HexDigit )? + /* ws: explicit */ +HexDigit ::= [0-9a-fA-F] + /* ws: explicit */ +BinaryIntegerLiteral + ::= '0b' BinaryDigits + /* ws: explicit */ +BinaryDigits + ::= BinaryDigit ( ( BinaryDigit | '_' )* BinaryDigit )? + /* ws: explicit */ +BinaryDigit + ::= [0-1] + /* ws: explicit */ +DecimalLiteral + ::= '.' Digits + | Digits '.' Digits? + /* ws: explicit */ +DoubleLiteral + ::= ( '.' Digits | Digits ( '.' Digits? )? ) [eE] [+#x2D]? Digits + /* ws: explicit */ +S ::= ( #x20 | #x9 | #xD | #xA )+ +PragmaContents + ::= Char* - ( Char* '#)' Char* ) & '#' +QuotAttrContentChar + ::= Char - ["{}<&] +AposAttrContentChar + ::= Char - ['{}<&] +ElementContentChar + ::= Char - [{}<&] +CDataSectionContents + ::= Char* - ( Char* ']]>' Char* ) & ']]' +DirCommentContents + ::= ( Char - '-' | '-' ( Char - '-' ) )* + /* ws: explicit */ +PITarget ::= NCName - ( ( 'X' | 'x' ) ( 'M' | 'm' ) ( 'L' | 'l' ) ) +DirPIContents + ::= Char* - ( Char* '?>' Char* ) & '?' +StringTemplateFixedPart + ::= ( Char - ( '{' | '}' | '`' ) | '{{' | '}}' | '``' )+ + /* ws: explicit */ +StringConstructorChars + ::= Char* - ( Char* ( '`{' | ']``' ) Char* ) & ( '`{' | ']`' ) +CommentContents + ::= ( Char+ - ( Char* ( '(:' | ':)' ) Char* ) ) - ( Char* '(' ) & ':' + | Char+ - ( Char* ( '(:' | ':)' ) Char* ) & '(' + /* ws: explicit */ +QNameOrKeywordDelimiter + ::= $ + | ':' + | Char - NameChar +NCNameDelimiter + ::= $ $ + | ( Char - NameChar ) ( $ | Char ) + | ':' ( Char - NameStartChar ) +NumericLiteralDelimiter + ::= QNameOrKeywordDelimiter + | '-' +GeneralCompDelimiter + ::= [^?] +DirElemConstructorDelimiter + ::= QName ( S QName S? '=' | S? [/>] ) +'*' << Wildcard +QNameOrKeywordDelimiter + \\ UnreservedQName 'NaN' 'allowing' 'ancestor' 'ancestor-or-self' 'and' 'array' 'as' 'ascending' 'at' 'attribute' 'base-uri' 'boundary-space' 'by' 'case' 'cast' 'castable' 'catch' 'child' 'collation' 'comment' 'construction' 'context' 'copy-namespaces' 'count' 'decimal-format' 'decimal-separator' 'declare' 'default' 'descendant' 'descendant-or-self' 'descending' 'digit' 'div' 'document' 'document-node' 'element' 'else' 'empty' 'empty-sequence' 'encoding' 'end' 'enum' 'eq' 'every' 'except' 'exponent-separator' 'external' 'false' 'finally' 'fixed' 'fn' 'following' 'following-or-self' 'following-sibling' 'following-sibling-or-self' 'follows' 'follows-or-is' 'for' 'function' 'ge' 'get' 'gnode' 'greatest' 'group' 'grouping-separator' 'gt' 'idiv' 'if' 'import' 'in' 'infinity' 'inherit' 'instance' 'intersect' 'is' 'is-not' 'item' 'jnode' 'key' 'lax' 'le' 'least' 'let' 'lt' 'map' 'member' 'minus-sign' 'mod' 'module' 'namespace' 'namespace-node' 'ne' 'next' 'no-inherit' 'no-preserve' 'node' 'of' 'only' 'option' 'or' 'order' 'ordered' 'ordering' 'otherwise' 'parent' 'pattern-separator' 'per-mille' 'percent' 'precedes' 'precedes-or-is' 'preceding' 'preceding-or-self' 'preceding-sibling' 'preceding-sibling-or-self' 'preserve' 'previous' 'processing-instruction' 'record' 'return' 'satisfies' 'schema' 'schema-attribute' 'schema-element' 'self' 'sliding' 'some' 'stable' 'start' 'strict' 'strip' 'switch' 'text' 'then' 'to' 'treat' 'true' 'try' 'tumbling' 'type' 'typeswitch' 'union' 'unordered' 'validate' 'value' 'variable' 'version' 'when' 'where' 'while' 'window' 'xquery' 'zero-digit' +Char \\ '' + | '>=' +ValueComp + ::= 'eq' + | 'ne' + | 'lt' + | 'le' + | 'gt' + | 'ge' +NodeComp ::= 'is' + | '<<' + | '>>' +ValidateExpr + ::= 'validate' ( ValidationMode | 'type' TypeName )? '{' Expr '}' +ValidationMode + ::= 'lax' + | 'strict' +ExtensionExpr + ::= Pragma+ '{' Expr? '}' +Pragma ::= '(#' S? EQName ( S PragmaContents )? '#)' + /* ws: explicit */ +SimpleMapExpr + ::= PathExpr ( '!' PathExpr )* +PathExpr ::= '/' ( RelativePathExpr / ) + | '//' RelativePathExpr + | RelativePathExpr +RelativePathExpr + ::= StepExpr ( ( '/' | '//' ) StepExpr )* +StepExpr ::= PostfixExpr + | AxisStep +AxisStep ::= ( ReverseStep | ForwardStep ) PredicateList +ForwardStep + ::= ForwardAxis NodeTest + | AbbrevForwardStep +ForwardAxis + ::= 'child' '::' + | 'descendant' '::' + | 'attribute' '::' + | 'self' '::' + | 'descendant-or-self' '::' + | 'following-sibling' '::' + | 'following' '::' +AbbrevForwardStep + ::= '@'? NodeTest +ReverseStep + ::= ReverseAxis NodeTest + | AbbrevReverseStep +ReverseAxis + ::= 'parent' '::' + | 'ancestor' '::' + | 'preceding-sibling' '::' + | 'preceding' '::' + | 'ancestor-or-self' '::' +AbbrevReverseStep + ::= '..' +NodeTest ::= KindTest + | NameTest +NameTest ::= EQName + | Wildcard +PostfixExpr + ::= PrimaryExpr ( Predicate | ArgumentList )* +ArgumentList + ::= '(' ( Argument ( ',' Argument )* )? ')' +PredicateList + ::= Predicate* +Predicate + ::= '[' Expr ']' +PrimaryExpr + ::= Literal + | VarRef + | ParenthesizedExpr + | ContextItemExpr + | FunctionCall + | OrderedExpr + | UnorderedExpr + | Constructor + | FunctionItemExpr +Literal ::= NumericLiteral + | StringLiteral +NumericLiteral + ::= IntegerLiteral + | DecimalLiteral + | DoubleLiteral +VarRef ::= '$' VarName +VarName ::= EQName +ParenthesizedExpr + ::= '(' Expr? ')' +ContextItemExpr + ::= '.' +OrderedExpr + ::= 'ordered' '{' Expr '}' +UnorderedExpr + ::= 'unordered' '{' Expr '}' +FunctionCall + ::= FunctionEQName ArgumentList +Argument ::= ExprSingle + | ArgumentPlaceholder +ArgumentPlaceholder + ::= '?' +Constructor + ::= DirectConstructor + | ComputedConstructor +DirectConstructor + ::= DirElemConstructor + | DirCommentConstructor + | DirPIConstructor +DirElemConstructor + ::= '<' QName DirAttributeList ( '/>' | '>' DirElemContent* '' ) + /* ws: explicit */ +DirAttributeList + ::= ( S ( QName S? '=' S? DirAttributeValue )? )* + /* ws: explicit */ +DirAttributeValue + ::= '"' ( EscapeQuot | QuotAttrValueContent )* '"' + | "'" ( EscapeApos | AposAttrValueContent )* "'" + /* ws: explicit */ +QuotAttrValueContent + ::= QuotAttrContentChar + | CommonContent +AposAttrValueContent + ::= AposAttrContentChar + | CommonContent +DirElemContent + ::= DirectConstructor + | CDataSection + | CommonContent + | ElementContentChar +CommonContent + ::= PredefinedEntityRef + | CharRef + | '{{' + | '}}' + | EnclosedExpr +DirCommentConstructor + ::= '' + /* ws: explicit */ +DirPIConstructor + ::= '' + /* ws: explicit */ +CDataSection + ::= '' + /* ws: explicit */ +ComputedConstructor + ::= CompDocConstructor + | CompElemConstructor + | CompAttrConstructor + | CompNamespaceConstructor + | CompTextConstructor + | CompCommentConstructor + | CompPIConstructor +CompDocConstructor + ::= 'document' '{' Expr '}' +CompElemConstructor + ::= 'element' ( EQName | '{' Expr '}' ) '{' ContentExpr? '}' +ContentExpr + ::= Expr +CompAttrConstructor + ::= 'attribute' ( EQName | '{' Expr '}' ) '{' Expr? '}' +CompNamespaceConstructor + ::= 'namespace' ( Prefix | '{' PrefixExpr '}' ) '{' URIExpr '}' +Prefix ::= NCName +PrefixExpr + ::= Expr +URIExpr ::= Expr +CompTextConstructor + ::= 'text' '{' Expr '}' +CompCommentConstructor + ::= 'comment' '{' Expr '}' +CompPIConstructor + ::= 'processing-instruction' ( NCName | '{' Expr '}' ) '{' Expr? '}' +FunctionItemExpr + ::= NamedFunctionRef + | InlineFunctionExpr +NamedFunctionRef + ::= EQName '#' IntegerLiteral +InlineFunctionExpr + ::= Annotation* 'function' '(' ParamList? ')' ( 'as' SequenceType )? FunctionBody +SingleType + ::= SimpleTypeName '?'? +TypeDeclaration + ::= 'as' SequenceType +SequenceType + ::= 'empty-sequence' '(' ')' + | ItemType ( OccurrenceIndicator / ) +OccurrenceIndicator + ::= '?' + | '*' + | '+' +ItemType ::= KindTest + | 'item' '(' ')' + | FunctionTest + | AtomicOrUnionType + | ParenthesizedItemType +AtomicOrUnionType + ::= EQName +KindTest ::= DocumentTest + | ElementTest + | AttributeTest + | SchemaElementTest + | SchemaAttributeTest + | PITest + | CommentTest + | TextTest + | NamespaceNodeTest + | AnyKindTest +AnyKindTest + ::= 'node' '(' ')' +DocumentTest + ::= 'document-node' '(' ( ElementTest | SchemaElementTest )? ')' +TextTest ::= 'text' '(' ')' +CommentTest + ::= 'comment' '(' ')' +NamespaceNodeTest + ::= 'namespace-node' '(' ')' +PITest ::= 'processing-instruction' '(' ( NCName | StringLiteral )? ')' +AttributeTest + ::= 'attribute' '(' ( AttribNameOrWildcard ( ',' TypeName )? )? ')' +AttribNameOrWildcard + ::= AttributeName + | '*' +SchemaAttributeTest + ::= 'schema-attribute' '(' AttributeDeclaration ')' +AttributeDeclaration + ::= AttributeName +ElementTest + ::= 'element' '(' ( ElementNameOrWildcard ( ',' TypeName '?'? )? )? ')' +ElementNameOrWildcard + ::= ElementName + | '*' +SchemaElementTest + ::= 'schema-element' '(' ElementDeclaration ')' +ElementDeclaration + ::= ElementName +AttributeName + ::= EQName +ElementName + ::= EQName +SimpleTypeName + ::= TypeName +TypeName ::= EQName +FunctionTest + ::= Annotation* ( AnyFunctionTest | TypedFunctionTest ) +AnyFunctionTest + ::= 'function' '(' '*' ')' +TypedFunctionTest + ::= 'function' '(' ( SequenceType ( ',' SequenceType )* )? ')' 'as' SequenceType +ParenthesizedItemType + ::= '(' ItemType ')' +URILiteral + ::= StringLiteral +RevalidationDecl + ::= 'declare' 'revalidation' ( 'strict' | 'lax' | 'skip' ) +InsertExprTargetChoice + ::= ( 'as' ( 'first' | 'last' ) )? 'into' + | 'after' + | 'before' +InsertExpr + ::= 'insert' ( 'node' | 'nodes' ) SourceExpr InsertExprTargetChoice TargetExpr +DeleteExpr + ::= 'delete' ( 'node' | 'nodes' ) TargetExpr +ReplaceExpr + ::= 'replace' ( 'value' 'of' )? 'node' TargetExpr 'with' ExprSingle +RenameExpr + ::= 'rename' 'node' TargetExpr 'as' NewNameExpr +SourceExpr + ::= ExprSingle +TargetExpr + ::= ExprSingle +NewNameExpr + ::= ExprSingle +UpdatingFunctionCall + ::= 'invoke' 'updating' PrimaryExpr '(' ( ExprSingle ( ',' ExprSingle )* )? ')' +CopyModifyExpr + ::= 'copy' '$' VarName ':=' ExprSingle ( ',' '$' VarName ':=' ExprSingle )* 'modify' ExprSingle 'return' ExprSingle +EQName ::= QName + | URIQualifiedName +Whitespace + ::= S^WS + | Comment + /* ws: definition */ +Comment ::= '(:' ( CommentContents | Comment )* ':)' + /* ws: explicit */ +FunctionEQName + ::= FunctionName + | URIQualifiedName +QName ::= FunctionName + | 'attribute' + | 'comment' + | 'document-node' + | 'element' + | 'empty-sequence' + | 'function' + | 'if' + | 'item' + | 'namespace-node' + | 'node' + | 'processing-instruction' + | 'schema-attribute' + | 'schema-element' + | 'switch' + | 'text' + | 'typeswitch' +FunctionName + ::= QName^Token + | 'after' + | 'ancestor' + | 'ancestor-or-self' + | 'and' + | 'as' + | 'ascending' + | 'before' + | 'case' + | 'cast' + | 'castable' + | 'child' + | 'collation' + | 'copy' + | 'count' + | 'declare' + | 'default' + | 'delete' + | 'descendant' + | 'descendant-or-self' + | 'descending' + | 'div' + | 'document' + | 'else' + | 'empty' + | 'end' + | 'eq' + | 'every' + | 'except' + | 'first' + | 'following' + | 'following-sibling' + | 'for' + | 'ge' + | 'group' + | 'gt' + | 'idiv' + | 'import' + | 'insert' + | 'instance' + | 'intersect' + | 'into' + | 'invoke' + | 'is' + | 'last' + | 'le' + | 'let' + | 'lt' + | 'mod' + | 'modify' + | 'module' + | 'namespace' + | 'ne' + | 'only' + | 'or' + | 'order' + | 'ordered' + | 'parent' + | 'preceding' + | 'preceding-sibling' + | 'rename' + | 'replace' + | 'return' + | 'satisfies' + | 'self' + | 'some' + | 'stable' + | 'start' + | 'to' + | 'transform' + | 'treat' + | 'try' + | 'union' + | 'unordered' + | 'validate' + | 'where' + | 'with' + | 'xquery' +NCName ::= NCName^Token + | 'after' + | 'and' + | 'as' + | 'ascending' + | 'before' + | 'case' + | 'cast' + | 'castable' + | 'collation' + | 'count' + | 'default' + | 'descending' + | 'div' + | 'else' + | 'empty' + | 'end' + | 'eq' + | 'except' + | 'for' + | 'ge' + | 'group' + | 'gt' + | 'idiv' + | 'instance' + | 'intersect' + | 'into' + | 'is' + | 'le' + | 'let' + | 'lt' + | 'mod' + | 'modify' + | 'ne' + | 'only' + | 'or' + | 'order' + | 'return' + | 'satisfies' + | 'stable' + | 'start' + | 'to' + | 'transform' + | 'treat' + | 'union' + | 'where' + | 'with' + + + +IntegerLiteral + ::= Digits +DecimalLiteral + ::= '.' Digits + | Digits '.' [0-9]* +DoubleLiteral + ::= ( '.' Digits | Digits ( '.' [0-9]* )? ) [eE] [+#x2D]? Digits +StringLiteral + ::= '"' ( PredefinedEntityRef | CharRef | EscapeQuot | [^"&] )* '"' + | "'" ( PredefinedEntityRef | CharRef | EscapeApos | [^'&] )* "'" +URIQualifiedName + ::= BracedURILiteral NCName +BracedURILiteral + ::= 'Q' '{' ( PredefinedEntityRef | CharRef | [^&{}] )* '}' +PredefinedEntityRef + ::= '&' ( 'lt' | 'gt' | 'amp' | 'quot' | 'apos' ) ';' +EscapeQuot + ::= '""' +EscapeApos + ::= "''" +ElementContentChar + ::= Char - [{}<&] +QuotAttrContentChar + ::= Char - ["{}<&] +AposAttrContentChar + ::= Char - ['{}<&] +PITarget ::= NCName - ( ( 'X' | 'x' ) ( 'M' | 'm' ) ( 'L' | 'l' ) ) +Name ::= NameStartChar NameChar* +NameStartChar + ::= ':' + | [A-Z] + | '_' + | [a-z] + | [#xC0-#xD6] + | [#xD8-#xF6] + | [#xF8-#x2FF] + | [#x370-#x37D] + | [#x37F-#x1FFF] + | [#x200C-#x200D] + | [#x2070-#x218F] + | [#x2C00-#x2FEF] + | [#x3001-#xD7FF] + | [#xF900-#xFDCF] + | [#xFDF0-#xFFFD] + | [#x10000-#xEFFFF] +NameChar ::= NameStartChar + | '-' + | '.' + | [0-9] + | #xB7 + | [#x0300-#x036F] + | [#x203F-#x2040] +CharRef ::= '&#' [0-9]+ ';' + | '&#x' [0-9a-fA-F]+ ';' +QName ::= PrefixedName + | UnprefixedName +PrefixedName + ::= Prefix ':' LocalPart +UnprefixedName + ::= LocalPart +Prefix ::= NCName +LocalPart + ::= NCName +NCName ::= Name - ( Char* ':' Char* ) +S ::= ( #x20 | #x9 | #xD | #xA )+ +Char ::= #x9 + | #xA + | #xD + | [#x20-#xD7FF] + | [#xE000-#xFFFD] + | [#x10000-#x10FFFF] +Digits ::= [0-9]+ +CommentContents + ::= ( ( Char+ - ( Char* ( '(:' | ':)' ) Char* ) ) - ( Char* '(' ) ) &':' + | ( Char+ - ( Char* ( '(:' | ':)' ) Char* ) ) &'(' +PragmaContents + ::= ( Char* - ( Char* '#)' Char* ) ) &'#' +DirCommentContents + ::= ( Char - '-' | '-' ( Char - '-' ) )* +DirPIContents + ::= ( Char* - ( Char* '?>' Char* ) ) &'?' +CDataSectionContents + ::= ( Char* - ( Char* ']]>' Char* ) ) &']]' +Wildcard ::= '*' + | NCName ':' '*' + | '*' ':' NCName + | BracedURILiteral '*' +EOF ::= $ +NonNCNameChar + ::= $ + | ':' + | Char - NameChar +DelimitingChar + ::= NonNCNameChar + | '-' + | '.' +DelimitingChar + \\ DecimalLiteral DoubleLiteral IntegerLiteral +NonNCNameChar + \\ NCName^Token QName^Token URIQualifiedName 'NaN' 'after' 'allowing' 'ancestor' 'ancestor-or-self' 'and' 'as' 'ascending' 'at' 'attribute' 'base-uri' 'before' 'boundary-space' 'by' 'case' 'cast' 'castable' 'catch' 'child' 'collation' 'comment' 'construction' 'context' 'copy' 'copy-namespaces' 'count' 'decimal-format' 'decimal-separator' 'declare' 'default' 'delete' 'descendant' 'descendant-or-self' 'descending' 'digit' 'div' 'document' 'document-node' 'element' 'else' 'empty' 'empty-sequence' 'encoding' 'end' 'eq' 'every' 'except' 'external' 'first' 'following' 'following-sibling' 'for' 'function' 'ge' 'greatest' 'group' 'grouping-separator' 'gt' 'idiv' 'if' 'import' 'in' 'infinity' 'inherit' 'insert' 'instance' 'intersect' 'into' 'is' 'item' 'last' 'lax' 'le' 'least' 'let' 'lt' 'minus-sign' 'mod' 'modify' 'module' 'namespace' 'namespace-node' 'ne' 'next' 'no-inherit' 'no-preserve' 'node' 'nodes' 'of' 'only' 'option' 'or' 'order' 'ordered' 'ordering' 'parent' 'pattern-separator' 'per-mille' 'percent' 'preceding' 'preceding-sibling' 'preserve' 'previous' 'processing-instruction' 'rename' 'replace' 'return' 'revalidation' 'satisfies' 'schema' 'schema-attribute' 'schema-element' 'self' 'skip' 'sliding' 'some' 'stable' 'start' 'strict' 'strip' 'switch' 'text' 'then' 'to' 'treat' 'try' 'tumbling' 'type' 'typeswitch' 'union' 'unordered' 'updating' 'validate' 'value' 'variable' 'version' 'when' 'where' 'window' 'with' 'xquery' 'zero-digit' +QName^Token + << 'after' 'ancestor' 'ancestor-or-self' 'and' 'as' 'ascending' 'attribute' 'before' 'case' 'cast' 'castable' 'child' 'collation' 'comment' 'copy' 'count' 'declare' 'default' 'delete' 'descendant' 'descendant-or-self' 'descending' 'div' 'document' 'document-node' 'element' 'else' 'empty' 'empty-sequence' 'end' 'eq' 'every' 'except' 'first' 'following' 'following-sibling' 'for' 'function' 'ge' 'group' 'gt' 'idiv' 'if' 'import' 'insert' 'instance' 'intersect' 'into' 'invoke' 'is' 'item' 'last' 'le' 'let' 'lt' 'mod' 'modify' 'module' 'namespace' 'namespace-node' 'ne' 'node' 'only' 'or' 'order' 'ordered' 'parent' 'preceding' 'preceding-sibling' 'processing-instruction' 'rename' 'replace' 'return' 'satisfies' 'schema-attribute' 'schema-element' 'self' 'some' 'stable' 'start' 'switch' 'text' 'to' 'transform' 'treat' 'try' 'typeswitch' 'union' 'unordered' 'validate' 'where' 'with' 'xquery' +NCName^Token + << 'after' 'and' 'as' 'ascending' 'before' 'case' 'cast' 'castable' 'collation' 'count' 'default' 'descending' 'div' 'else' 'empty' 'end' 'eq' 'except' 'for' 'ge' 'group' 'gt' 'idiv' 'instance' 'intersect' 'into' 'is' 'le' 'let' 'lt' 'mod' 'modify' 'ne' 'only' 'or' 'order' 'return' 'satisfies' 'stable' 'start' 'to' 'transform' 'treat' 'union' 'where' 'with' +'*' << Wildcard \ No newline at end of file diff --git a/grammars/XQuery-Update-eXist-Legacy.ebnf b/grammars/XQuery-Update-eXist-Legacy.ebnf new file mode 100644 index 00000000..34df757f --- /dev/null +++ b/grammars/XQuery-Update-eXist-Legacy.ebnf @@ -0,0 +1,35 @@ +/* XQuery Update Facility — eXist Legacy (XQUFEL) + * Reference grammar for eXist-db's proprietary update syntax. + * See: http://exist-db.org/exist/apps/doc/update_ext.xml + * + * These are eXist-db specific extensions, NOT part of any W3C specification. + * They predate the W3C XQuery Update Facility and use a different syntax. + * + * Integration: ExistUpdateExpr should be added as an alternative to ExprSingle. + * Keywords: 'update' must be added to FunctionName, NCName, and disambiguation lines. + */ + +/* MODIFIED PRODUCTION: + * ExprSingle: add ExistUpdateExpr as alternative + * ExprSingle ::= ... | ExistUpdateExpr | OrExpr + */ + +/* NEW PRODUCTIONS */ +ExistUpdateExpr + ::= 'update' ( ExistInsertExpr | ExistReplaceExpr | ExistValueExpr | ExistDeleteExpr | ExistRenameExpr ) +ExistInsertExpr + ::= 'insert' ExprSingle ( 'into' | 'following' | 'preceding' ) ExprSingle +ExistReplaceExpr + ::= 'replace' ExprSingle 'with' ExprSingle +ExistValueExpr + ::= 'value' ExprSingle 'with' ExprSingle +ExistDeleteExpr + ::= 'delete' ExprSingle +ExistRenameExpr + ::= 'rename' ExprSingle 'as' ExprSingle + +/* NEW KEYWORDS to add to FunctionName, NCName, and disambiguation lines: + * 'update' + * Note: 'insert', 'delete', 'replace', 'rename', 'into', 'with', 'value', + * 'following', 'preceding' are already keywords from W3C Update Facility 3.0. + */ diff --git a/index.html.tmpl b/index.html.tmpl index ca1c6a0d..9e24f656 100755 --- a/index.html.tmpl +++ b/index.html.tmpl @@ -3,65 +3,16 @@ eXide - - - - - - -
    • +
    • + View +
    • @@ -165,88 +161,83 @@
    • -
    • - Buffers -
        -
      -
    • -
    • - Application +
    • - +
    • + App +
    • @@ -265,7 +256,7 @@ Checkout
    • - Synchronize and commit + Synchronize and Commit
    @@ -276,21 +267,26 @@ Rename
  • - Select element + Select Element
  • - Remove tags + Remove Tags
+
  • + Window +
      +
    +
  • Help
  • -
    - - Current app: Unknown - - - - - - -
    - - - - - - - + + + + + + + + + + + + + + +
    @@ -340,24 +324,45 @@
      +
    -
    +
    + + + +
    +
    +
    + +
    -
    -
    -
      -
    • -
    +
    + + +
    +
    +
    +
    +
      +
    • +
    +
    -
    -
    - +
    @@ -367,23 +372,71 @@
    -
      -
    • - + -
    • -
    - - +
    +
      +
    • + + +
    • +
    +
    +
    +
    + + + +
    +
      +
      +
      -
      +