From debab5b6ad1cc79a62a702281061a6205c699b22 Mon Sep 17 00:00:00 2001 From: duncdrum Date: Tue, 8 Sep 2026 17:59:48 +0200 Subject: [PATCH 1/7] feat(editor): validate XML with native eXist schemas Prefer $EXIST_HOME/schema grammars (exist#6528) via jaxv XSD 1.1, with synced resources/schema fallbacks, until catalog-based jaxp works (exist#6686). Co-authored-by: Cursor --- cypress/e2e/xml_validate_spec.cy.js | 110 ++ modules/api.json | 2 +- modules/api/editor.xqm | 274 +++- resources/schema/README.md | 10 + resources/schema/catalog.xml | 24 +- resources/schema/collection.xconf.xsd | 47 +- resources/schema/conf.xsd | 1509 ++++++++++++++++++++++ resources/schema/controller-config.xsd | 31 + resources/schema/descriptor.xsd | 43 + resources/schema/expath-pkg.xsd | 231 ++++ resources/schema/mime-types.xsd | 62 + resources/schema/schema-version-type.xsd | 22 + resources/schema/security-manager.xsd | 67 + resources/schema/server.xsd | 130 ++ resources/schema/users.xsd | 60 + 15 files changed, 2590 insertions(+), 32 deletions(-) create mode 100644 cypress/e2e/xml_validate_spec.cy.js create mode 100644 resources/schema/README.md create mode 100644 resources/schema/conf.xsd create mode 100644 resources/schema/controller-config.xsd create mode 100644 resources/schema/descriptor.xsd create mode 100644 resources/schema/expath-pkg.xsd create mode 100644 resources/schema/mime-types.xsd create mode 100644 resources/schema/schema-version-type.xsd create mode 100644 resources/schema/security-manager.xsd create mode 100644 resources/schema/server.xsd create mode 100644 resources/schema/users.xsd diff --git a/cypress/e2e/xml_validate_spec.cy.js b/cypress/e2e/xml_validate_spec.cy.js new file mode 100644 index 00000000..06b26869 --- /dev/null +++ b/cypress/e2e/xml_validate_spec.cy.js @@ -0,0 +1,110 @@ +describe('XML validation (api/editor/validate)', () => { + beforeEach(() => { + cy.loginXHR('admin', '') + }) + + const validate = (xml) => + cy.request({ + method: 'POST', + url: '/eXide/api/editor/validate', + headers: { + 'Content-Type': 'application/xml', + Accept: 'application/json' + }, + body: xml + }) + + it('accepts a valid collection.xconf via native schema when available', () => { + const xml = ` + + +` + validate(xml).then((res) => { + expect(res.body).to.have.property('status', 'valid') + expect(res.body).to.have.property('schema', 'collection.xconf.xsd') + // Docker/CI with exist#6528 ships $EXIST_HOME/schema/; assert native path. + expect(res.body.grammar).to.equal('native') + }) + }) + + it('rejects an invalid collection.xconf', () => { + // Empty collection fails the XSD 1.1 assert that at least one of + // index / triggers / validation must be present. + const xml = ` +` + validate(xml).then((res) => { + expect(res.body).to.have.property('status', 'invalid') + expect(res.body).to.have.property('schema', 'collection.xconf.xsd') + expect(res.body.errors).to.be.an('array').that.is.not.empty + expect(res.body.errors[0].message).to.match(/assert|count\(\*\)/i) + }) + }) + + it('honours xsi:schemaLocation for collection.xconf', () => { + const xml = ` + + +` + validate(xml).then((res) => { + expect(res.body).to.have.property('status', 'valid') + expect(res.body).to.have.property('schema', 'collection.xconf.xsd') + expect(res.body.grammar).to.be.oneOf(['native', 'bundled']) + }) + }) + + it('validates expath-pkg.xml', () => { + const xml = ` + + Test package +` + validate(xml).then((res) => { + expect(res.body).to.have.property('status', 'valid') + expect(res.body).to.have.property('schema', 'expath-pkg.xsd') + expect(res.body.grammar).to.equal('native') + }) + }) + + it('validates no-namespace mime-types.xml', () => { + const xml = ` + + + XML + .xml + +` + validate(xml).then((res) => { + expect(res.body).to.have.property('status', 'valid') + expect(res.body).to.have.property('schema', 'mime-types.xsd') + expect(res.body.grammar).to.equal('native') + }) + }) + + it('rejects invalid no-namespace mime-types.xml', () => { + const xml = ` + + + bad + .xml + +` + validate(xml).then((res) => { + expect(res.body).to.have.property('status', 'invalid') + expect(res.body).to.have.property('schema', 'mime-types.xsd') + expect(res.body.errors).to.be.an('array').that.is.not.empty + }) + }) + + it('marks plain XML without a known grammar as well-formed only', () => { + const xml = `hi` + validate(xml).then((res) => { + expect(res.body).to.have.property('status', 'valid') + expect(res.body).to.have.property('grammar', 'none') + }) + }) +}) diff --git a/modules/api.json b/modules/api.json index 6704bd20..dedd44b7 100644 --- a/modules/api.json +++ b/modules/api.json @@ -832,7 +832,7 @@ }, "/api/editor/validate": { "post": { - "summary": "Validate XML against schema catalog", + "summary": "Validate XML against native eXist grammars when available (XSD 1.1)", "operationId": "editor:validate", "tags": [ "editor" diff --git a/modules/api/editor.xqm b/modules/api/editor.xqm index ab1ee033..a72011f7 100644 --- a/modules/api/editor.xqm +++ b/modules/api/editor.xqm @@ -110,28 +110,286 @@ declare function editor:modules($request as map(*)) { } }; +declare variable $editor:XSD_11 := + "http://www.w3.org/XML/XMLSchema/v1.1"; + +declare variable $editor:XSI_NS := + "http://www.w3.org/2001/XMLSchema-instance"; + +declare variable $editor:SCHEMA_DIR_DB := + "/db/apps/eXide/resources/schema/"; + +(:~ + : Known eXist grammars keyed by target namespace (exist#6528 native set). + :) +declare variable $editor:SCHEMA_BY_NS := map { + "http://exist-db.org/collection-config/1.0": "collection.xconf.xsd", + "http://exist.sourceforge.net/NS/exist": "controller-config.xsd", + "http://expath.org/ns/pkg": "expath-pkg.xsd", + "http://exist-db.org/Configuration": "security-manager.xsd" +}; + +(:~ + : No-namespace eXist config roots → grammar filename. + :) +declare variable $editor:SCHEMA_BY_ROOT := map { + "exist": "conf.xsd", + "xquery-app": "descriptor.xsd", + "mime-types": "mime-types.xsd", + "auth": "users.xsd", + "server": "server.xsd" +}; + +(:~ + : True when the given filesystem path exists. File module namespace varies by + : eXist-db version — same approach as admin:status. + :) +declare function editor:file-exists($path as xs:string) as xs:boolean { + let $escaped := replace($path, "'", "''") + return + try { + if ("http://expath.org/ns/file" = util:registered-modules()) then + util:eval( + "import module namespace file='http://expath.org/ns/file'; " || + "file:exists('" || $escaped || "')" + ) + else if ("http://exist-db.org/xquery/file" = util:registered-modules()) then + util:eval( + "import module namespace file='http://exist-db.org/xquery/file' " || + "at 'java:org.exist.xquery.modules.file.FileModule'; " || + "file:exists('" || $escaped || "')" + ) + else + false() + } catch * { + false() + } +}; + +(:~ + : Convert an absolute filesystem path to a file: URI (Unix and Windows). + :) +declare function editor:path-to-file-uri($path as xs:string) as xs:anyURI { + let $normalized := replace($path, "\\", "/") + let $with-slashes := + if (matches($normalized, "^[A-Za-z]:/")) then + "file:///" || $normalized + else if (starts-with($normalized, "/")) then + "file://" || $normalized + else + "file:///" || $normalized + return + xs:anyURI(replace($with-slashes, " ", "%20")) +}; + +(:~ + : Resolve a known grammar filename to native `$EXIST_HOME/schema/` when + : present (exist#6528), else the app-bundled fallback (eXide#842). + : Returns map { "uri", "source" ("native"|"bundled"), "name" }. + :) +declare function editor:resolve-grammar-file($name as xs:string) as map(*) { + let $native := system:get-exist-home() || "/schema/" || $name + return + if (editor:file-exists($native)) then + map { + "uri": editor:path-to-file-uri($native), + "source": "native", + "name": $name + } + else + map { + "uri": xs:anyURI($editor:SCHEMA_DIR_DB || $name), + "source": "bundled", + "name": $name + } +}; + +(:~ + : If $hint is a known grammar basename (optionally with path), return it. + :) +declare function editor:known-grammar-name($hint as xs:string?) as xs:string? { + if (empty($hint) or $hint eq "") then + () + else + let $base := replace($hint, "^.*/", "") + return + if ( + $base = ( + map:keys($editor:SCHEMA_BY_NS) ! $editor:SCHEMA_BY_NS(.), + map:keys($editor:SCHEMA_BY_ROOT) ! $editor:SCHEMA_BY_ROOT(.) + ) + ) then + $base + else + () +}; + +(:~ + : Prefer xsi:schemaLocation / xsi:noNamespaceSchemaLocation when present, + : then namespace map, then no-namespace root map. + : Returns map { "uri", "source", "name" } or the empty sequence. + : + : Interim: uses validation:jaxv-report with an explicit schema URI because + : jaxp + system/OASIS catalog does not yet apply native XSD 1.1 grammars + : (exist#6686). When that lands, prefer catalog resolution again. + :) +declare function editor:resolve-schema($root as element()) as map(*)? { + let $schema-loc-attr := + $root/@*[namespace-uri(.) eq $editor:XSI_NS and local-name(.) eq "schemaLocation"] + let $no-ns-loc-attr := + $root/@*[namespace-uri(.) eq $editor:XSI_NS and local-name(.) eq "noNamespaceSchemaLocation"] + let $schema-loc := + if (exists($schema-loc-attr)) then normalize-space($schema-loc-attr) else "" + let $no-ns-loc := + if (exists($no-ns-loc-attr)) then normalize-space($no-ns-loc-attr) else "" + let $from-schema-location := + if ($schema-loc ne "") then + let $tokens := tokenize($schema-loc, "\s+") + let $pairs := + for $i in (1 to (count($tokens) idiv 2)) + let $ns := $tokens[($i - 1) * 2 + 1] + let $loc := $tokens[($i - 1) * 2 + 2] + return + if ($ns eq namespace-uri($root)) then $loc else () + let $loc := head($pairs) + return + if (empty($loc)) then + () + else if (matches($loc, "^(file:|/db/)")) then + map { + "uri": xs:anyURI($loc), + "source": "instance", + "name": replace($loc, "^.*/", "") + } + else + let $known := editor:known-grammar-name($loc) + return + if (exists($known)) then + editor:resolve-grammar-file($known) + else if (map:contains($editor:SCHEMA_BY_NS, namespace-uri($root))) then + editor:resolve-grammar-file($editor:SCHEMA_BY_NS(namespace-uri($root))) + else + () + else + () + let $from-no-ns := + if (exists($from-schema-location)) then + () + else if ($no-ns-loc ne "") then + if (matches($no-ns-loc, "^(file:|/db/)")) then + map { + "uri": xs:anyURI($no-ns-loc), + "source": "instance", + "name": replace($no-ns-loc, "^.*/", "") + } + else + let $known := editor:known-grammar-name($no-ns-loc) + return + if (exists($known)) then + editor:resolve-grammar-file($known) + else + () + else + () + let $from-ns := + if (exists(($from-schema-location, $from-no-ns))) then + () + else if (map:contains($editor:SCHEMA_BY_NS, namespace-uri($root))) then + editor:resolve-grammar-file($editor:SCHEMA_BY_NS(namespace-uri($root))) + else + () + let $from-root := + if (exists(($from-schema-location, $from-no-ns, $from-ns))) then + () + else if (namespace-uri($root) eq "" and map:contains($editor:SCHEMA_BY_ROOT, local-name($root))) then + editor:resolve-grammar-file($editor:SCHEMA_BY_ROOT(local-name($root))) + else + () + return + ($from-schema-location, $from-no-ns, $from-ns, $from-root)[1] +}; + +declare function editor:validation-result( + $report as element(), + $grammar as map(*)? +) as map(*) { + let $base := + if (exists($grammar)) then + map { + "grammar": $grammar?source, + "schema": $grammar?name + } + else + map { + "grammar": "none", + "schema": () + } + return + if ($report/status eq "valid") then + map:merge(($base, map { "status": "valid" })) + else + map:merge(( + $base, + map { + "status": "invalid", + "errors": array { + for $msg in $report/message + return map { + "line": (xs:integer($msg/@line), 0)[1], + "message": string($msg) + } + } + } + )) +}; + (:~ : POST /api/editor/validate — XML validation. : Migrated from validate-xml.xq. + : + : Resolves a grammar for known eXist/EXPath documents (native + : `$EXIST_HOME/schema/` when present — exist#6528 — else bundled copies under + : resources/schema/; see eXide#842). Instance xsi:schemaLocation / + : noNamespaceSchemaLocation is honoured when it points at a known grammar or + : an absolute file:/db URI. Validation uses validation:jaxv-report + XSD 1.1 + : (interim until exist#6686 restores reliable catalog-based jaxp). + : + : XML without a resolvable grammar is checked for well-formedness only. :) declare function editor:validate($request as map(*)) { let $xml := $request?body let $validate := ($request?parameters?validate, true())[1] return try { - let $parsed := - if ($validate) then - validation:jaxp-parse($xml, true(), - doc("/db/apps/eXide/resources/schema/catalog.xml")) - else - parse-xml($xml) - return map { "status": "valid" } + if (not($validate)) then + let $_ := + if ($xml instance of node()) then $xml + else parse-xml($xml) + return map { "status": "valid", "grammar": "none", "schema": () } + else + let $node := + if ($xml instance of node()) then $xml + else parse-xml($xml) + let $root := + if ($node instance of document-node()) then $node/* + else $node + let $grammar := editor:resolve-schema($root) + return + if (exists($grammar)) then + editor:validation-result( + validation:jaxv-report($node, $grammar?uri, $editor:XSD_11), + $grammar + ) + else + map { "status": "valid", "grammar": "none", "schema": () } } catch * { map { "status": "invalid", + "grammar": "none", + "schema": (), "errors": array { map { - "line": $err:line-number, + "line": ($err:line-number, 0)[1], "message": $err:description } } diff --git a/resources/schema/README.md b/resources/schema/README.md new file mode 100644 index 00000000..c3884107 --- /dev/null +++ b/resources/schema/README.md @@ -0,0 +1,10 @@ +# eXide schema fallbacks (eXide#842) + +Copies of eXist-db native grammars from `$EXIST_HOME/schema/` (exist#6528). + +`editor:validate` prefers the native files when present; these copies are the +fallback for older servers. Keep them in sync with exist's `schema/` tree. + +Long-term these files (and `catalog.xml`) can be removed once catalog-based +jaxp validation works against the native set (exist#6686) and unsupported +servers without `$EXIST_HOME/schema/` are gone. diff --git a/resources/schema/catalog.xml b/resources/schema/catalog.xml index 9a80cc30..8b6fdd7f 100644 --- a/resources/schema/catalog.xml +++ b/resources/schema/catalog.xml @@ -1,4 +1,26 @@ + - \ No newline at end of file + + + + + + + + + + + diff --git a/resources/schema/collection.xconf.xsd b/resources/schema/collection.xconf.xsd index 504584f8..f5522b8e 100644 --- a/resources/schema/collection.xconf.xsd +++ b/resources/schema/collection.xconf.xsd @@ -6,13 +6,15 @@ xmlns:dcterms="http://purl.org/dc/terms/" elementFormDefault="qualified" targetNamespace="http://exist-db.org/collection-config/1.0" - version="1.0.0"> + version="1.2.2"> + + Schema for eXist-db Collection Configuration files /db/system/config/db/**/collection.xconf Schema for eXist-db Collection Configuration Files - 2011-10-09T18:47:21.319+01:00 + 2011-10-09T18:47:21.319+01:00 Adam Retter @@ -28,6 +30,7 @@ + @@ -56,15 +59,15 @@ - + - + - + @@ -75,14 +78,14 @@ - + - + @@ -90,7 +93,7 @@ - + @@ -99,7 +102,7 @@ - + @@ -143,9 +146,9 @@ - + - + @@ -226,7 +229,7 @@ - + @@ -267,7 +270,7 @@ Trigger Configuration - + @@ -319,14 +322,14 @@ @@ -338,7 +341,7 @@ - + @@ -488,7 +491,7 @@ - + @@ -509,7 +512,7 @@ - + @@ -618,7 +621,7 @@ - + diff --git a/resources/schema/conf.xsd b/resources/schema/conf.xsd new file mode 100644 index 00000000..4cefe554 --- /dev/null +++ b/resources/schema/conf.xsd @@ -0,0 +1,1509 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The name of the parameter + + + + + + + The value of the parameter + + + + + + + Optional human-readable description of the parameter's purpose. + + + + + + + Set to "no" to disable this parameter without removing it from the configuration. + + + + + + + + + + + + + + + + + + + + + + + + + + + + The full class name of the trigger implementation + + + + + + + Set to "no" to disable this trigger without removing it from the configuration. + + + + + + + + + + + + + + + + + Maximum number of connections allowed. + + + + + + + Minimum number of connections to keep alive. + + + + + + + Defines how often the database will flush its + internal buffers to disk. The sync thread will interrupt + normal database operation after the specified number of + milliseconds and write all dirty pages to disk. + + + + + + + Defines how long the database instance will wait for running + operations to complete before it forces a shutdown. Forcing + a shutdown may leave the db in an unclean state and may + trigger a recovery run on restart. +

+ Setting wait-before-shutdown="-1" means that the server will + wait for all threads to return, no matter how long it takes. + No thread will be killed. + + + + + + + + + + + Maximum number of queries in the query-pool. + + + + + + + Number of copies of the same query kept in the query-pool. + Value "-1" effectively disables caching. Queries cannot be shared + by threads, each thread needs a private copy of a query. + + + + + + + + + + + If this attribute is set to yes, automatic recovery is enabled. + + + + + + + If set to "yes", eXist will not sync the journal file + immediately after every transaction commit. Instead, + it will wait until the current file buffer (32kb) + is really full. This can speed up eXist on some systems + where a file sync is an expensive operation (mainly windows + XP; not necessary on Linux). However, group-commit="yes" + will increase the risk of an already committed + operation being rolled back after a database crash. + + + + + + + This attribute sets the directory where journal files are to be + written. If no directory is specified, the default path is to + the data directory. + + + + + + + This attributes sets the maximum allowed size of the journal + file. Once the journal reaches this limit, a checkpoint will be + triggered and the journal will be cleaned. However, the database + waits for running transactions to return before processing this + checkpoint. In the event one of these transactions writes a lot + of data to the journal file, the file will grow until the + transaction has completed. Hence, the size limit is not enforced + in all cases. + + + + + + + This attribute determines whether or not to protect the journal + during operating system failures. That is, it determines whether + the database forces a file-sync on the journal after every + commit. +

+ If this attribute is set to "yes", the journal is protected + against operating system failures. However, this will slow + performance - especially on Windows systems. +

+ If set to "no", eXist will rely on the operating system to flush + out the journal contents to disk. In the worst case scenario, + in which there is a complete system failure, some committed + transactions might not have yet been written to the journal, + and so will be rolled back. + + + + + + + Try to restart the db even if crash recovery failed. This is + dangerous because there might be corruptions inside the + data files. The transaction log will be cleared, all locks removed + and the db reindexed. +

+ Set this option to "yes" if you need to make sure that the db is + online, even after a fatal crash. Errors encountered during recovery + are written to the log files. Scan the log files to see if any problems + occurred. + + + + + + + If set to "yes", a consistency check will be run on the database + if an error was detected during crash recovery. This option requires + force-restart to be set to "yes", otherwise it has no effect. +

+ The consistency check outputs a report to the directory {files}/sanity + and if inconsistencies are found in the db, it writes an emergency + backup to the same directory. + + + + + + + + + + + This attribute limits the size of XML fragments constructed + using XQuery, and thus sets the maximum amount of main memory a + query is allowed to use. This limit is expressed as the maximum + number of nodes allowed for an in-memory DOM tree. The purpose + of this option is to avoid memory shortages on the server in + cases where users are allowed to run queries that produce very + large output fragments. + + + + + + + This attribute sets the maximum amount of time (expressed in + milliseconds) that the query can take before it is killed.. + + + + + + + + + + The minimum number of pages that must be read from a + cache between check intervals to be not considered for + shrinking. This is a measure for the "load" of the cache. Caches + with high load will never be shrinked. A negative value means that + shrinkage will not be performed. + + + + + + + The maximum amount of memory to use for database page buffers. + Each database file has an associated page buffer for B+-tree and + data pages. However, the memory specified via cacheSize is shared + between all page buffers. It represents an absolute maximum, which + would be occupied if all page buffers were completely full. +

+ The cacheSize should typically not be more than half of the size of + the JVM heap size (set by the JVM -Xmx parameter). It can be larger + if you have a large-memory JVM (usually a 64bit JVM) + + + + + + + Specifies whether eXist should check the max cache size on startup + and reduce it if it is too large. +

+ This value should normally be set to true. +

+ Only set this value to false if: +

+ a) You know what you are doing! + b) You have a JVM with tons of memory (typically using a 64-bit + JVM, which is the scenario this setting is intended for). + c) You are really sure you've complied with a) and b) above. +

+ Setting this value to false may cause memory issues which may lead to + database corruptions, since it disables the automated max cache size + checks! You have been warned! ;-) + + + + + + + Maximum amount of memory (in megabytes) to use for collection caches. + Memory calculation is just approximate. If your collections are very + different in size, it might be possible that the actual amount of + memory used exceeds the specified limit. You should thus be careful + with this setting. + + + + + + + Selects a database backend. Currently, "native" is the only valid setting. + + + + + + + TODO + + + + + + + Path to the directory where database files are stored. + + + + + + + TODO + + + + + + + The amount of disk space (in megabytes) which should be available for + the database to continue operations. If free disk space goes below + the configured limit, eXist-db will flush all buffers to disk and + switch to read-only mode in order to prevent potential data loss. + Set the limit large enough to allow all pending operations to + complete. Set to -1 to disable. The default is 1 gigabyte. + + + + + + + Size of the temporary buffer used by eXist for caching index + data while indexing a document. If set to -1, eXist will use the + entire free memory to buffer index entries and will flush the + cache once the memory is full. +

+ If set to a value > 0, the buffer will be fixed to the given size. + The specified number corresponds to the number of nodes the + buffer can hold, in thousands. Usually, a good default could be + nodesBuffer="1000". + + + + + + + The size of one page on the disk. This is the smallest unit + transferred from and to the database files. Should be a multiple of + the operating system's file system page size (usually 4096). + + + + + + + As defined by POSIX.1 for _POSIX_CHOWN_RESTRICTED. +

+ When posix-chown-restricted="true" (the default) then: + 1. Only a superuser process can change the user ID of the file. + 2. A non-superuser process can change the group ID of the file + if the process owns the file (the effective user ID equals + the user ID of the file), and group equals either the + effective group ID of the process or one of the + process’s supplementary group IDs. + This means that when posix-chown-restricted="true", you can’t change + the user ID of your files. You can change the group ID of files that + you own, but only to groups that you belong to. +

+ When posix-chown-restricted="false" you can change the user ID of + any file that you own, effectively "giving away the file" to + another user. Such a setting has negative security implications, + further details can be seen in the "Rationale" section for the + chown function in the POSIX.1-2017 (Issue 7, 2018 edition) standard. + See: http://pubs.opengroup.org/onlinepubs/9699919799/functions/chown.html#tag_16_59_07 + + + + + + + When copying Collections and Documents within the database, the + default (`false`), is not to preserve their attributes + (modification time, mode, user-id, group-id, and ACL). +

+ NOTE: Not preserving attributes, is inline with both the GNU and + BSD `cp` commands, and therefore expected behaviour; The target + Collection or Document is created following the rules of the + target parent, and the effective user and their umask. +

+ Setting preserve-on-copy="true" changes the default behaviour + so that the target Collection or Document of a copy, has the same + attributes as the source. +

+ The preserve-on-copy setting can be overridden on a case-by-case + basis by setting the `preserve` flag to either `true` or `false` + when calling xmldb:copy(), or via any other API that supports copy. + Omitting the preserve flag when calling a copy operation, implies + the behaviour that is set in this configuration. + + + + + + + + + + + + + + Disables the database lock table which tracks database locks. The Lock Table is enabled by default + and allows reporting on database locking via JMX. +

+ NOTE: Tracking locks via the Lock Table imposes a small overhead per-Lock. Once users + have finished testing their system to ensure correct operation, they may wish to disable + this in production to ensure the absolute best performance. +

+ This can also be set via the Java System Properties `org.exist.lock-manager.lock-table.disabled`, + or (legacy) `exist.locktable.disable`. + + + + + + + When set above 0, this captures n frames of each threads stack that performs a try/lock/release + operation. These frames are visible from JMX reporting. + In addition, when the logging level for the Lock Table is set in log4j2.xml to TRACE the lock + events are written to the locks.log file. +

+ This can also be set via the Java System Properties `org.exist.lock-manager.lock-table.trace-stack-depth`, + or (legacy) `exist.locktable.trace.stack.depth`. + + + + + + + + + + + Set to true to have documents participate in the same hierarchical + path based locking strategy as Collections. +

+ This has a performance and concurrency impact, but will ensure + that you cannot have deadlocks between Collections and Documents. +

+ NOTE: in future this will likely be set to `true` by default. +

+ This can also be set via the Java System Property `org.exist.lock-manager.document.use-path-locks`. + + + + + + + + + + Set to true to enable Multi-Writer/Multi-Reader semantics for + the database Collection/Document Hierarchy as opposed to the default (false) + for Single-Writer/Multi-Reader. +

+ NOTE: Whilst enabling Multiple-Writers on the Collection and Document Hierarchy can improve concurrent + through-put for write-heavy workloads, it can also can lead to deadlocks unless the User's + Collection Hierarchy is carefully designed to isolate query/database writes between Collection combs. + It is highly recommended that users leave this as the default setting. For more information, see: + "Locking and Cache Improvements for eXist-db", 2018-02-05, Section "Attempt 6" Page 58 - + https://www.evolvedbinary.com/technical-reports/exist-db/locking-and-cache-improvements/ +

+ This can also be set via the Java System Properties `org.exist.lock-manager.paths-multiple-writers`, + or (legacy) `exist.lockmanager.paths-multiwriter`. + + + + + + + Used by developers for diagnosing illegal lock upgrade issues. When enabled + checks for lock upgrading within the same thread, i.e. READ_LOCK -> WRITE_LOCK + are enabled. When an illegal upgrade is detected a LockException is thrown. + Such behaviour will likely corrupt any database writes, and should only be + used by developers when debugging database issues. +

+ This can also be set via the Java System Properties `org.exist.lock-manager.upgrade-check`, + or (legacy) `exist.lockmanager.upgrade.check`. + + + + + + + Used by developers for diagnosing lock performance issues. When enabled + checks for detecting when a thread wants to acquire the WRITE_LOCK + but another thread holds the READ_LOCK are enabled. When such operations + are detected a log message is written to locks.log at WARN level. +

+ This can also be set via the Java System Properties `org.exist.lock-manager.warn-wait-on-read-for-write`, + or (legacy) `exist.lockmanager.warn.waitonreadforwrite`. + + + + + + + + + + + The root collection for deployed applications. Application collections will be saved below + this collection. + + + + + + + + + + + + + + Defines the class to use to Cache InputStreams when reading binary documents + from the database or from a read once source such as a http request (e.g. request:get-data()). + There are currently three options available: + + - org.exist.util.io.FileFilterInputStreamCache + Default. Temporary binary streams are cached to a temporary file on disk. + + - org.exist.util.io.MemoryMappedFileFilterInputStreamCache + Temporary binary streams are cached to a temporary file on disk which + has been mapped into memory. Faster than FileFilterInputStreamCache. + Not reliable on Windows platforms. + + - org.exist.util.io.MemoryFilterInputStreamCache + Temporary binary streams are cached in memory. + This is the fastest approach. However it can result in out of memory + errors under heavy load or if using large binary files. + + Where temporary files are used, they will be deleted after use. + However, due to a bug in the JVM on Windows platforms, temporary files cannot be deleted, so instead + they are re-cycled and re-used and deleted if the database is restarted. + + + + + + + + + + + + + + + + + + + + Unique identifier for the module + + + + + + + The full class name of the module implementation + + + + + + + The index file name + + + + + + + The connection timeout + + + + + + + Number of documents to index before the n-gram index + is flushed to disk. Higher values improve throughput + at the cost of memory during bulk ingest. + + + + + + + The n in n-gram: the number of characters per token. + Default 3 produces trigrams, which support efficient + substring searches via the ngram:contains() function. + + + + + + + The buffer size + + + + + + + Set to "no" to disable this index module without removing it from the configuration. + + + + + + + + + + + + + + + + + XPath expression to the node to index (e.g. "//book/title"). + Mutually exclusive with qname. + + + + + + + Qualified name of the element or attribute to index + (e.g. "title" or "@id"). Mutually exclusive with path. + + + + + + + XQuery/XML Schema type to cast node values to when indexing + (e.g. "xs:integer", "xs:dateTime"). Determines the comparison + semantics available on this index. + + + + + + + + + + + + + Should equality comparisons between strings be case-sensitive or + insensitive: "yes" or "no". + + + + + + + Defines the maximum nesting depth of nodes which will be indexed + in the DOM index. Nodes below the specified nesting depth will + not be indexed in the DOM file. This has only an effect when + retrieving query results or for some types of XPath subexpressions, + like equality comparisons. + + + + + + + Preserve the white space inside a mixed content node: "yes" or "no". + + + + + + + Should leading or trailing whitespace be removed from a text node? + Set to "leading", "trailing", "both" or "none". + Changing the parameter will only have an effect on newly loaded + files, not old ones. + + + + + + + + + + + + + + + + + + + + + + + + + The type of the job to schedule. Must be either "system" + or "user". + + system - System jobs require the database to be in a consistent state. + All database operations will be stopped until the method returns or + throws an exception. Any exception will be caught and a warning written to + the log. + + user - User jobs may be scheduled at any time and may be mutually exclusive + or non-exclusive + + + + + + + + + + + + + + The name of the job + + + + + + + If the job is written in Java then this should be the name of the + class that extends either org.exist.storage.SystemTask or + org.exist.scheduler.UserJavaJob + + + + + + + If the job is written in XQuery (not suitable for system jobs) then + this should be a path to an XQuery stored in the database. e.g. + /db/myCollection/myJob.xql + XQuery job's will be launched under the guest account initially, + although the running XQuery may switch permissions through + calls to xmldb:login(). + + + + + + + To define a firing pattern for the Job using Cron style syntax + use this attribute otherwise for a periodic job use the period + attribute. Not applicable to startup jobs. + + + + + + + Boolean: yes/true, no/false. Default: true. If true and an exception is + encountered then the job is unscheduled for further execution until a + restart; otherwise, the exception is ignored. + + + + + + + Can be used to define an explicit period for firing the job instead + of a Cron style syntax. The period should be in milliseconds. + Not applicable to startup jobs. + + + + + + + Can be used with a period to delay the start of a job. If unspecified jobs + will start as soon as the database and scheduler are initialised. + + + + + + + Can be used with a period to define for how many periods a job should be + executed. If unspecified jobs will repeat for every period indefinitely. + + + + + + + Set to "no" to disable this job without removing it from the configuration. + + + + + + + + + + + + + + + + + + + + + + + The name of the feature flag + + + + + + + The value of the feature flag + + + + + + + Set to "no" to disable this feature flag without removing it from the configuration. + + + + + + + + + + + + + + + + + + + + + + + The name of the property + + + + + + + The value of the property + + + + + + + Set to "no" to disable this property without removing it from the configuration. + + + + + + + + + + + + + + + + + The name of the feature flag + + + + + + + The value of the feature flag + + + + + + + Set to "no" to disable this feature flag without removing it from the configuration. + + + + + + + + + + + + + The Java classname of a parser which implements org.xml.sax.XMLReader + and is capable of parsing HTML and emitting an XML Sax Stream. +

+ Whichever library you use for this, it must be present on the classpath + perhaps the best way to do this is to place it into $EXIST_HOME/lib/user +

+ Examples include: + - org.codelibs.nekohtml.parsers.SAXParser + The Cyber NekoHTML parser from https://sourceforge.net/projects/nekohtml/ +

+ - org.ccil.cowan.tagsoup.Parser + The TagSoup parser from http://home.ccil.org/~cowan/XML/tagsoup/ + + + + + + + + + + + + + + + + + The class implementing the custom filter + + + + + + + + + + For debugging: add an exist:id attribute to every element, showing + the internal node identifier (as a long int) assigned to this node. + Possible values are: "none", "element", "all". "all" displays the + node of every element node; "element" displays the id only for the + root nodes of the returned XML fragments. + + + + + + + + + + + + + + Should the output be compressed when serializing documents? + Sometimes useful with remote clients. + Remember to add a statement like this to your client code: + service.setProperty("compress-output", "yes"); + to uncompress the retrieved result in the client too. + + + + + + + Should the XML Declaration for a document be serialized + if it is present? + + + + + + + Should the original persisted XML Declaration for a document be serialized + if it is present? + + + + + + + Should the Doctype Declaration for a document be serialized + if it is present? + + + + + + + Should the database expand XInclude tags by default? + + + + + + + Should the database evaluate XSL processing instructions + when serializing documents? + + + + + + + Should the serializer pretty-print (indent) XML? + + + + + + + Matches for attribute values can also be tagged using the character + sequence "||" to demarcate the matching text string. Since this + changes the content of the attribute value, the feature is disabled + by default. + + + + + + + The database can highlight matches in the text content of a node by + tagging the matching text string with <exist:match>. Clearly, this + only works for XPath expressions using the some indexes. +

+ Set the parameter to "yes" to enable this feature. + + + + + + + + + + + + + + The attribute name + + + + + + + The attribute value + + + + + + + The attribute type + + + + + + + + + + + + + + + + + The name of the class that implements javax.xml.transform.TransformerFactory +

+ for Saxon (XSLT 2.0 support): + - "net.sf.saxon.TransformerFactoryImpl" +

+ for Xalan (XSLT 1.0 support): + - "org.apache.xalan.processor.TransformerFactoryImpl" + + + + + + + Set to "no" to disable XSL stylesheet caching. + + + + + + + + + + + + + + + + + The URI pointing to the according catalog + + + + + + + + + + + + + + Specifies the maximum number of entries the cache may contain. + + + + + + + + + + + + The time (in seconds) after which an unused grammar should + expire and be removed from the grammar pool. + + + + + + + + + + + + + + + should XML source files be validated against a schema or DTD before + storing them? The setting is passed to the XML parser. The actual + effects depend on the parser you use. eXist comes with Xerces which + can validate against both: schemas and DTDs. +

+ Possible values: "yes", "no", "auto". "auto" will leave validation + to the parser. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Fully-qualified class name of the Java module implementation. + The class must implement org.exist.xquery.Module. + Mutually exclusive with src. + + + + + + + Namespace URI that this module is bound to + (e.g. "http://exist-db.org/xquery/math"). + Must match the URI declared inside the module source. + + + + + + + Path to an XQuery library module source file. + Used instead of class for XQuery-implemented modules. + Mutually exclusive with class. + + + + + + + Set to "no" to disable this module without removing it from the configuration. + + + + + + + + + + + + + eXist supports calls to arbitrary Java methods from within + XQuery. Setting to "yes" might introduce a security risk. + + + + + + + Set to "yes" to disable deprecated functions + + + + + + + Set to "yes" to enable the new query-rewriting optimizer. This + is work in progress and may lead to incorrect queries. Use at your + own risk. + + + + + + + Set to "yes" to enable backward compatibility (untyped argument + checks for instance) + + + + + + + Set to "yes" if a call to doc(), xmldb:document(), collection() or + xmldb:xcollection() should raise an error (FODC0002) when an + XML resource cannot be retrieved. + Set to "no" (default) if such a call should return an empty sequence + when the resource cannot be retrieved. + + + + + + + Controls whether range indexes must be defined on all collections + in the context sequence before the query engine will use them. + "strict" — the index is used only if every collection in the context + defines it; otherwise the engine falls back to a full scan. + "always" — the engine uses an available index even if only some + collections define it; results may be incomplete if others lack the index. + + + + + + + + + + + + + + + + + Defines the maximum number of page splits allowed within a document + before a defragmentation run will be triggered. + + + + + + + For debugging only. If the parameter is set to "yes", a consistency + check will be run on every modified document after every XUpdate + request. It checks if the persistent DOM is complete and all + pointers in the structural index point to valid storage addresses + containing valid nodes. + + + + + + + Percentage by which the DOM storage file grows when it needs to + be expanded to accommodate new data. A value of 20 means the + file grows by 20% of its current size each time it is extended. + + + + + + + + + Load this configuration into Saxon + + + + + + + If `configuration-file` is a relative filename, it is relative to $EXIST_HOME/etc + (this is the same directory as the eXist-db configuration file `conf.xml`) + `configuration-file` may also be an absolute filename. + + + + + + + + + Defines the RPC server backend specific options used to process the requests. + + + + + + + + Defines per document specific cache settings. + + + + + + + Defines the maximum amount of bytes stored in memory for each content file instance. + + + + + + + + + Defines content file pool handling. + + + + + + + Defines the maximum content file pool size. If this size is exhausted an error is thrown. Using -1 sets no limit. + + + + + + + Defines the maximum amount of idle pool entries that will not be removed for later reuse. + + + + + + + + + + + + Global vector embedding model registry. When vector-field or vector:embed + omits model-path, the registry supplies path and dimension. Add models + here to use custom or additional embedding models. + + + + + + + + + Model identifier (e.g. all-MiniLM-L6-v2) + + + + + Local path or HuggingFace/API URL + + + + + Embedding dimension (default 384) + + + + + + + + + + + + + diff --git a/resources/schema/controller-config.xsd b/resources/schema/controller-config.xsd new file mode 100644 index 00000000..8bb533c9 --- /dev/null +++ b/resources/schema/controller-config.xsd @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/schema/descriptor.xsd b/resources/schema/descriptor.xsd new file mode 100644 index 00000000..f5e160a8 --- /dev/null +++ b/resources/schema/descriptor.xsd @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/resources/schema/expath-pkg.xsd b/resources/schema/expath-pkg.xsd new file mode 100644 index 00000000..4b338930 --- /dev/null +++ b/resources/schema/expath-pkg.xsd @@ -0,0 +1,231 @@ + + + + + A schema for EXPath Packaging (i.e. expath-pkg.xml) file as per the EXPath Packaging System - Candidate Module 9 May 2012' specification. + + Schema for EXPath Packaging + 2013-11-03T11:36:19.343+01:00 + Adam Retter + + + + + + + + + A simple description of the package for humans. + + + + + A URI to find more information about the package. + + + + + A dependency of this package. + + + + + + + The name of the package. A package is named using an IRI, as defined by [RFC 3987], excepted any IRI using the file: scheme (most frequent choices are http: and urn: scheme URIs). Note that the definition of IRI excludes relative references. + + + + + + + The version of the packaging specification the package conforms to. + + + + + + + + The standard component kinds supported by this specification, and how they contribute to the package descriptor document type. Every component has the same basic information: it associates a public URI to a specific file within the content directory. The file element contains a path, relative to the package content directory. Both elements in a component are of type anyURI. + + + + + + An XSLT file is associated a public import URI. This is the URI to use in an XSLT import instruction (aka xsl:import) to import the XSLT file provided in the package. + + + + + An XQuery library module is referenced by its namespace URI. Thus the xquery element associates a namespace URI to an XQuery file. An importing module just need to use an import statement of the form import module namespace xx = "<namespace-uri>'";. An XQuery main module is associated a public URI. Usually an XQuery package will provide functions through library modules, but in some cases one can want to provide main modules as well. + + + + + An XProc pipeline, like an XSLT stylesheet, is associated a public import URI, aimed to be used in an XProc p:import statement. + + + + + An XML schema can be imported using its target namespace. It is not possible to set several files as several sources for the schema. If the schema is spread over multiple files, there must be one top-level file that includes the other files. + + + + + A RelaxNG schema, like an XSLT stylesheet, is associated a public import URI, aimed to be used in the include element for an RNG schema. + + + + + A RelaxNG schema, like an XSLT stylesheet, is associated a public import URI, aimed to be used in an import directive for an RNC schema. + + + + + A Schematron schema is associated a public URI. + + + + + An NVDL script is associated a public URI. + + + + + A DTD file is associated a public URI. + + + + + A resource file is associated a public URI. This can be any kind of file. It has to be used in accordance to its content. For instance accessing a text file through fn:unparsed-text() is correct, while using fn:doc() is not (it will raise an error because it parses the content as XML). + + + + + + + + + + + The URI of the Namespace of the XQuery Library Module. + + + + + The URI of where the XQuery Main Module should be accessible from. + + + + + + The @namespace and @import-uri attributes are mutually exclusive. + + + + + + + + The URI of the Target Namespace of the XML Schema. + + + + + The import-uri can be used to define a schema location for this schema component. This can be useful for schema without target namespace, or for some specific usages, like when using xs:redefine. + + + + + + The @namespace and @import-uri attributes are mutually exclusive. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Contains a path, relative to the package content directory. + + + + + + Must specify a 'package' or 'processor', and one of 'versions', 'semver', or ('semver-min' and/or 'semver-max'). + + + + + + + The exact set of acceptable versions for the secondary package, separated by spaces. + + + + + + + + + The @package and @processor attributes are mutually exclusive. + + + + + + The @versions, @semver, and (@semver-min and/or @semver-max) attributes are mutually exclusive. + + + + + + + + + + + A SemVer 2.0.0 version number. + + + + + + + A subpart (i.e. only the major version, or the major and the minor versions, or the major, the minor and the patch version) of a SemVer 2.0.0 version number. For instance 1.9 is a valid SemVer template (because it does not have any patch number). + + + + + diff --git a/resources/schema/mime-types.xsd b/resources/schema/mime-types.xsd new file mode 100644 index 00000000..be052821 --- /dev/null +++ b/resources/schema/mime-types.xsd @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/resources/schema/schema-version-type.xsd b/resources/schema/schema-version-type.xsd new file mode 100644 index 00000000..ef64c96a --- /dev/null +++ b/resources/schema/schema-version-type.xsd @@ -0,0 +1,22 @@ + + + + + + + + + Native XSD semver; mirrors the paired schema's xs:schema/@version (not eXist product version). See schema/README.md. + + + + + + + diff --git a/resources/schema/security-manager.xsd b/resources/schema/security-manager.xsd new file mode 100644 index 00000000..a5cbed6c --- /dev/null +++ b/resources/schema/security-manager.xsd @@ -0,0 +1,67 @@ + + + + + + + LDAP Security Manager Schema + 2012-01-14 + + + Adam + Retter + + adam@exist-db.org + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/resources/schema/server.xsd b/resources/schema/server.xsd new file mode 100644 index 00000000..ff206f79 --- /dev/null +++ b/resources/schema/server.xsd @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + + + + The context path of the service. Specify an url-pattern as for a servlet-mapping. + Example: /xmlrpc/* forwards all paths beginning with /xmlrpc to the xmlrpc service + + + + + + + + + + + + + + + + + + + + Configures the stand-alone server: the stand-alone server is a minimal + webserver with just three services enabled by default: + WebDAV, XML-RPC, REST + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Define a forwarding for requests to the server root, i.e. + if you access the server without specifying a path + + + + + + + The destination path to which the request will be + forwarded. + + + + + + + + + Define URL forwardings + + + + + + + Requests to the given path will be forwarded + + + + + + + The destination path to which the request will be + forwarded + + + + + + + + + + + + \ No newline at end of file diff --git a/resources/schema/users.xsd b/resources/schema/users.xsd new file mode 100644 index 00000000..45da5443 --- /dev/null +++ b/resources/schema/users.xsd @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + Must be at least guest and dba groups, hence minOccurs=2 + + + + + + + + + + + + + + + + Must contain the admin user at least, hence minOccurs=1 + + + + + + Each user must be in at least one group, hence minOccurs=1 + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 48e66f0f6f90704d627e2d39ca822e7ea1c74d23 Mon Sep 17 00:00:00 2001 From: duncdrum Date: Tue, 29 Sep 2026 17:03:10 +0200 Subject: [PATCH 2/7] chore(schema): sync bundled fallbacks and align with exist-core#6687 - Re-sync resources/schema/conf.xsd (2.6.0 -> 2.7.0, omit-xml-declaration doc/attribute change) and controller-config.xsd (1.1.1 -> 1.1.2, new optional attribute) from exist-core's schema/ tree, which drifted since this branch was cut. - Update comments in editor.xqm, resources/schema/catalog.xml and resources/schema/README.md: exist#6686 (catalog-based jaxp resolution of native XSD 1.1 grammars by namespace) is fixed upstream as of exist-core#6687 / eXist 7.0.0-beta4, not still open. eXide keeps validation:jaxv-report + an explicit resolved URI rather than switching to validation:jaxp-report + catalog, since stable eXist releases still predate that fix and the WEB-INF system catalog doesn't cover every namespace in SCHEMA_BY_NS. Co-Authored-By: Claude Sonnet 5 --- modules/api/editor.xqm | 14 ++++++++++---- resources/schema/README.md | 5 +++-- resources/schema/catalog.xml | 10 +++++++--- resources/schema/conf.xsd | 17 ++++++----------- resources/schema/controller-config.xsd | 7 ++++++- 5 files changed, 32 insertions(+), 21 deletions(-) diff --git a/modules/api/editor.xqm b/modules/api/editor.xqm index a72011f7..0f6c9ba0 100644 --- a/modules/api/editor.xqm +++ b/modules/api/editor.xqm @@ -229,9 +229,14 @@ declare function editor:known-grammar-name($hint as xs:string?) as xs:string? { : then namespace map, then no-namespace root map. : Returns map { "uri", "source", "name" } or the empty sequence. : - : Interim: uses validation:jaxv-report with an explicit schema URI because - : jaxp + system/OASIS catalog does not yet apply native XSD 1.1 grammars - : (exist#6686). When that lands, prefer catalog resolution again. + : Uses validation:jaxv-report with an explicit schema URI rather than + : validation:jaxp-report + catalog. jaxp's catalog resolution of native XSD + : 1.1 grammars by namespace was fixed upstream (exist#6686, exist-core#6687, + : eXist 7.0.0-beta4+) but stable releases predating that fix still hit the + : old gap, and an explicit URI also covers grammars outside the WEB-INF + : system catalog (expath-pkg, security-manager, users, server). Revisit once + : the fix is in a stable release and/or the system catalog covers the full + : SCHEMA_BY_NS/SCHEMA_BY_ROOT set. :) declare function editor:resolve-schema($root as element()) as map(*)? { let $schema-loc-attr := @@ -352,7 +357,8 @@ declare function editor:validation-result( : resources/schema/; see eXide#842). Instance xsi:schemaLocation / : noNamespaceSchemaLocation is honoured when it points at a known grammar or : an absolute file:/db URI. Validation uses validation:jaxv-report + XSD 1.1 - : (interim until exist#6686 restores reliable catalog-based jaxp). + : against the resolved URI; see editor:resolve-schema for why this isn't + : validation:jaxp-report + catalog. : : XML without a resolvable grammar is checked for well-formedness only. :) diff --git a/resources/schema/README.md b/resources/schema/README.md index c3884107..df4897d4 100644 --- a/resources/schema/README.md +++ b/resources/schema/README.md @@ -6,5 +6,6 @@ Copies of eXist-db native grammars from `$EXIST_HOME/schema/` (exist#6528). fallback for older servers. Keep them in sync with exist's `schema/` tree. Long-term these files (and `catalog.xml`) can be removed once catalog-based -jaxp validation works against the native set (exist#6686) and unsupported -servers without `$EXIST_HOME/schema/` are gone. +jaxp validation against the native set — fixed upstream (exist#6686, +exist-core#6687) as of eXist 7.0.0-beta4 — reaches a stable release, and +unsupported servers without `$EXIST_HOME/schema/` are gone. diff --git a/resources/schema/catalog.xml b/resources/schema/catalog.xml index 8b6fdd7f..b0f9d7b6 100644 --- a/resources/schema/catalog.xml +++ b/resources/schema/catalog.xml @@ -4,9 +4,13 @@ editor:validate prefers validation:jaxv-report against $EXIST_HOME/schema/.xsd when present (exist#6528), else the sibling - copies in this directory (eXide#842). Catalog-based jaxp resolution for these - native XSD 1.1 grammars is not yet reliable (exist#6686); this catalog is kept - for tooling and for a future switch back to system/OASIS catalog validation. + copies in this directory (eXide#842). Catalog-based jaxp resolution of these + native XSD 1.1 grammars by namespace was fixed upstream (exist#6686, + exist-core#6687, eXist 7.0.0-beta4+), but stable releases predating that fix + still hit the old gap, and the WEB-INF system catalog doesn't yet cover every + namespace in SCHEMA_BY_NS (expath-pkg, security-manager). This catalog is + kept for tooling and for a future switch back to system/OASIS catalog + validation once both conditions clear. Fallback XSDs are synced from exist's schema/ tree and will be removed once older servers without $EXIST_HOME/schema/ are out of support. diff --git a/resources/schema/conf.xsd b/resources/schema/conf.xsd index 4cefe554..1e505b5e 100644 --- a/resources/schema/conf.xsd +++ b/resources/schema/conf.xsd @@ -8,7 +8,7 @@ + version="2.7.0"> @@ -1057,16 +1057,11 @@ - Should the XML Declaration for a document be serialized - if it is present? - - - - - - - Should the original persisted XML Declaration for a document be serialized - if it is present? + Should the XML Declaration be omitted when serializing a document? + If not omitted, the persisted XML Declaration of the document is + used if present, otherwise a default XML Declaration with version + "1.0" and encoding "UTF-8" is generated. The version, encoding and + standalone serialization parameters take precedence over both. diff --git a/resources/schema/controller-config.xsd b/resources/schema/controller-config.xsd index 8bb533c9..105858d6 100644 --- a/resources/schema/controller-config.xsd +++ b/resources/schema/controller-config.xsd @@ -4,7 +4,7 @@ xmlns:exist="http://exist.sourceforge.net/NS/exist" elementFormDefault="qualified" targetNamespace="http://exist.sourceforge.net/NS/exist" - version="1.1.1"> + version="1.1.2"> @@ -19,6 +19,11 @@ + + + If true, the servlet may not be deployed: a request then gets 404 rather than an error. + + From 2730fdda8d84881a201eb0024bf4ae0575222061 Mon Sep 17 00:00:00 2001 From: duncdrum Date: Tue, 29 Sep 2026 17:12:25 +0200 Subject: [PATCH 3/7] fix(editor): close schemaLocation file-read gap, require root corroboration editor:resolve-schema had two issues found by code review: - xsi:schemaLocation / xsi:noNamespaceSchemaLocation values matching file:|/db/ were used verbatim as the validation:jaxv-report schema URI. A client could point noNamespaceSchemaLocation at an arbitrary local path (e.g. file:///etc/passwd) and force the server to open it as a grammar. Only /db/ is honoured now, since eXist's own permission checks already gate access there; a raw file: hint is never trusted. - No-namespace documents were matched to an eXist config grammar by root local-name alone. Names like or are generic enough that an unrelated document could collide and get force-validated (and marked invalid) against users.xsd/server.xsd. Now also requires the root's documented required first child (SCHEMA_BY_ROOT_CHILD) to be present before matching. Co-Authored-By: Claude Sonnet 5 --- modules/api/editor.xqm | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/modules/api/editor.xqm b/modules/api/editor.xqm index 0f6c9ba0..fbee1277 100644 --- a/modules/api/editor.xqm +++ b/modules/api/editor.xqm @@ -140,6 +140,21 @@ declare variable $editor:SCHEMA_BY_ROOT := map { "server": "server.xsd" }; +(:~ + : Required first child (no namespace) for each $editor:SCHEMA_BY_ROOT root — + : a cheap corroborating signal so a generic root name alone (e.g. , + : — plausible root names for unrelated documents) doesn't + : force-match an unrelated document onto an eXist config grammar and report + : it invalid. + :) +declare variable $editor:SCHEMA_BY_ROOT_CHILD := map { + "exist": "db-connection", + "xquery-app": "allow-source", + "mime-types": "mime-type", + "auth": "groups", + "server": "listener" +}; + (:~ : True when the given filesystem path exists. File module namespace varies by : eXist-db version — same approach as admin:status. @@ -260,7 +275,7 @@ declare function editor:resolve-schema($root as element()) as map(*)? { return if (empty($loc)) then () - else if (matches($loc, "^(file:|/db/)")) then + else if (matches($loc, "^/db/")) then map { "uri": xs:anyURI($loc), "source": "instance", @@ -281,7 +296,7 @@ declare function editor:resolve-schema($root as element()) as map(*)? { if (exists($from-schema-location)) then () else if ($no-ns-loc ne "") then - if (matches($no-ns-loc, "^(file:|/db/)")) then + if (matches($no-ns-loc, "^/db/")) then map { "uri": xs:anyURI($no-ns-loc), "source": "instance", @@ -306,7 +321,12 @@ declare function editor:resolve-schema($root as element()) as map(*)? { let $from-root := if (exists(($from-schema-location, $from-no-ns, $from-ns))) then () - else if (namespace-uri($root) eq "" and map:contains($editor:SCHEMA_BY_ROOT, local-name($root))) then + else if ( + namespace-uri($root) eq "" and + map:contains($editor:SCHEMA_BY_ROOT, local-name($root)) and + exists($root/*[namespace-uri(.) eq "" and + local-name(.) eq $editor:SCHEMA_BY_ROOT_CHILD(local-name($root))]) + ) then editor:resolve-grammar-file($editor:SCHEMA_BY_ROOT(local-name($root))) else () @@ -356,9 +376,10 @@ declare function editor:validation-result( : `$EXIST_HOME/schema/` when present — exist#6528 — else bundled copies under : resources/schema/; see eXide#842). Instance xsi:schemaLocation / : noNamespaceSchemaLocation is honoured when it points at a known grammar or - : an absolute file:/db URI. Validation uses validation:jaxv-report + XSD 1.1 - : against the resolved URI; see editor:resolve-schema for why this isn't - : validation:jaxp-report + catalog. + : an absolute /db/ URI — never a raw file: URI, which would let a client + : force the server to open an arbitrary local file as a schema. Validation + : uses validation:jaxv-report + XSD 1.1 against the resolved URI; see + : editor:resolve-schema for why this isn't validation:jaxp-report + catalog. : : XML without a resolvable grammar is checked for well-formedness only. :) From 33f3651aeade7c7a183dcac93be90214b2ff3af0 Mon Sep 17 00:00:00 2001 From: duncdrum Date: Wed, 21 Oct 2026 10:13:50 +0200 Subject: [PATCH 4/7] fix(editor): corroborate shared eXist namespace, show validation mode in status The controller-config grammar was matched on the general-purpose eXist namespace alone, so REST envelopes and fragments were reported invalid. Require a root for that namespace. Also show "Validated against X" vs "Well-formed only" in the editor status. Co-Authored-By: Claude Sonnet 5.5 --- cypress/e2e/xml_validate_spec.cy.js | 9 ++++++++ modules/api/editor.xqm | 34 +++++++++++++++++++++++++---- src/xml-helper.js | 2 +- 3 files changed, 40 insertions(+), 5 deletions(-) diff --git a/cypress/e2e/xml_validate_spec.cy.js b/cypress/e2e/xml_validate_spec.cy.js index 06b26869..a3e41e1f 100644 --- a/cypress/e2e/xml_validate_spec.cy.js +++ b/cypress/e2e/xml_validate_spec.cy.js @@ -107,4 +107,13 @@ describe('XML validation (api/editor/validate)', () => { expect(res.body).to.have.property('grammar', 'none') }) }) + + it('does not force-match other documents in the shared eXist namespace', () => { + const xml = ` +1` + validate(xml).then((res) => { + expect(res.body).to.have.property('status', 'valid') + expect(res.body).to.have.property('grammar', 'none') + }) + }) }) diff --git a/modules/api/editor.xqm b/modules/api/editor.xqm index fbee1277..5d8e79fa 100644 --- a/modules/api/editor.xqm +++ b/modules/api/editor.xqm @@ -129,6 +129,32 @@ declare variable $editor:SCHEMA_BY_NS := map { "http://exist-db.org/Configuration": "security-manager.xsd" }; +(:~ + : Namespaces shared by unrelated eXist documents (REST envelopes, + : controller fragments, …) → the only root element that identifies + : the grammar. Namespaces not listed here match on namespace alone. + :) +declare variable $editor:SCHEMA_NS_ROOT := map { + "http://exist.sourceforge.net/NS/exist": "configuration" +}; + +(:~ + : Grammar filename for the root's namespace, or the empty sequence when the + : namespace is unknown or is shared and the root name doesn't corroborate it. + :) +declare function editor:grammar-by-ns($root as element()) as xs:string? { + let $ns := namespace-uri($root) + return + if ( + map:contains($editor:SCHEMA_BY_NS, $ns) and + (not(map:contains($editor:SCHEMA_NS_ROOT, $ns)) or + local-name($root) eq $editor:SCHEMA_NS_ROOT($ns)) + ) then + $editor:SCHEMA_BY_NS($ns) + else + () +}; + (:~ : No-namespace eXist config roots → grammar filename. :) @@ -286,8 +312,8 @@ declare function editor:resolve-schema($root as element()) as map(*)? { return if (exists($known)) then editor:resolve-grammar-file($known) - else if (map:contains($editor:SCHEMA_BY_NS, namespace-uri($root))) then - editor:resolve-grammar-file($editor:SCHEMA_BY_NS(namespace-uri($root))) + else if (exists(editor:grammar-by-ns($root))) then + editor:resolve-grammar-file(editor:grammar-by-ns($root)) else () else @@ -314,8 +340,8 @@ declare function editor:resolve-schema($root as element()) as map(*)? { let $from-ns := if (exists(($from-schema-location, $from-no-ns))) then () - else if (map:contains($editor:SCHEMA_BY_NS, namespace-uri($root))) then - editor:resolve-grammar-file($editor:SCHEMA_BY_NS(namespace-uri($root))) + else if (exists(editor:grammar-by-ns($root))) then + editor:resolve-grammar-file(editor:grammar-by-ns($root)) else () let $from-root := diff --git a/src/xml-helper.js b/src/xml-helper.js index 21663d2f..b88ef43b 100644 --- a/src/xml-helper.js +++ b/src/xml-helper.js @@ -318,7 +318,7 @@ eXide.edit.XMLModeHelper = (function () { editorUtils.setAnnotations(this.editor, annotations); } else { this.parent.clearErrors(); - this.parent.updateStatus(""); + this.parent.updateStatus(data.schema ? "Validated against " + data.schema : "Well-formed only"); } }; From 2e21f304a3f3ba0fdbc396324c6efd11cd67dcc6 Mon Sep 17 00:00:00 2001 From: duncdrum Date: Tue, 3 Nov 2026 12:52:51 +0100 Subject: [PATCH 5/7] test(cypress): expect Well-formed only after no-schema validate xml_wellformed_spec still asserted an empty #error-status after a successful fix, but compileError now writes the informational status Joe asked for. Co-authored-by: Cursor --- cypress/e2e/xml_wellformed_spec.cy.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/cypress/e2e/xml_wellformed_spec.cy.js b/cypress/e2e/xml_wellformed_spec.cy.js index b64e701c..9bafdb2b 100644 --- a/cypress/e2e/xml_wellformed_spec.cy.js +++ b/cypress/e2e/xml_wellformed_spec.cy.js @@ -49,13 +49,17 @@ describe('XML well-formedness reporting', () => { .and('match', /oops|well-formed|terminated|error/i) }) - it('clears the error once the document is well-formed again', () => { + it('replaces the error with a well-formed status once the document is fixed', () => { openNewXMLFile() validateNow('\n \n \n') cy.get('#error-status', { timeout: 10000 }).invoke('text').should('not.be.empty') + // No matching grammar → informational "Well-formed only" (not an empty + // status). Schema hits show "Validated against …" instead. validateNow('\n \n') - cy.get('#error-status', { timeout: 10000 }).invoke('text').should('be.empty') + cy.get('#error-status', { timeout: 10000 }) + .invoke('text') + .should('match', /Well-formed only/i) }) }) From 3c902a273999ff57338ceda0cc673a9d6917057c Mon Sep 17 00:00:00 2001 From: duncdrum Date: Tue, 3 Nov 2026 13:00:10 +0100 Subject: [PATCH 6/7] fix(editor): show XML validation outcome outside the error pill MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move "Well-formed only" / "Validated against …" to a calm #validation-status segment so successful validation no longer looks like an error. Co-authored-by: Cursor --- cypress/e2e/xml_wellformed_spec.cy.js | 17 +++++++++---- index.html.tmpl | 1 + resources/css/eXide.css | 16 ++++++++++++ src/editor.js | 36 ++++++++++++++++++++++++++- src/xml-helper.js | 5 +++- 5 files changed, 68 insertions(+), 7 deletions(-) diff --git a/cypress/e2e/xml_wellformed_spec.cy.js b/cypress/e2e/xml_wellformed_spec.cy.js index 9bafdb2b..cd364a6d 100644 --- a/cypress/e2e/xml_wellformed_spec.cy.js +++ b/cypress/e2e/xml_wellformed_spec.cy.js @@ -54,12 +54,19 @@ describe('XML well-formedness reporting', () => { validateNow('\n \n \n') cy.get('#error-status', { timeout: 10000 }).invoke('text').should('not.be.empty') + cy.get('#exide-err-pill').should('have.class', 'has-error') - // No matching grammar → informational "Well-formed only" (not an empty - // status). Schema hits show "Validated against …" instead. + // Success uses the calm #validation-status segment, not the error pill. + // No matching grammar → "Well-formed only"; schema hits → "Validated against …". validateNow('\n \n') - cy.get('#error-status', { timeout: 10000 }) - .invoke('text') - .should('match', /Well-formed only/i) + cy.get('#error-status', { timeout: 10000 }).invoke('text').should('be.empty') + cy.get('#exide-err-pill').should('not.have.class', 'has-error') + // Assert text on the element itself — .should('not.have.attr') yields + // undefined, so chaining .invoke('text') after it is unsafe. + cy.get('#validation-status', { timeout: 10000 }) + .should('be.visible') + .and(($el) => { + expect($el.text()).to.match(/Well-formed only/i) + }) }) }) diff --git a/index.html.tmpl b/index.html.tmpl index 586229c0..a19362be 100755 --- a/index.html.tmpl +++ b/index.html.tmpl @@ -400,6 +400,7 @@ Ln 1, Col 1 + diff --git a/resources/css/eXide.css b/resources/css/eXide.css index fcb2b6d8..b4dc1278 100644 --- a/resources/css/eXide.css +++ b/resources/css/eXide.css @@ -1106,6 +1106,19 @@ Sub level menu list items (undo style from Top level List Items) font-weight: 600; } +/* Calm XML validation outcome — not an error; kept off the #error-status channel */ +#validation-status { + color: #c6f6d5; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 360px; + flex-shrink: 1; +} +#validation-status[hidden] { + display: none !important; +} + /* Hidden — error text is mirrored into #exide-err-pill by error-status-ui.js */ #error-status { display: none; @@ -3841,6 +3854,9 @@ body.dark #status-bar { body.dark #status-bar .status-separator { color: #2c5282; } +body.dark #validation-status { + color: #9ae6b4; +} body.dark #status a:link, body.dark #status a:visited { color: #90cdf4; } diff --git a/src/editor.js b/src/editor.js index 3e15ba07..df3996a3 100644 --- a/src/editor.js +++ b/src/editor.js @@ -469,6 +469,7 @@ eXide.edit.Editor = (function () { // Set up the status bar this.status = document.getElementById("error-status"); + this.validationStatus = document.getElementById("validation-status"); this.status.addEventListener("click", function (ev) { ev.preventDefault(); var path = this.pathname; @@ -740,6 +741,7 @@ eXide.edit.Editor = (function () { $this.editor.focus(); eXide.app.toggleRunStatus(doc); + this.clearValidationStatus(); if (doc.getModeHelper()) { doc.getModeHelper().activate(doc); } @@ -1235,6 +1237,7 @@ eXide.edit.Editor = (function () { } }); this.updateStatus(""); + this.clearValidationStatus(); this.$triggerEvent("activate", [doc]); eXide.app.toggleRunStatus(doc); @@ -1294,13 +1297,44 @@ eXide.edit.Editor = (function () { }; /** - * Update the status bar. + * Update the status bar error channel (#error-status → red pill). + * Non-empty messages clear the calm validation segment so valid and + * invalid never share the same chrome. */ Constr.prototype.updateStatus = function(msg, href) { this.status.textContent = msg; if (href) { this.status.href = href; } + if (msg) { + this.clearValidationStatus(); + } + }; + + /** + * Show a non-error XML validation outcome in #validation-status + * ("Well-formed only" / "Validated against …"). Clears the error + * channel so the red pill does not linger over a successful result. + */ + Constr.prototype.setValidationStatus = function(msg) { + if (!this.validationStatus) { + return; + } + var text = msg || ""; + this.validationStatus.textContent = text; + if (text) { + this.validationStatus.hidden = false; + this.status.textContent = ""; + if (this.status.dataset) { + delete this.status.dataset.error; + } + } else { + this.validationStatus.hidden = true; + } + }; + + Constr.prototype.clearValidationStatus = function() { + this.setValidationStatus(""); }; Constr.prototype.evalError = function(msg, gotoLine, errObj) { diff --git a/src/xml-helper.js b/src/xml-helper.js index b88ef43b..a74514f5 100644 --- a/src/xml-helper.js +++ b/src/xml-helper.js @@ -313,12 +313,15 @@ eXide.edit.XMLModeHelper = (function () { }); } if (errors.length > 0) { + this.parent.clearValidationStatus(); this.parent.updateStatus(errors[0].message, doc.getPath() + "#" + errors[0].line); } editorUtils.setAnnotations(this.editor, annotations); } else { this.parent.clearErrors(); - this.parent.updateStatus(data.schema ? "Validated against " + data.schema : "Well-formed only"); + this.parent.setValidationStatus( + data.schema ? "Validated against " + data.schema : "Well-formed only" + ); } }; From 34f777a1a08185f572fecfcb0c0b5c182a9f090c Mon Sep 17 00:00:00 2001 From: duncdrum Date: Tue, 3 Nov 2026 13:04:14 +0100 Subject: [PATCH 7/7] fix(editor): style XML validation status as a calm green pill Distinguish success from the red error pill with a check badge, separator, and dedicated Cypress coverage for schema-ok / well-formed / schema-error. Co-authored-by: Cursor --- cypress/e2e/xml_validation_status_spec.cy.js | 62 ++++++++++++++++++++ cypress/e2e/xml_wellformed_spec.cy.js | 9 ++- index.html.tmpl | 6 +- resources/css/eXide.css | 26 +++++++- src/editor.js | 11 +++- 5 files changed, 105 insertions(+), 9 deletions(-) create mode 100644 cypress/e2e/xml_validation_status_spec.cy.js diff --git a/cypress/e2e/xml_validation_status_spec.cy.js b/cypress/e2e/xml_validation_status_spec.cy.js new file mode 100644 index 00000000..5896c55e --- /dev/null +++ b/cypress/e2e/xml_validation_status_spec.cy.js @@ -0,0 +1,62 @@ +describe('XML validation status UX', () => { + beforeEach(() => { + cy.loginXHR('admin', '') + cy.visit('/eXide/index.html', { + onBeforeLoad(win) { win.localStorage.setItem('eXide.firstTime', '0') } + }) + cy.dismissDialog() + }) + + function openNewXMLFile() { + cy.get('#new').click() + cy.get('#dialog-templates').closest('.eXide-dialog', { timeout: 10000 }).should('have.attr', 'open') + cy.get('select.type-select').select('XML') + cy.get('#dialog-templates').closest('.eXide-dialog') + .find('.eXide-dialog-buttons button').contains('Create').click() + } + + function validateNow(text) { + cy.window().then((win) => { + const editor = win.eXide.app.getEditor() + const doc = editor.getActiveDocument() + doc.setText(text) + doc.lastValidation = 0 + return editor.validator.triggerNow(doc) + }) + } + + it('shows Validated against for collection.xconf without an error pill', () => { + openNewXMLFile() + validateNow(` + + +`) + cy.get('#exide-err-pill').should('not.have.class', 'has-error') + cy.get('#validation-status', { timeout: 10000 }).should('be.visible') + cy.get('#validation-status-label') + .invoke('text') + .should('match', /Validated against collection\.xconf\.xsd/i) + cy.get('#status-bar').screenshot('validation-status-schema-ok') + }) + + it('shows Well-formed only without an error pill', () => { + openNewXMLFile() + validateNow('hi') + cy.get('#exide-err-pill').should('not.have.class', 'has-error') + cy.get('#validation-status', { timeout: 10000 }).should('be.visible') + cy.get('#validation-status-label') + .invoke('text') + .should('match', /Well-formed only/i) + cy.get('#status-bar').screenshot('validation-status-wellformed-only') + }) + + it('shows schema errors on the error pill and clears validation status', () => { + openNewXMLFile() + validateNow(` +`) + cy.get('#validation-status').should('not.be.visible') + cy.get('#exide-err-pill').should('have.class', 'has-error') + cy.get('#error-status').invoke('text').should('match', /assert|count/i) + cy.get('#status-bar').screenshot('validation-status-schema-error') + }) +}) diff --git a/cypress/e2e/xml_wellformed_spec.cy.js b/cypress/e2e/xml_wellformed_spec.cy.js index cd364a6d..3056e0e2 100644 --- a/cypress/e2e/xml_wellformed_spec.cy.js +++ b/cypress/e2e/xml_wellformed_spec.cy.js @@ -63,10 +63,9 @@ describe('XML well-formedness reporting', () => { cy.get('#exide-err-pill').should('not.have.class', 'has-error') // Assert text on the element itself — .should('not.have.attr') yields // undefined, so chaining .invoke('text') after it is unsafe. - cy.get('#validation-status', { timeout: 10000 }) - .should('be.visible') - .and(($el) => { - expect($el.text()).to.match(/Well-formed only/i) - }) + cy.get('#validation-status', { timeout: 10000 }).should('be.visible') + cy.get('#validation-status-label') + .invoke('text') + .should('match', /Well-formed only/i) }) }) diff --git a/index.html.tmpl b/index.html.tmpl index a19362be..2453c2a9 100755 --- a/index.html.tmpl +++ b/index.html.tmpl @@ -400,7 +400,11 @@ Ln 1, Col 1 - + + diff --git a/resources/css/eXide.css b/resources/css/eXide.css index b4dc1278..862b8bf0 100644 --- a/resources/css/eXide.css +++ b/resources/css/eXide.css @@ -1107,16 +1107,34 @@ Sub level menu list items (undo style from Top level List Items) } /* Calm XML validation outcome — not an error; kept off the #error-status channel */ +#validation-status-sep[hidden], +#validation-status[hidden] { + display: none !important; +} #validation-status { + display: inline-flex; + align-items: center; + gap: 5px; + padding: 1px 8px; + border-radius: 10px; + background: rgba(154, 230, 180, 0.22); color: #c6f6d5; + font-size: 11px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 360px; flex-shrink: 1; + user-select: none; } -#validation-status[hidden] { - display: none !important; +#validation-status .fa { + font-size: 11px; + color: #9ae6b4; + flex-shrink: 0; +} +#validation-status-label { + overflow: hidden; + text-overflow: ellipsis; } /* Hidden — error text is mirrored into #exide-err-pill by error-status-ui.js */ @@ -3855,6 +3873,10 @@ body.dark #status-bar .status-separator { color: #2c5282; } body.dark #validation-status { + background: rgba(154, 230, 180, 0.15); + color: #c6f6d5; +} +body.dark #validation-status .fa { color: #9ae6b4; } body.dark #status a:link, body.dark #status a:visited { diff --git a/src/editor.js b/src/editor.js index df3996a3..8f19ddc1 100644 --- a/src/editor.js +++ b/src/editor.js @@ -470,6 +470,8 @@ eXide.edit.Editor = (function () { // Set up the status bar this.status = document.getElementById("error-status"); this.validationStatus = document.getElementById("validation-status"); + this.validationStatusLabel = document.getElementById("validation-status-label"); + this.validationStatusSep = document.getElementById("validation-status-sep"); this.status.addEventListener("click", function (ev) { ev.preventDefault(); var path = this.pathname; @@ -1321,15 +1323,22 @@ eXide.edit.Editor = (function () { return; } var text = msg || ""; - this.validationStatus.textContent = text; + var label = this.validationStatusLabel || this.validationStatus; + label.textContent = text; if (text) { this.validationStatus.hidden = false; + if (this.validationStatusSep) { + this.validationStatusSep.hidden = false; + } this.status.textContent = ""; if (this.status.dataset) { delete this.status.dataset.error; } } else { this.validationStatus.hidden = true; + if (this.validationStatusSep) { + this.validationStatusSep.hidden = true; + } } };