diff --git a/CHANGES-parse-all-declarations.md b/CHANGES-parse-all-declarations.md new file mode 100644 index 000000000..396959e36 --- /dev/null +++ b/CHANGES-parse-all-declarations.md @@ -0,0 +1,98 @@ +# Feature: Parse All XSLT 3.0 Declarations & Graceful Degradation + +## Summary + +This change enables the XSLT parser and compiler to handle all 18 XSLT 3.0 +top-level declaration types. Previously, only 5 declarations were parsed +(Accumulator, Template, Output, Mode, Function); the remaining 13 caused +immediate hard errors that prevented the entire stylesheet from compiling. + +Now all declarations are parsed into the AST. Declarations that lack full +compiler support are gracefully skipped instead of aborting, allowing +stylesheets that *contain* unsupported declarations to still process their +templates. + +## Test Results + +| Metric | Before | After | Delta | +|--------|--------|-------|-------| +| Passed | 1,129 | 1,178 | **+49** | +| Failed | 604 | 738 | +134 | +| Error | 12,831 | 12,585 | **-246** | +| "Unsupported declaration" errors | ~9,003 | 37 | **-8,966** | + +The +134 in "Failed" is actually progress — those tests now compile and run +but produce wrong results (due to skipped declarations like global variables), +rather than aborting outright. This makes them visible for future work. + +## Changes + +### 1. AST: `From for Declaration` implementations (`xee-xslt-ast/src/ast_core.rs`) + +Added `From for Declaration` trait implementations for 13 types that were +missing them: + +- `CharacterMap`, `DecimalFormat`, `GlobalContextItem` +- `Import`, `ImportSchema`, `Include` +- `Key`, `NamespaceAlias` +- `Param`, `PreserveSpace`, `StripSpace` +- `UsePackage`, `Variable` + +These are required for the `DeclarationParser` trait to work (it requires both +`InstructionParser` and `Into`). + +### 2. Parser: Hook up all declarations (`xee-xslt-ast/src/names.rs`) + +Extended `DeclarationName::parse()` from 5 variants to 17 variants. Only +`UsePackage` remains unsupported (no `InstructionParser` implementation exists +for it yet; affects only 37 tests). + +### 3. Compiler: Graceful skip (`xee-xslt-compiler/src/ast_ir.rs`) + +Changed the `declaration()` method from returning `Err(Unsupported(...))` for +unknown declarations to returning `Ok(())` with explicit match arms: + +- **Compiled:** `Template`, `Mode`, `Output` (as before) +- **Pre-processed:** `Import`, `Include` (handled during import resolution) +- **Skipped gracefully:** `Function`, `Variable`, `Param`, `Key`, + `StripSpace`, `PreserveSpace`, `DecimalFormat`, `CharacterMap`, + `NamespaceAlias`, `ImportSchema`, `UsePackage`, `GlobalContextItem`, + `Accumulator` + +### 4. Import cycle detection (`xee-xslt-compiler/src/ast_ir.rs`) + +Added `HashSet` tracking of visited stylesheet paths during +`xsl:import` and `xsl:include` processing. Previously, self-referential or +circular imports caused a stack overflow crash. Now they produce a clear error: +`"Circular import detected: '/path/to/file.xsl'"`. + +## New Error Landscape + +After these changes, the dominant error categories are: + +| Error | Count | Cause | +|-------|-------|-------| +| XPST0008 Name not defined | 4,629 | Global variables/params skipped but referenced | +| Instruction not supported: SourceDocument | 1,677 | Test harness uses `` | +| Failed parsing XSLT: Attribute | 1,352 | Various attribute parsing gaps | +| XPST0017 function not found | 911 | User-defined `xsl:function` not callable from XPath | +| No stylesheet found | 333 | Test runner can't locate stylesheet | + +## Recommended Next Steps + +1. **Global `xsl:variable` / `xsl:param` support** — Would resolve ~4,629 + XPST0008 errors. Requires architectural work to make global bindings + available in template function scopes. + +2. **`xsl:function` compilation** — Would resolve ~911 XPST0017 errors. + Requires registering user-defined functions in the `StaticContext` so XPath + expressions can resolve them. + +3. **`xsl:source-document` instruction** — Would unblock ~1,677 tests. Many + test stylesheets use this instruction to load input. + +## Patch File + +Saved as: `0001-parse-all-xslt-declarations-and-skip-gracefully.patch` + +Apply with: `git apply 0001-parse-all-xslt-declarations-and-skip-gracefully.patch` diff --git a/CHANGES-xslt-param-validation-and-runner.md b/CHANGES-xslt-param-validation-and-runner.md new file mode 100644 index 000000000..f336d5e1c --- /dev/null +++ b/CHANGES-xslt-param-validation-and-runner.md @@ -0,0 +1,247 @@ +# Feature: XSLT Param Validation, Required Param Runtime Errors, and Test Runner Wiring + +## Summary + +This change completes a focused pass on XSLT parameter handling around three +areas that were previously missing or only partially wired: + +1. Static validation for invalid `xsl:param` declarations +2. Dynamic errors for missing required stylesheet/template parameters +3. Test runner support needed to exercise these semantics correctly + +The implementation does not yet solve parameter propagation for +`xsl:apply-templates`. That remains the next major gap. But it does make the +named-template and global-parameter behavior materially more correct and fixes +several targeted conformance cases. + +## What Was Implemented + +### 1. Static validation for invalid required params + +Added validation in `xee-xslt-compiler/src/ast_ir.rs` so that an `xsl:param` +with `required="yes"` cannot also provide a `select` attribute or child +sequence constructor. + +This now raises `XTSE0010` during compilation instead of silently compiling the +stylesheet. + +Applied to: + +- global `xsl:param` +- template-local `xsl:param` + +### 2. IR now records whether a parameter is required + +Extended `xee-ir/src/ir.rs` so `ir::Param` carries a `required: bool` field. + +This is important because requiredness must survive lowering from XSLT AST into +IR and then into bytecode generation, especially for named templates. + +### 3. Required template params now fail at runtime + +Updated `xee-ir/src/function_compiler.rs` to emit explicit runtime checks for +required parameters that do not have defaults. + +Implementation details: + +- existing default-parameter handling already used `VarIsAbsent` +- required params now also use `VarIsAbsent` +- if the argument is absent, the compiler emits a dedicated bytecode raise path +- the interpreter maps that to `XTDE0700` + +This keeps required-param behavior localized and avoids overloading unrelated +error paths. + +### 4. Required global stylesheet params now fail at runtime + +Updated `xee-interpreter/src/interpreter/interpret.rs` so global stylesheet +params marked `required` now raise `XTDE0050` if no externally supplied value +exists in the dynamic context. + +Before this change, missing required stylesheet params simply fell through and +executed as if no error existed. + +### 5. Named template parameter matching is stricter + +Updated named-template lookup/matching in `xee-ir/src/declaration_compiler.rs` +and `xee-ir/src/function_compiler.rs`. + +Behavior changes: + +- named templates are keyed by stable local-name strings instead of debug + formatting +- `xsl:with-param` names are checked against the declared params of the target + named template +- undeclared non-tunnel params now raise `XTSE0680` + +This fixes cases where `call-template` supplied a parameter the callee never +declared. + +### 6. Added dedicated bytecode support for raising required-param errors + +Added `RaiseError(RaisedError)` to +`xee-interpreter/src/interpreter/instruction.rs` with a current specialized +variant for `XTDE0700`. + +This keeps the runtime implementation simple: + +- compiler emits a specific instruction +- interpreter decodes it and returns the correct XSLT error + +The goal was to avoid a larger general-purpose exception mechanism for this +targeted fix. + +### 7. Test runner now passes stylesheet variables into the dynamic context + +Updated `xee-testrunner/src/testcase/xslt.rs` so XSLT tests actually load the +environment variables and pass them into the program's dynamic context. + +Without this, required global-param tests could not be evaluated correctly, +because externally supplied stylesheet parameters were ignored by the runner. + +### 8. Test runner now honors `initial-template` + +Updated `xee-testrunner/src/testcase/xslt.rs` and +`xee-interpreter/src/interpreter/runnable.rs` so XSLT conformance tests can run +an initial named template instead of always starting from the normal main +template path. + +Implementation details: + +- parse `` from the test definition +- look up the named template in runtime declarations +- invoke it directly through the interpreter +- supply explicit absent placeholders for unsupplied template arguments so + required-param checks run in the callee rather than failing early on arity + +This was necessary for `param-0116` to test the intended behavior. + +### 9. XSLT compile errors are now surfaced as compilation errors in the runner + +Updated `xee-testrunner/src/testcase/xslt.rs` so stylesheet compilation errors +are handled like XPath compilation errors: + +- if the test expects an error, compare against the error code +- otherwise report a compilation error + +Previously these cases were often classified as generic environment errors, +which made static-error conformance checks less meaningful. + +## Files Changed + +### Compiler and IR + +- `xee-xslt-compiler/src/ast_ir.rs` +- `xee-ir/src/ir.rs` +- `xee-ir/src/declaration_compiler.rs` +- `xee-ir/src/function_compiler.rs` +- `xee-xpath-compiler/src/ast_ir.rs` +- `xee-ir/tests/test_xml_ir.rs` + +### Interpreter + +- `xee-interpreter/src/error.rs` +- `xee-interpreter/src/function/inline_function.rs` +- `xee-interpreter/src/declaration/decl.rs` +- `xee-interpreter/src/interpreter/instruction.rs` +- `xee-interpreter/src/interpreter/interpret.rs` +- `xee-interpreter/src/interpreter/runnable.rs` + +### Test Runner + +- `xee-testrunner/src/testcase/xslt.rs` + +## Error Codes Added or Wired In + +The following XSLT error codes are now present and used by this change: + +- `XTSE0010` for invalid `required="yes"` declarations with a default/select +- `XTDE0050` for missing required global stylesheet params +- `XTSE0680` for undeclared `xsl:with-param` on `xsl:call-template` +- `XTDE0700` for missing required template params at runtime + +## Verification + +### Focused Rust crate tests + +Verified with: + +```bash +cargo test -q -p xee-interpreter -p xee-ir -p xee-xslt-compiler -p xee-testrunner +``` + +Result: all tests passed. + +### Targeted conformance cases fixed + +These cases were explicitly rerun and now pass: + +- `param-0111` +- `param-0112` +- `param-0116` +- `variable-2202` + +### Aggregate conformance snapshot after this change + +`decl/param`: + +- Passed: 11 +- Failed: 0 +- Error: 13 +- WrongE: 7 + +`decl/variable`: + +- Passed: 45 +- Failed: 4 +- Error: 49 +- WrongE: 10 + +## What This Does Not Yet Solve + +The main remaining semantic gap is still `xsl:apply-templates` parameter +transport and matched-template params. + +Known example still failing: + +- `variable-1201` produces `ABC_ABC` instead of `ABC_25` + +That indicates: + +1. `xsl:apply-templates` is still dropping `xsl:with-param` +2. matched templates still are not consuming local `xsl:param` as callable + parameters + +## Commit Readiness + +This is a reasonable checkpoint commit. + +Why it is commit-worthy: + +- it is internally coherent +- crate-level regression tests are green +- several targeted conformance cases are now fixed +- the runner changes are necessary for the parameter work to be testable at all +- the remaining failure area is clearly isolated to `apply-templates` param + propagation, not to this required-param work + +Why it is not a "finished param system" commit: + +- `apply-templates` parameter propagation is still missing +- aggregate declaration suites still contain substantial `Error` and `WrongE` + counts + +Suggested commit framing: + +- "Add XSLT required param validation and runner support" +- or "Implement XSLT param validation and initial-template runner support" + +## Next Step + +The next implementation target should be end-to-end support for +`xsl:apply-templates` parameters: + +1. extend IR `ApplyTemplates` to carry params +2. lower `ApplyTemplatesContent::WithParam` +3. pass params through runtime template dispatch +4. compile matched templates with consumable template params \ No newline at end of file diff --git a/CHANGES-xslt-scoping-and-template-context.md b/CHANGES-xslt-scoping-and-template-context.md new file mode 100644 index 000000000..220e44502 --- /dev/null +++ b/CHANGES-xslt-scoping-and-template-context.md @@ -0,0 +1,245 @@ +# Feature: XSLT Variable Scoping, Named-Template Focus, and Sequence Constructor Fixes + +## Summary + +This change is a follow-up to the earlier parameter-runtime work. Its focus is +not new error codes, but correctness of variable and parameter binding across +XSLT and shared XPath lowering. + +The main areas improved here are: + +1. lexical scoping for XSLT local variables and params +2. context/focus plumbing for named templates +3. XPath declaration scoping for `let`, `for`, quantified expressions, and + inline function parameters +4. sequence-constructor lowering so `xsl:variable` is handled correctly even + when it does not appear as the first item in a constructor + +This pass fixes a real cluster of declaration failures, especially those that +previously reported internal compiler errors around missing variables or +mis-bound shadowed variables. + +## What Was Implemented + +### 1. Lexical variable scopes in the shared IR variable table + +Updated `xee-ir/src/variables.rs` so variable bindings are no longer stored in +one flat map for the entire conversion pass. + +The `Variables` helper now maintains a stack of lexical scopes and exposes +separate operations for: + +- looking up an existing variable binding +- declaring a fresh local binding in the current scope +- entering and exiting a lexical scope + +This fixes two correctness problems: + +- local variables and params can now shadow outer bindings correctly +- a variable is no longer visible within its own initializer unless an outer + binding of the same name exists + +That matters for both XSLT local variable declarations and the shared XPath IR +converter used to compile expressions inside XSLT instructions. + +### 2. Template-local scopes are isolated between separately compiled templates + +Updated `xee-xslt-compiler/src/ast_ir.rs` so each template compilation pushes a +fresh variable scope before registering its params and lowering its body. + +Before this change, template-local parameter bindings could leak across +templates because the compiler reused the same variable table while compiling +the whole stylesheet. + +The result was a class of bogus internal failures where one template body could +end up referencing a runtime name that had only been declared while compiling a +different template. + +This was visible in cases such as `param-0107`, where the unresolved variable +coming out of lowering was not from the current template at all. + +### 3. Named templates now receive the current focus triple + +Updated the XSLT AST lowering and IR call path so named templates receive the +current focus, matching what matched templates already received. + +Concretely: + +- `xee-ir/src/ir.rs` now records optional call-site context on + `ir::CallTemplate` +- `xee-xslt-compiler/src/ast_ir.rs` captures the current context when lowering + `xsl:call-template` +- named template function definitions now include hidden leading params for: + - current item + - current position + - current size +- `xee-ir/src/declaration_compiler.rs` now registers only the explicit template + params for name validation, skipping those hidden focus params +- `xee-ir/src/function_compiler.rs` passes either the current focus triple or + explicit absent markers when compiling a named-template call + +This fixes named-template bodies and defaults that depend on the current node, +for example template params with defaults such as `select="@id"`. + +### 4. Matched and named template param defaults now compile with the right scope + +The earlier apply-templates work already moved matched-template defaults under a +live context. This pass tightens the surrounding lexical scope handling so both +matched and named templates compile defaults while the correct template-local +bindings are visible and isolated. + +That improves cases where one param default references another param, and cases +where a default references the context item. + +### 5. Sequence constructors now handle `xsl:variable` anywhere, not just first + +Updated `xee-xslt-compiler/src/ast_ir.rs` so sequence-constructor lowering no +longer assumes that only the first item in a constructor might be a variable +declaration. + +Previously: + +- `xsl:variable` was recognized only if it appeared at the head of the current + constructor slice +- a later `xsl:variable` would fall through into normal instruction lowering +- that triggered the internal error path: + `Internal bug: variable node should have been processed already` + +The new recursive lowering handles variable declarations anywhere in the +constructor while still preserving `let` nesting semantics. + +This directly fixed `param-0107`. + +### 6. XPath declaration sites now use real lexical declaration scopes + +Updated `xee-xpath-compiler/src/ast_ir.rs` so XPath declaration forms no longer +use the old “lookup or create” variable registration behavior. + +Adjusted sites include: + +- `let` expressions +- `for` expressions +- quantified expressions +- inline function parameters + +These declaration sites now push a fresh scope, declare their binding names in +that scope, lower the body, and then pop the scope. + +This is necessary because XSLT param and variable expressions can contain XPath +shadowing forms such as: + +```xpath +($y, (for $y in ('x', 'y', 'z') return $y)) +``` + +Without lexical declaration scopes, the inner `$y` could be compiled against +the wrong binding or reuse an outer runtime name. + +## Files Changed + +### IR and Compiler Infrastructure + +- `xee-ir/src/variables.rs` +- `xee-ir/src/ir.rs` +- `xee-ir/src/declaration_compiler.rs` +- `xee-ir/src/function_compiler.rs` + +### XSLT Lowering + +- `xee-xslt-compiler/src/ast_ir.rs` + +### XPath Lowering + +- `xee-xpath-compiler/src/ast_ir.rs` + +## Verification + +### Focused Rust crate tests + +Verified with: + +```bash +cargo test -q -p xee-ir -p xee-interpreter -p xee-xslt-compiler +``` + +Result: all tests passed. + +### Targeted conformance cases rerun during this pass + +Explicitly rerun and confirmed fixed: + +- `param-0107` + +Explicitly rerun and confirmed improved but still not fully correct: + +- `variable-3301` + - no longer fails with an internal compiler error + - now reaches result comparison and fails with an XML mismatch + +### Aggregate conformance snapshot after this change + +`decl/param`: + +- Passed: 13 +- Failed: 0 +- Error: 11 +- WrongE: 7 + +`decl/variable`: + +- Passed: 58 +- Failed: 10 +- Error: 32 +- WrongE: 8 + +Compared to the previous checkpoint, this is a net improvement in both suites, +especially by removing part of the internal-variable-resolution failure class. + +## What This Does Not Yet Solve + +Several known areas remain open: + +1. tunnel-parameter semantics are still incomplete +2. some remaining `decl/variable` failures now show behavioral mismatches rather + than compiler crashes +3. unsupported instruction families such as `next-match`, `apply-imports`, and + `for-each-group` still account for some remaining declaration-suite failures + +One useful sign of progress is that a number of reducers that previously failed +with internal compiler errors now fail later and more specifically, which means +the binding model is materially closer to correct. + +## Commit Readiness + +This is a reasonable checkpoint commit. + +Why it is commit-worthy: + +- it fixes real correctness bugs in shared variable scoping +- it removes a class of internal compiler errors rather than only changing test + outcomes superficially +- focused Rust regression tests are green +- declaration-suite pass counts improved in both `param` and `variable` +- it is logically cohesive with the current XSLT declaration work + +Why it is not a final declaration-semantics commit: + +- tunnel parameters are still incomplete +- some template/application behavior is still wrong even though the compiler no + longer crashes +- several remaining failures are due to still-unsupported XSLT instruction + families + +Suggested commit framing: + +- `Fix XSLT variable scoping and named-template context` +- or `Improve XSLT template param scoping and context binding` + +## Next Step + +The next highest-value target is likely one of: + +1. the tunnel-parameter cluster around tests such as `variable-0203` +2. the remaining behavioral mismatch in `variable-3301` +3. unsupported instruction families that are now standing out more clearly + after the scoping fixes \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index ec93621f8..708e9d9f8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2924,6 +2924,7 @@ dependencies = [ "strum 0.27.1", "test-generator", "thiserror 2.0.12", + "url", "v_jsonescape", "xee-name", "xee-schema-type", diff --git a/vendor/xslt-tests/filters b/vendor/xslt-tests/filters index f5218eac4..af749c093 100644 --- a/vendor/xslt-tests/filters +++ b/vendor/xslt-tests/filters @@ -55,24 +55,18 @@ accessor-003 accessor-004 accessor-005 accessor-006 -accessor-008 accessor-010 accessor-013 accessor-014 accessor-015 -accessor-016 -accessor-020 accessor-021 accessor-022 -accessor-024 accessor-025 accessor-026 accessor-027 accessor-028 -accessor-030 accessor-034 accessor-035 -accessor-036 accessor-037 accessor-038 accessor-050 @@ -262,43 +256,15 @@ apply-imports-001 = apply-templates apply-templates-001 apply-templates-002 -conflict-resolution-0101 -conflict-resolution-0102a conflict-resolution-0102b -conflict-resolution-0102c -conflict-resolution-0104a conflict-resolution-0104b -conflict-resolution-0104c -conflict-resolution-0106 -conflict-resolution-0107 -conflict-resolution-0108a conflict-resolution-0108b -conflict-resolution-0108c -conflict-resolution-0110a conflict-resolution-0110b -conflict-resolution-0110c -conflict-resolution-0112 -conflict-resolution-0201 conflict-resolution-0401b conflict-resolution-0501 -conflict-resolution-0502 conflict-resolution-0503 -conflict-resolution-0601 -conflict-resolution-0701 -conflict-resolution-0702 -conflict-resolution-0801 -conflict-resolution-0802 -conflict-resolution-1001 -conflict-resolution-1101 -conflict-resolution-1102 -conflict-resolution-1201 -conflict-resolution-1202a conflict-resolution-1202b -conflict-resolution-1202c conflict-resolution-1204 -conflict-resolution-1205 -conflict-resolution-1301 -conflict-resolution-1401 conflict-resolution-1402 conflict-resolution-1501 = arrays @@ -365,139 +331,46 @@ square-array-142 square-array-143 square-array-201 = as -as-0101 -as-0102 -as-0103 -as-0104 -as-0105 as-0106 as-0106a as-0106b -as-0107 -as-0108 -as-0109 as-0110 as-0110a as-0110b as-0111 as-0111a as-0111b -as-0112 as-0112a -as-0112b as-0113 as-0114 -as-0115 -as-0116 -as-0117 -as-0118 -as-0119 -as-0120 -as-0121 -as-0122 as-0123 -as-0124 -as-0125 -as-0126 -as-0127 -as-0128 -as-0129 -as-0130 -as-0131 -as-0132 -as-0133 -as-0134 -as-0135 -as-0136 -as-0137 -as-0138 -as-0139 -as-0140 -as-0141 as-0142 -as-0143 -as-0144 as-0144a -as-0145 -as-0146 as-0147 as-0148 as-0149 -as-0150 as-0151 as-0152 -as-0201 -as-0301 -as-0302 -as-0303 -as-0304 -as-0401 -as-0402 -as-0501 as-0501a -as-0501b as-0601 as-0701 as-0702 -as-0703 -as-0801 as-0801a -as-0801b as-0802 as-0802a as-0802b -as-0901 as-1001 as-1101 as-1201 -as-1202 -as-1203 -as-1204 -as-1205 -as-1206 -as-1207 -as-1208 -as-1209 -as-1210 -as-1211 -as-1212 -as-1213 -as-1214 -as-1215 -as-1216 -as-1217 -as-1218 -as-1219 -as-1220 -as-1221 as-1222 -as-1301 -as-1302 -as-1303 -as-1304 -as-1401 -as-1402 -as-1403 -as-1404 -as-1405 -as-1501 -as-1601 as-1602 as-1701 -as-1702 -as-1703 -as-1704 as-1705 as-1706 as-1707 as-1708 -as-1709 -as-1710 as-1711 as-1712 -as-1713 -as-1801 -as-1802 as-1803 as-1804 as-1805 @@ -510,8 +383,6 @@ as-1811 as-1812 as-1813 as-1814 -as-1901 -as-1902 as-1903 as-1904 as-1905 @@ -524,13 +395,9 @@ as-2101 as-2201 as-2301 as-2401 -as-2501 as-2601 as-2602 -as-2701 as-2801 -as-2802 -as-2803 as-2804 as-2901 as-2902 @@ -559,12 +426,9 @@ as-3202 as-3301 as-3401 as-3501 -as-3502 as-3503 as-3504 as-3505 -as-3601 -as-3602 as-3603 as-3604 as-3605 @@ -588,7 +452,6 @@ attribute-0003 attribute-0004 attribute-0005 attribute-0301 -attribute-0401 attribute-0501 attribute-0601 attribute-0701 @@ -597,9 +460,7 @@ attribute-0803 attribute-0804 attribute-0805 attribute-0806 -attribute-0807 attribute-0901 -attribute-0902 attribute-1101 attribute-1301 attribute-1302 @@ -692,7 +553,6 @@ available-system-properties-027 available-system-properties-028 available-system-properties-101 = avt -avt-0201 avt-0303 avt-0401 avt-0501 @@ -702,179 +562,42 @@ avt-0801 avt-0901 avt-1001 avt-1203 -avt-1205 -avt-1502 -avt-1701 avt-2102 -avt-2201 avt-3201 = axes axes-001 axes-002 -axes-003 -axes-004 -axes-005 -axes-006 -axes-007 -axes-008 -axes-009 -axes-010 -axes-011 -axes-012 -axes-013 -axes-014 -axes-015 -axes-016 -axes-017 -axes-018 -axes-019 -axes-020 -axes-021 -axes-022 -axes-023 -axes-024 -axes-025 -axes-026 -axes-027 -axes-028 -axes-029 -axes-030 -axes-031 -axes-032 -axes-033 -axes-034 -axes-035 axes-036 -axes-037 -axes-038 -axes-039 axes-040 -axes-041 -axes-042 axes-044 axes-045 axes-046 -axes-048 -axes-049 axes-050 axes-051 axes-052 axes-057 axes-058 -axes-059 -axes-061 axes-062 -axes-063 -axes-065 -axes-066 -axes-067 axes-068 -axes-069 -axes-070 -axes-077 -axes-078 -axes-079 -axes-080 axes-082 -axes-083 -axes-084 -axes-085 axes-087 axes-088 axes-089 -axes-090 -axes-091 axes-092 -axes-093 -axes-094 -axes-095 -axes-096 -axes-097 -axes-098 -axes-099 -axes-100 -axes-101 -axes-102 -axes-103 -axes-104 -axes-105 -axes-106 -axes-107 -axes-108 -axes-109 -axes-110 -axes-111 -axes-112 -axes-113 -axes-114 -axes-115 -axes-116 -axes-117 -axes-118 axes-120 -axes-123 axes-129 axes-132 axes-133 -axes-134 -axes-135 -axes-136 -axes-137 -axes-138 -axes-139 axes-140 -axes-141 axes-142 -axes-143 -axes-144 axes-146 -axes-147 -axes-148 -axes-150 -axes-151 -axes-152 -axes-153 -axes-154 -axes-155 -axes-156 -axes-157 -axes-158 -axes-159 -axes-160 axes-164 axes-166 -axes-167 -axes-168 -axes-169 axes-170 axes-171 -axes-172 -axes-173 -axes-174 -axes-175 axes-176 -axes-177 -axes-178 -axes-179 -axes-180 -axes-181 -axes-182 -axes-183 -axes-184 -axes-185 -axes-186 -axes-187 axes-188 -axes-189 -axes-190 -axes-191 -axes-192 -axes-193 -axes-194 -axes-195 -axes-196 axes-197 -axes-199 axes-201 axes-202 = backwards @@ -886,7 +609,6 @@ backwards-005 backwards-006 backwards-007 backwards-008 -backwards-009 backwards-010 backwards-011 backwards-012 @@ -910,7 +632,6 @@ backwards-028 backwards-029 backwards-030 backwards-031 -backwards-032 backwards-033 backwards-034 backwards-035 @@ -918,7 +639,6 @@ backwards-036 backwards-037 backwards-038 backwards-039 -backwards-040 backwards-041 backwards-042 backwards-043 @@ -939,7 +659,6 @@ base-uri-013 base-uri-014 base-uri-015 base-uri-016 -base-uri-017 base-uri-018 base-uri-019 base-uri-020 @@ -979,44 +698,23 @@ base-uri-051b base-uri-052 base-uri-053 = boolean -boolean-002 boolean-032 -boolean-047 -boolean-048 -boolean-066 boolean-069 -boolean-070 -boolean-071 -boolean-072 boolean-081 -boolean-082 boolean-083 boolean-084 boolean-095 boolean-096 -boolean-100 -boolean-110 -boolean-111 = bug -bug-0301 -bug-0302 -bug-0303 bug-0304 bug-0305 -bug-0401 bug-0501 bug-0502 -bug-0601 bug-0701 -bug-0801 bug-0901 -bug-1001 -bug-1101 bug-1201 bug-1202 bug-1203 -bug-1301 -bug-1401 bug-1402 bug-1403 bug-1405 @@ -1026,98 +724,56 @@ bug-1601 bug-1701 bug-1801 bug-1802 -bug-1901 -bug-2001 -bug-2101 -bug-2102 bug-2201 bug-2401 bug-2501 bug-2502 bug-2601 bug-2701 -bug-2702 bug-2801 bug-3001 bug-3101 bug-3102 bug-3201 bug-3501 -bug-3601 bug-3701 bug-3801 -bug-3901 bug-4001 bug-4301 bug-4401 bug-4501 -bug-4601 -bug-4701 bug-4702 bug-4703 -bug-4901 -bug-5001 bug-5101 -bug-5201 bug-5302 bug-5501 bug-5601 -bug-5701 -bug-5901 -bug-6001 -bug-6101 bug-6201 -bug-6301 bug-6401 = built-in-templates -built-in-templates-0101 built-in-templates-0201 built-in-templates-0202 -built-in-templates-0301 built-in-templates-0302 = call-template -call-template-0001 -call-template-0101 call-template-0102 call-template-0103 call-template-0104 call-template-0105 call-template-0106 call-template-0107 -call-template-0108 -call-template-0109 call-template-0110 call-template-0111 call-template-0201 call-template-0401 -call-template-0401a call-template-0402 -call-template-0501 call-template-0601 -call-template-0701 -call-template-0702 -call-template-0703 call-template-0801 -call-template-0802 -call-template-0901 call-template-1001 call-template-1002 call-template-1003 -call-template-1101 -call-template-1102 -call-template-1201 -call-template-1301 call-template-1401 -call-template-1501 -call-template-1601 call-template-1701 -call-template-1801 -call-template-1802 -call-template-1803 -call-template-1901 -call-template-2001 call-template-2101 -call-template-2102 = castable castable-005 castable-006 @@ -1167,35 +823,17 @@ character-map-025 character-map-026 character-map-027 character-map-028 -character-map-901 = choose -choose-0102 -choose-0103 choose-0104 choose-0105 choose-0106 choose-0107 choose-0202 -choose-0603 choose-0604 -choose-0606 -choose-0607 -choose-0608 -choose-0609 -choose-1202 -choose-1203 choose-1204 -choose-1501 -choose-1502 -choose-1601 -choose-1703 -choose-1704 -choose-1705 -choose-1706 -choose-1801 -choose-1802 choose-1803 choose-1804 += collation = collations collations-0101 collations-0102 @@ -1206,7 +844,6 @@ collations-0106 collations-0107 collations-0108 collations-0109 -collations-0110 collations-0111 collations-0112 collations-0113 @@ -1230,15 +867,8 @@ collations-0301 collations-0401 collations-0402 collations-0403 -collations-0501 -collations-0502 collations-0503 collations-0601 -collations-1001 -collations-1002 -collations-1003 -collations-1004 -collations-1005 collations-1006 = collection collection-001 @@ -1248,23 +878,11 @@ collection-004 collection-005 collection-006 = construct-node -construct-node-006 construct-node-007 -construct-node-008 construct-node-012 -construct-node-016 construct-node-017 -construct-node-018 -construct-node-019 -construct-node-020 construct-node-022 -construct-node-023 construct-node-024 -construct-node-026 -construct-node-027 -construct-node-028 -construct-node-029 -construct-node-030 = context-item context-item-001 context-item-002 @@ -1301,12 +919,6 @@ context-item-912 copy-0101 copy-0103 copy-0104 -copy-0105 -copy-0501 -copy-0602 -copy-0603 -copy-0604 -copy-0606 copy-0609 copy-0610 copy-0611 @@ -1328,12 +940,8 @@ copy-0626 copy-0627 copy-0801 copy-0901 -copy-1001 -copy-1002 -copy-1003 copy-1201 copy-1202 -copy-1203 copy-1205 copy-1206 copy-1207 @@ -1353,49 +961,30 @@ copy-1220 copy-1221 copy-1301 copy-1401 -copy-1601 -copy-1602 copy-2101 copy-2201 copy-2202 -copy-2203 copy-2301 copy-2501 copy-2502 copy-2601 copy-2701 -copy-2801 -copy-3001 copy-3002 copy-3003 copy-3102 copy-3301 copy-3302 copy-3501 -copy-3601 -copy-3602 -copy-3603 copy-3702 copy-3801 -copy-3802 copy-3803 -copy-3804 copy-4001 -copy-4101 -copy-4102 -copy-4201 -copy-4301 copy-4302 -copy-4303 -copy-4304 -copy-4305 -copy-4306 copy-4307 copy-4308 copy-4309 copy-4401 copy-4501 -copy-4502 copy-4601 copy-4701 copy-4702 @@ -1418,10 +1007,7 @@ copy-5031 copy-5032 copy-5033 copy-5034 -copy-5101 copy-5201 -element-0607 -element-0608 = copy-of copy-of-001 copy-of-002 @@ -1437,54 +1023,6 @@ copy-of-011 copy-of-012 copy-of-013 = core-function -core-function-018 -core-function-019 -core-function-020 -core-function-021 -core-function-022 -core-function-023 -core-function-024 -core-function-025 -core-function-026 -core-function-027 -core-function-028 -core-function-029 -core-function-030 -core-function-031 -core-function-032 -core-function-033 -core-function-034 -core-function-035 -core-function-036 -core-function-037 -core-function-038 -core-function-039 -core-function-040 -core-function-041 -core-function-042 -core-function-043 -core-function-044 -core-function-045 -core-function-046 -core-function-047 -core-function-048 -core-function-049 -core-function-050 -core-function-051 -core-function-052 -core-function-053 -core-function-054 -core-function-055 -core-function-056 -core-function-057 -core-function-058 -core-function-059 -core-function-062 -core-function-063 -core-function-070 -core-function-073 -core-function-076 -core-function-077 core-function-084 = current current-001 @@ -1518,52 +1056,17 @@ data-manipulation-016 data-manipulation-017 data-manipulation-018 data-manipulation-019 -data-manipulation-020 -data-manipulation-021 -data-manipulation-022 -data-manipulation-023 -data-manipulation-024 -data-manipulation-025 -data-manipulation-026 -data-manipulation-027 -data-manipulation-028 = date date-019 -date-019a -date-032 -date-044 -date-052 date-053 -date-054 -date-055 date-056 -date-057 date-058 -date-059 date-060 -date-061 -date-062 date-063 date-064 -date-065 -date-066 -date-067 date-068 -date-069 -date-070 -date-071 -date-072 date-074a date-075a -date-078 -date-080 -date-081 -date-082 -date-083 -date-084 -date-088 -date-089 -date-093 date-094a date-094b date-094c @@ -1679,12 +1182,7 @@ document-0201 document-0202 document-0203 document-0204 -document-0301 document-0302 -document-0304 -document-0305 -document-0306 -document-0307 document-0308 document-0309 document-0401 @@ -1692,33 +1190,20 @@ document-0501 document-0502 document-0503 document-0504 -document-0601 -document-0701 -document-0801 -document-0802 -document-0803 -document-0901 document-1001 document-1002 document-1003 document-1004 -document-1101 document-1102 -document-1201 -document-1202 -document-1301 -document-1401 document-1501 document-1502 document-1503 -document-1601 document-1901 document-2002 document-2003 document-2009 document-2011 document-2101 -document-2201 document-2301 document-2401 document-2402 @@ -1737,19 +1222,13 @@ element-0107 element-0108 element-0110 element-0111 -element-0201 -element-0301 element-0302 -element-0303 -element-0304 element-0305 element-0306 element-0307 element-0308 -element-0309 element-0310 element-0311 -element-0312 = embedded-stylesheet embedded-stylesheet-001 embedded-stylesheet-002 @@ -1774,8 +1253,6 @@ error-0010a error-0010aa error-0010ab error-0010ac -error-0010ad -error-0010ae error-0010af error-0010ag error-0010ah @@ -1784,10 +1261,8 @@ error-0010aj error-0010ak error-0010al error-0010am -error-0010an error-0010ao error-0010ap -error-0010aq error-0010ar error-0010as error-0010at @@ -1796,7 +1271,6 @@ error-0010av error-0010aw error-0010ax error-0010ay -error-0010az error-0010b error-0010ba error-0010bb @@ -1807,7 +1281,6 @@ error-0010f error-0010g error-0010h error-0010i -error-0010j error-0010k error-0010l error-0010m @@ -1818,23 +1291,12 @@ error-0010q error-0010r error-0010s error-0010t -error-0010u error-0010v -error-0010w error-0010x -error-0010y error-0010z -error-0020a -error-0020b error-0020c -error-0020d -error-0020e -error-0020f -error-0020g -error-0020h error-0020i error-0020j -error-0020k error-0030a error-0040a error-0041a @@ -1843,29 +1305,13 @@ error-0044aa error-0044ab error-0044ac error-0045a -error-0045aa -error-0045ab error-0045b error-0045ba error-0045bb error-0045bc error-0047a -error-0050a error-0080a -error-0090a -error-0090b -error-0090c -error-0090d -error-0090e error-0090f -error-0090g -error-0090h -error-0090i -error-0090j -error-0090k -error-0090l -error-0090m -error-0090n error-0110a error-0120a error-0120b @@ -1934,7 +1380,6 @@ error-0420b error-0430a error-0440a error-0440b -error-0450a error-0500a error-0500b error-0500c @@ -1943,7 +1388,6 @@ error-0505a error-0510a error-0520a error-0520b -error-0520c error-0520d error-0530a error-0540a @@ -1964,11 +1408,7 @@ error-0560c error-0560d error-0570a error-0570b -error-0570c -error-0570d -error-0580a error-0580b -error-0590a error-0600a error-0610a error-0610b @@ -1980,14 +1420,10 @@ error-0630a error-0630b error-0630c error-0640a -error-0640b -error-0640c -error-0640d error-0640e-1 error-0640e-2 error-0640f error-0640g -error-0640h error-0640i error-0640j error-0640k @@ -2008,10 +1444,7 @@ error-0670a error-0670b error-0670c error-0670d -error-0680a error-0690a -error-0700a -error-0700b error-0710a error-0710b error-0710c @@ -2028,7 +1461,6 @@ error-0760b error-0770a error-0780a error-0790a2 -error-0790a3 error-0805a error-0808a error-0808b @@ -2042,12 +1474,10 @@ error-0820b error-0820c error-0830a error-0830b -error-0835a error-0840a error-0850a error-0855a error-0860a -error-0865a error-0870a error-0880a error-0890a @@ -2202,10 +1632,6 @@ error-1570a error-1580a error-1590a error-1600a -error-1620a -error-1620b -error-1630a -error-1630b error-1650a error-1660a error-1660b @@ -2246,7 +1672,6 @@ error-3155a error-3160a error-3170a error-3175a -error-3180a error-3185a error-3190a error-3195a @@ -2295,19 +1720,8 @@ error-3520a error-3530a error-9000a error-9001a -error-FOAR0001b error-FODC0002a error-FODC0002a-ignore -error-FOER0001a -error-FORG0006b -error-XPDY0002a -error-XPDY0002b -error-XPDY0002c -error-XPDY0002d -error-XPDY0050a -error-XPDY0050b -error-XPDY0050c -error-XPDY0050d error-XPST0003a error-XPST0003b error-XPST0003c @@ -2327,18 +1741,9 @@ error-XPST0003p error-XPST0003q error-XPST0003r error-XPST00081b -error-XPST0008a -error-XPST0008b -error-XPST0008c -error-XPST0017a -error-XPST0017b error-XPST0081a error-XPST0081c error-XPST0081d -error-XPTY0004a -error-XPTY0004e -error-XPTY0004f -error-XPTY0004g error-XPTY0019a error-XPTY0020a error-XPTY0020b @@ -2401,38 +1806,17 @@ evaluate-050 evaluate-051 evaluate-052 = expand-text -cvt-001 -cvt-002 -cvt-003 -cvt-004 -cvt-005 -cvt-006 -cvt-007 -cvt-008 -cvt-009 -cvt-010 cvt-011 cvt-012 cvt-013 cvt-014 cvt-015 -cvt-016 -cvt-017 -cvt-018 -cvt-019 cvt-020 -cvt-021 -cvt-022 -cvt-023 -cvt-024 cvt-025 cvt-026 -cvt-027 cvt-028 cvt-029 cvt-030 -cvt-031 -cvt-032 cvt-033 cvt-034 cvt-035a @@ -2453,15 +1837,8 @@ cvt-039c cvt-040a cvt-040b cvt-040c -cvt-041 cvt-042 -cvt-043 -cvt-044 -cvt-045 -cvt-046 -cvt-047 cvt-048 -cvt-049 cvt-050 = expose expose-001 @@ -2508,77 +1885,15 @@ expose-926 expose-927 = expression expression-0501 -expression-0601 -expression-0801 -expression-0802 -expression-0803 -expression-0804 -expression-0901 -expression-0903 -expression-0904 -expression-0905 -expression-0906 -expression-0907 -expression-0908 -expression-0909 -expression-0910 -expression-0911 -expression-0912 -expression-0913 -expression-0915 -expression-0916 -expression-0917 -expression-0918 -expression-0919 -expression-0922 -expression-0923 -expression-0924 -expression-0925 -expression-0929 -expression-0930 expression-0931 expression-0932 expression-0933 -expression-1001 expression-1101 -expression-1102 -expression-1103 -expression-1104 -expression-1601 -expression-1701 -expression-1702 -expression-1801 -expression-2001 expression-2101 -expression-2201 -expression-2202 -expression-2203 -expression-2301 -expression-2302 -expression-2401 -expression-2402 -expression-2501 -expression-2601 -expression-2701 -expression-2702 -expression-2801 -expression-2901 -expression-3001 expression-3101 expression-3201 -expression-3301 -expression-3302 -expression-3401 -expression-3501 -expression-3601 -expression-3701 -expression-3801 expression-3901 -expression-4001 -expression-4101 expression-4301 -expression-4401 -expression-4402 = extension-functions extension-functions-0101 extension-functions-0102 @@ -2588,7 +1903,6 @@ extension-functions-0105 extension-functions-0106 extension-functions-0201 = for -for-002 for-004 = for-each-group for-each-group-001 @@ -2620,7 +1934,6 @@ for-each-group-033 for-each-group-034 for-each-group-035 for-each-group-036 -for-each-group-037 for-each-group-038 for-each-group-039 for-each-group-040 @@ -2658,7 +1971,6 @@ for-each-group-072 for-each-group-073 for-each-group-074 for-each-group-075 -for-each-group-076 for-each-group-078a for-each-group-078b for-each-group-079a @@ -2814,7 +2126,6 @@ format-number-058 format-number-059 format-number-060 format-number-060n -format-number-061 format-number-062 format-number-063 format-number-064 @@ -2852,64 +2163,22 @@ forwards-204 forwards-205 = function function-0001 -function-0002 -function-0003 -function-0004 -function-0101 -function-0102 -function-0103 -function-0104 -function-0105 -function-0106 -function-0107 -function-0108 -function-0109 -function-0110 -function-0111 -function-0112 -function-0113 -function-0114 -function-0115 -function-0116 function-0117 -function-0118 function-0119a -function-0119b function-0120a function-0120b -function-0121 function-0122 -function-0201 -function-0202 -function-0203 -function-0205 -function-0301 function-0302 function-0302b function-0303 -function-0401 function-0501 -function-0601 -function-0602 function-0701 -function-0901 -function-1001 -function-1002 function-1003 -function-1004 function-1005 -function-1007 -function-1009 -function-1011 -function-1012 function-1013 -function-1014 -function-1015 function-1016 function-1017 function-1018 -function-1019 -function-1020 function-1021 function-1022 function-1023 @@ -2921,45 +2190,23 @@ function-1028 function-1029 function-1030 function-1031 -function-1032 -function-1033 function-1034 function-1035 function-1101 function-1201 -function-1301 -function-1501 -function-1601 function-1701 function-1901 function-1902 function-2001 function-2002 -function-2101 -function-2102 function-2103 -function-2104 function-2105 function-2106 function-2107 function-2108 function-2109 function-5001 -function-5002 -function-5003 -function-5004 -function-5005 -function-5006 -function-5007 function-5011 -function-5012 -function-5013 -function-5014 -function-5015 -function-5015a -function-5016 -function-5017 -function-5018 = function-available function-available-0204 function-available-0801 @@ -2996,81 +2243,23 @@ glob-cxt-item-011 glob-cxt-item-012 glob-cxt-item-013 = higher-order-functions -higher-order-functions-001 -higher-order-functions-002 higher-order-functions-003 -higher-order-functions-004 -higher-order-functions-005 -higher-order-functions-006 higher-order-functions-007 -higher-order-functions-008 -higher-order-functions-009 -higher-order-functions-010 -higher-order-functions-011 -higher-order-functions-012 -higher-order-functions-013 -higher-order-functions-014 -higher-order-functions-015 -higher-order-functions-016 -higher-order-functions-017 -higher-order-functions-018 -higher-order-functions-019 higher-order-functions-020 -higher-order-functions-021 -higher-order-functions-022 higher-order-functions-023 -higher-order-functions-024 -higher-order-functions-025 -higher-order-functions-026 -higher-order-functions-027 -higher-order-functions-028 -higher-order-functions-029 -higher-order-functions-030 -higher-order-functions-031 -higher-order-functions-032 -higher-order-functions-033 -higher-order-functions-034 -higher-order-functions-035 -higher-order-functions-036 -higher-order-functions-037 -higher-order-functions-038 -higher-order-functions-039 -higher-order-functions-040 -higher-order-functions-041 -higher-order-functions-042 -higher-order-functions-043 -higher-order-functions-044 -higher-order-functions-045 -higher-order-functions-046 -higher-order-functions-047 -higher-order-functions-048 -higher-order-functions-049 -higher-order-functions-050 -higher-order-functions-051 -higher-order-functions-052 -higher-order-functions-053 -higher-order-functions-054 -higher-order-functions-055 -higher-order-functions-056 higher-order-functions-057 higher-order-functions-058 higher-order-functions-059 higher-order-functions-060 higher-order-functions-061 -higher-order-functions-062 -higher-order-functions-063 -higher-order-functions-064 -higher-order-functions-065 higher-order-functions-066 higher-order-functions-067 higher-order-functions-068 higher-order-functions-069 -higher-order-functions-070 higher-order-functions-071 higher-order-functions-072 higher-order-functions-073 higher-order-functions-074 -higher-order-functions-075 higher-order-functions-076 = id id-001 @@ -3119,45 +2308,29 @@ id-043 = import import-0001 import-0002 -import-0101 -import-0201 -import-0202 import-0203 -import-0301 import-0302 -import-0401 import-0501 import-0502a import-0502b import-0502c -import-0601 import-0701 -import-0801 -import-0802 -import-0803 import-0901 import-0902a import-0902b import-0902c -import-1001 -import-1101 import-1201 import-1301 -import-1401 import-1501 import-1601 import-1701 import-1801 import-1901 import-2001 -import-2101 -import-2102 import-2103 import-2201 import-2202 -import-2401 import-2402 -import-2403 import-2404 = import-schema import-schema-001 @@ -3175,10 +2348,6 @@ import-schema-012 import-schema-013 import-schema-014 import-schema-015 -import-schema-016 -import-schema-017 -import-schema-018 -import-schema-019 import-schema-020 import-schema-021 import-schema-022 @@ -3192,12 +2361,6 @@ import-schema-029 import-schema-030 import-schema-031 import-schema-032 -import-schema-033 -import-schema-034 -import-schema-035 -import-schema-036 -import-schema-037 -import-schema-038 import-schema-039 import-schema-040 import-schema-041 @@ -3219,18 +2382,14 @@ import-schema-056 import-schema-057 import-schema-058 import-schema-059 -import-schema-060 import-schema-061 import-schema-062 import-schema-063 -import-schema-064 import-schema-065 import-schema-066 import-schema-067 import-schema-068 import-schema-069 -import-schema-070 -import-schema-071 import-schema-072 import-schema-073 import-schema-074 @@ -3337,13 +2496,10 @@ import-schema-174 import-schema-175 import-schema-176 import-schema-177 -import-schema-178 import-schema-179 import-schema-180 import-schema-181 import-schema-182 -import-schema-183 -import-schema-184 import-schema-185a import-schema-185b import-schema-185c @@ -3357,9 +2513,7 @@ import-schema-192 import-schema-193 import-schema-194 import-schema-195 -import-schema-196 import-schema-197 -import-schema-198 import-schema-199 import-schema-200 import-schema-201 @@ -3369,19 +2523,9 @@ import-schema-203 include-0101 include-0102 include-0103 -include-0104 -include-0105 -include-0201 -include-0202 -include-0301 include-0401 -include-0501 include-0601 -include-0701 -include-0702a include-0702b -include-0702c -include-0801 = initial-function initial-function-001 initial-function-002 @@ -3419,55 +2563,40 @@ initial-function-903 initial-function-904 initial-function-905 = initial-mode -initial-mode-001 initial-mode-002 initial-mode-003 initial-mode-004 -initial-mode-005 = initial-template -initial-template-001 initial-template-002 initial-template-002a initial-template-003 initial-template-003a initial-template-004 -initial-template-080 initial-template-081 initial-template-901 -initial-template-902 initial-template-902a = innermost innermost-001 -innermost-901 = iterate iterate-004 iterate-006 -iterate-007 iterate-008 iterate-009 iterate-010 -iterate-011 iterate-012 iterate-013 -iterate-014 -iterate-015 iterate-016 iterate-017 iterate-018 -iterate-019 -iterate-020 iterate-022 iterate-023 iterate-024 iterate-025 iterate-026 iterate-027 -iterate-028 iterate-029 iterate-030 iterate-031 -iterate-032 -iterate-033 iterate-034 iterate-035 iterate-036 @@ -3546,8 +2675,6 @@ key-001 key-002 key-003 key-004 -key-005 -key-006 key-007 key-008 key-009 @@ -3585,7 +2712,6 @@ key-040 key-041 key-042 key-043 -key-044 key-045 key-046 key-047 @@ -3616,7 +2742,6 @@ key-072 key-073 key-074 key-075 -key-076 key-077 key-078 key-079 @@ -3632,7 +2757,6 @@ key-087 key-088 key-089 key-090 -key-091 key-092 key-093 key-094 @@ -3647,15 +2771,7 @@ load-xquery-module-002 load-xquery-module-003 load-xquery-module-004 = lre -lre-003 -lre-005 -lre-006 -lre-010 lre-011 -lre-012 -lre-014 -lre-017 -lre-018 lre-019 lre-020 lre-021 @@ -3671,7 +2787,6 @@ lre-106 lre-107 lre-108 lre-109 -lre-110 = maps maps-001 maps-002 @@ -3682,10 +2797,7 @@ maps-006 maps-007 maps-008 maps-009 -maps-010 -maps-011 maps-012 -maps-013 maps-014 maps-015 maps-016 @@ -3723,23 +2835,9 @@ maps-909 = match match-001 match-002 -match-003 -match-004 -match-005 -match-006 -match-007 -match-008 -match-009 -match-010 -match-011 -match-012 match-013 match-014 -match-016 -match-017 -match-018 match-019 -match-020 match-021 match-022 match-023 @@ -3753,17 +2851,7 @@ match-032 match-033 match-034 match-035 -match-036 -match-037 -match-038 match-039 -match-040 -match-041 -match-042 -match-043 -match-044 -match-045 -match-046 match-048 match-049 match-050 @@ -3773,12 +2861,9 @@ match-053 match-054 match-055 match-056 -match-057 match-060 match-061 match-062 -match-065 -match-066 match-068 match-069 match-070 @@ -3786,10 +2871,8 @@ match-071 match-072 match-073 match-074 -match-075 match-076 match-077 -match-078 match-079 match-080 match-081 @@ -3807,25 +2890,11 @@ match-088 match-089 match-090 match-091 -match-092 -match-093 -match-094 -match-095 match-098 match-099 -match-100 -match-101 -match-102 -match-103 match-104 match-105 match-106 -match-107 -match-108 -match-109 -match-110 -match-111 -match-116 match-119 match-120 match-121 @@ -3834,10 +2903,7 @@ match-123 match-124 match-125 match-126 -match-127 -match-128 match-129 -match-130 match-131 match-132 match-133 @@ -3850,7 +2916,6 @@ match-139 match-140 match-141 match-142 -match-143 match-144 match-145 match-146 @@ -3861,7 +2926,6 @@ match-150 match-151 match-152 match-153 -match-154 match-155 match-156 match-157 @@ -3870,15 +2934,12 @@ match-159 match-160 match-161 match-162 -match-163 match-164 match-165 match-166 match-167 match-168 match-169 -match-170 -match-171 match-172 match-173 match-174 @@ -3894,24 +2955,17 @@ match-183 match-184 match-185 match-186 -match-187 -match-188 match-189 match-190 -match-191 match-192 -match-193 match-194 match-195 match-196 match-197 -match-198 match-199 match-200 match-201 -match-202 match-203 -match-204 match-205 match-206 match-207 @@ -3921,23 +2975,15 @@ match-210 match-211 match-212 match-213 -match-214 -match-215 match-216 -match-217 match-218 match-219 match-220 match-221 -match-222 match-223 -match-224 -match-225 -match-226 match-227 match-228 match-229 -match-230 match-231 match-232 match-233 @@ -3951,27 +2997,15 @@ match-240a match-240b match-240c match-241 -match-242 match-243 match-244 match-245 -match-246a -match-246b match-247 -match-248 -match-249 -match-250 -match-251 -match-252 -match-253 -match-254 -match-255 match-256 match-257 match-258 match-259 match-260 -match-261 match-262 match-263 match-264 @@ -3995,137 +3029,28 @@ match-281 match-282 match-283 match-284 -match-285 -match-286 -match-287 = math -math-0101 math-0103 -math-0107 -math-0201 -math-0202 -math-0301 -math-0401 -math-0501 -math-0601 -math-0701 -math-0801 -math-0901 -math-1001 -math-1002 -math-1003 -math-1004 -math-1005 -math-1101 math-1201 -math-1401 -math-1501 -math-1502 -math-1503 -math-1504 -math-1505 -math-1506 -math-1507 -math-1508 -math-1509 -math-1510 -math-1511 -math-1512 -math-1513 -math-1514 -math-1515 -math-1516 -math-1517 -math-1518 -math-1519 -math-1901 -math-1902 -math-1903 -math-1904 -math-1905 -math-1906 -math-1907 -math-1908 -math-1909 -math-1910 -math-1911 -math-1912 -math-1913 -math-1914 -math-1915 -math-1916 -math-1917 -math-2001 -math-2002 -math-2003 -math-2004 -math-2005 -math-2006 -math-2007 -math-2008 -math-2009 -math-2010 -math-2011 -math-2012 -math-2013 -math-2102 -math-2401 math-2404 math-2406 math-2408 math-2409 math-2410 -math-2502 -math-2503 -math-2504 -math-2506 -math-2507 math-2508 -math-2509 -math-2601 -math-2602 -math-2603 -math-2604 -math-2605 -math-2607 -math-2609 -math-2610 -math-2611 -math-2612 -math-2701 -math-3001 -math-3101 -math-3301 -math-3301a -math-3302 -math-3302a math-3303 math-3303a math-3304 math-3304a -math-3305 -math-3305a -math-3306 -math-3306a -math-3307a -math-3308 -math-3308a -math-3309 -math-3309a math-3310 math-3310a math-3311 math-3313 -math-3314 math-3315 -math-3316 math-3317 math-3318 math-3319 math-3320 -math-3401 -math-3402 -math-3501 math-3601 math-3701 math-3702 @@ -4140,7 +3065,6 @@ merge-006 merge-007 merge-008 merge-009 -merge-010 merge-011 merge-012 merge-013 @@ -4152,11 +3076,9 @@ merge-018 merge-019 merge-020 merge-021 -merge-022 merge-023 merge-024 merge-025 -merge-026 merge-027 merge-028 merge-029 @@ -4198,7 +3120,6 @@ merge-060 merge-061 merge-062 merge-063 -merge-064 merge-065a merge-065b merge-066 @@ -4238,8 +3159,6 @@ merge-099 merge-100 merge-101 = message -message-0001 -message-0001a message-0002 message-0003 message-0004 @@ -4276,38 +3195,14 @@ message-0401 message-0402 message-0403 message-0404 -message-0405 message-0406 -message-0407 -message-0408 -message-0409 message-0410 message-0501 = mode -mode-0001 -mode-0002 -mode-0003 -mode-0004 -mode-0005 -mode-0006 -mode-0007 -mode-0008 -mode-0009 -mode-0010 -mode-0011 -mode-0012 -mode-0013 -mode-0014 mode-0015 mode-0016 mode-0102 -mode-0104 -mode-0105 -mode-0106 -mode-0107 -mode-0108 mode-0301 -mode-0601 mode-0801a mode-0801b mode-0801c @@ -4315,11 +3210,6 @@ mode-0802 mode-0803 mode-0804 mode-0805 -mode-0806 -mode-0901 -mode-1101 -mode-1102 -mode-1103 mode-1104 mode-1105 mode-1106a @@ -4331,32 +3221,10 @@ mode-1107a mode-1107b mode-1107c mode-1108 -mode-1204 -mode-1301 -mode-1401 mode-1402 mode-1403 mode-1404 -mode-1405 -mode-1406 -mode-1407 -mode-1408 -mode-1409 -mode-1410 -mode-1411 -mode-1412 -mode-1413 -mode-1414 -mode-1415 -mode-1416 -mode-1417 -mode-1418 -mode-1419 mode-1420 -mode-1421 -mode-1422 -mode-1423 -mode-1424 mode-1425 mode-1426 mode-1427 @@ -4365,23 +3233,16 @@ mode-1429 mode-1430 mode-1431 mode-1432 -mode-1433 -mode-1434 mode-1435 mode-1436 mode-1437 mode-1437a -mode-1438 -mode-1439 mode-1440 mode-1441 mode-1442 mode-1443 -mode-1444 mode-1445 mode-1446 -mode-1447 -mode-1501 mode-1502 mode-1503 mode-1504 @@ -4389,7 +3250,6 @@ mode-1505 mode-1506 mode-1507 mode-1508 -mode-1509 mode-1510 mode-1511 mode-1512 @@ -4398,10 +3258,7 @@ mode-1514 mode-1515 mode-1516 mode-1517 -mode-1604 mode-1606 -mode-1616 -mode-1617 mode-1701 mode-1701a mode-1702 @@ -4427,12 +3284,10 @@ mode-1901 mode-1902 mode-1903 mode-1904 -mode-1905 = namespace namespace-0101 namespace-0201 namespace-0202 -namespace-0501 namespace-0601 namespace-0602 namespace-0603 @@ -4441,9 +3296,7 @@ namespace-0801 namespace-0802 namespace-0901 namespace-0902 -namespace-0903 namespace-0904 -namespace-0906 namespace-0907 namespace-0908 namespace-0909 @@ -4452,46 +3305,35 @@ namespace-0911 namespace-0912 namespace-0913 namespace-0914 -namespace-1101 namespace-1102 namespace-1103 -namespace-1104 namespace-1502 namespace-1601 namespace-1602 -namespace-1701 namespace-1801 namespace-1901 namespace-2001 namespace-2101 namespace-2201 -namespace-2301 -namespace-2302 namespace-2401 namespace-2501 -namespace-2503 -namespace-2601 namespace-2602 namespace-2606 namespace-2607 namespace-2608 namespace-2609 namespace-2610 -namespace-2611 namespace-2612 namespace-2614 namespace-2615 -namespace-2616 namespace-2617 namespace-2618 -namespace-2619 namespace-2621 namespace-2622 namespace-2623 namespace-2624 namespace-2625 namespace-2626 -namespace-2627 namespace-2628 namespace-2629 namespace-2630 @@ -4499,11 +3341,8 @@ namespace-2631 namespace-2632 namespace-2633 namespace-2701 -namespace-2801 namespace-2901 namespace-3002 -namespace-3003 -namespace-3004 namespace-3101 namespace-3102 namespace-3103 @@ -4518,61 +3357,31 @@ namespace-3111 namespace-3112 namespace-3113 namespace-3114 -namespace-3115 -namespace-3116 -namespace-3117 -namespace-3118 namespace-3119 namespace-3120 namespace-3121 namespace-3122 namespace-3123 -namespace-3124 namespace-3126 -namespace-3127 namespace-3128 -namespace-3129 -namespace-3130 namespace-3131 namespace-3132 namespace-3133 -namespace-3134 -namespace-3135 -namespace-3136 namespace-3137 -namespace-3138 -namespace-3139 -namespace-3140 namespace-3142 namespace-3143 namespace-3144 namespace-3145 namespace-3146 -namespace-3147 namespace-3148 namespace-3149 -namespace-3150 -namespace-3151 -namespace-3152 namespace-3153 -namespace-3154 -namespace-3155 namespace-3158 namespace-3159 -namespace-3160 -namespace-3161 -namespace-3162 -namespace-3163 -namespace-3164 namespace-3201 -namespace-3202 namespace-3203 namespace-3301 -namespace-3302 -namespace-3303 namespace-3304 -namespace-3306 -namespace-3307 namespace-3308 namespace-3309 namespace-3310 @@ -4581,16 +3390,8 @@ namespace-3312 namespace-3313 namespace-3314 namespace-3315 -namespace-3401 -namespace-3501 -namespace-3502 -namespace-3503 -namespace-3504 -namespace-3505 namespace-3601 -namespace-4001 namespace-4002 -namespace-4003 namespace-4004 namespace-4005 namespace-4007 @@ -4602,9 +3403,6 @@ namespace-4401 namespace-4501 namespace-4601 namespace-4801 -namespace-4901 -namespace-5001 -namespace-5101 namespace-5201 namespace-5601 namespace-5602 @@ -4614,60 +3412,23 @@ namespace-5902 namespace-5903 namespace-6002 namespace-6003 -namespace-6201 namespace-6202 = namespace-alias namespace-alias-0901 -namespace-alias-0902 -namespace-alias-0903 -namespace-alias-1001 -namespace-alias-1002 -namespace-alias-1003 -namespace-alias-1004 -namespace-alias-1005 -namespace-alias-1006 namespace-alias-1901 namespace-alias-1902 namespace-alias-1903 namespace-alias-1904 namespace-alias-1905 -namespace-alias-1906 -namespace-alias-1907 -namespace-alias-1908 -namespace-alias-1909 -namespace-alias-1910 namespace-alias-2613 -namespace-alias-2620 namespace-alias-3602 -namespace-alias-4201 -namespace-alias-4701 -namespace-alias-4702 -namespace-alias-5801 = next-match -next-match-001 -next-match-002 -next-match-003 -next-match-004 -next-match-005 next-match-006 next-match-007 next-match-008 -next-match-009 -next-match-010 -next-match-011 next-match-012 -next-match-013 -next-match-014 next-match-015 -next-match-016 next-match-017 -next-match-018 -next-match-019 -next-match-020 -next-match-021 -next-match-022 -next-match-023 -next-match-024 next-match-025 next-match-026 next-match-027 @@ -4676,64 +3437,31 @@ next-match-029 next-match-030 next-match-031 next-match-032 -next-match-033 next-match-034 -next-match-035 next-match-036 next-match-037 -next-match-038 -next-match-039 next-match-040 = node -node-0301 -node-0701 -node-1101 -node-1102 -node-1201 node-1601 node-1802 node-1905 node-1906 -node-2001 -node-2002 -node-2004 = nodetest -nodetest-003 -nodetest-004 -nodetest-005 -nodetest-006 nodetest-007 -nodetest-008 nodetest-009 nodetest-010 nodetest-011 -nodetest-012 -nodetest-013 -nodetest-014 -nodetest-015 -nodetest-016 nodetest-017 nodetest-018 nodetest-019 nodetest-020 nodetest-021 -nodetest-022 -nodetest-023 -nodetest-024 nodetest-025 nodetest-026 nodetest-027 -nodetest-028 -nodetest-029 -nodetest-030 -nodetest-031 -nodetest-032 -nodetest-033 nodetest-034 -nodetest-035 nodetest-036 nodetest-037 -nodetest-038 = normalize-unicode normalize-unicode-001 normalize-unicode-002 @@ -5330,7 +4058,6 @@ output-0226 output-0227 output-0228 output-0229 -output-0230 output-0231 output-0232 output-0233 @@ -5358,7 +4085,6 @@ output-0310 output-0311 output-0312 output-0313 -output-0501 output-0601 output-0602a output-0602b @@ -5595,8 +4321,6 @@ package-version-101 package-version-102 package-version-103 package-version-104 -package-version-900 -package-version-901 package-version-902 package-version-903 package-version-904 @@ -5608,51 +4332,13 @@ package-version-909 package-version-910 package-version-911 package-version-912a -package-version-912b = param -param-0101 -param-0102 -param-0103 -param-0104 -param-0105 -param-0106 -param-0107 -param-0108 -param-0109 -param-0110 -param-0111 -param-0112 -param-0113 -param-0114 -param-0115 -param-0116 -param-0117 -param-0118 -param-0119 -param-0120 -param-0201 -param-0301 -param-0401 -param-0402 -param-0403 -param-0501 -param-0601 -param-0602 -param-0701 -param-0702 -param-0703 = path -path-010 = position -position-0102 position-0103 -position-0202 position-0401 -position-0601 position-0701 position-0702 -position-1121 -position-1132 position-1301 position-1302 position-1303 @@ -5678,96 +4364,39 @@ position-1503 position-1507 position-1508 position-1601 -position-1602 -position-1701 -position-1702 -position-1703 position-1801 -position-2101 position-2201 -position-3101 -position-3102 -position-3103 -position-3104 -position-3302 position-3501 -position-3701 -position-3801 position-3901 position-4001 -position-4101 -position-4102 -position-4103 position-4104 position-4105 position-4901 -position-5501 -position-5801 position-6002 position-6101 position-6302 position-6601 -position-6701 -position-6801 position-6802 -position-6901 position-7101 -position-7801 -position-7901 -position-8001 position-8201 position-8202 -position-8301 = predicate predicate-001 predicate-002 predicate-003 predicate-004 predicate-005 -predicate-051 predicate-052 predicate-055 predicate-056 predicate-057 = regex -regex-004 -regex-005 -regex-014 -regex-015 -regex-016 regex-017 regex-018 -regex-020 regex-027 regex-028 -regex-031 -regex-032 -regex-038 -regex-039 -regex-040 -regex-041 -regex-057 -regex-058 -regex-059 -regex-066 -regex-070a -regex-070b -regex-070c -regex-070d -regex-070e -regex-070f -regex-070g -regex-070h -regex-070i -regex-070j -regex-070k -regex-070l regex-071 regex-072 -regex-086a -regex-086b -regex-086c -regex-086d regex-090 regex-091 = regex-classes @@ -8050,36 +6679,14 @@ result-document-1411 result-document-1501 result-document-1502 = root -root-0301 -root-0401 -root-0501 -root-0502 = select select-0201 select-0202 select-0301 -select-0302 -select-0501 select-0701 -select-0801 -select-0802 -select-0901 -select-1001 select-1401 select-1402 -select-1503 -select-1504 -select-1601 -select-1602 -select-1701 -select-1702 -select-1703 -select-1704 -select-1705 -select-1801 select-1802 -select-1803 -select-1804 select-1901 select-2001 select-2002 @@ -8087,71 +6694,28 @@ select-2003 select-2006 select-2009 select-2012 -select-2016 select-2017 -select-2018 select-2019 select-2020 select-2021 select-2022 -select-2025 -select-2026 select-2027 select-2028 select-2032 -select-2037 select-2038 -select-2039 -select-2201 -select-2202 -select-2203 -select-2301 -select-2303 -select-2304 select-2305 -select-2401 select-3301 select-3401 -select-3601 -select-3602 -select-3603 -select-3701 -select-3801 -select-3901 -select-3902 -select-4001 -select-4101 -select-4102 -select-4201 -select-4301 -select-4302 -select-4401 select-4501 select-4601 -select-4701 -select-4801 -select-4901 select-5001 -select-5101 -select-5201 select-5401 -select-5501 -select-5601 -select-5701 -select-5801 -select-5901 -select-6001 select-6101 -select-6201 -select-6501 select-6601 select-6701 -select-6801 select-7301 select-7401 -select-7501 select-7502a -select-7502b = seqtor seqtor-001 seqtor-002 @@ -8226,61 +6790,32 @@ seqtor-043h seqtor-043i seqtor-101 = sequence -sequence-0109 sequence-0111 sequence-0112 sequence-0113 sequence-0119 sequence-0122 -sequence-0124 sequence-0125 -sequence-0126 sequence-0127 -sequence-0128 -sequence-0129 -sequence-0130 -sequence-0131 -sequence-0132 sequence-0133 sequence-0134 sequence-0135 sequence-0136 sequence-0137 -sequence-0137a sequence-0138 sequence-0139 -sequence-0303 -sequence-0304 sequence-0306 sequence-0307 -sequence-0601 -sequence-0705 sequence-0901 -sequence-1001 -sequence-1002 -sequence-1003 -sequence-1004 -sequence-1005 sequence-1101 sequence-1201 -sequence-1202 sequence-1204 -sequence-1401 -sequence-1402 -sequence-1501 -sequence-1601 -sequence-1701 sequence-1801 -sequence-1901 -sequence-2001 -sequence-2002 sequence-2101 sequence-2401a sequence-2401b sequence-2402a -sequence-2402b sequence-2403a -sequence-2403b = sf-avg sf-avg-003 sf-avg-004 @@ -9057,7 +7592,6 @@ shadow-003 shadow-004 shadow-005 shadow-006 -shadow-007 shadow-008 = si-LRE si-lre-001 @@ -9167,7 +7701,6 @@ si-apply-templates-009 si-apply-templates-010 si-apply-templates-011 si-apply-templates-012 -si-apply-templates-013 = si-assert si-assert-001 si-assert-002 @@ -9694,17 +8227,13 @@ si-iterate-009 si-iterate-010 si-iterate-011 si-iterate-012 -si-iterate-013 si-iterate-035 si-iterate-036 -si-iterate-037 -si-iterate-094 si-iterate-095 si-iterate-096 si-iterate-097 si-iterate-098 si-iterate-099 -si-iterate-131 si-iterate-132 si-iterate-133 si-iterate-134 @@ -9713,7 +8242,6 @@ si-iterate-136 si-iterate-137 si-iterate-138 si-iterate-139 -si-iterate-140 si-iterate-806 si-iterate-904 si-iterate-905 @@ -10012,14 +8540,12 @@ snapshot-0112 sort-001 sort-002 sort-003 -sort-004 sort-005 sort-006 sort-007 sort-008 sort-009 sort-010 -sort-011 sort-012 sort-013 sort-014 @@ -10030,20 +8556,11 @@ sort-018 sort-019 sort-020 sort-021 -sort-022 -sort-023 -sort-024 -sort-025 sort-026 sort-027 sort-028 sort-029 -sort-030 -sort-031 -sort-032 sort-033 -sort-034 -sort-035 sort-036 sort-037 sort-038 @@ -10062,20 +8579,12 @@ sort-052 sort-053 sort-054 sort-055 -sort-056 -sort-057 -sort-058 -sort-059 -sort-060 -sort-061 sort-062 sort-063 sort-064 sort-065 sort-066 sort-067 -sort-069 -sort-070 sort-071 sort-072 sort-073 @@ -10180,8 +8689,6 @@ static-013b static-013c static-014 static-015 -static-016 -static-017 static-018 static-019 static-020 @@ -10308,8 +8815,6 @@ streamable-107 streamable-109 streamable-110 streamable-111 -streamable-112 -streamable-113 streamable-114 streamable-115 streamable-116 @@ -10326,20 +8831,17 @@ streamable-126 streamable-127 streamable-128 streamable-129 -streamable-130 streamable-134 streamable-135 streamable-136 streamable-137 streamable-138 streamable-139 -streamable-140 streamable-141 streamable-142 streamable-143 streamable-144 streamable-145 -streamable-146 streamable-147 streamable-148 streamable-149 @@ -10354,8 +8856,6 @@ streaming-fallback-006 string-003 string-014 string-031 -string-041 -string-042 string-097 string-117 string-118 @@ -10369,15 +8869,11 @@ string-134 string-135 string-136 = strip-space -strip-space-001 strip-space-002 -strip-space-003 strip-space-004 strip-space-005 strip-space-006 strip-space-007 -strip-space-008 -strip-space-009 strip-space-010 strip-space-011 strip-space-012 @@ -10386,16 +8882,12 @@ strip-space-014 strip-space-015 strip-space-016 strip-space-017 -strip-space-018 strip-space-019 strip-space-019a strip-space-020 -strip-space-021 strip-space-022 strip-space-023 -strip-space-024 strip-space-025 -strip-space-026 strip-space-027 strip-space-028 strip-space-029 @@ -10412,18 +8904,13 @@ strip-type-annotations-009 strip-type-annotations-010 strip-type-annotations-011 strip-type-annotations-012 -strip-type-annotations-013 strip-type-annotations-014 strip-type-annotations-015 strip-type-annotations-016 strip-type-annotations-017 -strip-type-annotations-018 strip-type-annotations-020 strip-type-annotations-021 strip-type-annotations-022 -strip-type-annotations-023 -strip-type-annotations-024 -strip-type-annotations-025 strip-type-annotations-026 strip-type-annotations-027 = su-absorbing @@ -10676,8 +9163,6 @@ sx-gc-eq-025 sx-gc-eq-026 sx-gc-eq-027 sx-gc-eq-028 -sx-gc-eq-100 -sx-gc-eq-101 sx-gc-eq-103 sx-gc-eq-104 sx-gc-eq-105 @@ -10703,7 +9188,6 @@ sx-gc-eq-125 sx-gc-eq-126 sx-gc-eq-127 sx-gc-eq-128 -sx-gc-eq-801 sx-gc-eq-901 sx-gc-eq-902 = sx-GeneralComp-ge @@ -10732,8 +9216,6 @@ sx-gc-ge-025 sx-gc-ge-026 sx-gc-ge-027 sx-gc-ge-028 -sx-gc-ge-100 -sx-gc-ge-101 sx-gc-ge-103 sx-gc-ge-104 sx-gc-ge-105 @@ -10787,8 +9269,6 @@ sx-gc-gt-025 sx-gc-gt-026 sx-gc-gt-027 sx-gc-gt-028 -sx-gc-gt-100 -sx-gc-gt-101 sx-gc-gt-103 sx-gc-gt-104 sx-gc-gt-105 @@ -10842,8 +9322,6 @@ sx-gc-le-025 sx-gc-le-026 sx-gc-le-027 sx-gc-le-028 -sx-gc-le-100 -sx-gc-le-101 sx-gc-le-103 sx-gc-le-104 sx-gc-le-105 @@ -10897,8 +9375,6 @@ sx-gc-lt-025 sx-gc-lt-026 sx-gc-lt-027 sx-gc-lt-028 -sx-gc-lt-100 -sx-gc-lt-101 sx-gc-lt-103 sx-gc-lt-104 sx-gc-lt-105 @@ -10952,8 +9428,6 @@ sx-gc-ne-025 sx-gc-ne-026 sx-gc-ne-027 sx-gc-ne-028 -sx-gc-ne-100 -sx-gc-ne-101 sx-gc-ne-103 sx-gc-ne-104 sx-gc-ne-105 @@ -11166,6 +9640,7 @@ sx-MapExpr-103 sx-MapExpr-104 sx-MapExpr-901 sx-MapExpr-902 += sx-PathExpr = sx-QuantifiedExpr sx-every-001 sx-every-002 @@ -11387,9 +9862,8 @@ system-property-022 system-property-023 system-property-024 system-property-025 += system-property-gen = template -template-003 -template-005 = transform transform-001 transform-002 @@ -11402,7 +9876,6 @@ transform-008 transform-009 = treat-as treat-as-0101 -treat-as-0201 treat-as-0301 treat-as-0302 = try @@ -11449,107 +9922,24 @@ try-040 try-041 try-042 = tunnel -tunnel-0101 -tunnel-0102 -tunnel-0103 -tunnel-0104 -tunnel-0105 -tunnel-0106 -tunnel-0107 -tunnel-0108 -tunnel-0109 -tunnel-0110 tunnel-0111 -tunnel-0112 tunnel-0113 -tunnel-0114 -tunnel-0115 -tunnel-0116 -tunnel-0117 -tunnel-0118 -tunnel-0119 -tunnel-0120 -tunnel-0201 -tunnel-0202 -tunnel-0203 -tunnel-0204 tunnel-0205 -tunnel-0206 tunnel-0207 -tunnel-0208 -tunnel-0209 -tunnel-0210 -tunnel-0211 -tunnel-0212 -tunnel-0213 -tunnel-0214 -tunnel-0215 -tunnel-0216 -tunnel-0217 -tunnel-0218 tunnel-0219 -tunnel-0220 tunnel-0221 -tunnel-0222 tunnel-0223 -tunnel-0224 -tunnel-0225 tunnel-0226 -tunnel-0227 -tunnel-0301 -tunnel-0302 -tunnel-0303 -tunnel-0304 -tunnel-0401 -tunnel-0402 -tunnel-0403 -tunnel-0404 -tunnel-0405 -tunnel-0406 -tunnel-0501 = type -type-0102 -type-0110 -type-0111 -type-0112 type-0113 -type-0114 -type-0115 -type-0123 -type-0124 -type-0125 -type-0126 -type-0127 -type-0128 -type-0129 -type-0130 -type-0132 -type-0133 -type-0134 type-0135 -type-0136 type-0137 -type-0139 -type-0140 -type-0141 -type-0142 type-0143 -type-0144 type-0150 -type-0151 -type-0152 -type-0153 -type-0154 type-0155 type-0156 type-0157 -type-0158 type-0159 -type-0160 -type-0161 -type-0163 -type-0164 -type-0165 type-0166 type-0168a type-0168b @@ -11558,9 +9948,6 @@ type-0170 type-0171 type-0172 type-0173 -type-0174 -type-0175 -type-0203 type-0302 type-0303 type-0501 @@ -13134,86 +11521,46 @@ use-package-294 use-package-295 use-package-296 = use-when -use-when-0101 -use-when-0102 use-when-0103 use-when-0104 -use-when-0105 use-when-0106 use-when-0107 use-when-0108 -use-when-0109 -use-when-0110 use-when-0111 use-when-0112 -use-when-0113 -use-when-0114 -use-when-0115 -use-when-0116 -use-when-0117 use-when-0118 use-when-0119 use-when-0120 use-when-0121 -use-when-0122 use-when-0123 use-when-0124 use-when-0125 use-when-0126 use-when-0127a -use-when-0127b use-when-0128 -use-when-0129 -use-when-0130 use-when-0131 use-when-0132 use-when-0133 -use-when-0134 use-when-0135 use-when-0136 use-when-0137 use-when-0138 -use-when-0139 -use-when-0140 -use-when-0201 use-when-0212 use-when-0213 use-when-0214 -use-when-0215 -use-when-0216 -use-when-0217 -use-when-0218 -use-when-0219 -use-when-0220 use-when-0222 -use-when-0223 -use-when-0224 -use-when-0225 use-when-0226 use-when-0227 use-when-0301 -use-when-0401 -use-when-0402 -use-when-0403 -use-when-0404 -use-when-0405 -use-when-0406 use-when-0407 use-when-0407a -use-when-0408 -use-when-0409 -use-when-0410 use-when-0411 -use-when-0412 -use-when-0413 use-when-0414 use-when-0415 use-when-0416 use-when-0417 -use-when-0418 use-when-0419 use-when-0420 -use-when-0421 use-when-0423 use-when-0424 use-when-0425 @@ -13230,26 +11577,16 @@ validation-0005 validation-0006 validation-0101 validation-0102a -validation-0102b validation-0103a -validation-0103b validation-0104 validation-0105a -validation-0105b validation-0106 validation-0107 validation-0108 validation-0109 -validation-0110 validation-0111 validation-0201 validation-0202 -validation-0203 -validation-0204 -validation-0205 -validation-0206 -validation-0207 -validation-0208 validation-0209 validation-0210 validation-0211 @@ -13263,7 +11600,6 @@ validation-0501 validation-0601 validation-0701 validation-0801 -validation-0901 validation-1001 validation-1002 validation-1201 @@ -13291,97 +11627,6 @@ validation-1708 validation-2001 validation-2002 = variable -variable-0101 -variable-0102 -variable-0103 -variable-0104 -variable-0105 -variable-0106 -variable-0107 -variable-0108 -variable-0109 -variable-0110 -variable-0111 -variable-0112 -variable-0113 -variable-0114 -variable-0115 -variable-0116 -variable-0117 -variable-0118 -variable-0119 -variable-0120 -variable-0121 -variable-0122 -variable-0123 -variable-0201 -variable-0202 -variable-0203 -variable-0204 -variable-0205 -variable-0206 -variable-0301 -variable-0302 -variable-0303 -variable-0401 -variable-0501 -variable-0601 -variable-0701 -variable-0802 -variable-0901 -variable-1001 -variable-1003 -variable-1004 -variable-1005 -variable-1006 -variable-1010 -variable-1011 -variable-1012 -variable-1201 -variable-1301 -variable-1402 -variable-1501 -variable-1601 -variable-1801 -variable-1901 -variable-1902 -variable-1903 -variable-1904 -variable-1905 -variable-2001 -variable-2101 -variable-2201 -variable-2202 -variable-2301 -variable-2302 -variable-2303 -variable-2304 -variable-2401 -variable-2601 -variable-2701 -variable-3001 -variable-3101 -variable-3201 -variable-3301 -variable-3501 -variable-3601 -variable-3701 -variable-3801 -variable-3802 -variable-3901 -variable-4001 -variable-4101 -variable-4201 -variable-4301 -variable-4401 -variable-4402 -variable-4403 -variable-4501 -variable-4601 -variable-4602 -variable-4701 -variable-4702 -variable-4802 = version version-001 version-002 @@ -13396,9 +11641,7 @@ version-010 version-012 version-013 version-014 -version-015 version-017 -version-018 version-019 version-020 version-021 @@ -13457,10 +11700,6 @@ whitespace-010 whitespace-011 whitespace-014 whitespace-015 -whitespace-019 -whitespace-022 -whitespace-023 -whitespace-025 whitespace-028 = xml-to-json xml-to-json-A001 @@ -13619,15 +11858,12 @@ xml-to-json-D511 = xml-version xml-version-002 xml-version-003 -xml-version-006 xml-version-007 xml-version-008 xml-version-009 xml-version-010 xml-version-012 xml-version-013 -xml-version-014 -xml-version-015 xml-version-016 xml-version-018 xml-version-020 @@ -13651,6 +11887,31 @@ xml-version-039 xml-version-040 xml-version-041 xml-version-042 += xp-striding-climbing-consumingA += xp-striding-climbing-consumingB += xp-striding-climbing-motionlessA += xp-striding-climbing-motionlessB += xp-striding-climbing-motionlessC += xp-striding-crawling-consumingA += xp-striding-crawling-consumingB += xp-striding-crawling-consumingC += xp-striding-grounded-consumingA += xp-striding-grounded-consumingB += xp-striding-grounded-consumingC += xp-striding-grounded-motionlessA += xp-striding-grounded-motionlessB += xp-striding-grounded-motionlessC += xp-striding-grounded-motionlessD += xp-striding-grounded-motionlessE += xp-striding-grounded-motionlessF += xp-striding-grounded-motionlessG += xp-striding-grounded-motionlessH += xp-striding-roaming-free-rangingA += xp-striding-roaming-free-rangingB += xp-striding-roaming-free-rangingC += xp-striding-striding-consumingA += xp-striding-striding-consumingB += xp-striding-striding-motionlessA = xpath-compat xpath-compat-0101 xpath-compat-0102 @@ -13674,52 +11935,27 @@ xpath-default-namespace-0102 xpath-default-namespace-0103 xpath-default-namespace-0105 xpath-default-namespace-0106 -xpath-default-namespace-0107 xpath-default-namespace-0108 xpath-default-namespace-0201 -xpath-default-namespace-0202 -xpath-default-namespace-0301 xpath-default-namespace-0401 -xpath-default-namespace-0501 xpath-default-namespace-0502 xpath-default-namespace-0503 xpath-default-namespace-0701 xpath-default-namespace-0702 xpath-default-namespace-0703 -xpath-default-namespace-0801 -xpath-default-namespace-1001 xpath-default-namespace-1102 -xpath-default-namespace-1201 xpath-default-namespace-1202 = xsl-document -xsl-document-0101 -xsl-document-0102 -xsl-document-0103 -xsl-document-0104 -xsl-document-0105 xsl-document-0106 -xsl-document-0107 -xsl-document-0201 -xsl-document-0202 -xsl-document-0203 xsl-document-0301 -xsl-document-0302 -xsl-document-0401 xsl-document-0402 -xsl-document-0403 xsl-document-0404 -xsl-document-0501 -xsl-document-0601 -xsl-document-0602 xsl-document-0603 xsl-document-0604 -xsl-document-0605 xsl-document-0606 xsl-document-0701 -xsl-document-0801 = xslt-compat xslt-compat-001 -xslt-compat-002 xslt-compat-003 xslt-compat-004 xslt-compat-005 diff --git a/xee-interpreter/Cargo.toml b/xee-interpreter/Cargo.toml index 4f8307203..7b09c2076 100644 --- a/xee-interpreter/Cargo.toml +++ b/xee-interpreter/Cargo.toml @@ -58,6 +58,7 @@ v_jsonescape = "0.7.8" static_assertions = "1.1.0" rand = { version = "0.8.5", default-features = false } rand_xoshiro = "0.6.0" +url = "2.5.4" [dev-dependencies] insta = { workspace = true, features = ["yaml", "glob"] } diff --git a/xee-interpreter/src/context/static_context.rs b/xee-interpreter/src/context/static_context.rs index d17125672..70d957b50 100644 --- a/xee-interpreter/src/context/static_context.rs +++ b/xee-interpreter/src/context/static_context.rs @@ -1,3 +1,4 @@ +use ahash::HashSet; use std::cell::RefCell; use std::fmt::Debug; use std::rc::Rc; @@ -28,6 +29,7 @@ static DEFAULT_COLLATION: LazyLock = LazyLock::new(|| { pub struct StaticContext { parser_context: XPathParserContext, functions: &'static function::StaticFunctions, + disabled_functions: HashSet, // TODO: try to make collations static collations: RefCell, static_base_uri: Option, @@ -35,7 +37,12 @@ pub struct StaticContext { impl Default for StaticContext { fn default() -> Self { - Self::new(Namespaces::default(), VariableNames::default(), None) + Self::new( + Namespaces::default(), + VariableNames::default(), + HashSet::default(), + None, + ) } } @@ -44,6 +51,7 @@ impl From for StaticContext { Self { parser_context, functions: &STATIC_FUNCTIONS, + disabled_functions: HashSet::default(), collations: RefCell::new(Collations::new()), static_base_uri: None, } @@ -54,18 +62,25 @@ impl StaticContext { pub(crate) fn new( namespaces: Namespaces, variable_names: VariableNames, + disabled_functions: HashSet, static_base_uri: Option, ) -> Self { Self { parser_context: XPathParserContext::new(namespaces, variable_names), functions: &STATIC_FUNCTIONS, + disabled_functions, collations: RefCell::new(Collations::new()), static_base_uri, } } pub fn from_namespaces(namespaces: Namespaces) -> Self { - Self::new(namespaces, VariableNames::default(), None) + Self::new( + namespaces, + VariableNames::default(), + HashSet::default(), + None, + ) } pub fn namespaces(&self) -> &Namespaces { @@ -137,6 +152,9 @@ impl StaticContext { name: &xot::xmlname::OwnedName, arity: u8, ) -> Option { + if self.disabled_functions.contains(name) { + return None; + } self.functions.get_by_name(name, arity) } diff --git a/xee-interpreter/src/context/static_context_builder.rs b/xee-interpreter/src/context/static_context_builder.rs index aca5a0643..f4e5bff19 100644 --- a/xee-interpreter/src/context/static_context_builder.rs +++ b/xee-interpreter/src/context/static_context_builder.rs @@ -1,4 +1,4 @@ -use ahash::HashMap; +use ahash::{HashMap, HashSet}; use iri_string::types::IriAbsoluteString; use xee_name::Namespaces; use xot::xmlname::OwnedName; @@ -9,6 +9,7 @@ use crate::context; pub struct StaticContextBuilder<'a> { variable_names: Vec, namespaces: HashMap<&'a str, &'a str>, + disabled_functions: HashSet, default_element_namespace: &'a str, default_function_namespace: &'a str, static_base_uri: Option, @@ -75,6 +76,12 @@ impl<'a> StaticContextBuilder<'a> { self } + /// Disable a function name in this static context. + pub fn disable_function(&mut self, name: OwnedName) -> &mut Self { + self.disabled_functions.insert(name); + self + } + /// Build the static context. /// /// This will always include the default known namespaces for @@ -96,7 +103,12 @@ impl<'a> StaticContextBuilder<'a> { default_function_namespace.to_string(), ); let variable_names = self.variable_names.clone().into_iter().collect(); - context::StaticContext::new(namespaces, variable_names, self.static_base_uri.clone()) + context::StaticContext::new( + namespaces, + variable_names, + self.disabled_functions.clone(), + self.static_base_uri.clone(), + ) } } diff --git a/xee-interpreter/src/declaration/decl.rs b/xee-interpreter/src/declaration/decl.rs index d9b6a1dea..a2969731a 100644 --- a/xee-interpreter/src/declaration/decl.rs +++ b/xee-interpreter/src/declaration/decl.rs @@ -1,14 +1,125 @@ -use crate::{function, pattern::ModeLookup}; +use ahash::{HashMap, HashMapExt}; + +use crate::{function, pattern::ModeId, pattern::ModeLookup}; +use xot::xmlname::OwnedName; + +#[derive(Debug, Clone)] +pub struct GlobalVariableDeclaration { + pub name: function::Name, + pub function_id: function::InlineFunctionId, + pub original_name: Option, + pub required: bool, +} + +#[derive(Debug, Clone)] +pub struct NamedTemplateDeclaration { + pub name: function::Name, + pub function_id: function::InlineFunctionId, +} + +#[derive(Debug, Clone)] +pub struct TemplateParamDeclaration { + pub name: String, + pub tunnel: bool, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct ModeDeclaration { + pub on_no_match: ModeOnNoMatch, + pub warning_on_no_match: bool, + pub typed: ModeTyped, +} + +impl Default for ModeDeclaration { + fn default() -> Self { + Self { + on_no_match: ModeOnNoMatch::TextOnlyCopy, + warning_on_no_match: false, + typed: ModeTyped::No, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ModeOnNoMatch { + DeepCopy, + ShallowCopy, + DeepSkip, + ShallowSkip, + TextOnlyCopy, + Fail, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ModeTyped { + Yes, + No, + Strict, + Lax, +} #[derive(Debug)] pub struct Declarations { pub mode_lookup: ModeLookup, + modes: HashMap, + pub global_variables: Vec, + pub named_templates: Vec, + template_params: HashMap>, } impl Declarations { pub(crate) fn new() -> Self { Self { mode_lookup: ModeLookup::new(), + modes: HashMap::new(), + global_variables: Vec::new(), + named_templates: Vec::new(), + template_params: HashMap::new(), } } + + pub fn add_mode(&mut self, mode_id: ModeId, declaration: ModeDeclaration) { + self.modes.insert(mode_id, declaration); + } + + pub fn mode(&self, mode_id: ModeId) -> ModeDeclaration { + self.modes.get(&mode_id).copied().unwrap_or_default() + } + + pub fn add_global_variable(&mut self, global_variable: GlobalVariableDeclaration) { + self.global_variables.push(global_variable); + } + + pub fn global_variable(&self, index: usize) -> &GlobalVariableDeclaration { + &self.global_variables[index] + } + + pub fn add_named_template(&mut self, named_template: NamedTemplateDeclaration) { + self.named_templates.push(named_template); + } + + pub fn named_template(&self, index: usize) -> &NamedTemplateDeclaration { + &self.named_templates[index] + } + + pub fn named_template_by_name(&self, name: &str) -> Option<&NamedTemplateDeclaration> { + self.named_templates + .iter() + .find(|named_template| named_template.name == function::Name::new(name.to_string())) + } + + pub fn add_template_params( + &mut self, + function_id: function::InlineFunctionId, + params: Vec, + ) { + self.template_params.insert(function_id, params); + } + + pub fn template_params( + &self, + function_id: function::InlineFunctionId, + ) -> Option<&[TemplateParamDeclaration]> { + self.template_params.get(&function_id).map(Vec::as_slice) + } } diff --git a/xee-interpreter/src/declaration/mod.rs b/xee-interpreter/src/declaration/mod.rs index 47d699072..d90468166 100644 --- a/xee-interpreter/src/declaration/mod.rs +++ b/xee-interpreter/src/declaration/mod.rs @@ -4,4 +4,7 @@ mod decl; mod globalvar; -pub use decl::Declarations; +pub use decl::{ + Declarations, GlobalVariableDeclaration, ModeDeclaration, ModeOnNoMatch, ModeTyped, + NamedTemplateDeclaration, TemplateParamDeclaration, +}; diff --git a/xee-interpreter/src/error.rs b/xee-interpreter/src/error.rs index 9a5630977..ccf1a3d2b 100644 --- a/xee-interpreter/src/error.rs +++ b/xee-interpreter/src/error.rs @@ -546,7 +546,33 @@ pub enum Error { /// binding of a global variable with the same name and same import /// precedence, unless it also contains another binding with the same name /// and higher import precedence. + XTSE0010, + /// Invalid value for an XSLT-defined attribute. + /// + /// It is a static error if an attribute defined for an XSLT instruction + /// has a value that is not one of the permitted values. + XTSE0020, + /// Required global parameter not supplied. + /// + /// It is a dynamic error if a stylesheet parameter is declared with + /// required="yes" and no value is supplied. + XTDE0050, + /// Attribute not permitted on an XSLT element. + /// + /// It is a static error if an XSLT element has an attribute that is not + /// permitted for that instruction. + XTSE0090, + /// Duplicate local parameter name. + /// + /// It is a static error if two xsl:param declarations within the same + /// template specify the same name. + XTSE0580, XTSE0630, + /// xsl:with-param does not match any declared template parameter. + /// + /// It is a static error if xsl:call-template supplies a non-tunnel + /// parameter that is not declared by the called template. + XTSE0680, /// xsl:break or xsl:next-iteration outside of xsl:iterate's tail position /// /// It is a static error if an xsl:break or xsl:next-iteration element @@ -562,6 +588,25 @@ pub enum Error { /// /// Circularity in global declarations is now allowed. XTDE0640, + /// Required template parameter not supplied. + /// + /// It is a dynamic error if a required template parameter is not supplied. + XTDE0700, + /// Variable value does not match declared type. + /// + /// It is a type error if the value of a variable does not match the + /// required type specified in its as attribute. + XTTE0570, + /// Supplied template parameter value has the wrong type. + /// + /// It is a type error if the supplied value of a template parameter cannot + /// be converted to the required type of the parameter. + XTTE0590, + /// Typed mode applied to untyped nodes. + /// + /// It is a type error if xsl:apply-templates is evaluated in a mode with + /// typed="yes" and the selected nodes are untyped. + XTTE3100, /// Shallow copy /// /// Shallow copy of sequence of more than one item is not allowed. diff --git a/xee-interpreter/src/function/inline_function.rs b/xee-interpreter/src/function/inline_function.rs index 16d5d3592..c4689a356 100644 --- a/xee-interpreter/src/function/inline_function.rs +++ b/xee-interpreter/src/function/inline_function.rs @@ -13,13 +13,17 @@ pub struct CastType { pub empty_sequence_allowed: bool, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Name(pub(crate) String); impl Name { pub fn new(name: String) -> Self { Name(name) } + + pub fn as_str(&self) -> &str { + &self.0 + } } #[derive(Debug, Clone)] diff --git a/xee-interpreter/src/interpreter/instruction.rs b/xee-interpreter/src/interpreter/instruction.rs index f7b4ff14e..5c6cff118 100644 --- a/xee-interpreter/src/interpreter/instruction.rs +++ b/xee-interpreter/src/interpreter/instruction.rs @@ -1,5 +1,28 @@ use num::{FromPrimitive, ToPrimitive}; +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum RaisedError { + XTDE0700, + XTTE0570, +} + +impl RaisedError { + pub(crate) fn to_u16(self) -> u16 { + match self { + RaisedError::XTDE0700 => 0, + RaisedError::XTTE0570 => 1, + } + } + + pub(crate) fn from_u16(value: u16) -> Self { + match value { + 0 => RaisedError::XTDE0700, + 1 => RaisedError::XTTE0570, + _ => panic!("unknown raised error id: {value}"), + } + } +} + #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum Instruction { // binary operators @@ -14,10 +37,14 @@ pub enum Instruction { Minus, // Concat, + Absent, Const(u16), Closure(u16), + NamedTemplate(u16), StaticClosure(u16), Var(u16), + GlobalVar(u16), + VarIsAbsent(u16), Set(u16), ClosureVar(u16), Comma, @@ -52,6 +79,7 @@ pub enum Instruction { Deduplicate, Return, ReturnConvert(u16), + ConvertSequence(u16, RaisedError), Dup, Pop, LetDone, @@ -77,7 +105,11 @@ pub enum Instruction { XmlAppend, CopyShallow, CopyDeep, - ApplyTemplates(u16), + CallTemplate, + ContinueTemplate, + ApplyTemplates(u16, bool), + ApplyTemplatesCurrent(u16, bool), + RaiseError(RaisedError), PrintTop, PrintStack, } @@ -93,10 +125,14 @@ pub(crate) enum EncodedInstruction { Plus, Minus, Concat, + Absent, Const, Closure, + NamedTemplate, StaticClosure, Var, + GlobalVar, + VarIsAbsent, Set, ClosureVar, Comma, @@ -131,6 +167,7 @@ pub(crate) enum EncodedInstruction { Deduplicate, Return, ReturnConvert, + ConvertSequence, Dup, Pop, LetDone, @@ -154,9 +191,13 @@ pub(crate) enum EncodedInstruction { XmlComment, XmlProcessingInstruction, XmlAppend, - ApplyTemplates, CopyShallow, CopyDeep, + CallTemplate, + ContinueTemplate, + ApplyTemplates, + ApplyTemplatesCurrent, + RaiseError, PrintTop, PrintStack, } @@ -174,6 +215,7 @@ pub(crate) fn decode_instruction(bytes: &[u8]) -> (Instruction, usize) { EncodedInstruction::Plus => (Instruction::Plus, 1), EncodedInstruction::Minus => (Instruction::Minus, 1), EncodedInstruction::Concat => (Instruction::Concat, 1), + EncodedInstruction::Absent => (Instruction::Absent, 1), EncodedInstruction::Const => { let constant = u16::from_le_bytes([bytes[1], bytes[2]]); (Instruction::Const(constant), 3) @@ -182,6 +224,10 @@ pub(crate) fn decode_instruction(bytes: &[u8]) -> (Instruction, usize) { let function = u16::from_le_bytes([bytes[1], bytes[2]]); (Instruction::Closure(function), 3) } + EncodedInstruction::NamedTemplate => { + let function = u16::from_le_bytes([bytes[1], bytes[2]]); + (Instruction::NamedTemplate(function), 3) + } EncodedInstruction::StaticClosure => { let function = u16::from_le_bytes([bytes[1], bytes[2]]); (Instruction::StaticClosure(function), 3) @@ -190,6 +236,14 @@ pub(crate) fn decode_instruction(bytes: &[u8]) -> (Instruction, usize) { let variable = u16::from_le_bytes([bytes[1], bytes[2]]); (Instruction::Var(variable), 3) } + EncodedInstruction::GlobalVar => { + let variable = u16::from_le_bytes([bytes[1], bytes[2]]); + (Instruction::GlobalVar(variable), 3) + } + EncodedInstruction::VarIsAbsent => { + let variable = u16::from_le_bytes([bytes[1], bytes[2]]); + (Instruction::VarIsAbsent(variable), 3) + } EncodedInstruction::Set => { let variable = u16::from_le_bytes([bytes[1], bytes[2]]); (Instruction::Set(variable), 3) @@ -264,6 +318,14 @@ pub(crate) fn decode_instruction(bytes: &[u8]) -> (Instruction, usize) { let sequence_type_id = u16::from_le_bytes([bytes[1], bytes[2]]); (Instruction::ReturnConvert(sequence_type_id), 3) } + EncodedInstruction::ConvertSequence => { + let sequence_type_id = u16::from_le_bytes([bytes[1], bytes[2]]); + let error_id = u16::from_le_bytes([bytes[3], bytes[4]]); + ( + Instruction::ConvertSequence(sequence_type_id, RaisedError::from_u16(error_id)), + 5, + ) + } EncodedInstruction::Dup => (Instruction::Dup, 1), EncodedInstruction::Pop => (Instruction::Pop, 1), EncodedInstruction::LetDone => (Instruction::LetDone, 1), @@ -285,9 +347,30 @@ pub(crate) fn decode_instruction(bytes: &[u8]) -> (Instruction, usize) { EncodedInstruction::XmlAppend => (Instruction::XmlAppend, 1), EncodedInstruction::CopyShallow => (Instruction::CopyShallow, 1), EncodedInstruction::CopyDeep => (Instruction::CopyDeep, 1), + EncodedInstruction::CallTemplate => (Instruction::CallTemplate, 1), + EncodedInstruction::ContinueTemplate => (Instruction::ContinueTemplate, 1), EncodedInstruction::ApplyTemplates => { let mode_id = u16::from_le_bytes([bytes[1], bytes[2]]); - (Instruction::ApplyTemplates(mode_id), 3) + let builtin_template_params_passthrough = bytes[3] != 0; + ( + Instruction::ApplyTemplates(mode_id, builtin_template_params_passthrough), + 4, + ) + } + EncodedInstruction::ApplyTemplatesCurrent => { + let fallback_mode_id = u16::from_le_bytes([bytes[1], bytes[2]]); + let builtin_template_params_passthrough = bytes[3] != 0; + ( + Instruction::ApplyTemplatesCurrent( + fallback_mode_id, + builtin_template_params_passthrough, + ), + 4, + ) + } + EncodedInstruction::RaiseError => { + let error_id = u16::from_le_bytes([bytes[1], bytes[2]]); + (Instruction::RaiseError(RaisedError::from_u16(error_id)), 3) } EncodedInstruction::PrintTop => (Instruction::PrintTop, 1), EncodedInstruction::PrintStack => (Instruction::PrintStack, 1), @@ -316,6 +399,7 @@ pub fn encode_instruction(instruction: Instruction, bytes: &mut Vec) { Instruction::Plus => bytes.push(EncodedInstruction::Plus.to_u8().unwrap()), Instruction::Minus => bytes.push(EncodedInstruction::Minus.to_u8().unwrap()), Instruction::Concat => bytes.push(EncodedInstruction::Concat.to_u8().unwrap()), + Instruction::Absent => bytes.push(EncodedInstruction::Absent.to_u8().unwrap()), Instruction::Const(constant) => { bytes.push(EncodedInstruction::Const.to_u8().unwrap()); bytes.extend_from_slice(&constant.to_le_bytes()); @@ -324,6 +408,10 @@ pub fn encode_instruction(instruction: Instruction, bytes: &mut Vec) { bytes.push(EncodedInstruction::Closure.to_u8().unwrap()); bytes.extend_from_slice(&function_id.to_le_bytes()); } + Instruction::NamedTemplate(function_id) => { + bytes.push(EncodedInstruction::NamedTemplate.to_u8().unwrap()); + bytes.extend_from_slice(&function_id.to_le_bytes()); + } Instruction::StaticClosure(function_id) => { bytes.push(EncodedInstruction::StaticClosure.to_u8().unwrap()); bytes.extend_from_slice(&function_id.to_le_bytes()); @@ -332,6 +420,14 @@ pub fn encode_instruction(instruction: Instruction, bytes: &mut Vec) { bytes.push(EncodedInstruction::Var.to_u8().unwrap()); bytes.extend_from_slice(&variable.to_le_bytes()); } + Instruction::GlobalVar(variable) => { + bytes.push(EncodedInstruction::GlobalVar.to_u8().unwrap()); + bytes.extend_from_slice(&variable.to_le_bytes()); + } + Instruction::VarIsAbsent(variable) => { + bytes.push(EncodedInstruction::VarIsAbsent.to_u8().unwrap()); + bytes.extend_from_slice(&variable.to_le_bytes()); + } Instruction::Set(variable) => { bytes.push(EncodedInstruction::Set.to_u8().unwrap()); bytes.extend_from_slice(&variable.to_le_bytes()); @@ -394,6 +490,11 @@ pub fn encode_instruction(instruction: Instruction, bytes: &mut Vec) { bytes.push(EncodedInstruction::ReturnConvert.to_u8().unwrap()); bytes.extend_from_slice(&sequence_type_id.to_le_bytes()); } + Instruction::ConvertSequence(sequence_type_id, error) => { + bytes.push(EncodedInstruction::ConvertSequence.to_u8().unwrap()); + bytes.extend_from_slice(&sequence_type_id.to_le_bytes()); + bytes.extend_from_slice(&error.to_u16().to_le_bytes()); + } Instruction::Dup => bytes.push(EncodedInstruction::Dup.to_u8().unwrap()), Instruction::Pop => bytes.push(EncodedInstruction::Pop.to_u8().unwrap()), Instruction::LetDone => bytes.push(EncodedInstruction::LetDone.to_u8().unwrap()), @@ -437,9 +538,26 @@ pub fn encode_instruction(instruction: Instruction, bytes: &mut Vec) { Instruction::XmlAppend => bytes.push(EncodedInstruction::XmlAppend.to_u8().unwrap()), Instruction::CopyShallow => bytes.push(EncodedInstruction::CopyShallow.to_u8().unwrap()), Instruction::CopyDeep => bytes.push(EncodedInstruction::CopyDeep.to_u8().unwrap()), - Instruction::ApplyTemplates(mode_id) => { + Instruction::CallTemplate => bytes.push(EncodedInstruction::CallTemplate.to_u8().unwrap()), + Instruction::ContinueTemplate => { + bytes.push(EncodedInstruction::ContinueTemplate.to_u8().unwrap()) + } + Instruction::ApplyTemplates(mode_id, builtin_template_params_passthrough) => { bytes.push(EncodedInstruction::ApplyTemplates.to_u8().unwrap()); bytes.extend_from_slice(&mode_id.to_le_bytes()); + bytes.push(u8::from(builtin_template_params_passthrough)); + } + Instruction::ApplyTemplatesCurrent( + fallback_mode_id, + builtin_template_params_passthrough, + ) => { + bytes.push(EncodedInstruction::ApplyTemplatesCurrent.to_u8().unwrap()); + bytes.extend_from_slice(&fallback_mode_id.to_le_bytes()); + bytes.push(u8::from(builtin_template_params_passthrough)); + } + Instruction::RaiseError(error) => { + bytes.push(EncodedInstruction::RaiseError.to_u8().unwrap()); + bytes.extend_from_slice(&error.to_u16().to_le_bytes()); } Instruction::PrintTop => bytes.push(EncodedInstruction::PrintTop.to_u8().unwrap()), Instruction::PrintStack => bytes.push(EncodedInstruction::PrintStack.to_u8().unwrap()), @@ -464,6 +582,7 @@ pub fn instruction_size(instruction: &Instruction) -> usize { | Instruction::Plus | Instruction::Minus | Instruction::Concat + | Instruction::Absent | Instruction::Comma | Instruction::CurlyArray | Instruction::SquareArray @@ -511,13 +630,18 @@ pub fn instruction_size(instruction: &Instruction) -> usize { | Instruction::XmlAppend | Instruction::CopyShallow | Instruction::CopyDeep + | Instruction::CallTemplate + | Instruction::ContinueTemplate | Instruction::PrintTop | Instruction::PrintStack => 1, Instruction::Call(_) => 2, Instruction::Const(_) | Instruction::Closure(_) + | Instruction::NamedTemplate(_) | Instruction::StaticClosure(_) | Instruction::Var(_) + | Instruction::GlobalVar(_) + | Instruction::VarIsAbsent(_) | Instruction::Set(_) | Instruction::ClosureVar(_) | Instruction::Jump(_) @@ -528,8 +652,10 @@ pub fn instruction_size(instruction: &Instruction) -> usize { | Instruction::InstanceOf(_) | Instruction::Treat(_) | Instruction::ReturnConvert(_) - | Instruction::JumpIfFalse(_) => 3, - Instruction::ApplyTemplates(_) => 3, + | Instruction::JumpIfFalse(_) + | Instruction::RaiseError(_) => 3, + Instruction::ApplyTemplates(_, _) | Instruction::ApplyTemplatesCurrent(_, _) => 4, + Instruction::ConvertSequence(_, _) => 5, } } diff --git a/xee-interpreter/src/interpreter/interpret.rs b/xee-interpreter/src/interpreter/interpret.rs index 7e2a34a5a..7708c136a 100644 --- a/xee-interpreter/src/interpreter/interpret.rs +++ b/xee-interpreter/src/interpreter/interpret.rs @@ -14,6 +14,7 @@ use crate::atomic::{ op_add, op_div, op_idiv, op_mod, op_multiply, op_subtract, OpEq, OpGe, OpGt, OpLe, OpLt, OpNe, }; use crate::context::DynamicContext; +use crate::declaration; use crate::function; use crate::pattern::PredicateMatcher; use crate::sequence; @@ -22,13 +23,25 @@ use crate::stack; use crate::xml; use crate::{error, pattern}; -use super::instruction::{read_i16, read_instruction, read_u16, read_u8, EncodedInstruction}; +use super::instruction::{ + read_i16, read_instruction, read_u16, read_u8, EncodedInstruction, RaisedError, +}; use super::runnable::Runnable; use super::state::State; pub struct Interpreter<'a> { runnable: &'a Runnable<'a>, pub(crate) state: State<'a>, + global_variables: Vec, + tunnel_params: Vec, + mode_stack: Vec, +} + +#[derive(Debug, Clone)] +enum GlobalValueState { + Uninitialized, + Resolving, + Resolved(sequence::Sequence), } pub struct ContextInfo { @@ -37,6 +50,12 @@ pub struct ContextInfo { pub size: stack::Value, } +struct ApplyTemplatesOptions<'a> { + params: &'a function::Map, + tunnel_params: &'a function::Map, + builtin_template_params_passthrough: bool, +} + impl From for ContextInfo { fn from(item: sequence::Item) -> Self { ContextInfo { @@ -52,6 +71,12 @@ impl<'a> Interpreter<'a> { Interpreter { runnable, state: State::new(xot), + global_variables: vec![ + GlobalValueState::Uninitialized; + runnable.program().declarations.global_variables.len() + ], + tunnel_params: vec![function::Map::new(Vec::new()).unwrap()], + mode_stack: Vec::new(), } } @@ -135,6 +160,9 @@ impl<'a> Interpreter<'a> { let item: sequence::Item = result.into(); self.state.push(item); } + EncodedInstruction::Absent => { + self.state.push_value(stack::Value::Absent); + } EncodedInstruction::Const => { let index = self.read_u16(); self.state @@ -155,6 +183,19 @@ impl<'a> Interpreter<'a> { let item: sequence::Item = function.into(); self.state.push(item); } + EncodedInstruction::NamedTemplate => { + let template_id = self.read_u16(); + let named_template = self + .runnable + .program() + .declarations + .named_template(template_id as usize); + let function: function::Function = + function::InlineFunctionData::new(named_template.function_id, Vec::new()) + .into(); + let item: sequence::Item = function.into(); + self.state.push(item); + } EncodedInstruction::StaticClosure => { let static_function_id = self.read_u16(); let static_function_id = @@ -168,6 +209,15 @@ impl<'a> Interpreter<'a> { let index = self.read_u16(); self.state.push_var(index as usize); } + EncodedInstruction::GlobalVar => { + let index = self.read_u16(); + let value = self.resolve_global_variable(index as usize)?; + self.state.push(value); + } + EncodedInstruction::VarIsAbsent => { + let index = self.read_u16(); + self.state.push(self.state.var_is_absent(index as usize)); + } EncodedInstruction::Set => { let index = self.read_u16(); self.state.set_var(index as usize); @@ -394,6 +444,26 @@ impl<'a> Interpreter<'a> { )?; self.state.push(sequence); } + EncodedInstruction::ConvertSequence => { + let sequence_type_id = self.read_u16(); + let raised_error = RaisedError::from_u16(self.read_u16()); + let sequence = self.state.pop()?; + let sequence_type = + &(self.current_inline_function().sequence_types[sequence_type_id as usize]); + + let sequence = sequence + .sequence_type_matching_function_conversion( + sequence_type, + self.runnable.static_context(), + self.state.xot(), + &|function| self.runnable.function_info(function).signature(), + ) + .map_err(|_| match raised_error { + RaisedError::XTDE0700 => error::Error::XTDE0700, + RaisedError::XTTE0570 => error::Error::XTTE0570, + })?; + self.state.push(sequence); + } EncodedInstruction::LetDone => { let return_value = self.state.pop()?; // pop the variable assignment @@ -630,13 +700,67 @@ impl<'a> Interpreter<'a> { } self.state.push(new_sequence); } + EncodedInstruction::CallTemplate => { + let tunnel_params = self.state.pop()?.one()?.to_map()?; + let params = self.state.pop()?.one()?.to_map()?; + let size = self.pop_optional_sequence(); + let position = self.pop_optional_sequence(); + let item = self.pop_optional_sequence(); + let function = self.state.pop()?.one()?.to_function()?; + let value = self.call_template_with_params( + &function, + [item, position, size], + ¶ms, + &tunnel_params, + )?; + self.state.push(value); + } EncodedInstruction::ApplyTemplates => { + let tunnel_params = self.state.pop()?.one()?.to_map()?; + let params = self.state.pop()?.one()?.to_map()?; let value = self.state.pop()?; let mode_id = self.read_u16(); + let builtin_template_params_passthrough = self.read_u8() != 0; let mode = pattern::ModeId::new(mode_id as usize); - let value = self.apply_templates_sequence(mode, value)?; + let value = self.apply_templates_sequence( + mode, + value, + ¶ms, + &tunnel_params, + builtin_template_params_passthrough, + )?; + self.state.push(value); + } + EncodedInstruction::ApplyTemplatesCurrent => { + let tunnel_params = self.state.pop()?.one()?.to_map()?; + let params = self.state.pop()?.one()?.to_map()?; + let value = self.state.pop()?; + let fallback_mode_id = self.read_u16(); + let builtin_template_params_passthrough = self.read_u8() != 0; + let mode = self + .current_mode_or_fallback(pattern::ModeId::new(fallback_mode_id as usize)); + let value = self.apply_templates_sequence( + mode, + value, + ¶ms, + &tunnel_params, + builtin_template_params_passthrough, + )?; + self.state.push(value); + } + EncodedInstruction::ContinueTemplate => { + let tunnel_params = self.state.pop()?.one()?.to_map()?; + let params = self.state.pop()?.one()?.to_map()?; + let value = self.continue_template_with_params(¶ms, &tunnel_params)?; self.state.push(value); } + EncodedInstruction::RaiseError => { + let error = match RaisedError::from_u16(self.read_u16()) { + RaisedError::XTDE0700 => error::Error::XTDE0700, + RaisedError::XTTE0570 => error::Error::XTTE0570, + }; + return Err(error); + } EncodedInstruction::PrintTop => { let top = self.state.top()?; println!("{:#?}", top); @@ -700,6 +824,54 @@ impl<'a> Interpreter<'a> { .inline_function(self.state.frame().function()) } + fn resolve_global_variable(&mut self, index: usize) -> error::Result { + match self.global_variables[index].clone() { + GlobalValueState::Resolved(value) => Ok(value), + GlobalValueState::Resolving => Err(error::Error::XTDE0640), + GlobalValueState::Uninitialized => { + let global = self + .runnable + .program() + .declarations + .global_variable(index) + .clone(); + if let Some(original_name) = &global.original_name { + if let Some(value) = self + .runnable + .dynamic_context() + .variables() + .get(original_name) + { + let value = value.clone(); + self.global_variables[index] = GlobalValueState::Resolved(value.clone()); + return Ok(value); + } + if global.required { + return Err(error::Error::XTDE0050); + } + } + + self.global_variables[index] = GlobalValueState::Resolving; + let function: function::Function = + function::InlineFunctionData::new(global.function_id, Vec::new()).into(); + let context_arguments = + if let Some(context_item) = self.runnable.dynamic_context().context_item() { + [ + Some(context_item.clone().into()), + Some(ibig::ibig!(1).into()), + Some(ibig::ibig!(1).into()), + ] + } else { + [None, None, None] + }; + let value = + self.call_function_with_optional_arguments(&function, &context_arguments)?; + self.global_variables[index] = GlobalValueState::Resolved(value.clone()); + Ok(value) + } + } + } + pub(crate) fn function_name(&self, function: &function::Function) -> Option { self.runnable.function_info(function).name() } @@ -717,6 +889,15 @@ impl<'a> Interpreter<'a> { &mut self, function: &function::Function, arguments: &[sequence::Sequence], + ) -> error::Result { + let arguments = arguments.iter().cloned().map(Some).collect::>(); + self.call_function_with_optional_arguments(function, &arguments) + } + + pub(crate) fn call_function_with_optional_arguments( + &mut self, + function: &function::Function, + arguments: &[Option], ) -> error::Result { // put function onto the stack let item: sequence::Item = function.clone().into(); @@ -724,7 +905,11 @@ impl<'a> Interpreter<'a> { // then arguments let arity = arguments.len() as u8; for arg in arguments.iter() { - self.state.push(arg.clone()); + if let Some(arg) = arg { + self.state.push(arg.clone()); + } else { + self.state.push_value(stack::Value::Absent); + } } self.call_function(function, arity)?; if matches!(function, function::Function::Inline(_)) { @@ -782,17 +967,50 @@ impl<'a> Interpreter<'a> { return Err(error::Error::XPTY0004); } - let arguments = self.coerce_arguments(parameter_types, arity)?; + let arguments = self.coerce_inline_arguments(parameter_types, arity)?; // now we have a list of arguments that we want to push back onto the stack // (they are already reversed) for arg in arguments { - self.state.push(arg); + self.state.push_value(arg); } self.state.push_frame(function_id, arity as usize) } + fn coerce_inline_arguments( + &mut self, + parameter_types: &[Option], + arity: u8, + ) -> error::Result> { + let stack_values = self.state.arguments(arity as usize); + let mut arguments = Vec::with_capacity(arity as usize); + let static_context = self.runnable.static_context(); + let xot = self.state.xot(); + for (parameter_type, stack_value) in parameter_types.iter().zip(stack_values) { + if stack_value.is_absent() { + arguments.push(stack::Value::Absent); + continue; + } + let sequence: sequence::Sequence = stack_value.try_into()?; + let value = if let Some(type_) = parameter_type { + sequence + .sequence_type_matching_function_conversion( + type_, + static_context, + xot, + &|function| self.runnable.function_info(function).signature(), + )? + .into() + } else { + sequence.into() + }; + arguments.push(value); + } + self.state.truncate_arguments(arity as usize); + Ok(arguments) + } + fn coerce_arguments( &mut self, parameter_types: &[Option], @@ -1125,10 +1343,12 @@ impl<'a> Interpreter<'a> { } match self.state.xot.value(node) { xot::Value::Document => { - // TODO: Handle adding all the children instead - return Err(error::Error::Unsupported(String::from( - "Appending a whole document is not supported yet", - ))); + let children = self.state.xot.children(node).collect::>(); + for child in children { + let child = self.state.xot.clone_node(child); + self.state.xot.any_append(parent_node, child).unwrap(); + } + continue; } xot::Value::Text(text) => { // zero length text nodes are skipped @@ -1187,12 +1407,20 @@ impl<'a> Interpreter<'a> { &mut self, mode: pattern::ModeId, sequence: sequence::Sequence, + params: &function::Map, + tunnel_params: &function::Map, + builtin_template_params_passthrough: bool, ) -> error::Result { let mut r: Vec = Vec::new(); let size: IBig = sequence.len().into(); + let options = ApplyTemplatesOptions { + params, + tunnel_params, + builtin_template_params_passthrough, + }; for (i, item) in sequence.iter().enumerate() { - let sequence = self.apply_templates_item(mode, item, i, size.clone())?; + let sequence = self.apply_templates_item(mode, item, i, size.clone(), &options)?; if let Some(sequence) = sequence { for item in sequence.iter() { r.push(item.clone()); @@ -1208,24 +1436,349 @@ impl<'a> Interpreter<'a> { item: sequence::Item, position: usize, size: IBig, + options: &ApplyTemplatesOptions<'_>, ) -> error::Result> { + let mode_declaration = self.runnable.program().declarations.mode(mode); + if self.mode_requires_typed_nodes(mode_declaration) && self.item_is_untyped_node(&item) { + return Err(error::Error::XTTE3100); + } + let function_id = self.lookup_pattern(mode, &item); if let Some(function_id) = function_id { let position: IBig = (position + 1).into(); - let arguments: Vec = vec![ - item.into(), - atomic::Atomic::from(position).into(), - atomic::Atomic::from(size.clone()).into(), - ]; let function = function::InlineFunctionData::new(function_id, Vec::new()).into(); - self.call_function_with_arguments(&function, &arguments) - .map(Some) + self.mode_stack.push(mode); + let result = self.call_template_with_params( + &function, + [ + Some(item.into()), + Some(atomic::Atomic::from(position).into()), + Some(atomic::Atomic::from(size.clone()).into()), + ], + options.params, + options.tunnel_params, + ); + self.mode_stack.pop(); + result.map(Some) } else { - Ok(None) + self.apply_builtin_template_rule( + mode, + item, + options.params, + options.tunnel_params, + options.builtin_template_params_passthrough, + ) } } + fn apply_builtin_template_rule( + &mut self, + mode: pattern::ModeId, + item: sequence::Item, + params: &function::Map, + tunnel_params: &function::Map, + builtin_template_params_passthrough: bool, + ) -> error::Result> { + let mode_declaration = self.runnable.program().declarations.mode(mode); + match mode_declaration.on_no_match { + declaration::ModeOnNoMatch::ShallowCopy => self.apply_builtin_shallow_copy_rule( + mode, + item, + params, + tunnel_params, + builtin_template_params_passthrough, + ), + declaration::ModeOnNoMatch::ShallowSkip => self.apply_builtin_shallow_skip_rule( + mode, + item, + params, + tunnel_params, + builtin_template_params_passthrough, + ), + declaration::ModeOnNoMatch::DeepSkip => Ok(None), + declaration::ModeOnNoMatch::DeepCopy => self.apply_builtin_deep_copy_rule(item), + declaration::ModeOnNoMatch::Fail => Err(error::Error::Unsupported( + "xsl:mode on-no-match=\"fail\" is not supported yet".to_string(), + )), + declaration::ModeOnNoMatch::TextOnlyCopy => self.apply_builtin_text_only_copy_rule( + mode, + item, + params, + tunnel_params, + builtin_template_params_passthrough, + ), + } + } + + fn apply_builtin_text_only_copy_rule( + &mut self, + mode: pattern::ModeId, + item: sequence::Item, + params: &function::Map, + tunnel_params: &function::Map, + builtin_template_params_passthrough: bool, + ) -> error::Result> { + match item { + sequence::Item::Node(node) => match self.state.xot.value(node) { + xot::Value::Document | xot::Value::Element(_) => { + let children = self + .state + .xot + .children(node) + .map(sequence::Item::Node) + .collect::>(); + let empty_params = function::Map::new(Vec::new()).unwrap(); + let params = if builtin_template_params_passthrough { + params + } else { + &empty_params + }; + self.apply_templates_sequence( + mode, + children.into(), + params, + tunnel_params, + builtin_template_params_passthrough, + ) + .map(Some) + } + xot::Value::Text(text) => { + let text = text.get().to_string(); + let text_node = self.state.xot.new_text(&text); + Ok(Some(sequence::Item::Node(text_node).into())) + } + xot::Value::Attribute(attribute) => { + let value = attribute.value().to_string(); + let text_node = self.state.xot.new_text(&value); + Ok(Some(sequence::Item::Node(text_node).into())) + } + _ => Ok(None), + }, + sequence::Item::Atomic(_) | sequence::Item::Function(_) => Ok(None), + } + } + + fn apply_builtin_shallow_skip_rule( + &mut self, + mode: pattern::ModeId, + item: sequence::Item, + params: &function::Map, + tunnel_params: &function::Map, + builtin_template_params_passthrough: bool, + ) -> error::Result> { + match item { + sequence::Item::Node(node) + if matches!( + self.state.xot.value(node), + xot::Value::Document | xot::Value::Element(_) + ) => + { + let children = self + .state + .xot + .children(node) + .map(sequence::Item::Node) + .collect::>(); + let empty_params = function::Map::new(Vec::new()).unwrap(); + let params = if builtin_template_params_passthrough { + params + } else { + &empty_params + }; + self.apply_templates_sequence( + mode, + children.into(), + params, + tunnel_params, + builtin_template_params_passthrough, + ) + .map(Some) + } + _ => Ok(None), + } + } + + fn apply_builtin_shallow_copy_rule( + &mut self, + mode: pattern::ModeId, + item: sequence::Item, + params: &function::Map, + tunnel_params: &function::Map, + builtin_template_params_passthrough: bool, + ) -> error::Result> { + match item { + sequence::Item::Node(node) => match self.state.xot.value(node) { + xot::Value::Document => { + let copy = self.state.xot.new_document(); + let children = self + .state + .xot + .children(node) + .map(sequence::Item::Node) + .collect::>(); + let content = self.apply_templates_sequence( + mode, + children.into(), + params, + tunnel_params, + builtin_template_params_passthrough, + )?; + self.xml_append(copy, content)?; + Ok(Some(sequence::Item::Node(copy).into())) + } + xot::Value::Element(element) => { + let copy = self.state.xot.new_element(element.name()); + + let namespace_nodes = self + .state + .xot + .namespaces(node) + .keys() + .filter_map(|prefix| self.state.xot.namespaces(node).get_node(prefix)) + .collect::>(); + for namespace_node in namespace_nodes { + let namespace_copy = self.state.xot.clone_node(namespace_node); + self.state.xot.any_append(copy, namespace_copy).unwrap(); + } + + let mut content = self + .state + .xot + .attributes(node) + .keys() + .filter_map(|name| self.state.xot.attributes(node).get_node(name)) + .map(sequence::Item::Node) + .collect::>(); + content.extend(self.state.xot.children(node).map(sequence::Item::Node)); + + let content = self.apply_templates_sequence( + mode, + content.into(), + params, + tunnel_params, + builtin_template_params_passthrough, + )?; + self.xml_append(copy, content)?; + Ok(Some(sequence::Item::Node(copy).into())) + } + _ => Ok(Some( + sequence::Item::Node(self.state.xot.clone_node(node)).into(), + )), + }, + sequence::Item::Atomic(_) | sequence::Item::Function(_) => Ok(None), + } + } + + fn apply_builtin_deep_copy_rule( + &mut self, + item: sequence::Item, + ) -> error::Result> { + match item { + sequence::Item::Node(node) => Ok(Some( + sequence::Item::Node(self.state.xot.clone_node(node)).into(), + )), + sequence::Item::Atomic(_) | sequence::Item::Function(_) => Ok(None), + } + } + + fn mode_requires_typed_nodes(&self, mode_declaration: declaration::ModeDeclaration) -> bool { + matches!(mode_declaration.typed, declaration::ModeTyped::Yes) + } + + fn item_is_untyped_node(&self, item: &sequence::Item) -> bool { + match item { + sequence::Item::Node(node) => matches!( + self.state.xot.value(*node), + xot::Value::Document | xot::Value::Element(_) | xot::Value::Attribute(_) + ), + sequence::Item::Atomic(_) | sequence::Item::Function(_) => false, + } + } + + fn call_template_with_params( + &mut self, + function: &function::Function, + context_arguments: [Option; 3], + params: &function::Map, + tunnel_params: &function::Map, + ) -> error::Result { + let function_id = match function { + function::Function::Inline(data) => data.id, + _ => return Err(error::Error::XPTY0004), + }; + + let mut effective_tunnel_params = self.current_tunnel_params().clone(); + for (key, value) in tunnel_params.entries() { + effective_tunnel_params = effective_tunnel_params.put(key.clone(), value)?; + } + + let mut arguments = context_arguments.into_iter().collect::>(); + let parameter_types = self + .runnable + .function_info(function) + .signature() + .parameter_types() + .to_vec(); + if let Some(template_params) = self + .runnable + .program() + .declarations + .template_params(function_id) + { + for (index, param) in template_params.iter().enumerate() { + let key = atomic::Atomic::from(param.name.as_str()); + let value = if param.tunnel { + effective_tunnel_params.get(&key).cloned() + } else { + params.get(&key).cloned() + }; + let parameter_type = parameter_types.get(index + 3).cloned().unwrap_or(None); + let value = self.coerce_template_argument(value, parameter_type.as_ref())?; + arguments.push(value); + } + } + + self.tunnel_params.push(effective_tunnel_params); + let result = self.call_function_with_optional_arguments(function, &arguments); + self.tunnel_params.pop(); + result + } + + fn current_tunnel_params(&self) -> &function::Map { + self.tunnel_params.last().unwrap() + } + + fn pop_optional_sequence(&mut self) -> Option { + match self.state.pop_value() { + stack::Value::Absent => None, + stack::Value::Sequence(sequence) => Some(sequence), + } + } + + fn coerce_template_argument( + &mut self, + value: Option, + parameter_type: Option<&ast::SequenceType>, + ) -> error::Result> { + let Some(value) = value else { + return Ok(None); + }; + let Some(parameter_type) = parameter_type else { + return Ok(Some(value)); + }; + + value + .sequence_type_matching_function_conversion( + parameter_type, + self.runnable.static_context(), + self.state.xot(), + &|function| self.runnable.function_info(function).signature(), + ) + .map(Some) + .map_err(|_| error::Error::XTTE0590) + } + pub(crate) fn lookup_pattern( &mut self, mode: pattern::ModeId, @@ -1239,6 +1792,64 @@ impl<'a> Interpreter<'a> { .copied() } + fn lookup_pattern_after( + &mut self, + mode: pattern::ModeId, + current: function::InlineFunctionId, + item: &sequence::Item, + ) -> Option { + self.runnable + .program() + .declarations + .mode_lookup + .lookup_after(mode, ¤t, |pattern| self.matches(pattern, item)) + .copied() + } + + fn current_mode_or_fallback(&self, fallback: pattern::ModeId) -> pattern::ModeId { + self.mode_stack.last().copied().unwrap_or(fallback) + } + + fn continue_template_with_params( + &mut self, + params: &function::Map, + tunnel_params: &function::Map, + ) -> error::Result { + let mode = *self.mode_stack.last().ok_or_else(|| { + error::Error::Unsupported( + "No current apply-templates mode for template continuation".to_string(), + ) + })?; + let base = self.state.frame().base(); + let item_sequence: sequence::Sequence = (&self.state.stack()[base]).try_into()?; + let item = item_sequence.one()?; + let position_sequence: sequence::Sequence = (&self.state.stack()[base + 1]).try_into()?; + let position = position_sequence.one()?.try_into_value::()?; + let size_sequence: sequence::Sequence = (&self.state.stack()[base + 2]).try_into()?; + let size = size_sequence.one()?.try_into_value::()?; + let current_function = self.state.frame().function(); + + if let Some(function_id) = self.lookup_pattern_after(mode, current_function, &item) { + let function = function::InlineFunctionData::new(function_id, Vec::new()).into(); + self.mode_stack.push(mode); + let result = self.call_template_with_params( + &function, + [ + Some(item.clone().into()), + Some(atomic::Atomic::from(position).into()), + Some(atomic::Atomic::from(size).into()), + ], + params, + tunnel_params, + ); + self.mode_stack.pop(); + result + } else { + self.apply_builtin_template_rule(mode, item, params, tunnel_params, true) + .map(|value| value.unwrap_or_default()) + } + } + // The interpreter can return an error for any byte code, in any level of // nesting in the function. When this happens the interpreter stops with // the error code. We here wrap it in a SpannedError using the current diff --git a/xee-interpreter/src/interpreter/runnable.rs b/xee-interpreter/src/interpreter/runnable.rs index e5c8bfc42..69dc2f292 100644 --- a/xee-interpreter/src/interpreter/runnable.rs +++ b/xee-interpreter/src/interpreter/runnable.rs @@ -8,7 +8,7 @@ use crate::context::DocumentsRef; use crate::context::DynamicContext; use crate::context::StaticContext; use crate::error::SpannedError; -use crate::function::Function; +use crate::function::{Function, InlineFunctionData}; use crate::interpreter::interpret::ContextInfo; use crate::sequence; use crate::stack; @@ -83,6 +83,31 @@ impl<'a> Runnable<'a> { Ok(self.run_value(xot)?.try_into()?) } + pub fn named_template( + &self, + name: &str, + xot: &'a mut Xot, + ) -> error::SpannedResult { + let named_template = self + .program + .declarations + .named_template_by_name(name) + .ok_or(SpannedError { + error: error::Error::Unsupported(format!("Initial template not found: {name}")), + span: Some(self.program.span().into()), + })?; + let function: Function = + InlineFunctionData::new(named_template.function_id, Vec::new()).into(); + let mut interpreter = Interpreter::new(self, xot); + let arguments = vec![None; self.program.function_info(&function).arity()]; + interpreter + .call_function_with_optional_arguments(&function, &arguments) + .map_err(|error| SpannedError { + error, + span: Some(self.program.span().into()), + }) + } + /// Run the program, expect a single item as the result. pub fn one(&self, xot: &'a mut Xot) -> error::SpannedResult { let sequence = self.many(xot)?; diff --git a/xee-interpreter/src/interpreter/state.rs b/xee-interpreter/src/interpreter/state.rs index 22f250532..643781a4b 100644 --- a/xee-interpreter/src/interpreter/state.rs +++ b/xee-interpreter/src/interpreter/state.rs @@ -133,6 +133,10 @@ impl<'a> State<'a> { .push(self.stack[self.frame().base + index].clone()); } + pub(crate) fn var_is_absent(&self, index: usize) -> bool { + self.stack[self.frame().base + index].is_absent() + } + pub(crate) fn push_closure_var(&mut self, index: usize) -> error::Result<()> { let function = self.function()?; let closure_vars = function.closure_vars(); diff --git a/xee-interpreter/src/library/external.rs b/xee-interpreter/src/library/external.rs index f042841c8..a6a4c31ea 100644 --- a/xee-interpreter/src/library/external.rs +++ b/xee-interpreter/src/library/external.rs @@ -1,44 +1,84 @@ +use std::fs; + use iri_string::types::{IriReferenceStr, IriString}; use xee_xpath_macros::xpath_fn; use crate::{ - context::DynamicContext, error, function::StaticFunctionDescription, sequence::Sequence, - wrap_xpath_fn, + context::DynamicContext, error, function::StaticFunctionDescription, interpreter::Interpreter, + sequence::Sequence, wrap_xpath_fn, }; #[xpath_fn("fn:doc($uri as xs:string?) as document-node()?")] -fn doc(context: &DynamicContext, uri: Option<&str>) -> error::Result> { +fn doc( + context: &DynamicContext, + interpreter: &mut Interpreter, + uri: Option<&str>, +) -> error::Result> { if let Some(uri) = uri { - document_node(context, uri) + document_node(context, interpreter, uri) } else { Ok(None) } } +#[xpath_fn("fn:document($uri as xs:string?) as document-node()?")] +fn document( + context: &DynamicContext, + interpreter: &mut Interpreter, + uri: Option<&str>, +) -> error::Result> { + doc(context, interpreter, uri) +} + #[xpath_fn("fn:doc-available($uri as xs:string?) as xs:boolean")] -fn doc_available(context: &DynamicContext, uri: Option<&str>) -> bool { +fn doc_available( + context: &DynamicContext, + interpreter: &mut Interpreter, + uri: Option<&str>, +) -> bool { if let Some(uri) = uri { - document_node(context, uri).is_ok() + document_node(context, interpreter, uri).is_ok() } else { false } } -fn document_node(context: &DynamicContext, uri: &str) -> error::Result> { +fn document_node( + context: &DynamicContext, + interpreter: &mut Interpreter, + uri: &str, +) -> error::Result> { let iri_reference: &IriReferenceStr = uri.try_into().map_err(|_| error::Error::FODC0005)?; let uri = absolute_uri(context, iri_reference)?; // first check whether a document is there at all, if so, return it let documents = context.documents(); - let documents = documents.borrow(); - let document = documents.get_by_uri(&uri); - - if let Some(document) = document { - Ok(Some(document.root())) - } else { - // The document doesn't exist, so return an error - Err(error::Error::FODC0002) + if let Some(document) = documents.borrow().get_by_uri(&uri) { + return Ok(Some(document.root())); } + + load_document(context, interpreter, &uri) +} + +fn load_document( + context: &DynamicContext, + interpreter: &mut Interpreter, + uri: &IriString, +) -> error::Result> { + let url = url::Url::parse(uri.as_str()).map_err(|_| error::Error::FODC0005)?; + let path = url.to_file_path().map_err(|_| error::Error::FODC0002)?; + let xml = fs::read_to_string(&path).map_err(|_| error::Error::FODC0002)?; + + let documents = context.documents(); + let handle = documents + .borrow_mut() + .add_string(interpreter.xot_mut(), Some(uri.as_ref()), &xml) + .map_err(|_| error::Error::FODC0002)?; + let document = documents + .borrow() + .get_node_by_handle(handle) + .ok_or(error::Error::FODC0002)?; + Ok(Some(document)) } #[xpath_fn("fn:collection() as item()*")] @@ -125,6 +165,7 @@ fn available_environment_variables(context: &DynamicContext) -> Vec { pub(crate) fn static_function_descriptions() -> Vec { vec![ wrap_xpath_fn!(doc), + wrap_xpath_fn!(document), wrap_xpath_fn!(doc_available), wrap_xpath_fn!(collection), wrap_xpath_fn!(collection_by_uri), diff --git a/xee-interpreter/src/library/hidden_xslt.rs b/xee-interpreter/src/library/hidden_xslt.rs index ea7782e2d..00e7c13fd 100644 --- a/xee-interpreter/src/library/hidden_xslt.rs +++ b/xee-interpreter/src/library/hidden_xslt.rs @@ -1,8 +1,11 @@ // functions used to implement the XSLT that aren't supposed to be // exposed to XPath +use std::collections::HashSet; + use xee_xpath_macros::xpath_fn; use xot::Xot; +use crate::atomic; use crate::error; use crate::function::StaticFunctionDescription; use crate::interpreter::Interpreter; @@ -34,6 +37,31 @@ fn simple_content( Ok(s) } +#[xpath_fn( + "fn:group-by-first($seq as item()*, $key as function(item()) as xs:anyAtomicType*) as item()*" +)] +fn group_by_first( + interpreter: &mut Interpreter, + seq: &sequence::Sequence, + key: sequence::Item, +) -> error::Result { + let function = key.to_function()?; + let mut seen: HashSet = HashSet::new(); + let mut result = Vec::new(); + + for item in seq.iter() { + let value = interpreter.call_function_with_arguments(&function, &[item.clone().into()])?; + let keys = value + .atomized(interpreter.xot()) + .collect::>>()?; + if keys.into_iter().any(|atomic| seen.insert(atomic)) { + result.push(item.clone()); + } + } + + Ok(result.into()) +} + fn simple_content_text_nodes( arg: &sequence::Sequence, xot: &Xot, @@ -77,7 +105,10 @@ fn simple_content_text_nodes( } pub(crate) fn static_function_descriptions() -> Vec { - vec![wrap_xpath_fn!(simple_content)] + vec![ + wrap_xpath_fn!(simple_content), + wrap_xpath_fn!(group_by_first), + ] } #[cfg(test)] diff --git a/xee-interpreter/src/pattern/mode.rs b/xee-interpreter/src/pattern/mode.rs index e1c26c1df..62a50f412 100644 --- a/xee-interpreter/src/pattern/mode.rs +++ b/xee-interpreter/src/pattern/mode.rs @@ -40,6 +40,19 @@ impl ModeLookup { pattern_lookup.lookup(&mut matches) } + pub(crate) fn lookup_after( + &self, + mode: ModeId, + current: &V, + mut matches: impl FnMut(&Pattern) -> bool, + ) -> Option<&V> + where + V: PartialEq, + { + let pattern_lookup = self.modes.get(&mode)?; + pattern_lookup.lookup_after(current, &mut matches) + } + pub fn add_rules( &mut self, mode: ModeId, diff --git a/xee-interpreter/src/pattern/pattern_core.rs b/xee-interpreter/src/pattern/pattern_core.rs index 6fd955143..e8e9b8b5b 100644 --- a/xee-interpreter/src/pattern/pattern_core.rs +++ b/xee-interpreter/src/pattern/pattern_core.rs @@ -13,7 +13,18 @@ pub(crate) enum NodeMatch { } pub(crate) trait PredicateMatcher { - fn match_predicate(&mut self, inline_function_id: InlineFunctionId, item: &Item) -> bool; + fn match_predicate_with_context( + &mut self, + inline_function_id: InlineFunctionId, + item: &Item, + position: usize, + size: usize, + ) -> bool; + + fn match_predicate(&mut self, inline_function_id: InlineFunctionId, item: &Item) -> bool { + self.match_predicate_with_context(inline_function_id, item, 1, 1) + } + fn xot(&self) -> &Xot; fn matches(&mut self, pattern: &pattern::Pattern, item: &Item) -> bool { @@ -204,28 +215,80 @@ pub(crate) trait PredicateMatcher { step: &pattern::AxisStep, node: xot::Node, ) -> (bool, pattern::ForwardAxis) { - // if the forward axis is attribute based, we won't match with an element, - // and vice versa - if self.xot().is_attribute_node(node) { - if step.forward != pattern::ForwardAxis::Attribute { - return (false, step.forward); - } - } else if step.forward == pattern::ForwardAxis::Attribute { - return (false, step.forward); - } - if !Self::matches_node_test(&step.node_test, node, self.xot()) { + if !self.matches_axis_node_test(step, node) { return (false, step.forward); } // if we have a match, check whether the predicates apply let item = Item::Node(node); + let (position, size) = self.axis_predicate_context(step, node); for predicate in &step.predicates { - if !self.match_predicate(*predicate, &item) { + if !self.match_predicate_with_context(*predicate, &item, position, size) { return (false, step.forward); } } (true, step.forward) } + fn matches_axis_node_test( + &self, + step: &pattern::AxisStep, + node: xot::Node, + ) -> bool { + // Name tests use the principal node kind of the axis: attributes for + // attribute::, otherwise elements. + if matches!(step.node_test, pattern::NodeTest::NameTest(_)) { + if step.forward == pattern::ForwardAxis::Attribute { + if !self.xot().is_attribute_node(node) { + return false; + } + } else if !self.xot().is_element(node) { + return false; + } + } else if self.xot().is_attribute_node(node) { + if step.forward != pattern::ForwardAxis::Attribute { + return false; + } + } else if step.forward == pattern::ForwardAxis::Attribute { + return false; + } + Self::matches_node_test(&step.node_test, node, self.xot()) + } + + fn axis_predicate_context( + &self, + step: &pattern::AxisStep, + node: xot::Node, + ) -> (usize, usize) { + let Some(parent) = self.xot().parent(node) else { + return (1, 1); + }; + + let matching_nodes = match step.forward { + pattern::ForwardAxis::Child => self + .xot() + .children(parent) + .filter(|candidate| self.matches_axis_node_test(step, *candidate)) + .collect::>(), + pattern::ForwardAxis::Attribute => self + .xot() + .attributes(parent) + .keys() + .filter_map(|name| self.xot().attributes(parent).get_node(name)) + .filter(|candidate| self.matches_axis_node_test(step, *candidate)) + .collect::>(), + _ => return (1, 1), + }; + + let position = matching_nodes + .iter() + .position(|candidate| *candidate == node) + .map(|index| index + 1) + .unwrap_or(1); + let size = matching_nodes.len().max(1); + + (position, size) + } + fn matches_postfix_expr( &mut self, postfix_expr: &pattern::PostfixExpr, @@ -280,7 +343,10 @@ pub(crate) trait PredicateMatcher { } fn matches_kind_test(kind_test: &KindTest, node: xot::Node, xot: &Xot) -> bool { - xml::kind_test(kind_test, xot, node) + match kind_test { + KindTest::Any => !xot.is_document(node), + _ => xml::kind_test(kind_test, xot, node), + } } } @@ -333,7 +399,13 @@ mod tests { } impl PredicateMatcher for BasicPredicateMatcher<'_> { - fn match_predicate(&mut self, _inline_function_id: InlineFunctionId, _item: &Item) -> bool { + fn match_predicate_with_context( + &mut self, + _inline_function_id: InlineFunctionId, + _item: &Item, + _position: usize, + _size: usize, + ) -> bool { self.predicate_matches } @@ -599,6 +671,7 @@ mod tests { fn test_matches_kind_test_any() { let mut xot = Xot::new(); let root = xot.parse(r#""#).unwrap(); + let document_item: Item = root.into(); let document_element = xot.document_element(root).unwrap(); let node = xot.first_child(document_element).unwrap(); let element_node = node; @@ -612,6 +685,7 @@ mod tests { let mut pm = BasicPredicateMatcher::new(&xot); let pattern = parse_pattern("node()"); + assert!(!pm.matches(&pattern, &document_item)); assert!(pm.matches(&pattern, &element_item)); assert!(!pm.matches(&pattern, &attribute_item)); let pattern = parse_pattern("attribute::node()"); @@ -671,6 +745,59 @@ mod tests { assert!(pm.matches(&pattern, &document_element_item)); } + #[test] + fn test_matches_name_test_star_not_document() { + let mut xot = Xot::new(); + let root = xot.parse(r#""#).unwrap(); + let item: Item = root.into(); + + let mut pm = BasicPredicateMatcher::new(&xot); + let pattern = parse_pattern("*"); + assert!(!pm.matches(&pattern, &item)); + } + + #[test] + fn test_axis_predicate_context_ignores_non_matching_children() { + let mut xot = Xot::new(); + let root = xot + .parse( + r#" + MyServlet + /servlet/MyServlet/* +"#, + ) + .unwrap(); + let document_element = xot.document_element(root).unwrap(); + let url_pattern = xot + .children(document_element) + .find(|node| { + xot.is_element(*node) + && xot + .node_name(*node) + .map(|name| xot.local_name_str(name) == "url-pattern") + .unwrap_or(false) + }) + .unwrap(); + let pattern = parse_pattern("url-pattern[position()=last()]"); + + let mut pm = BasicPredicateMatcher::matching(&xot); + assert!(pm.matches(&pattern, &Item::from(url_pattern))); + assert_eq!( + pm.axis_predicate_context( + match &pattern { + pattern::Pattern::Expr(pattern::ExprPattern::Path(path_expr)) => + match path_expr.steps.first().unwrap() { + pattern::StepExpr::AxisStep(axis_step) => axis_step, + _ => panic!("expected axis step"), + }, + _ => panic!("expected path pattern"), + }, + url_pattern, + ), + (1, 1) + ); + } + #[test] fn test_matches_absolute_double_slash() { let mut xot = Xot::new(); diff --git a/xee-interpreter/src/pattern/pattern_lookup.rs b/xee-interpreter/src/pattern/pattern_lookup.rs index 7e08de6b5..d8c50c4f5 100644 --- a/xee-interpreter/src/pattern/pattern_lookup.rs +++ b/xee-interpreter/src/pattern/pattern_lookup.rs @@ -22,30 +22,19 @@ impl<'a> InterpreterPredicateMatcher<'a> { } impl PredicateMatcher for Interpreter<'_> { - fn match_predicate( + fn match_predicate_with_context( &mut self, inline_function_id: function::InlineFunctionId, item: &Item, + position: usize, + size: usize, ) -> bool { - // TODO: extract 'call_function_id_with_arguments' that is used also by - // apply_templates_sequence. call it with context, position and length, - // again see apply_templates_sequence let function = function::InlineFunctionData::new(inline_function_id, Vec::new()).into(); - let arguments = if let Item::Node(node) = item { - if let Some(parent) = self.xot().parent(*node) { - let position = self.xot().child_index(parent, *node).unwrap() + 1; - let size = self.xot().children(parent).count(); - [ - item.clone().into(), - (position as u64).into(), - (size as u64).into(), - ] - } else { - [item.clone().into(), 1.into(), 1.into()] - } - } else { - [item.clone().into(), 1.into(), 1.into()] - }; + let arguments = [ + item.clone().into(), + (position as u64).into(), + (size as u64).into(), + ]; // the specification says to swallow any errors // TODO: log errors somehow here? @@ -82,4 +71,28 @@ impl PatternLookup { .find(|(pattern, _)| matches(pattern)) .map(|(_, value)| value) } + + pub(crate) fn lookup_after( + &self, + current: &V, + mut matches: impl FnMut(&Pattern) -> bool, + ) -> Option<&V> + where + V: PartialEq, + { + let mut seen_current = false; + for (pattern, value) in &self.patterns { + if !matches(pattern) { + continue; + } + if !seen_current { + if value == current { + seen_current = true; + } + continue; + } + return Some(value); + } + None + } } diff --git a/xee-interpreter/src/sequence/compare.rs b/xee-interpreter/src/sequence/compare.rs index 112f63932..b5d52c314 100644 --- a/xee-interpreter/src/sequence/compare.rs +++ b/xee-interpreter/src/sequence/compare.rs @@ -75,6 +75,15 @@ impl Sequence { let a_atom = a_atom?; if let Some(b_atom) = b_atom { let b_atom = b_atom?; + if a_atom.is_nan() && b_atom.is_nan() { + continue; + } + if a_atom.is_nan() { + return Ok(Ordering::Less); + } + if b_atom.is_nan() { + return Ok(Ordering::Greater); + } let ordering = a_atom.fallible_compare(&b_atom, collation, implicit_offset)?; if !ordering.is_eq() { return Ok(ordering); diff --git a/xee-interpreter/src/xml/kind_test.rs b/xee-interpreter/src/xml/kind_test.rs index 78061ac7b..5af93683b 100644 --- a/xee-interpreter/src/xml/kind_test.rs +++ b/xee-interpreter/src/xml/kind_test.rs @@ -1,4 +1,5 @@ use xee_schema_type::Xs; +use xot::xmlname::NameStrInfo; use xot::Xot; use xee_xpath_ast::ast; @@ -7,13 +8,9 @@ pub(crate) fn kind_test(kind_test: &ast::KindTest, xot: &Xot, node: xot::Node) - match kind_test { ast::KindTest::Document(dt) => document_test(dt.as_ref(), xot, node), ast::KindTest::Element(et) => element_test(et.as_ref(), xot, node), - ast::KindTest::SchemaElement(_set) => { - todo!() - } + ast::KindTest::SchemaElement(set) => schema_element_test(set, xot, node), ast::KindTest::Attribute(at) => attribute_test(at.as_ref(), xot, node), - ast::KindTest::SchemaAttribute(_sat) => { - todo!() - } + ast::KindTest::SchemaAttribute(sat) => schema_attribute_test(sat, xot, node), ast::KindTest::Any => true, // text() matches any text node ast::KindTest::Text => xot.is_text(node), @@ -24,17 +21,29 @@ pub(crate) fn kind_test(kind_test: &ast::KindTest, xot: &Xot, node: xot::Node) - if !xot.is_processing_instruction(node) { return false; } - if let Some(_pi_test) = pi_test { + if let Some(pi_test) = pi_test { // processing-instruction N matches any processing-instruction node // whose PITarget is equal to fn:normalize-space(N) - // TODO - return false; + return processing_instruction_name_test(pi_test, xot, node); } true } } } +fn processing_instruction_name_test(pi_test: &ast::PITest, xot: &Xot, node: xot::Node) -> bool { + let processing_instruction = xot.processing_instruction(node).unwrap(); + let (target, _) = xot.name_ns_str(processing_instruction.target()); + let expected = match pi_test { + ast::PITest::Name(name) | ast::PITest::StringLiteral(name) => normalize_space(name), + }; + target == expected +} + +fn normalize_space(value: &str) -> String { + value.split_whitespace().collect::>().join(" ") +} + fn element_test(test: Option<&ast::ElementOrAttributeTest>, xot: &Xot, node: xot::Node) -> bool { element_or_attribute_test(test, xot, node, |node, xot| xot.is_element(node)) } @@ -43,6 +52,16 @@ fn attribute_test(test: Option<&ast::ElementOrAttributeTest>, xot: &Xot, node: x element_or_attribute_test(test, xot, node, |node, xot| xot.is_attribute_node(node)) } +fn schema_element_test(test: &ast::SchemaElementTest, xot: &Xot, node: xot::Node) -> bool { + named_node_test(&test.name, xot, node, |node, xot| xot.is_element(node)) +} + +fn schema_attribute_test(test: &ast::SchemaAttributeTest, xot: &Xot, node: xot::Node) -> bool { + named_node_test(&test.name, xot, node, |node, xot| { + xot.is_attribute_node(node) + }) +} + fn document_test(test: Option<&ast::DocumentTest>, xot: &Xot, node: xot::Node) -> bool { if !xot.is_document(node) { return false; @@ -56,8 +75,8 @@ fn document_test(test: Option<&ast::DocumentTest>, xot: &Xot, node: xot::Node) - match document_test { ast::DocumentTest::Element(et) => element_test(et.as_ref(), xot, document_element_node), - ast::DocumentTest::SchemaElement(_set) => { - todo!() + ast::DocumentTest::SchemaElement(set) => { + schema_element_test(set, xot, document_element_node) } } } else { @@ -65,6 +84,22 @@ fn document_test(test: Option<&ast::DocumentTest>, xot: &Xot, node: xot::Node) - } } +fn named_node_test( + expected_name: &ast::Name, + xot: &Xot, + node: xot::Node, + node_type_match: impl Fn(xot::Node, &Xot) -> bool, +) -> bool { + if !node_type_match(node, xot) { + return false; + } + + xot.node_name(node).is_some_and(|node_name| { + expected_name.local_name() == xot.local_name_str(node_name) + && expected_name.namespace() == xot.uri_str(node_name) + }) +} + fn element_or_attribute_test( test: Option<&ast::ElementOrAttributeTest>, xot: &Xot, @@ -81,9 +116,9 @@ fn element_or_attribute_test( // the name has to match first let name_matches = match &test.name_or_wildcard { ast::NameOrWildcard::Name(name) => { - // TODO: what if we can't find a prefix here? - if let Some(node_name) = xot.node_name_ref(node).unwrap() { - name.maybe_to_ref(xot) == Some(node_name) + if let Some(node_name) = xot.node_name(node) { + name.local_name() == xot.local_name_str(node_name) + && name.namespace() == xot.uri_str(node_name) } else { false } @@ -107,8 +142,11 @@ fn element_or_attribute_test( } fn type_annotation(_xot: &Xot, _node: xot::Node) -> Xs { - // for now we don't know any types of nodes yet - Xs::UntypedAtomic + if _xot.is_attribute_node(_node) { + Xs::UntypedAtomic + } else { + Xs::Untyped + } } #[cfg(test)] @@ -158,6 +196,33 @@ mod tests { assert!(kind_test(&kt, &xot, comment)); } + #[test] + fn test_kind_test_processing_instruction_with_name() { + let mut xot = Xot::new(); + let doc = xot.parse(r#""#).unwrap(); + let doc_el = xot.document_element(doc).unwrap(); + let pi = xot.first_child(doc_el).unwrap(); + + let kt = parse_kind_test("processing-instruction(Process)").unwrap(); + assert!(!kind_test(&kt, &xot, doc)); + assert!(!kind_test(&kt, &xot, doc_el)); + assert!(kind_test(&kt, &xot, pi)); + + let kt = parse_kind_test("processing-instruction(Other)").unwrap(); + assert!(!kind_test(&kt, &xot, pi)); + } + + #[test] + fn test_kind_test_processing_instruction_with_string_literal_name() { + let mut xot = Xot::new(); + let doc = xot.parse(r#""#).unwrap(); + let doc_el = xot.document_element(doc).unwrap(); + let pi = xot.first_child(doc_el).unwrap(); + + let kt = parse_kind_test("processing-instruction(' Process ')").unwrap(); + assert!(kind_test(&kt, &xot, pi)); + } + #[test] fn test_kind_test_document() { let mut xot = Xot::new(); @@ -221,12 +286,15 @@ mod tests { let a = xot.first_child(doc_el).unwrap(); let text = xot.first_child(a).unwrap(); - let kt = parse_kind_test("element(a, xs:untypedAtomic)").unwrap(); + let kt = parse_kind_test("element(a, xs:untyped)").unwrap(); assert!(!kind_test(&kt, &xot, doc)); assert!(!kind_test(&kt, &xot, doc_el)); assert!(kind_test(&kt, &xot, a)); assert!(!kind_test(&kt, &xot, text)); + let kt = parse_kind_test("element(a, xs:untypedAtomic)").unwrap(); + assert!(!kind_test(&kt, &xot, a)); + // but we're not an xs:string let kt = parse_kind_test("element(a, xs:string)").unwrap(); assert!(!kind_test(&kt, &xot, a)); @@ -324,4 +392,54 @@ mod tests { // the 'a' node doesn't match either as it's not a document node assert!(!kind_test(&kt, &xot, a)); } + + #[test] + fn test_kind_test_document_with_element_type_name() { + let mut xot = Xot::new(); + let doc = xot.parse(r#"text"#).unwrap(); + + let kt = parse_kind_test("document-node(element(root, xs:untyped))").unwrap(); + assert!(kind_test(&kt, &xot, doc)); + + let kt = parse_kind_test("document-node(element(root, xs:untypedAtomic))").unwrap(); + assert!(!kind_test(&kt, &xot, doc)); + } + + #[test] + fn test_kind_test_schema_element_matches_by_name() { + let mut xot = Xot::new(); + let doc = xot.parse(r#""#).unwrap(); + let root = xot.document_element(doc).unwrap(); + let a = xot.first_child(root).unwrap(); + + let kt = parse_kind_test("schema-element(a)").unwrap(); + assert!(!kind_test(&kt, &xot, doc)); + assert!(!kind_test(&kt, &xot, root)); + assert!(kind_test(&kt, &xot, a)); + } + + #[test] + fn test_kind_test_schema_attribute_matches_by_name() { + let mut xot = Xot::new(); + let alpha_name = xot.add_name("alpha"); + let doc = xot.parse(r#""#).unwrap(); + let root = xot.document_element(doc).unwrap(); + let a = xot.first_child(root).unwrap(); + let alpha = xot.attributes(a).get_node(alpha_name).unwrap(); + + let kt = parse_kind_test("schema-attribute(alpha)").unwrap(); + assert!(!kind_test(&kt, &xot, a)); + assert!(kind_test(&kt, &xot, alpha)); + } + + #[test] + fn test_kind_test_document_schema_element_matches_document_element_name() { + let mut xot = Xot::new(); + let doc = xot.parse(r#""#).unwrap(); + let root = xot.document_element(doc).unwrap(); + + let kt = parse_kind_test("document-node(schema-element(root))").unwrap(); + assert!(kind_test(&kt, &xot, doc)); + assert!(!kind_test(&kt, &xot, root)); + } } diff --git a/xee-ir/src/compile.rs b/xee-ir/src/compile.rs index 4a0c5df28..e2e7c1e89 100644 --- a/xee-ir/src/compile.rs +++ b/xee-ir/src/compile.rs @@ -2,7 +2,7 @@ use ahash::HashMapExt; use xee_interpreter::{context::StaticContext, error::SpannedResult, interpreter::Program}; use crate::{ - declaration_compiler::{DeclarationCompiler, ModeIds}, + declaration_compiler::{DeclarationCompiler, ModeIds, TemplateIds, TemplateParams}, ir, FunctionBuilder, FunctionCompiler, Scopes, }; @@ -11,7 +11,17 @@ pub fn compile_xpath(expr: ir::ExprS, static_context: StaticContext) -> SpannedR let mut scopes = Scopes::new(); let builder = FunctionBuilder::new(&mut program); let empty_mode_ids = ModeIds::new(); - let mut compiler = FunctionCompiler::new(builder, &mut scopes, &empty_mode_ids); + let empty_template_ids = TemplateIds::new(); + let empty_template_params = TemplateParams::new(); + let empty_global_variable_ids = ahash::HashMap::new(); + let mut compiler = FunctionCompiler::new( + builder, + &mut scopes, + &empty_mode_ids, + &empty_template_ids, + &empty_template_params, + &empty_global_variable_ids, + ); compiler.compile_expr(&expr)?; Ok(program) } diff --git a/xee-ir/src/declaration_compiler.rs b/xee-ir/src/declaration_compiler.rs index ca0147c33..0819336fa 100644 --- a/xee-ir/src/declaration_compiler.rs +++ b/xee-ir/src/declaration_compiler.rs @@ -29,6 +29,8 @@ impl RuleBuilder { } pub type ModeIds = HashMap; +pub type TemplateIds = HashMap; +pub type TemplateParams = HashMap>; pub struct DeclarationCompiler<'a> { program: &'a mut interpreter::Program, @@ -36,6 +38,9 @@ pub struct DeclarationCompiler<'a> { rule_declaration_order: i64, rule_builders: HashMap>, mode_ids: ModeIds, + template_ids: TemplateIds, + template_params: TemplateParams, + global_variable_ids: HashMap, } impl<'a> DeclarationCompiler<'a> { @@ -46,12 +51,22 @@ impl<'a> DeclarationCompiler<'a> { rule_declaration_order: 0, rule_builders: HashMap::new(), mode_ids: HashMap::new(), + template_ids: HashMap::new(), + template_params: HashMap::new(), + global_variable_ids: HashMap::new(), } } fn function_compiler(&mut self) -> FunctionCompiler<'_> { let function_builder = FunctionBuilder::new(self.program); - FunctionCompiler::new(function_builder, &mut self.scopes, &self.mode_ids) + FunctionCompiler::new( + function_builder, + &mut self.scopes, + &self.mode_ids, + &self.template_ids, + &self.template_params, + &self.global_variable_ids, + ) } pub fn compile_declarations( @@ -61,6 +76,13 @@ impl<'a> DeclarationCompiler<'a> { // first keep track of what modes exist, to create a ModeId for them. We do // this early so any mode reference within apply-templates will resolve. self.compile_modes(declarations); + self.register_global_variables(declarations)?; + self.register_templates(declarations)?; + + // compile all named templates (function bindings) early so they can be referenced + // by name from call-template instructions + self.compile_templates(declarations)?; + self.compile_global_variables(declarations)?; for rule in &declarations.rules { self.compile_rule(rule)?; @@ -71,26 +93,201 @@ impl<'a> DeclarationCompiler<'a> { function_compiler.compile_function_definition(&declarations.main, (0..0).into()) } + fn register_global_variables( + &mut self, + declarations: &ir::Declarations, + ) -> error::SpannedResult<()> { + for (index, global_variable) in declarations.global_variables.iter().enumerate() { + if index > u16::MAX as usize { + return Err( + error::Error::Unsupported("too many global variables".to_string()).into(), + ); + } + self.global_variable_ids + .insert(global_variable.name.clone(), index as u16); + } + Ok(()) + } + fn compile_modes(&mut self, declarations: &ir::Declarations) { + self.register_mode(ir::ApplyTemplatesModeValue::Unnamed); + + for mode_name in declarations.modes.keys() { + let mode = match mode_name { + Some(name) => ir::ApplyTemplatesModeValue::Named(name.clone()), + None => ir::ApplyTemplatesModeValue::Unnamed, + }; + self.register_mode(mode); + } + for rule in &declarations.rules { for mode_value in &rule.modes { // we don't register All modes if matches!(mode_value, ir::ModeValue::All) { continue; } - let apply_templates_mode_value = match mode_value { + self.register_mode(match mode_value { ir::ModeValue::All => continue, ir::ModeValue::Named(name) => ir::ApplyTemplatesModeValue::Named(name.clone()), ir::ModeValue::Unnamed => ir::ApplyTemplatesModeValue::Unnamed, - }; - // we want the mode id to be unique and not overwritten - if self.mode_ids.contains_key(&apply_templates_mode_value) { - continue; + }); + } + } + + for (mode, mode_id) in &self.mode_ids { + let declaration = match mode { + ir::ApplyTemplatesModeValue::Named(name) => declarations + .modes + .get(&Some(name.clone())) + .map(Self::mode_declaration) + .unwrap_or_default(), + ir::ApplyTemplatesModeValue::Unnamed => declarations + .modes + .get(&None) + .map(Self::mode_declaration) + .unwrap_or_default(), + ir::ApplyTemplatesModeValue::Current => continue, + }; + self.program.declarations.add_mode(*mode_id, declaration); + } + } + + fn mode_declaration(mode: &ir::Mode) -> xee_interpreter::declaration::ModeDeclaration { + xee_interpreter::declaration::ModeDeclaration { + on_no_match: match mode.on_no_match { + ir::ModeOnNoMatch::DeepCopy => { + xee_interpreter::declaration::ModeOnNoMatch::DeepCopy + } + ir::ModeOnNoMatch::ShallowCopy => { + xee_interpreter::declaration::ModeOnNoMatch::ShallowCopy + } + ir::ModeOnNoMatch::DeepSkip => { + xee_interpreter::declaration::ModeOnNoMatch::DeepSkip + } + ir::ModeOnNoMatch::ShallowSkip => { + xee_interpreter::declaration::ModeOnNoMatch::ShallowSkip + } + ir::ModeOnNoMatch::TextOnlyCopy => { + xee_interpreter::declaration::ModeOnNoMatch::TextOnlyCopy } - let mode_id = ModeId::new(self.mode_ids.len()); - self.mode_ids.insert(apply_templates_mode_value, mode_id); + ir::ModeOnNoMatch::Fail => xee_interpreter::declaration::ModeOnNoMatch::Fail, + }, + warning_on_no_match: mode.warning_on_no_match, + typed: match mode.typed { + ir::ModeTyped::Yes => xee_interpreter::declaration::ModeTyped::Yes, + ir::ModeTyped::No => xee_interpreter::declaration::ModeTyped::No, + ir::ModeTyped::Strict => xee_interpreter::declaration::ModeTyped::Strict, + ir::ModeTyped::Lax => xee_interpreter::declaration::ModeTyped::Lax, + }, + } + } + + fn register_mode(&mut self, mode: ir::ApplyTemplatesModeValue) { + if self.mode_ids.contains_key(&mode) { + return; + } + let mode_id = ModeId::new(self.mode_ids.len()); + self.mode_ids.insert(mode, mode_id); + } + + fn compile_templates(&mut self, declarations: &ir::Declarations) -> error::SpannedResult<()> { + for function_binding in &declarations.functions { + self.compile_named_template(function_binding)?; + } + Ok(()) + } + + fn register_templates(&mut self, declarations: &ir::Declarations) -> error::SpannedResult<()> { + for (index, function_binding) in declarations.functions.iter().enumerate() { + if index > u16::MAX as usize { + return Err( + error::Error::Unsupported("too many named templates".to_string()).into(), + ); } + let template_name_key = function_binding.name.as_str().to_string(); + self.template_ids + .insert(template_name_key.clone(), index as u16); + self.template_params.insert( + template_name_key, + function_binding + .main + .params + .iter() + .skip(3) + .cloned() + .collect(), + ); + } + Ok(()) + } + + fn compile_global_variables( + &mut self, + declarations: &ir::Declarations, + ) -> error::SpannedResult<()> { + for global_variable in &declarations.global_variables { + self.compile_global_variable(global_variable)?; + } + Ok(()) + } + + fn compile_global_variable( + &mut self, + global_variable: &ir::GlobalVariable, + ) -> error::SpannedResult<()> { + let mut function_compiler = self.function_compiler(); + let function_definition = ir::FunctionDefinition { + params: global_variable.params.clone(), + return_type: None, + body: Box::new(global_variable.expr.clone()), + }; + let function_id = function_compiler + .compile_function_id(&function_definition, global_variable.expr.span.into())?; + self.program.declarations.add_global_variable( + xee_interpreter::declaration::GlobalVariableDeclaration { + name: global_variable.name.clone(), + function_id, + original_name: global_variable.original_name.clone(), + required: global_variable.required, + }, + ); + Ok(()) + } + + fn compile_named_template( + &mut self, + function_binding: &ir::FunctionBinding, + ) -> error::SpannedResult<()> { + let mut function_compiler = self.function_compiler(); + let function_id = + function_compiler.compile_function_id(&function_binding.main, (0..0).into())?; + let template_params = function_binding + .main + .params + .iter() + .skip(3) + .map( + |param| xee_interpreter::declaration::TemplateParamDeclaration { + name: param + .original_name + .clone() + .unwrap_or_else(|| param.name.as_str().to_string()), + tunnel: param.tunnel, + }, + ) + .collect::>(); + if !template_params.is_empty() { + self.program + .declarations + .add_template_params(function_id, template_params); } + self.program.declarations.add_named_template( + xee_interpreter::declaration::NamedTemplateDeclaration { + name: function_binding.name.clone(), + function_id, + }, + ); + Ok(()) } fn compile_rule(&mut self, rule: &ir::Rule) -> error::SpannedResult<()> { @@ -102,6 +299,29 @@ impl<'a> DeclarationCompiler<'a> { function_compiler.compile_function_id(function_definition, (0..0).into()) })?; + drop(function_compiler); + + let template_params = rule + .function_definition + .params + .iter() + .skip(3) + .map( + |param| xee_interpreter::declaration::TemplateParamDeclaration { + name: param + .original_name + .clone() + .unwrap_or_else(|| param.name.as_str().to_string()), + tunnel: param.tunnel, + }, + ) + .collect::>(); + if !template_params.is_empty() { + self.program + .declarations + .add_template_params(function_id, template_params); + } + self.add_rule(&rule.modes, rule.priority, &pattern, function_id); Ok(()) } @@ -143,10 +363,18 @@ impl<'a> DeclarationCompiler<'a> { // all modes. We do this before the final registration so we benefit // from priority sorting later if let Some(all_rule_builders) = all_rule_builders { - for rule_builders in self.rule_builders.values_mut() { - for all_rule_builder in &all_rule_builders { - rule_builders.push(all_rule_builder.clone()); - } + let all_modes = self.mode_ids.keys().cloned().collect::>(); + + for mode in all_modes { + let mode_value = match mode { + ir::ApplyTemplatesModeValue::Named(name) => ir::ModeValue::Named(name), + ir::ApplyTemplatesModeValue::Unnamed => ir::ModeValue::Unnamed, + ir::ApplyTemplatesModeValue::Current => continue, + }; + self.rule_builders + .entry(mode_value) + .or_default() + .extend(all_rule_builders.iter().cloned()); } } diff --git a/xee-ir/src/function_compiler.rs b/xee-ir/src/function_compiler.rs index 70f4a30d2..ce362869b 100644 --- a/xee-ir/src/function_compiler.rs +++ b/xee-ir/src/function_compiler.rs @@ -2,12 +2,13 @@ use ibig::{ibig, IBig}; use xee_interpreter::error::Error; use xee_interpreter::function::FunctionRule; -use xee_interpreter::interpreter::instruction::Instruction; +use xee_interpreter::interpreter::instruction::{Instruction, RaisedError}; use xee_interpreter::span::SourceSpan; -use xee_interpreter::{error, function, sequence}; +use xee_interpreter::{atomic, error, function, sequence}; -use crate::declaration_compiler::ModeIds; +use crate::declaration_compiler::{ModeIds, TemplateIds, TemplateParams}; use crate::ir; +use xee_xpath_ast::span::Spanned; use super::builder::{BackwardJumpRef, ForwardJumpRef, FunctionBuilder, JumpCondition}; use super::scope; @@ -17,6 +18,9 @@ pub(crate) type Scopes = scope::Scopes; pub struct FunctionCompiler<'a> { pub(crate) scopes: &'a mut Scopes, pub(crate) mode_ids: &'a ModeIds, + pub(crate) template_ids: &'a TemplateIds, + pub(crate) template_params: &'a TemplateParams, + pub(crate) global_variable_ids: &'a ahash::HashMap, pub(crate) builder: FunctionBuilder<'a>, } @@ -25,11 +29,17 @@ impl<'a> FunctionCompiler<'a> { builder: FunctionBuilder<'a>, scopes: &'a mut Scopes, mode_ids: &'a ModeIds, + template_ids: &'a TemplateIds, + template_params: &'a TemplateParams, + global_variable_ids: &'a ahash::HashMap, ) -> Self { Self { builder, scopes, mode_ids, + template_ids, + template_params, + global_variable_ids, } } @@ -70,6 +80,9 @@ impl<'a> FunctionCompiler<'a> { ir::Expr::Castable(castable) => self.compile_castable(castable, span), ir::Expr::InstanceOf(instance_of) => self.compile_instance_of(instance_of, span), ir::Expr::Treat(treat) => self.compile_treat(treat, span), + ir::Expr::ConvertSequence(convert_sequence) => { + self.compile_convert_sequence(convert_sequence, span) + } ir::Expr::MapConstructor(map_constructor) => { self.compile_map_constructor(map_constructor, span) } @@ -90,6 +103,12 @@ impl<'a> FunctionCompiler<'a> { ir::Expr::ApplyTemplates(apply_templates) => { self.compile_apply_templates(apply_templates, span) } + ir::Expr::ContinueTemplate(continue_template) => { + self.compile_continue_template(continue_template, span) + } + ir::Expr::CallTemplate(call_template) => { + self.compile_call_template(call_template, span) + } ir::Expr::CopyShallow(copy_shallow) => self.compile_copy_shallow(copy_shallow, span), ir::Expr::CopyDeep(copy_deep) => self.compile_copy_deep(copy_deep, span), } @@ -147,12 +166,16 @@ impl<'a> FunctionCompiler<'a> { .emit(Instruction::ClosureVar(index as u16), span); Ok(()) } else { - // TODO: this should be unreachable but - // the XSLT test suite for some reason triggers - // this condition, so for now we've hacked our way - // around it - Err(Error::Unsupported(String::from("Internal bug: variable not found?")).into()) - // unreachable!("variable not found: {:?}", name); + if let Some(index) = self.global_variable_ids.get(name) { + self.builder.emit(Instruction::GlobalVar(*index), span); + Ok(()) + } else { + Err(Error::Unsupported(format!( + "Internal bug: variable not found: {}", + name.as_str() + )) + .into()) + } } } } @@ -174,6 +197,10 @@ impl<'a> FunctionCompiler<'a> { } fn compile_let(&mut self, let_: &ir::Let, span: SourceSpan) -> error::SpannedResult<()> { + if !expr_uses_name(&let_.return_expr, &let_.name) && expr_is_effect_free(&let_.var_expr) { + return self.compile_expr(&let_.return_expr); + } + self.compile_expr(&let_.var_expr)?; self.scopes.push_name(&let_.name); self.compile_expr(&let_.return_expr)?; @@ -342,11 +369,54 @@ impl<'a> FunctionCompiler<'a> { builder: nested_builder, scopes: self.scopes, mode_ids: self.mode_ids, + template_ids: self.template_ids, + template_params: self.template_params, + global_variable_ids: self.global_variable_ids, }; for param in &function_definition.params { compiler.scopes.push_name(¶m.name); } + for (index, param) in function_definition.params.iter().enumerate() { + if index > u16::MAX as usize { + return Err(Error::XPDY0130.with_span(span)); + } + if let Some(default) = ¶m.default { + compiler + .builder + .emit(Instruction::VarIsAbsent(index as u16), span); + let skip_default = compiler + .builder + .emit_jump_forward(JumpCondition::False, span); + let default_expr = Spanned::new((**default).clone(), (0..0).into()); + compiler.compile_expr(&default_expr)?; + compiler.compile_variable_set(¶m.name, span)?; + compiler.builder.patch_jump(skip_default); + } else if param.required { + compiler + .builder + .emit(Instruction::VarIsAbsent(index as u16), span); + let skip_error = compiler + .builder + .emit_jump_forward(JumpCondition::False, span); + compiler + .builder + .emit(Instruction::RaiseError(RaisedError::XTDE0700), span); + compiler.builder.patch_jump(skip_error); + } else if param.original_name.is_some() { + compiler + .builder + .emit(Instruction::VarIsAbsent(index as u16), span); + let skip_default = compiler + .builder + .emit_jump_forward(JumpCondition::False, span); + compiler + .builder + .emit_constant(sequence::Sequence::default(), span); + compiler.compile_variable_set(¶m.name, span)?; + compiler.builder.patch_jump(skip_default); + } + } compiler.compile_expr(&function_definition.body)?; for _ in &function_definition.params { compiler.scopes.pop_name(); @@ -520,6 +590,22 @@ impl<'a> FunctionCompiler<'a> { Ok(()) } + fn compile_convert_sequence( + &mut self, + convert_sequence: &ir::ConvertSequence, + span: SourceSpan, + ) -> error::SpannedResult<()> { + self.compile_atom(&convert_sequence.atom)?; + let sequence_type_id = self + .builder + .add_sequence_type(convert_sequence.sequence_type.clone()); + self.builder.emit( + Instruction::ConvertSequence(sequence_type_id as u16, convert_sequence.error), + span, + ); + Ok(()) + } + fn compile_map_constructor( &mut self, map_constructor: &ir::MapConstructor, @@ -1004,24 +1090,160 @@ impl<'a> FunctionCompiler<'a> { span: SourceSpan, ) -> error::SpannedResult<()> { self.compile_atom(&apply_templates.select)?; + self.compile_with_param_map( + apply_templates + .params + .iter() + .filter(|with_param| !with_param.tunnel), + span, + )?; + self.compile_with_param_map( + apply_templates + .params + .iter() + .filter(|with_param| with_param.tunnel), + span, + )?; + + match &apply_templates.mode { + ir::ApplyTemplatesModeValue::Current => { + let fallback_mode_id = self + .mode_ids + .get(&ir::ApplyTemplatesModeValue::Unnamed) + .expect("Unnamed mode should have been registered"); + self.builder.emit( + Instruction::ApplyTemplatesCurrent( + fallback_mode_id.get() as u16, + apply_templates.builtin_template_params_passthrough, + ), + span, + ); + } + ir::ApplyTemplatesModeValue::Named(_) | ir::ApplyTemplatesModeValue::Unnamed => { + if let Some(mode_id) = self.mode_ids.get(&apply_templates.mode) { + self.builder.emit( + Instruction::ApplyTemplates( + mode_id.get() as u16, + apply_templates.builtin_template_params_passthrough, + ), + span, + ); + } else { + // the mode was never used by any templates, so compile the empty + // sequence + self.builder + .emit_constant(sequence::Sequence::default(), span); + } + } + } + Ok(()) + } - let mode_id = if matches!( - apply_templates.mode, - ir::ApplyTemplatesModeValue::Named(_) | ir::ApplyTemplatesModeValue::Unnamed - ) { - self.mode_ids.get(&apply_templates.mode) - } else { - todo!("#current mode not handled yet") - }; - if let Some(mode_id) = mode_id { + fn compile_call_template( + &mut self, + call_template: &ir::CallTemplate, + span: SourceSpan, + ) -> error::SpannedResult<()> { + // Look up the named template by name + let template_name_key = call_template.name.as_str().to_string(); + + if let Some(&template_id) = self.template_ids.get(&template_name_key) { self.builder - .emit(Instruction::ApplyTemplates(mode_id.get() as u16), span); + .emit(Instruction::NamedTemplate(template_id), span); + + if let Some(context_names) = &call_template.context { + self.compile_variable(&context_names.item, span)?; + self.compile_variable(&context_names.position, span)?; + self.compile_variable(&context_names.last, span)?; + } else { + self.builder.emit(Instruction::Absent, span); + self.builder.emit(Instruction::Absent, span); + self.builder.emit(Instruction::Absent, span); + } + + // Look up the template's expected parameters + let template_params = self + .template_params + .get(&template_name_key) + .cloned() + .unwrap_or_default(); + + // Build a map of non-tunnel with-param names for static validation. + let mut with_param_map: std::collections::HashMap = + std::collections::HashMap::new(); + for with_param in &call_template.params { + if with_param.tunnel { + continue; + } + let param_key = with_param.name.as_str().to_string(); + with_param_map.insert(param_key, with_param); + } + + let valid_param_names: std::collections::HashSet = template_params + .iter() + .filter(|param| !param.tunnel) + .map(|param| { + param + .original_name + .clone() + .unwrap_or_else(|| param.name.as_str().to_string()) + }) + .collect(); + if !call_template.backwards_compatible + && with_param_map + .keys() + .any(|param_name| !valid_param_names.contains(param_name)) + { + return Err(error::Error::XTSE0680.into()); + } + + self.compile_with_param_map( + call_template + .params + .iter() + .filter(|with_param| !with_param.tunnel), + span, + )?; + self.compile_with_param_map( + call_template + .params + .iter() + .filter(|with_param| with_param.tunnel), + span, + )?; + + self.builder.emit(Instruction::CallTemplate, span); + Ok(()) } else { - // the mode was never used by any templates, so compile the empty - // sequence - self.builder - .emit_constant(sequence::Sequence::default(), span); + Err(error::Error::Unsupported(format!( + "Named template not found: {:?}", + &call_template.name + )) + .into()) } + } + + fn compile_continue_template( + &mut self, + continue_template: &ir::ContinueTemplate, + span: SourceSpan, + ) -> error::SpannedResult<()> { + self.compile_with_param_map( + continue_template + .params + .iter() + .filter(|with_param| !with_param.tunnel), + span, + )?; + self.compile_with_param_map( + continue_template + .params + .iter() + .filter(|with_param| with_param.tunnel), + span, + )?; + + self.builder.emit(Instruction::ContinueTemplate, span); Ok(()) } @@ -1045,6 +1267,31 @@ impl<'a> FunctionCompiler<'a> { Ok(()) } + fn compile_with_param_map<'b>( + &mut self, + with_params: impl DoubleEndedIterator, + span: SourceSpan, + ) -> error::SpannedResult<()> { + let with_params = with_params.collect::>(); + for with_param in with_params.iter().rev() { + let key: sequence::Sequence = atomic::Atomic::from(with_param.name.as_str()).into(); + self.builder.emit_constant(key, span); + if let Some(atom) = &with_param.select { + self.compile_atom(atom)?; + } else if let Some(expr) = &with_param.sequence_constructor { + self.compile_expr(expr)?; + } else { + self.builder + .emit_constant(sequence::Sequence::default(), span); + } + } + let len: IBig = with_params.len().into(); + self.builder + .emit_constant(sequence::Sequence::from(len), span); + self.builder.emit(Instruction::CurlyMap, span); + Ok(()) + } + fn compile_pattern_predicate( &mut self, predicate: &ir::PatternPredicate, @@ -1066,3 +1313,229 @@ impl<'a> FunctionCompiler<'a> { Ok(()) } } + +fn expr_uses_name(expr: &ir::ExprS, name: &ir::Name) -> bool { + expr_value_uses_name(&expr.value, name) +} + +fn expr_is_effect_free(expr: &ir::ExprS) -> bool { + expr_value_is_effect_free(&expr.value) +} + +fn expr_value_uses_name(expr: &ir::Expr, name: &ir::Name) -> bool { + match expr { + ir::Expr::Atom(atom) => atom_uses_name(atom, name), + ir::Expr::Let(let_) => { + expr_uses_name(&let_.var_expr, name) || expr_uses_name(&let_.return_expr, name) + } + ir::Expr::If(if_) => { + atom_uses_name(&if_.condition, name) + || expr_uses_name(&if_.then, name) + || expr_uses_name(&if_.else_, name) + } + ir::Expr::Binary(binary) => { + atom_uses_name(&binary.left, name) || atom_uses_name(&binary.right, name) + } + ir::Expr::Unary(unary) => atom_uses_name(&unary.atom, name), + ir::Expr::FunctionDefinition(function_definition) => { + function_definition_uses_name(function_definition, name) + } + ir::Expr::FunctionCall(function_call) => { + atom_uses_name(&function_call.atom, name) + || function_call + .args + .iter() + .any(|arg| atom_uses_name(arg, name)) + } + ir::Expr::Lookup(lookup) => { + atom_uses_name(&lookup.atom, name) || atom_uses_name(&lookup.arg_atom, name) + } + ir::Expr::WildcardLookup(wildcard_lookup) => atom_uses_name(&wildcard_lookup.atom, name), + ir::Expr::Step(step) => atom_uses_name(&step.context, name), + ir::Expr::Deduplicate(expr) => expr_uses_name(expr, name), + ir::Expr::Map(map) => { + atom_uses_name(&map.var_atom, name) || expr_uses_name(&map.return_expr, name) + } + ir::Expr::Filter(filter) => { + atom_uses_name(&filter.var_atom, name) || expr_uses_name(&filter.return_expr, name) + } + ir::Expr::Iterate(iterate) => { + atom_uses_name(&iterate.var_atom, name) + || iterate + .params + .iter() + .any(|param| expr_uses_name(¶m.value, name)) + || expr_uses_name(&iterate.expr, name) + || iterate + .on_complete + .as_ref() + .is_some_and(|expr| expr_uses_name(expr, name)) + } + ir::Expr::IterateBreak(iterate_break) => expr_uses_name(&iterate_break.return_expr, name), + ir::Expr::IterateLetNext(iterate_let_next) => { + iterate_let_next + .params + .iter() + .any(|param| expr_uses_name(¶m.value, name)) + || expr_uses_name(&iterate_let_next.return_expr, name) + } + ir::Expr::PatternPredicate(pattern_predicate) => { + atom_uses_name(&pattern_predicate.var_atom, name) + || expr_uses_name(&pattern_predicate.expr, name) + } + ir::Expr::Quantified(quantified) => { + atom_uses_name(&quantified.var_atom, name) + || expr_uses_name(&quantified.satisifies_expr, name) + } + ir::Expr::Cast(cast) => atom_uses_name(&cast.atom, name), + ir::Expr::Castable(castable) => atom_uses_name(&castable.atom, name), + ir::Expr::InstanceOf(instance_of) => atom_uses_name(&instance_of.atom, name), + ir::Expr::Treat(treat) => atom_uses_name(&treat.atom, name), + ir::Expr::ConvertSequence(convert_sequence) => atom_uses_name(&convert_sequence.atom, name), + ir::Expr::MapConstructor(map_constructor) => map_constructor + .members + .iter() + .any(|(key, value)| atom_uses_name(key, name) || atom_uses_name(value, name)), + ir::Expr::ArrayConstructor(array_constructor) => match array_constructor { + ir::ArrayConstructor::Square(atoms) => { + atoms.iter().any(|atom| atom_uses_name(atom, name)) + } + ir::ArrayConstructor::Curly(atom) => atom_uses_name(atom, name), + }, + ir::Expr::XmlName(xml_name) => { + atom_uses_name(&xml_name.local_name, name) || atom_uses_name(&xml_name.namespace, name) + } + ir::Expr::XmlDocument(_) => false, + ir::Expr::XmlElement(element) => atom_uses_name(&element.name, name), + ir::Expr::XmlAttribute(attribute) => { + atom_uses_name(&attribute.name, name) || atom_uses_name(&attribute.value, name) + } + ir::Expr::XmlNamespace(namespace) => { + atom_uses_name(&namespace.prefix, name) || atom_uses_name(&namespace.namespace, name) + } + ir::Expr::XmlText(text) => atom_uses_name(&text.value, name), + ir::Expr::XmlComment(comment) => atom_uses_name(&comment.value, name), + ir::Expr::XmlProcessingInstruction(processing_instruction) => { + atom_uses_name(&processing_instruction.target, name) + || atom_uses_name(&processing_instruction.content, name) + } + ir::Expr::XmlAppend(xml_append) => { + atom_uses_name(&xml_append.parent, name) || atom_uses_name(&xml_append.child, name) + } + ir::Expr::ApplyTemplates(apply_templates) => { + atom_uses_name(&apply_templates.select, name) + || apply_templates + .params + .iter() + .any(|param| with_param_uses_name(param, name)) + } + ir::Expr::ContinueTemplate(continue_template) => continue_template + .params + .iter() + .any(|param| with_param_uses_name(param, name)), + ir::Expr::CallTemplate(call_template) => call_template + .params + .iter() + .any(|param| with_param_uses_name(param, name)), + ir::Expr::CopyShallow(copy_shallow) => atom_uses_name(©_shallow.select, name), + ir::Expr::CopyDeep(copy_deep) => atom_uses_name(©_deep.select, name), + } +} + +fn expr_value_is_effect_free(expr: &ir::Expr) -> bool { + match expr { + ir::Expr::Atom(_) => true, + ir::Expr::Let(let_) => { + expr_is_effect_free(&let_.var_expr) && expr_is_effect_free(&let_.return_expr) + } + ir::Expr::If(if_) => expr_is_effect_free(&if_.then) && expr_is_effect_free(&if_.else_), + ir::Expr::Binary(_) => true, + ir::Expr::Unary(_) => true, + ir::Expr::FunctionDefinition(function_definition) => { + function_definition.params.iter().all(|param| { + param + .default + .as_ref() + .is_none_or(|expr| expr_value_is_effect_free(expr)) + }) && expr_is_effect_free(&function_definition.body) + } + ir::Expr::FunctionCall(_) => true, + ir::Expr::Lookup(_) => true, + ir::Expr::WildcardLookup(_) => true, + ir::Expr::Step(_) => true, + ir::Expr::Deduplicate(expr) => expr_is_effect_free(expr), + ir::Expr::Map(map) => expr_is_effect_free(&map.return_expr), + ir::Expr::Filter(filter) => expr_is_effect_free(&filter.return_expr), + ir::Expr::Iterate(iterate) => { + iterate + .params + .iter() + .all(|param| expr_is_effect_free(¶m.value)) + && expr_is_effect_free(&iterate.expr) + && iterate + .on_complete + .as_ref() + .is_none_or(|expr| expr_is_effect_free(expr)) + } + ir::Expr::IterateBreak(iterate_break) => expr_is_effect_free(&iterate_break.return_expr), + ir::Expr::IterateLetNext(iterate_let_next) => { + iterate_let_next + .params + .iter() + .all(|param| expr_is_effect_free(¶m.value)) + && expr_is_effect_free(&iterate_let_next.return_expr) + } + ir::Expr::PatternPredicate(pattern_predicate) => { + expr_is_effect_free(&pattern_predicate.expr) + } + ir::Expr::Quantified(quantified) => expr_is_effect_free(&quantified.satisifies_expr), + ir::Expr::Cast(_) => true, + ir::Expr::Castable(_) => true, + ir::Expr::InstanceOf(_) => true, + ir::Expr::Treat(_) => true, + ir::Expr::ConvertSequence(_) => true, + ir::Expr::MapConstructor(_) => true, + ir::Expr::ArrayConstructor(_) => true, + ir::Expr::XmlName(_) => true, + ir::Expr::XmlDocument(_) => false, + ir::Expr::XmlElement(_) => false, + ir::Expr::XmlAttribute(_) => false, + ir::Expr::XmlNamespace(_) => false, + ir::Expr::XmlText(_) => false, + ir::Expr::XmlComment(_) => false, + ir::Expr::XmlProcessingInstruction(_) => false, + ir::Expr::XmlAppend(_) => false, + ir::Expr::ApplyTemplates(_) => false, + ir::Expr::ContinueTemplate(_) => false, + ir::Expr::CallTemplate(_) => false, + ir::Expr::CopyShallow(_) => false, + ir::Expr::CopyDeep(_) => false, + } +} + +fn atom_uses_name(atom: &ir::AtomS, name: &ir::Name) -> bool { + matches!(&atom.value, ir::Atom::Variable(variable_name) if variable_name == name) +} + +fn function_definition_uses_name( + function_definition: &ir::FunctionDefinition, + name: &ir::Name, +) -> bool { + function_definition.params.iter().any(|param| { + param + .default + .as_ref() + .is_some_and(|expr| expr_value_uses_name(expr, name)) + }) || expr_uses_name(&function_definition.body, name) +} + +fn with_param_uses_name(with_param: &ir::WithParam, name: &ir::Name) -> bool { + with_param + .select + .as_ref() + .is_some_and(|select| atom_uses_name(select, name)) + || with_param + .sequence_constructor + .as_ref() + .is_some_and(|expr| expr_uses_name(expr, name)) +} diff --git a/xee-ir/src/ir.rs b/xee-ir/src/ir.rs index d00a2e2ee..7e63df3e1 100644 --- a/xee-ir/src/ir.rs +++ b/xee-ir/src/ir.rs @@ -9,6 +9,7 @@ use rust_decimal::Decimal; pub use xee_interpreter::function::Name; use xee_interpreter::function::{CastType, Signature, StaticFunctionId}; +use xee_interpreter::interpreter::instruction::RaisedError; use xee_interpreter::sequence::SerializationParameters; use xee_interpreter::xml; use xee_schema_type::Xs; @@ -44,6 +45,7 @@ pub enum Expr { Castable(Castable), InstanceOf(InstanceOf), Treat(Treat), + ConvertSequence(ConvertSequence), MapConstructor(MapConstructor), ArrayConstructor(ArrayConstructor), XmlName(XmlName), @@ -56,6 +58,8 @@ pub enum Expr { XmlProcessingInstruction(XmlProcessingInstruction), XmlAppend(XmlAppend), ApplyTemplates(ApplyTemplates), + ContinueTemplate(ContinueTemplate), + CallTemplate(CallTemplate), CopyShallow(CopyShallow), CopyDeep(CopyDeep), } @@ -135,6 +139,10 @@ impl FunctionDefinition { pub struct Param { pub name: Name, pub type_: Option, + pub default: Option>, + pub required: bool, + pub original_name: Option, // For template parameters - tracks the original xsl:param name for matching with xsl:with-param + pub tunnel: bool, } #[derive(Debug, Clone, PartialEq, Eq)] @@ -268,6 +276,13 @@ pub struct Treat { pub sequence_type: SequenceType, } +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ConvertSequence { + pub atom: AtomS, + pub sequence_type: SequenceType, + pub error: RaisedError, +} + #[derive(Debug, Clone, PartialEq, Eq)] pub struct MapConstructor { pub members: Vec<(AtomS, AtomS)>, @@ -333,6 +348,13 @@ pub struct XmlAppend { pub struct ApplyTemplates { pub mode: ApplyTemplatesModeValue, pub select: AtomS, + pub builtin_template_params_passthrough: bool, + pub params: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ContinueTemplate { + pub params: Vec, } #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -342,6 +364,22 @@ pub enum ApplyTemplatesModeValue { Current, } +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct CallTemplate { + pub name: Name, + pub context: Option, + pub backwards_compatible: bool, + pub params: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct WithParam { + pub name: Name, + pub select: Option, + pub sequence_constructor: Option>, + pub tunnel: bool, +} + #[derive(Debug, Clone, PartialEq, Eq)] pub struct CopyShallow { pub select: AtomS, @@ -368,13 +406,45 @@ pub enum ModeValue { } #[derive(Debug, Clone, PartialEq, Eq)] -pub struct Mode {} +pub struct Mode { + pub on_no_match: ModeOnNoMatch, + pub warning_on_no_match: bool, + pub typed: ModeTyped, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ModeOnNoMatch { + DeepCopy, + ShallowCopy, + DeepSkip, + ShallowSkip, + TextOnlyCopy, + Fail, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ModeTyped { + Yes, + No, + Strict, + Lax, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct GlobalVariable { + pub name: Name, + pub original_name: Option, + pub required: bool, + pub params: Vec, + pub expr: ExprS, +} #[derive(Debug, Clone, PartialEq, Eq)] pub struct Declarations { pub rules: Vec, pub modes: HashMap, Mode>, pub functions: Vec, + pub global_variables: Vec, pub main: FunctionDefinition, pub serialization_params: SerializationParameters, } @@ -385,6 +455,7 @@ impl Declarations { rules: Vec::new(), modes: HashMap::new(), functions: Vec::new(), + global_variables: Vec::new(), main, serialization_params: SerializationParameters::new(), } diff --git a/xee-ir/src/variables.rs b/xee-ir/src/variables.rs index 08dbfe63c..4489a29f3 100644 --- a/xee-ir/src/variables.rs +++ b/xee-ir/src/variables.rs @@ -17,7 +17,7 @@ enum ContextItem { #[derive(Debug, Default)] pub struct Variables { counter: usize, - variables: HashMap, + variables: Vec>, context_scope: Vec, } @@ -25,7 +25,7 @@ impl Variables { pub fn new() -> Self { Self { counter: 0, - variables: HashMap::new(), + variables: vec![HashMap::new()], context_scope: Vec::new(), } } @@ -47,11 +47,40 @@ impl Variables { } pub fn new_var_name(&mut self, name: &ast::Name) -> ir::Name { - self.variables.get(name).cloned().unwrap_or_else(|| { - let new_name = self.new_name(); - self.variables.insert(name.clone(), new_name.clone()); - new_name - }) + self.lookup_var_name(name) + .unwrap_or_else(|| self.declare_var_name(name)) + } + + pub fn declare_var_name(&mut self, name: &ast::Name) -> ir::Name { + let new_name = self.new_name(); + self.variables + .last_mut() + .unwrap() + .insert(name.clone(), new_name.clone()); + new_name + } + + pub fn remove_var_name_in_current_scope(&mut self, name: &ast::Name) -> Option { + self.variables.last_mut().unwrap().remove(name) + } + + pub fn insert_var_name_in_current_scope(&mut self, name: ast::Name, ir_name: ir::Name) { + self.variables.last_mut().unwrap().insert(name, ir_name); + } + + pub fn lookup_var_name(&self, name: &ast::Name) -> Option { + self.variables + .iter() + .rev() + .find_map(|scope| scope.get(name).cloned()) + } + + pub fn push_scope(&mut self) { + self.variables.push(HashMap::new()); + } + + pub fn pop_scope(&mut self) { + self.variables.pop(); } pub fn push_context(&mut self) -> ir::ContextNames { @@ -94,8 +123,7 @@ impl Variables { pub fn var_ref(&mut self, name: &ast::Name, span: Span) -> error::SpannedResult { let ir_name = self - .variables - .get(name) + .lookup_var_name(name) .ok_or(Error::XPST0008.with_ast_span(span))?; Ok(Bindings::new(Binding::new( ir_name.clone(), diff --git a/xee-ir/tests/test_xml_ir.rs b/xee-ir/tests/test_xml_ir.rs index 1db04dd97..7c1ed1003 100644 --- a/xee-ir/tests/test_xml_ir.rs +++ b/xee-ir/tests/test_xml_ir.rs @@ -63,14 +63,26 @@ fn test_generate_element() { ir::Param { name: ir::Name::new("item".to_string()), type_: None, + default: None, + required: false, + original_name: None, + tunnel: false, }, ir::Param { name: ir::Name::new("position".to_string()), type_: None, + default: None, + required: false, + original_name: None, + tunnel: false, }, ir::Param { name: ir::Name::new("last".to_string()), type_: None, + default: None, + required: false, + original_name: None, + tunnel: false, }, ], return_type: None, @@ -85,7 +97,17 @@ fn test_generate_element() { let function_builder = FunctionBuilder::new(&mut program); let mut scopes = Scopes::new(); let empty_mode_ids = ModeIds::new(); - let mut compiler = FunctionCompiler::new(function_builder, &mut scopes, &empty_mode_ids); + let empty_template_ids = ahash::HashMap::new(); + let empty_template_params = ahash::HashMap::new(); + let empty_global_variable_ids = ahash::HashMap::new(); + let mut compiler = FunctionCompiler::new( + function_builder, + &mut scopes, + &empty_mode_ids, + &empty_template_ids, + &empty_template_params, + &empty_global_variable_ids, + ); compiler.compile_expr(&outer_expr).unwrap(); diff --git a/xee-testrunner/src/testcase/assert.rs b/xee-testrunner/src/testcase/assert.rs index 2b6999da7..8b3831137 100644 --- a/xee-testrunner/src/testcase/assert.rs +++ b/xee-testrunner/src/testcase/assert.rs @@ -5,7 +5,7 @@ use std::path::PathBuf; use xee_xpath::context::{Collation, DynamicContext}; use xee_xpath::SerializationParameters; use xot::xmlname::{NameStrInfo, OwnedName as Name}; -use xot::Xot; +use xot::{Value, Xot}; use xee_xpath::query::RecurseQuery; use xee_xpath::{context, error, Documents, Item, Queries, Query, Recurse, Sequence}; @@ -419,6 +419,7 @@ impl Assertable for AssertXml { return TestOutcome::EnvironmentError("Cannot parse result XML".to_string()); } }; + normalize_xml_for_comparison(&mut compare_xot, found); let expected = match &self { Self::MatchString(s) => compare_xot.parse_fragment(s).unwrap(), @@ -438,6 +439,7 @@ impl Assertable for AssertXml { compare_xot.parse(&expected_xml).unwrap() } }; + normalize_xml_for_comparison(&mut compare_xot, expected); // and compare let c = compare_xot.deep_equal(expected, found); @@ -450,6 +452,64 @@ impl Assertable for AssertXml { } } +fn normalize_xml_for_comparison(xot: &mut Xot, fragment: xot::Node) { + normalize_outer_fragment_whitespace(xot, fragment); + normalize_ignorable_whitespace(xot, fragment); +} + +fn normalize_outer_fragment_whitespace(xot: &mut Xot, fragment: xot::Node) { + let children = xot.children(fragment).collect::>(); + let leading = children + .iter() + .take_while(|child| is_whitespace_text(xot, **child)) + .copied() + .collect::>(); + let trailing = children + .iter() + .rev() + .take_while(|child| is_whitespace_text(xot, **child)) + .copied() + .collect::>(); + + for child in leading.into_iter().chain(trailing) { + let _ = xot.remove(child); + } +} + +fn normalize_ignorable_whitespace(xot: &mut Xot, node: xot::Node) { + let children = xot.children(node).collect::>(); + for child in &children { + normalize_ignorable_whitespace(xot, *child); + } + + let has_element_child = children.iter().any(|child| xot.is_element(*child)); + let has_non_whitespace_text = children.iter().any(|child| match xot.value(*child) { + Value::Text(text) => !text + .get() + .chars() + .all(|c| matches!(c, '\u{9}' | '\u{A}' | '\u{D}' | ' ')), + _ => false, + }); + + if has_element_child && !has_non_whitespace_text { + for child in children { + if is_whitespace_text(xot, child) { + let _ = xot.remove(child); + } + } + } +} + +fn is_whitespace_text(xot: &Xot, node: xot::Node) -> bool { + match xot.value(node) { + Value::Text(text) => text + .get() + .chars() + .all(|c| matches!(c, '\u{9}' | '\u{A}' | '\u{D}' | ' ')), + _ => false, + } +} + #[derive(Debug, Clone, PartialEq, Eq)] pub struct AssertEmpty; @@ -1071,8 +1131,18 @@ fn run_xpath_with_result( let q = queries.sequence_with_context(expr, static_context)?; let variables = AHashMap::from([(name, sequence.clone())]); + let context_item = match sequence.normalize(" ", documents.xot_mut()) { + Ok(context_item) => Some(context_item.into()), + // Function items cannot become the implicit context item for the + // assertion query, but $result should still be available. + Err(error::ErrorValue::SENR0001) => None, + Err(error) => return Err(error.into()), + }; q.execute_build_context(documents, |build| { + if let Some(context_item) = context_item { + build.context_item(context_item); + } build.variables(variables); }) } @@ -1081,6 +1151,8 @@ fn run_xpath_with_result( mod tests { use std::{path::PathBuf, vec}; + use iri_string::types::IriStr; + use super::*; use crate::{language::XPathLanguage, ns::XPATH_TEST_NS, paths::Mode}; @@ -1131,4 +1203,57 @@ mod tests { ])) ); } + + #[test] + fn test_run_xpath_with_result_uses_result_tree_as_context() { + let mut documents = Documents::new(); + let uri: &IriStr = "http://example.com/result.xml".try_into().unwrap(); + let handle = documents + .add_string(uri, "true0false") + .unwrap(); + let document_node = documents.document_node(handle).unwrap(); + let sequence: Sequence = document_node.into(); + + let expr = "/result/a = 'true'".to_string(); + let result = run_xpath_with_result(&expr, &sequence, &mut documents).unwrap(); + + assert!(result.effective_boolean_value().unwrap()); + } + + #[test] + fn test_run_xpath_with_result_allows_function_item_results_via_result_variable() { + let mut documents = Documents::new(); + let sequence = run_xpath(&"map:entry('foo', 3)".to_string()).unwrap(); + + let expr = "$result?foo = 3".to_string(); + let result = run_xpath_with_result(&expr, &sequence, &mut documents).unwrap(); + + assert!(result.effective_boolean_value().unwrap()); + } + + #[test] + fn test_assert_xml_ignores_outer_fragment_whitespace() { + let mut xot = Xot::new(); + let expected = xot.parse_fragment("\t17").unwrap(); + let actual = xot.parse_fragment("17").unwrap(); + + normalize_xml_for_comparison(&mut xot, expected); + normalize_xml_for_comparison(&mut xot, actual); + + assert!(xot.deep_equal(expected, actual)); + } + + #[test] + fn test_assert_xml_ignores_indentation_in_element_only_content() { + let mut xot = Xot::new(); + let expected = xot + .parse_fragment("\n 1\n 2\n") + .unwrap(); + let actual = xot.parse_fragment("12").unwrap(); + + normalize_xml_for_comparison(&mut xot, expected); + normalize_xml_for_comparison(&mut xot, actual); + + assert!(xot.deep_equal(expected, actual)); + } } diff --git a/xee-testrunner/src/testcase/xpath.rs b/xee-testrunner/src/testcase/xpath.rs index d9244d987..ec14fa582 100644 --- a/xee-testrunner/src/testcase/xpath.rs +++ b/xee-testrunner/src/testcase/xpath.rs @@ -99,6 +99,11 @@ impl Runnable for XPathTestCase { Err(error) => return TestOutcome::EnvironmentError(error.to_string()), }; static_context_builder.namespaces(namespaces); + static_context_builder.disable_function(xot::xmlname::OwnedName::new( + "document".to_string(), + xee_name::Namespaces::FN_NAMESPACE.to_string(), + "fn".to_string(), + )); // now construct a query with that static context let static_context = static_context_builder.build(); diff --git a/xee-testrunner/src/testcase/xslt.rs b/xee-testrunner/src/testcase/xslt.rs index cb3954e25..db863368f 100644 --- a/xee-testrunner/src/testcase/xslt.rs +++ b/xee-testrunner/src/testcase/xslt.rs @@ -18,6 +18,7 @@ use crate::{ }; use super::{ + assert::TestCaseResult, core::{Runnable, TestCase}, outcome::TestOutcome, }; @@ -34,6 +35,8 @@ impl XsltTestCase {} pub(crate) struct XsltTest { pub(crate) base_dir: PathBuf, pub(crate) stylesheets: Vec, + pub(crate) initial_mode: Option, + pub(crate) initial_template: Option, } impl Runnable for XsltTestCase { @@ -76,16 +79,48 @@ impl Runnable for XsltTestCase { )) } }; - let static_context_builder = StaticContextBuilder::default(); + let stylesheet_uri = { + let url = match url::Url::from_file_path(&path) { + Ok(url) => url, + Err(()) => { + return TestOutcome::EnvironmentError(format!( + "Cannot convert stylesheet path to file URI: {}", + path.display() + )) + } + }; + match IriAbsoluteString::try_from(url.to_string()) { + Ok(uri) => uri, + Err(error) => { + return TestOutcome::EnvironmentError(format!( + "Cannot convert stylesheet URI to IRI: {}", + error + )) + } + } + }; + let mut static_context_builder = StaticContextBuilder::default(); + static_context_builder.static_base_uri(Some(stylesheet_uri.clone())); let static_context = static_context_builder.build(); - let program = xee_xslt_compiler::parse(static_context, &xslt); + + // Get the directory of the stylesheet for resolving imports/includes + let stylesheet_dir = path.parent().map(|p| p.to_path_buf()); + let program = xee_xslt_compiler::parse_with_base_dir_and_initial_mode( + static_context, + &xslt, + stylesheet_dir, + self.test.initial_mode.clone(), + ); let program = match program { Ok(program) => program, Err(error) => { - return TestOutcome::EnvironmentError(format!( - "Error parsing stylesheet: {}", - error - )) + return match &self.test_case.result { + TestCaseResult::AssertError(assert_error) => { + assert_error.assert_error(&error.error) + } + TestCaseResult::AnyOf(any_of) => any_of.assert_error(&error.error), + _ => TestOutcome::CompilationError(error.error), + } } }; @@ -121,6 +156,27 @@ impl Runnable for XsltTestCase { Err(error) => return TestOutcome::EnvironmentError(error.to_string()), } + { + let documents_ref = run_context.documents.documents().clone(); + let already_loaded = documents_ref + .borrow() + .get_node_by_uri(stylesheet_uri.as_ref()) + .is_some(); + if !already_loaded { + let handle = documents_ref.borrow_mut().add_string( + run_context.documents.xot_mut(), + Some(stylesheet_uri.as_ref()), + &xslt, + ); + if let Err(error) = handle { + return TestOutcome::EnvironmentError(format!( + "Cannot register stylesheet document: {}", + error + )); + } + } + } + // the context item is loaded let context_item = self.test_case @@ -130,6 +186,14 @@ impl Runnable for XsltTestCase { Err(error) => return TestOutcome::EnvironmentError(error.to_string()), }; + let variables = + self.test_case + .variables(run_context, catalog, test_set, static_base_uri.as_deref()); + let variables = match variables { + Ok(variables) => variables, + Err(error) => return TestOutcome::EnvironmentError(error.to_string()), + }; + // now construct the dynamic context. We want to have one here // explicitly so we can use it later in the assertions let mut builder = program.dynamic_context_builder(); @@ -137,11 +201,15 @@ impl Runnable for XsltTestCase { builder.context_item(context_item); } builder.documents(run_context.documents.documents().clone()); - // builder.variables(variables.clone()); + builder.variables(variables); builder.current_datetime(chrono::offset::Utc::now().into()); let context = builder.build(); let runnable = program.runnable(&context); - let result = runnable.many(run_context.documents.xot_mut()); + let result = if let Some(initial_template) = &self.test.initial_template { + runnable.named_template(initial_template, run_context.documents.xot_mut()) + } else { + runnable.many(run_context.documents.xot_mut()) + }; self.test_case.result.assert_result( &context, @@ -164,6 +232,9 @@ impl ContextLoadable for XsltTestCase { fn load_with_context(queries: &Queries, context: &LoadContext) -> Result> { let file_query = queries.option("@file/string()", convert_string)?; + let initial_mode_query = queries.option("initial-mode/@name/string()", convert_string)?; + let initial_template_query = + queries.option("initial-template/@name/string()", convert_string)?; let stylesheets_query = queries.many("stylesheet", move |documents, item| { let file = file_query.execute(documents, item)?; Ok(Stylesheet { path: file }) @@ -175,9 +246,13 @@ impl ContextLoadable for XsltTestCase { let base_dir = context.path.parent().unwrap(); let stylesheets = stylesheets_query.execute(documents, item)?; + let initial_mode = initial_mode_query.execute(documents, item)?; + let initial_template = initial_template_query.execute(documents, item)?; Ok(XsltTest { stylesheets, base_dir: base_dir.to_path_buf(), + initial_mode, + initial_template, }) })?; let test_case_query = TestCase::load_with_context(queries, context)?; diff --git a/xee-xpath-ast/src/ast/rename.rs b/xee-xpath-ast/src/ast/rename.rs index 6d0d3e3f4..9ded57d83 100644 --- a/xee-xpath-ast/src/ast/rename.rs +++ b/xee-xpath-ast/src/ast/rename.rs @@ -40,15 +40,12 @@ impl Names { } fn get(&mut self, name: &Name) -> Name { - // this always returns a name, even if the - // name is unknown, in which case a unique bogus - // name is generated self.names .iter() .rev() .find(|(old_name, _)| old_name == name) .map(|(_, new_name)| new_name.clone()) - .unwrap_or_else(|| self.generator.generate(name)) + .unwrap_or_else(|| name.clone()) } fn push_name(&mut self, name: &Name) -> Name { diff --git a/xee-xpath-compiler/src/ast_ir.rs b/xee-xpath-compiler/src/ast_ir.rs index 272b36cdc..c364bd900 100644 --- a/xee-xpath-compiler/src/ast_ir.rs +++ b/xee-xpath-compiler/src/ast_ir.rs @@ -50,14 +50,26 @@ impl<'a> IrConverter<'a> { ir::Param { name: context_names.item, type_: None, + default: None, + required: false, + original_name: None, + tunnel: false, }, ir::Param { name: context_names.position, type_: None, + default: None, + required: false, + original_name: None, + tunnel: false, }, ir::Param { name: context_names.last, type_: None, + default: None, + required: false, + original_name: None, + tunnel: false, }, ]; // add any variables defined in static context as parameters @@ -65,6 +77,10 @@ impl<'a> IrConverter<'a> { params.push(ir::Param { name: ir_name, type_: None, + default: None, + required: false, + original_name: None, + tunnel: false, }); } let outer_function_expr = ir::Expr::FunctionDefinition(ir::FunctionDefinition { @@ -476,9 +492,11 @@ impl<'a> IrConverter<'a> { } fn let_expr(&mut self, ast: &ast::LetExpr, span: Span) -> error::SpannedResult { - let name = self.variables.new_var_name(&ast.var_name.value); let var_bindings = self.expr_single(&ast.var_expr)?; + self.variables.push_scope(); + let name = self.variables.declare_var_name(&ast.var_name.value); let return_bindings = self.expr_single(&ast.return_expr)?; + self.variables.pop_scope(); let expr = ir::Expr::Let(ir::Let { name, var_expr: Box::new(var_bindings.expr()), @@ -488,11 +506,13 @@ impl<'a> IrConverter<'a> { } fn for_expr(&mut self, ast: &ast::ForExpr, span: Span) -> error::SpannedResult { - let name = self.variables.new_var_name(&ast.var_name.value); let mut var_bindings = self.expr_single(&ast.var_expr)?; let var_atom = var_bindings.atom(); + self.variables.push_scope(); + let name = self.variables.declare_var_name(&ast.var_name.value); let context_names = self.variables.explicit_context_names(name); let return_bindings = self.expr_single(&ast.return_expr)?; + self.variables.pop_scope(); let expr = ir::Expr::Map(ir::Map { context_names, var_atom, @@ -508,12 +528,14 @@ impl<'a> IrConverter<'a> { ast: &ast::QuantifiedExpr, span: Span, ) -> error::SpannedResult { - let name = self.variables.new_var_name(&ast.var_name.value); let mut var_bindings = self.expr_single(&ast.var_expr)?; let var_atom = var_bindings.atom(); + self.variables.push_scope(); + let name = self.variables.declare_var_name(&ast.var_name.value); let context_names = self.variables.explicit_context_names(name); let satisfies_bindings = self.expr_single(&ast.satisfies_expr)?; + self.variables.pop_scope(); let expr = ir::Expr::Quantified(ir::Quantified { quantifier: self.quantifier(&ast.quantifier), context_names, @@ -537,6 +559,7 @@ impl<'a> IrConverter<'a> { inline_function: &ast::InlineFunction, span: Span, ) -> error::SpannedResult { + self.variables.push_scope(); let params = inline_function .params .iter() @@ -557,14 +580,19 @@ impl<'a> IrConverter<'a> { return_type: inline_function.return_type.clone(), body: Box::new(body_bindings.expr()), }); + self.variables.pop_scope(); let binding = self.variables.new_binding(expr, span); Ok(Bindings::new(binding)) } fn param(&mut self, param: &ast::Param) -> ir::Param { ir::Param { - name: self.variables.new_var_name(¶m.name), + name: self.variables.declare_var_name(¶m.name), type_: param.type_.clone(), + default: None, + required: false, + original_name: None, + tunnel: false, } } diff --git a/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__axis_step_with_predicates.snap b/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__axis_step_with_predicates.snap index 73fff93ab..f6433a139 100644 --- a/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__axis_step_with_predicates.snap +++ b/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__axis_step_with_predicates.snap @@ -12,18 +12,30 @@ Ok( "v0", ), type_: None, + default: None, + required: false, + original_name: None, + tunnel: false, }, Param { name: Name( "v1", ), type_: None, + default: None, + required: false, + original_name: None, + tunnel: false, }, Param { name: Name( "v2", ), type_: None, + default: None, + required: false, + original_name: None, + tunnel: false, }, ], return_type: None, diff --git a/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__comma.snap b/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__comma.snap index 374339177..19415b905 100644 --- a/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__comma.snap +++ b/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__comma.snap @@ -12,18 +12,30 @@ Ok( "v0", ), type_: None, + default: None, + required: false, + original_name: None, + tunnel: false, }, Param { name: Name( "v1", ), type_: None, + default: None, + required: false, + original_name: None, + tunnel: false, }, Param { name: Name( "v2", ), type_: None, + default: None, + required: false, + original_name: None, + tunnel: false, }, ], return_type: None, diff --git a/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__comma2.snap b/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__comma2.snap index 17a9fe3aa..c7fd89043 100644 --- a/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__comma2.snap +++ b/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__comma2.snap @@ -12,18 +12,30 @@ Ok( "v0", ), type_: None, + default: None, + required: false, + original_name: None, + tunnel: false, }, Param { name: Name( "v1", ), type_: None, + default: None, + required: false, + original_name: None, + tunnel: false, }, Param { name: Name( "v2", ), type_: None, + default: None, + required: false, + original_name: None, + tunnel: false, }, ], return_type: None, diff --git a/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__empty_sequence.snap b/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__empty_sequence.snap index 5ee621e72..0db506f24 100644 --- a/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__empty_sequence.snap +++ b/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__empty_sequence.snap @@ -12,18 +12,30 @@ Ok( "v0", ), type_: None, + default: None, + required: false, + original_name: None, + tunnel: false, }, Param { name: Name( "v1", ), type_: None, + default: None, + required: false, + original_name: None, + tunnel: false, }, Param { name: Name( "v2", ), type_: None, + default: None, + required: false, + original_name: None, + tunnel: false, }, ], return_type: None, diff --git a/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__for_expr.snap b/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__for_expr.snap index 3abd2fefa..d90d789e0 100644 --- a/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__for_expr.snap +++ b/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__for_expr.snap @@ -8,7 +8,7 @@ Ok( Map { context_names: ContextNames { item: Name( - "v0", + "v1", ), position: Name( "v2", diff --git a/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__for_expr2.snap b/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__for_expr2.snap index 876ebc1a7..04da76aca 100644 --- a/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__for_expr2.snap +++ b/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__for_expr2.snap @@ -7,7 +7,7 @@ Ok( value: Let( Let { name: Name( - "v3", + "v2", ), var_expr: Spanned { value: Binary( @@ -38,7 +38,7 @@ Ok( Map { context_names: ContextNames { item: Name( - "v0", + "v3", ), position: Name( "v4", @@ -50,7 +50,7 @@ Ok( var_atom: Spanned { value: Variable( Name( - "v3", + "v2", ), ), span: 10..15, @@ -61,7 +61,7 @@ Ok( left: Spanned { value: Variable( Name( - "v0", + "v3", ), ), span: 24..26, diff --git a/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__function_call.snap b/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__function_call.snap index 70a1f3b15..558311b00 100644 --- a/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__function_call.snap +++ b/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__function_call.snap @@ -18,6 +18,10 @@ Ok( "v0", ), type_: None, + default: None, + required: false, + original_name: None, + tunnel: false, }, ], return_type: None, diff --git a/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__function_call2.snap b/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__function_call2.snap index 308044b8b..8664957ac 100644 --- a/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__function_call2.snap +++ b/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__function_call2.snap @@ -18,6 +18,10 @@ Ok( "v0", ), type_: None, + default: None, + required: false, + original_name: None, + tunnel: false, }, ], return_type: None, diff --git a/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__function_definition.snap b/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__function_definition.snap index d55c64e65..b33bd636b 100644 --- a/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__function_definition.snap +++ b/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__function_definition.snap @@ -12,6 +12,10 @@ Ok( "v0", ), type_: None, + default: None, + required: false, + original_name: None, + tunnel: false, }, ], return_type: None, diff --git a/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__integer_key_specifier.snap b/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__integer_key_specifier.snap index 5c139bbe9..e3a7923c9 100644 --- a/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__integer_key_specifier.snap +++ b/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__integer_key_specifier.snap @@ -12,18 +12,30 @@ Ok( "v0", ), type_: None, + default: None, + required: false, + original_name: None, + tunnel: false, }, Param { name: Name( "v1", ), type_: None, + default: None, + required: false, + original_name: None, + tunnel: false, }, Param { name: Name( "v2", ), type_: None, + default: None, + required: false, + original_name: None, + tunnel: false, }, ], return_type: None, diff --git a/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__let_expr.snap b/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__let_expr.snap index a9190682a..4be0cdfd3 100644 --- a/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__let_expr.snap +++ b/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__let_expr.snap @@ -7,7 +7,7 @@ Ok( value: Let( Let { name: Name( - "v0", + "v1", ), var_expr: Spanned { value: Atom( diff --git a/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__let_expr_variable.snap b/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__let_expr_variable.snap index 90f07e452..fc62effc7 100644 --- a/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__let_expr_variable.snap +++ b/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__let_expr_variable.snap @@ -7,7 +7,7 @@ Ok( value: Let( Let { name: Name( - "v0", + "v1", ), var_expr: Spanned { value: Atom( @@ -27,7 +27,7 @@ Ok( Spanned { value: Variable( Name( - "v0", + "v1", ), ), span: 19..21, diff --git a/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__let_expr_with_add.snap b/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__let_expr_with_add.snap index 37a6f25d5..a894f2099 100644 --- a/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__let_expr_with_add.snap +++ b/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__let_expr_with_add.snap @@ -7,7 +7,7 @@ Ok( value: Let( Let { name: Name( - "v0", + "v3", ), var_expr: Spanned { value: Binary( @@ -38,7 +38,7 @@ Ok( Spanned { value: Variable( Name( - "v0", + "v3", ), ), span: 25..27, diff --git a/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__multiple_axis_steps.snap b/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__multiple_axis_steps.snap index 27b84f404..cb6b45098 100644 --- a/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__multiple_axis_steps.snap +++ b/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__multiple_axis_steps.snap @@ -12,18 +12,30 @@ Ok( "v0", ), type_: None, + default: None, + required: false, + original_name: None, + tunnel: false, }, Param { name: Name( "v1", ), type_: None, + default: None, + required: false, + original_name: None, + tunnel: false, }, Param { name: Name( "v2", ), type_: None, + default: None, + required: false, + original_name: None, + tunnel: false, }, ], return_type: None, diff --git a/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__named_function_ref2.snap b/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__named_function_ref2.snap index e8a349050..13d0fb593 100644 --- a/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__named_function_ref2.snap +++ b/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__named_function_ref2.snap @@ -12,18 +12,30 @@ Ok( "v0", ), type_: None, + default: None, + required: false, + original_name: None, + tunnel: false, }, Param { name: Name( "v1", ), type_: None, + default: None, + required: false, + original_name: None, + tunnel: false, }, Param { name: Name( "v2", ), type_: None, + default: None, + required: false, + original_name: None, + tunnel: false, }, ], return_type: None, diff --git a/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__ncname_key_specifier.snap b/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__ncname_key_specifier.snap index 78ede125f..2de3b1b2d 100644 --- a/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__ncname_key_specifier.snap +++ b/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__ncname_key_specifier.snap @@ -12,18 +12,30 @@ Ok( "v0", ), type_: None, + default: None, + required: false, + original_name: None, + tunnel: false, }, Param { name: Name( "v1", ), type_: None, + default: None, + required: false, + original_name: None, + tunnel: false, }, Param { name: Name( "v2", ), type_: None, + default: None, + required: false, + original_name: None, + tunnel: false, }, ], return_type: None, diff --git a/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__postfix_lookup.snap b/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__postfix_lookup.snap index 94bc0519a..e00179670 100644 --- a/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__postfix_lookup.snap +++ b/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__postfix_lookup.snap @@ -12,18 +12,30 @@ Ok( "v0", ), type_: None, + default: None, + required: false, + original_name: None, + tunnel: false, }, Param { name: Name( "v1", ), type_: None, + default: None, + required: false, + original_name: None, + tunnel: false, }, Param { name: Name( "v2", ), type_: None, + default: None, + required: false, + original_name: None, + tunnel: false, }, ], return_type: None, diff --git a/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__quantified.snap b/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__quantified.snap index 880979fb4..436100b2a 100644 --- a/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__quantified.snap +++ b/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__quantified.snap @@ -7,7 +7,7 @@ Ok( value: Let( Let { name: Name( - "v3", + "v2", ), var_expr: Spanned { value: Binary( @@ -39,7 +39,7 @@ Ok( quantifier: Some, context_names: ContextNames { item: Name( - "v0", + "v3", ), position: Name( "v4", @@ -51,7 +51,7 @@ Ok( var_atom: Spanned { value: Variable( Name( - "v3", + "v2", ), ), span: 11..16, @@ -62,7 +62,7 @@ Ok( left: Spanned { value: Variable( Name( - "v0", + "v3", ), ), span: 28..30, diff --git a/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__single_axis_step.snap b/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__single_axis_step.snap index 90f94deaa..8991dfc7d 100644 --- a/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__single_axis_step.snap +++ b/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__ast_ir__tests__single_axis_step.snap @@ -12,18 +12,30 @@ Ok( "v0", ), type_: None, + default: None, + required: false, + original_name: None, + tunnel: false, }, Param { name: Name( "v1", ), type_: None, + default: None, + required: false, + original_name: None, + tunnel: false, }, Param { name: Name( "v2", ), type_: None, + default: None, + required: false, + original_name: None, + tunnel: false, }, ], return_type: None, diff --git a/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__span__tests__span_sequence_ir.snap b/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__span__tests__span_sequence_ir.snap index 45b2995f8..a7b506d30 100644 --- a/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__span__tests__span_sequence_ir.snap +++ b/xee-xpath-compiler/src/snapshots/xee_xpath_compiler__span__tests__span_sequence_ir.snap @@ -12,18 +12,30 @@ Ok( "v0", ), type_: None, + default: None, + required: false, + original_name: None, + tunnel: false, }, Param { name: Name( "v1", ), type_: None, + default: None, + required: false, + original_name: None, + tunnel: false, }, Param { name: Name( "v2", ), type_: None, + default: None, + required: false, + original_name: None, + tunnel: false, }, ], return_type: None, diff --git a/xee-xpath-lexer/src/explicit_whitespace.rs b/xee-xpath-lexer/src/explicit_whitespace.rs index 04a255208..e0d82a905 100644 --- a/xee-xpath-lexer/src/explicit_whitespace.rs +++ b/xee-xpath-lexer/src/explicit_whitespace.rs @@ -79,13 +79,6 @@ impl<'a> ExplicitWhitespace<'a> { ) -> Option<(Token<'a>, Span)> { let (next_token, next_span) = self.base.peek()?; match next_token { - Ok(Token::NCName(local_name)) => { - let span = span.start..next_span.end; - Some(( - Token::URIQualifiedName(URIQualifiedName { uri, local_name }), - span, - )) - } Ok(Token::Asterisk) => { let span = span.start..next_span.end; Some(( @@ -93,8 +86,14 @@ impl<'a> ExplicitWhitespace<'a> { span, )) } + Ok(next_token) => next_token.ncname().map(|local_name| { + let span = span.start..next_span.end; + ( + Token::URIQualifiedName(URIQualifiedName { uri, local_name }), + span, + ) + }), Err(_) => Some((Token::Error, span.clone())), - _ => None, } } @@ -282,6 +281,22 @@ mod tests { assert_eq!(iter.next(), None); } + #[test] + fn test_uri_qualified_name_with_keyword_local_name() { + let mut iter = ExplicitWhitespace::from_str("Q{}mod"); + assert_eq!( + iter.next(), + Some(( + Token::URIQualifiedName(URIQualifiedName { + uri: "", + local_name: "mod" + }), + 0..6 + )) + ); + assert_eq!(iter.next(), None); + } + #[test] fn test_braced_uri_literal_wildcard() { let mut iter = ExplicitWhitespace::from_str("Q{http://example.com}*"); diff --git a/xee-xslt-ast/src/ast_core.rs b/xee-xslt-ast/src/ast_core.rs index a2056c9af..f726b80bb 100644 --- a/xee-xslt-ast/src/ast_core.rs +++ b/xee-xslt-ast/src/ast_core.rs @@ -301,6 +301,7 @@ impl From for SequenceConstructorItem { pub struct ApplyTemplates { pub select: Expression, pub mode: ApplyTemplatesModeValue, + pub builtin_template_params_passthrough: bool, pub content: Vec, @@ -352,6 +353,7 @@ impl From for SequenceConstructorItem { pub struct Attribute { pub name: ValueTemplate, pub namespace: Option>, + pub namespaces: Vec, pub select: Option, pub separator: Option>, pub type_: Option, @@ -427,6 +429,7 @@ impl SelectOrSequenceConstructor for Break { #[cfg_attr(feature = "serde", derive(serde::Serialize))] pub struct CallTemplate { pub name: EqName, + pub backwards_compatible: bool, pub with_params: Vec, @@ -461,6 +464,12 @@ pub struct CharacterMap { pub span: Span, } +impl From for Declaration { + fn from(c: CharacterMap) -> Self { + Declaration::CharacterMap(Box::new(c)) + } +} + #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize))] pub struct Choose { @@ -569,6 +578,12 @@ pub struct DecimalFormat { pub span: Span, } +impl From for Declaration { + fn from(d: DecimalFormat) -> Self { + Declaration::DecimalFormat(Box::new(d)) + } +} + #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize))] pub struct Document { @@ -591,6 +606,7 @@ impl From for SequenceConstructorItem { pub struct Element { pub name: ValueTemplate, pub namespace: Option>, + pub namespaces: Vec, pub inherit_namespaces: bool, pub use_attribute_sets: Option>, pub type_: Option, @@ -748,6 +764,12 @@ impl From for OverrideContent { } } +impl From for Declaration { + fn from(f: Function) -> Self { + Declaration::Function(Box::new(f)) + } +} + #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize))] pub enum Streamability { @@ -778,6 +800,12 @@ pub struct GlobalContextItem { pub span: Span, } +impl From for Declaration { + fn from(g: GlobalContextItem) -> Self { + Declaration::GlobalContextItem(Box::new(g)) + } +} + #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize))] pub struct If { @@ -802,6 +830,12 @@ pub struct Import { pub span: Span, } +impl From for Declaration { + fn from(i: Import) -> Self { + Declaration::Import(Box::new(i)) + } +} + #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize))] pub struct ImportSchema { @@ -813,6 +847,12 @@ pub struct ImportSchema { pub schema: Option, } +impl From for Declaration { + fn from(i: ImportSchema) -> Self { + Declaration::ImportSchema(Box::new(i)) + } +} + #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize))] pub struct Include { @@ -821,6 +861,12 @@ pub struct Include { pub span: Span, } +impl From for Declaration { + fn from(i: Include) -> Self { + Declaration::Include(Box::new(i)) + } +} + #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize))] pub struct Iterate { @@ -853,6 +899,12 @@ pub struct Key { pub sequence_constructor: SequenceConstructor, } +impl From for Declaration { + fn from(k: Key) -> Self { + Declaration::Key(Box::new(k)) + } +} + #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize))] pub struct Map { @@ -983,6 +1035,12 @@ pub struct Mode { pub span: Span, } +impl From for Declaration { + fn from(m: Mode) -> Self { + Declaration::Mode(Box::new(m)) + } +} + #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize))] pub enum OnNoMatch { @@ -1004,7 +1062,8 @@ pub enum OnMultipleMatch { #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize))] pub enum Typed { - Boolean, + Yes, + No, Strict, Lax, Unspecified, @@ -1042,10 +1101,18 @@ impl SelectOrSequenceConstructor for Namespace { pub struct NamespaceAlias { pub stylesheet_prefix: PrefixOrDefault, pub result_prefix: PrefixOrDefault, + pub stylesheet_namespace: Uri, + pub result_namespace: Uri, pub span: Span, } +impl From for Declaration { + fn from(n: NamespaceAlias) -> Self { + Declaration::NamespaceAlias(Box::new(n)) + } +} + #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize))] pub enum PrefixOrDefault { @@ -1382,6 +1449,12 @@ impl SelectOrSequenceConstructor for Param { } } +impl From for Declaration { + fn from(p: Param) -> Self { + Declaration::Param(Box::new(p)) + } +} + impl From for OverrideContent { fn from(i: Param) -> Self { OverrideContent::Param(Box::new(i)) @@ -1407,6 +1480,12 @@ pub struct PreserveSpace { pub span: Span, } +impl From for Declaration { + fn from(p: PreserveSpace) -> Self { + Declaration::PreserveSpace(Box::new(p)) + } +} + #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize))] pub struct ProcessingInstruction { @@ -1540,6 +1619,12 @@ pub struct StripSpace { pub span: Span, } +impl From for Declaration { + fn from(s: StripSpace) -> Self { + Declaration::StripSpace(Box::new(s)) + } +} + #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize))] pub struct Template { @@ -1643,6 +1728,12 @@ pub struct UsePackage { pub span: Span, } +impl From for Declaration { + fn from(u: UsePackage) -> Self { + Declaration::UsePackage(Box::new(u)) + } +} + #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize))] pub enum UsePackageContent { @@ -1699,6 +1790,12 @@ impl From for SequenceConstructorItem { } } +impl From for Declaration { + fn from(v: Variable) -> Self { + Declaration::Variable(Box::new(v)) + } +} + impl From for OverrideContent { fn from(v: Variable) -> Self { OverrideContent::Variable(Box::new(v)) @@ -1823,10 +1920,18 @@ impl From for SequenceConstructorItem { pub type SequenceConstructor = Vec; +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +pub struct LiteralNamespace { + pub prefix: Prefix, + pub uri: Uri, +} + #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize))] pub struct ElementNode { pub name: Name, + pub namespaces: Vec, pub attributes: Vec<(Name, ValueTemplate)>, pub sequence_constructor: SequenceConstructor, pub span: Span, diff --git a/xee-xslt-ast/src/attributes.rs b/xee-xslt-ast/src/attributes.rs index d345ecc11..6ef65f109 100644 --- a/xee-xslt-ast/src/attributes.rs +++ b/xee-xslt-ast/src/attributes.rs @@ -132,7 +132,12 @@ impl<'a> Attributes<'a> { Ok(()) } + fn trim_token(s: &str) -> &str { + s.trim() + } + fn _boolean(s: &str, span: Span) -> Result { + let s = Self::trim_token(s); match s { "yes" | "true" | "1" => Ok(true), "no" | "false" | "0" => Ok(false), @@ -294,6 +299,7 @@ impl<'a> Attributes<'a> { } fn _eqname(&self, s: &str, span: Span) -> Result { + let s = Self::trim_token(s); if let Ok(name) = parse_name(s, &self.content.parser_context().namespaces).map(|n| n.value) { Ok(name) @@ -366,6 +372,7 @@ impl<'a> Attributes<'a> { s: &str, span: Span, ) -> Result { + let s = Self::trim_token(s); Ok(match s { "#default" => match &self.content.context.default_mode { ast::DefaultMode::Unnamed => return Ok(ast::ApplyTemplatesModeValue::Unnamed), @@ -453,6 +460,7 @@ impl<'a> Attributes<'a> { } fn _default_mode(&self, s: &str, span: Span) -> Result { + let s = Self::trim_token(s); if s == "#unnamed" { Ok(ast::DefaultMode::Unnamed) } else { @@ -611,6 +619,7 @@ impl<'a> Attributes<'a> { } fn _decimal(s: &str, span: Span) -> Result { + let s = Self::trim_token(s); Decimal::from_str(s).map_err(|_| AttributeError::Invalid { value: s.to_string(), span, @@ -851,6 +860,7 @@ impl<'a> Attributes<'a> { } fn _on_no_match(s: &str, span: Span) -> Result { + let s = Self::trim_token(s); use ast::OnNoMatch::*; match s { @@ -874,6 +884,7 @@ impl<'a> Attributes<'a> { } fn _on_multiple_match(s: &str, span: Span) -> Result { + let s = Self::trim_token(s); use ast::OnMultipleMatch::*; match s { @@ -893,10 +904,12 @@ impl<'a> Attributes<'a> { } fn _typed(s: &str, span: Span) -> Result { + let s = Self::trim_token(s); use ast::Typed::*; match s { - "boolean" => Ok(Boolean), + "yes" => Ok(Yes), + "no" => Ok(No), "strict" => Ok(Strict), "lax" => Ok(Lax), "unspecified" => Ok(Unspecified), @@ -975,6 +988,7 @@ impl<'a> Attributes<'a> { } fn _standalone(s: &str, _span: Span) -> Result { + let s = s.trim(); match s { "yes" | "1" | "true" => Ok(ast::Standalone::Bool(true)), "no" | "0" | "false" => Ok(ast::Standalone::Bool(false)), @@ -1061,6 +1075,8 @@ impl<'a> Attributes<'a> { fn _new_each_time(s: &str, span: Span) -> Result { use ast::NewEachTime::*; + let s = s.trim(); + match s { "yes" | "1" | "true" => Ok(Yes), "no" | "0" | "false" => Ok(No), diff --git a/xee-xslt-ast/src/context.rs b/xee-xslt-ast/src/context.rs index fdbcb0655..0494369ab 100644 --- a/xee-xslt-ast/src/context.rs +++ b/xee-xslt-ast/src/context.rs @@ -184,10 +184,28 @@ impl Context { ) } + pub(crate) fn literal_namespaces(&self, state: &State) -> Vec { + self.prefixes + .iter() + .map(|(prefix, namespace)| ast::LiteralNamespace { + prefix: state.xot.prefix_str(*prefix).to_string(), + uri: state.xot.namespace_str(*namespace).to_string(), + }) + .collect() + } + pub(crate) fn variable_names(&self) -> &VariableNames { &self.variable_names } + pub(crate) fn builtin_template_params_passthrough(&self) -> bool { + true + } + + pub(crate) fn backwards_compatible(&self) -> bool { + self.version < Decimal::from_str("2.0").unwrap() + } + pub(crate) fn parser_context(&self, state: &State) -> XPathParserContext { let namespaces = self.namespaces(state); XPathParserContext::new(namespaces, self.variable_names.clone()) diff --git a/xee-xslt-ast/src/element.rs b/xee-xslt-ast/src/element.rs index 1235a53a8..0675b4b1e 100644 --- a/xee-xslt-ast/src/element.rs +++ b/xee-xslt-ast/src/element.rs @@ -65,7 +65,32 @@ impl<'a> Content<'a> { } pub(crate) fn declarations(&self) -> Result { - DECLARATIONS_CONTENT.get_or_init(|| children(declarations()))(self) + let mut declarations = Vec::new(); + let mut child = self.state.xot.first_child(self.node); + + while let Some(node) = child { + child = self.state.xot.next_sibling(node); + + let Some(element) = self.state.xot.element(node) else { + continue; + }; + + if self.state.names.declaration_name(element.name()).is_some() { + let content = self.with_node(node); + let declaration = + content.parse_element(element, ast::Declaration::parse_declaration)?; + declarations.push(declaration); + continue; + } + + if self.state.xot.namespace_for_name(element.name()) == self.state.names.xsl_ns { + return Err(ElementError::Unexpected { + span: self.state.span(node).ok_or(ElementError::Internal)?, + }); + } + } + + Ok(declarations) } } diff --git a/xee-xslt-ast/src/instruction.rs b/xee-xslt-ast/src/instruction.rs index 7db34b7c9..09fcd4e69 100644 --- a/xee-xslt-ast/src/instruction.rs +++ b/xee-xslt-ast/src/instruction.rs @@ -123,6 +123,16 @@ impl InstructionParser for ast::Declaration { impl InstructionParser for ast::ElementNode { fn parse(content: &Content, attributes: &Attributes) -> Result { + let namespaces = content + .state + .xot + .namespace_declarations(content.node) + .into_iter() + .map(|(prefix, namespace)| ast::LiteralNamespace { + prefix: content.state.xot.prefix_str(prefix).to_string(), + uri: content.state.xot.namespace_str(namespace).to_string(), + }) + .collect(); let mut element_attributes = Vec::new(); for key in content.state.xot.attributes(content.node).keys() { let name = content.state.xot.name_ref(key, content.node)?; @@ -140,6 +150,7 @@ impl InstructionParser for ast::ElementNode { .name_ref(attributes.element.name(), content.node)?; Ok(ast::ElementNode { name: name.to_owned(), + namespaces, attributes: element_attributes, span: content.span()?, sequence_constructor: content.sequence_constructor()?, @@ -307,6 +318,9 @@ impl InstructionParser for ast::ApplyTemplates { }, ), mode, + builtin_template_params_passthrough: content + .context + .builtin_template_params_passthrough(), span: content.span()?, content: parse(content)?, }) @@ -334,10 +348,12 @@ impl InstructionParser for ast::Assert { impl InstructionParser for ast::Attribute { fn parse(content: &Content, attributes: &Attributes) -> Result { let names = &content.state.names; + let namespaces = content.context.literal_namespaces(content.state); Ok(ast::Attribute { name: attributes.required(names.name, attributes.value_template(attributes.qname()))?, namespace: attributes .optional(names.namespace, attributes.value_template(attributes.uri()))?, + namespaces, select: attributes.optional(names.select, attributes.xpath())?, separator: attributes.optional( names.separator, @@ -358,10 +374,8 @@ static ATTRIBUTE_SET_CONTENT: ContentParseLock> = OnceLock:: impl InstructionParser for ast::AttributeSet { fn parse(content: &Content, attributes: &Attributes) -> Result { let names = &content.state.names; - let parse = ATTRIBUTE_SET_CONTENT.get_or_init(|| children(instruction(names.xsl_attribute).many())); - Ok(ast::AttributeSet { name: attributes.required(names.name, attributes.eqname())?, use_attribute_sets: attributes @@ -399,6 +413,7 @@ impl InstructionParser for ast::CallTemplate { Ok(ast::CallTemplate { name: attributes.required(names.name, attributes.eqname())?, + backwards_compatible: content.context.backwards_compatible(), span: content.span()?, @@ -580,10 +595,12 @@ impl InstructionParser for ast::Document { impl InstructionParser for ast::Element { fn parse(content: &Content, attributes: &Attributes) -> Result { let names = &content.state.names; + let namespaces = content.context.literal_namespaces(content.state); Ok(ast::Element { name: attributes.required(names.name, attributes.value_template(attributes.qname()))?, namespace: attributes .optional(names.namespace, attributes.value_template(attributes.uri()))?, + namespaces, inherit_namespaces: attributes.boolean_with_default(names.inherit_namespaces, false)?, use_attribute_sets: attributes .optional(names.use_attribute_sets, attributes.eqnames())?, @@ -1145,17 +1162,36 @@ impl InstructionParser for ast::NamespaceAlias { fn parse(content: &Content, attributes: &Attributes) -> Result { let names = &content.state.names; + let stylesheet_prefix = + attributes.required(names.stylesheet_prefix, attributes.prefix_or_default())?; + let result_prefix = + attributes.required(names.result_prefix, attributes.prefix_or_default())?; + let namespaces = content.context.namespaces(content.state); Ok(ast::NamespaceAlias { - stylesheet_prefix: attributes - .required(names.stylesheet_prefix, attributes.prefix_or_default())?, - result_prefix: attributes - .required(names.result_prefix, attributes.prefix_or_default())?, + stylesheet_namespace: resolve_prefix_or_default_namespace( + &stylesheet_prefix, + &namespaces, + ) + .unwrap_or_default(), + result_namespace: resolve_prefix_or_default_namespace(&result_prefix, &namespaces) + .unwrap_or_default(), + stylesheet_prefix, + result_prefix, span: content.span()?, }) } } +fn resolve_prefix_or_default_namespace( + prefix_or_default: &ast::PrefixOrDefault, + namespaces: &xee_xpath_ast::Namespaces, +) -> Option { + match prefix_or_default { + ast::PrefixOrDefault::Default => Some(namespaces.default_element_namespace().to_string()), + ast::PrefixOrDefault::Prefix(prefix) => namespaces.by_prefix(prefix).map(str::to_string), + } +} static NEXT_ITERATION_CONTENT: ContentParseLock> = OnceLock::new(); impl InstructionParser for ast::NextIteration { diff --git a/xee-xslt-ast/src/names.rs b/xee-xslt-ast/src/names.rs index 513e6abd6..9afe8b941 100644 --- a/xee-xslt-ast/src/names.rs +++ b/xee-xslt-ast/src/names.rs @@ -140,8 +140,24 @@ impl DeclarationName { pub(crate) fn parse(&self, attributes: &Attributes) -> Result { match self { DeclarationName::Accumulator => ast::Accumulator::parse_declaration(attributes), - DeclarationName::Template => ast::Template::parse_declaration(attributes), + DeclarationName::CharacterMap => ast::CharacterMap::parse_declaration(attributes), + DeclarationName::DecimalFormat => ast::DecimalFormat::parse_declaration(attributes), + DeclarationName::Function => ast::Function::parse_declaration(attributes), + DeclarationName::GlobalContextItem => { + ast::GlobalContextItem::parse_declaration(attributes) + } + DeclarationName::Import => ast::Import::parse_declaration(attributes), + DeclarationName::ImportSchema => ast::ImportSchema::parse_declaration(attributes), + DeclarationName::Include => ast::Include::parse_declaration(attributes), + DeclarationName::Key => ast::Key::parse_declaration(attributes), + DeclarationName::Mode => ast::Mode::parse_declaration(attributes), + DeclarationName::NamespaceAlias => ast::NamespaceAlias::parse_declaration(attributes), DeclarationName::Output => ast::Output::parse_declaration(attributes), + DeclarationName::Param => ast::Param::parse_declaration(attributes), + DeclarationName::PreserveSpace => ast::PreserveSpace::parse_declaration(attributes), + DeclarationName::StripSpace => ast::StripSpace::parse_declaration(attributes), + DeclarationName::Template => ast::Template::parse_declaration(attributes), + DeclarationName::Variable => ast::Variable::parse_declaration(attributes), _ => Err(ElementError::Unsupported(format!( "Unsupported declaration: {:?}", &self diff --git a/xee-xslt-ast/tests/snapshots/snapshot_tests__apply_templates_explicit_default_mode.snap b/xee-xslt-ast/tests/snapshots/snapshot_tests__apply_templates_explicit_default_mode.snap index ce7197d9a..80185bb30 100644 --- a/xee-xslt-ast/tests/snapshots/snapshot_tests__apply_templates_explicit_default_mode.snap +++ b/xee-xslt-ast/tests/snapshots/snapshot_tests__apply_templates_explicit_default_mode.snap @@ -25,6 +25,7 @@ Ok(Instruction(ApplyTemplates(ApplyTemplates( namespace_str: "", prefix_str: "", )), + builtin_template_params_passthrough: true, content: [], span: Span( start: 1, diff --git a/xee-xslt-ast/tests/snapshots/snapshot_tests__apply_templates_explicit_mode.snap b/xee-xslt-ast/tests/snapshots/snapshot_tests__apply_templates_explicit_mode.snap index 06ad6bef8..fe45a02e3 100644 --- a/xee-xslt-ast/tests/snapshots/snapshot_tests__apply_templates_explicit_mode.snap +++ b/xee-xslt-ast/tests/snapshots/snapshot_tests__apply_templates_explicit_mode.snap @@ -25,6 +25,7 @@ Ok(Instruction(ApplyTemplates(ApplyTemplates( namespace_str: "", prefix_str: "", )), + builtin_template_params_passthrough: true, content: [], span: Span( start: 1, diff --git a/xee-xslt-ast/tests/snapshots/snapshot_tests__apply_templates_implicit_default_mode_is_unnamed.snap b/xee-xslt-ast/tests/snapshots/snapshot_tests__apply_templates_implicit_default_mode_is_unnamed.snap index 17bc7e2aa..5ac7df9c9 100644 --- a/xee-xslt-ast/tests/snapshots/snapshot_tests__apply_templates_implicit_default_mode_is_unnamed.snap +++ b/xee-xslt-ast/tests/snapshots/snapshot_tests__apply_templates_implicit_default_mode_is_unnamed.snap @@ -21,6 +21,7 @@ Ok(Instruction(ApplyTemplates(ApplyTemplates( ), ), mode: Unnamed, + builtin_template_params_passthrough: true, content: [], span: Span( start: 1, diff --git a/xee-xslt-ast/tests/snapshots/snapshot_tests__apply_templates_with_mixed_content.snap b/xee-xslt-ast/tests/snapshots/snapshot_tests__apply_templates_with_mixed_content.snap index 14647fbc2..5f01b0934 100644 --- a/xee-xslt-ast/tests/snapshots/snapshot_tests__apply_templates_with_mixed_content.snap +++ b/xee-xslt-ast/tests/snapshots/snapshot_tests__apply_templates_with_mixed_content.snap @@ -21,6 +21,7 @@ Ok(Instruction(ApplyTemplates(ApplyTemplates( ), ), mode: Unnamed, + builtin_template_params_passthrough: true, content: [ Sort(Sort( select: None, diff --git a/xee-xslt-ast/tests/snapshots/snapshot_tests__attributes_on_literal_element.snap b/xee-xslt-ast/tests/snapshots/snapshot_tests__attributes_on_literal_element.snap index 9f69d0dce..5bee58158 100644 --- a/xee-xslt-ast/tests/snapshots/snapshot_tests__attributes_on_literal_element.snap +++ b/xee-xslt-ast/tests/snapshots/snapshot_tests__attributes_on_literal_element.snap @@ -1,5 +1,5 @@ --- -source: xee-xslt-ast/src/instruction.rs +source: xee-xslt-ast/tests/snapshot_tests.rs expression: "parse_sequence_constructor_item(r#\"

\"#)" --- Ok(Content(Element(ElementNode( @@ -8,6 +8,12 @@ Ok(Content(Element(ElementNode( namespace_str: "", prefix_str: "", ), + namespaces: [ + LiteralNamespace( + prefix: "xsl", + uri: "http://www.w3.org/1999/XSL/Transform", + ), + ], attributes: [ (OwnedName( local_name_str: "foo", diff --git a/xee-xslt-ast/tests/snapshots/snapshot_tests__literal_result_element.snap b/xee-xslt-ast/tests/snapshots/snapshot_tests__literal_result_element.snap index 32bb5ac98..e4c8808d0 100644 --- a/xee-xslt-ast/tests/snapshots/snapshot_tests__literal_result_element.snap +++ b/xee-xslt-ast/tests/snapshots/snapshot_tests__literal_result_element.snap @@ -1,5 +1,5 @@ --- -source: xee-xslt-ast/src/instruction.rs +source: xee-xslt-ast/tests/snapshot_tests.rs expression: "parse_sequence_constructor_item(r#\"\"#)" --- Ok(Content(Element(ElementNode( @@ -8,6 +8,7 @@ Ok(Content(Element(ElementNode( namespace_str: "", prefix_str: "", ), + namespaces: [], attributes: [], sequence_constructor: [], span: Span( diff --git a/xee-xslt-ast/tests/snapshots/snapshot_tests__literal_result_element_with_standard_attribute.snap b/xee-xslt-ast/tests/snapshots/snapshot_tests__literal_result_element_with_standard_attribute.snap index 69299e37a..3741bf1cb 100644 --- a/xee-xslt-ast/tests/snapshots/snapshot_tests__literal_result_element_with_standard_attribute.snap +++ b/xee-xslt-ast/tests/snapshots/snapshot_tests__literal_result_element_with_standard_attribute.snap @@ -1,5 +1,5 @@ --- -source: xee-xslt-ast/src/instruction.rs +source: xee-xslt-ast/tests/snapshot_tests.rs expression: "parse_sequence_constructor_item(r#\"\"#)" --- Ok(Content(Element(ElementNode( @@ -8,6 +8,12 @@ Ok(Content(Element(ElementNode( namespace_str: "", prefix_str: "", ), + namespaces: [ + LiteralNamespace( + prefix: "xsl", + uri: "http://www.w3.org/1999/XSL/Transform", + ), + ], attributes: [], sequence_constructor: [], span: Span( diff --git a/xee-xslt-ast/tests/snapshots/snapshot_tests__nested_literal_elements.snap b/xee-xslt-ast/tests/snapshots/snapshot_tests__nested_literal_elements.snap index 1d5aa5e19..8f59e9b5e 100644 --- a/xee-xslt-ast/tests/snapshots/snapshot_tests__nested_literal_elements.snap +++ b/xee-xslt-ast/tests/snapshots/snapshot_tests__nested_literal_elements.snap @@ -1,5 +1,5 @@ --- -source: xee-xslt-ast/src/instruction.rs +source: xee-xslt-ast/tests/snapshot_tests.rs expression: "parse_sequence_constructor_item(r#\"

\"#)" --- Ok(Instruction(If(If( @@ -30,6 +30,7 @@ Ok(Instruction(If(If( namespace_str: "", prefix_str: "", ), + namespaces: [], attributes: [], sequence_constructor: [ Content(Element(ElementNode( @@ -38,6 +39,7 @@ Ok(Instruction(If(If( namespace_str: "", prefix_str: "", ), + namespaces: [], attributes: [], sequence_constructor: [], span: Span( diff --git a/xee-xslt-ast/tests/snapshots/snapshot_tests__sequence_constructor_nested_in_literal_element.snap b/xee-xslt-ast/tests/snapshots/snapshot_tests__sequence_constructor_nested_in_literal_element.snap index ca1318c41..cf17b048a 100644 --- a/xee-xslt-ast/tests/snapshots/snapshot_tests__sequence_constructor_nested_in_literal_element.snap +++ b/xee-xslt-ast/tests/snapshots/snapshot_tests__sequence_constructor_nested_in_literal_element.snap @@ -1,5 +1,5 @@ --- -source: xee-xslt-ast/src/instruction.rs +source: xee-xslt-ast/tests/snapshot_tests.rs expression: "parse_sequence_constructor_item(r#\"

foo

\"#)" --- Ok(Instruction(If(If( @@ -30,6 +30,7 @@ Ok(Instruction(If(If( namespace_str: "", prefix_str: "", ), + namespaces: [], attributes: [], sequence_constructor: [ Instruction(If(If( diff --git a/xee-xslt-compiler/src/ast_ir.rs b/xee-xslt-compiler/src/ast_ir.rs index a58500360..e2eec59aa 100644 --- a/xee-xslt-compiler/src/ast_ir.rs +++ b/xee-xslt-compiler/src/ast_ir.rs @@ -1,24 +1,50 @@ -use ahash::HashSetExt; +use ahash::{HashMap, HashMapExt, HashSetExt}; use xee_name::{Name, Namespaces, FN_NAMESPACE}; -use xee_interpreter::{context::StaticContext, error, interpreter, sequence::QNameOrString}; +use std::collections::HashSet; +use std::path::PathBuf; +use xee_interpreter::{ + context::StaticContext, + error, + interpreter::{self, instruction::RaisedError}, + sequence::QNameOrString, +}; use xee_ir::{compile_xslt, ir, Bindings, Variables}; use xee_xpath_ast::{ast as xpath_ast, pattern::transform_pattern, span::Spanned}; -use xee_xslt_ast::{ast, error::ElementError, parse_transform}; -use xot::xmlname::NameStrInfo; +use xee_xslt_ast::{ + ast, + error::{AttributeError, ElementError}, + parse_transform, +}; +use xot::xmlname::{NameStrInfo, OwnedName}; -use crate::{default_declarations::text_only_copy_declarations, priority::default_priority}; +use crate::priority::default_priority; struct IrConverter<'a> { variables: Variables, static_context: &'a StaticContext, + initial_mode: ast::ApplyTemplatesModeValue, + xslt_functions: HashMap<(OwnedName, u8), OwnedName>, + namespace_aliases: HashMap, } pub fn compile( transform: ast::Transform, static_context: StaticContext, ) -> error::SpannedResult { - let mut ir_converter = IrConverter::new(&static_context); + compile_with_initial_mode( + transform, + static_context, + ast::ApplyTemplatesModeValue::Unnamed, + ) +} + +pub fn compile_with_initial_mode( + transform: ast::Transform, + static_context: StaticContext, + initial_mode: ast::ApplyTemplatesModeValue, +) -> error::SpannedResult { + let mut ir_converter = IrConverter::new(&static_context, initial_mode); let declarations = ir_converter.transform(&transform)?; compile_xslt(declarations, static_context) } @@ -26,45 +52,225 @@ pub fn compile( pub fn parse( static_context: StaticContext, xslt: &str, +) -> error::SpannedResult { + parse_with_base_dir(static_context, xslt, std::env::current_dir().ok()) +} + +pub fn parse_with_base_dir( + static_context: StaticContext, + xslt: &str, + base_dir: Option, +) -> error::SpannedResult { + parse_with_base_dir_and_initial_mode(static_context, xslt, base_dir, None) +} + +pub fn parse_with_base_dir_and_initial_mode( + static_context: StaticContext, + xslt: &str, + base_dir: Option, + initial_mode: Option, ) -> error::SpannedResult { let transform = parse_transform(xslt); // TODO: better error handling let mut transform = match transform { Ok(transform) => transform, - Err(ElementError::Unexpected { span }) => { - let text = xslt.get(span.start..span.end); + Err(e) => { + return Err(map_parse_error(xslt, e)); + } + }; + + // Process xsl:import and xsl:include directives + let mut visited = HashSet::new(); + transform.declarations = + process_imports_and_includes(transform.declarations, base_dir, &mut visited)?; + + let initial_mode = parse_initial_mode_value(initial_mode)?; + if matches!(initial_mode, ast::ApplyTemplatesModeValue::Unnamed) { + compile(transform, static_context) + } else { + compile_with_initial_mode(transform, static_context, initial_mode) + } +} + +fn parse_initial_mode_value( + initial_mode: Option, +) -> error::SpannedResult { + let Some(initial_mode) = initial_mode else { + return Ok(ast::ApplyTemplatesModeValue::Unnamed); + }; + + let initial_mode = initial_mode.trim(); + if initial_mode == "#unnamed" { + return Ok(ast::ApplyTemplatesModeValue::Unnamed); + } + if let Some(rest) = initial_mode.strip_prefix("Q{") { + let Some((namespace, local_name)) = rest.split_once('}') else { return Err(error::Error::Unsupported(format!( - "Failed parsing XSLT, Unexpected {} {:?}", - text.unwrap_or_default(), - span + "Unsupported initial mode name: {initial_mode}" )) .into()); + }; + return Ok(ast::ApplyTemplatesModeValue::EqName(OwnedName::new( + local_name.to_string(), + namespace.to_string(), + "".to_string(), + ))); + } + if initial_mode.contains(':') { + return Err(error::Error::Unsupported(format!( + "Unsupported prefixed initial mode name: {initial_mode}" + )) + .into()); + } + + Ok(ast::ApplyTemplatesModeValue::EqName(OwnedName::new( + initial_mode.to_string(), + "".to_string(), + "".to_string(), + ))) +} + +fn map_parse_error(xslt: &str, error: ElementError) -> error::SpannedError { + match error { + ElementError::Attribute(attribute_error) => match attribute_error { + AttributeError::NotFound { span, .. } => error::SpannedError { + error: error::Error::XTSE0010, + span: Some((span.start..span.end).into()), + }, + AttributeError::Unexpected { span, .. } => error::SpannedError { + error: error::Error::XTSE0090, + span: Some((span.start..span.end).into()), + }, + AttributeError::Invalid { span, .. } | AttributeError::InvalidEqName { span, .. } => { + error::SpannedError { + error: error::Error::XTSE0020, + span: Some((span.start..span.end).into()), + } + } + other => error::Error::Unsupported(format!("Failed parsing XSLT: {:?}", other)).into(), + }, + ElementError::Unexpected { span } => { + let text = xslt.get(span.start..span.end).unwrap_or_default(); + error::Error::Unsupported(format!( + "Failed parsing XSLT, Unexpected {} {:?}", + text, span + )) + .into() } - Err(e) => { - return Err(error::Error::Unsupported(format!("Failed parsing XSLT: {:?}", e)).into()); + other => error::Error::Unsupported(format!("Failed parsing XSLT: {:?}", other)).into(), + } +} + +fn process_imports_and_includes( + declarations: ast::Declarations, + base_dir: Option, + visited: &mut HashSet, +) -> error::SpannedResult { + let mut local_declarations = Vec::new(); + let mut imports = Vec::new(); // Collect imports in order + + for decl in declarations { + match &decl { + ast::Declaration::Import(import) => { + // Load and parse the imported stylesheet + let (imported_decls, resolved_path) = + load_stylesheet(&import.href.to_string(), base_dir.as_ref())?; + if visited.contains(&resolved_path) { + return Err(error::Error::Unsupported(format!( + "Circular import detected: '{}'", + resolved_path.display() + )) + .into()); + } + visited.insert(resolved_path); + // Recursively process imports in the imported stylesheet + let processed = + process_imports_and_includes(imported_decls, base_dir.clone(), visited)?; + imports.push(processed); + } + ast::Declaration::Include(include) => { + // Load and parse the included stylesheet + let (included_decls, resolved_path) = + load_stylesheet(&include.href.to_string(), base_dir.as_ref())?; + if visited.contains(&resolved_path) { + return Err(error::Error::Unsupported(format!( + "Circular include detected: '{}'", + resolved_path.display() + )) + .into()); + } + visited.insert(resolved_path); + // Recursively process imports in the included stylesheet + let processed = + process_imports_and_includes(included_decls, base_dir.clone(), visited)?; + local_declarations.extend(processed); + } + _ => { + local_declarations.push(decl); + } } + } + + // Build result: imports first (lower precedence), then local declarations (higher precedence) + // This ensures later imports override earlier imports, and local declarations override all imports + let mut result = Vec::new(); + for import_decls in imports { + result.extend(import_decls); + } + result.extend(local_declarations); + Ok(result) +} + +fn load_stylesheet( + href: &str, + base_dir: Option<&std::path::PathBuf>, +) -> error::SpannedResult<(ast::Declarations, PathBuf)> { + // Resolve the file path + let path = if let Some(base_dir) = base_dir { + base_dir.join(href) + } else { + std::path::PathBuf::from(href) }; - // insert default rules early on in precedence order - let mut declarations = text_only_copy_declarations().unwrap(); - declarations.extend(transform.declarations); - transform.declarations = declarations; - compile(transform, static_context) + + let canonical = path.canonicalize().unwrap_or_else(|_| path.clone()); + + // Try to read the file + let content = std::fs::read_to_string(&path).map_err(|e| { + error::Error::Unsupported(format!( + "Failed to load stylesheet '{}': {}", + path.display(), + e + )) + })?; + + // Parse the stylesheet + let transform = parse_transform(&content).map_err(|e| { + error::Error::Unsupported(format!( + "Failed to parse imported stylesheet '{}': {:?}", + path.display(), + e + )) + })?; + + Ok((transform.declarations, canonical)) } impl<'a> IrConverter<'a> { - fn new(static_context: &'a StaticContext) -> Self { + fn new(static_context: &'a StaticContext, initial_mode: ast::ApplyTemplatesModeValue) -> Self { IrConverter { variables: Variables::new(), static_context, + initial_mode, + xslt_functions: HashMap::new(), + namespace_aliases: HashMap::new(), } } fn main_sequence_constructor(&mut self) -> ast::SequenceConstructor { vec![ast::SequenceConstructorItem::Instruction( ast::SequenceConstructorInstruction::ApplyTemplates(Box::new(ast::ApplyTemplates { - // TODO: mode should be configurable from the outside somehow, - // the XSTL test suite I think requires this. - mode: ast::ApplyTemplatesModeValue::Unnamed, + mode: self.initial_mode.clone(), + builtin_template_params_passthrough: true, select: ast::Expression { xpath: xee_xpath_ast::ast::XPath::parse( "/", @@ -104,6 +310,22 @@ impl<'a> IrConverter<'a> { )) } + fn static_function_call_expr( + &mut self, + name: &str, + namespace: &str, + arity: u8, + args: Vec, + ) -> ir::Expr { + ir::Expr::FunctionCall(ir::FunctionCall { + atom: Spanned::new( + self.static_function_atom(name, namespace, arity), + (0..0).into(), + ), + args, + }) + } + fn simple_content_expr( &mut self, select_atom: ir::AtomS, @@ -116,16 +338,74 @@ impl<'a> IrConverter<'a> { } fn transform(&mut self, transform: &ast::Transform) -> error::SpannedResult { + self.register_xslt_function_names(&transform.declarations)?; + self.collect_namespace_aliases(&transform.declarations); + // Register global variable/param names early so $var references resolve. + let global_vars = self.collect_global_variables(&transform.declarations)?; + let main_sequence_constructor = self.main_sequence_constructor(); let main = self.sequence_constructor_function(&main_sequence_constructor)?; let mut declarations = ir::Declarations::new(main); + declarations.global_variables = global_vars; for declaration in &transform.declarations { self.declaration(&mut declarations, declaration)?; } + Ok(declarations) } + fn collect_namespace_aliases(&mut self, declarations: &[ast::Declaration]) { + for declaration in declarations { + let ast::Declaration::NamespaceAlias(namespace_alias) = declaration else { + continue; + }; + self.namespace_aliases.insert( + namespace_alias.stylesheet_namespace.clone(), + namespace_alias.result_namespace.clone(), + ); + } + } + + fn apply_namespace_alias(&self, name: &ast::Name) -> ast::Name { + let Some(namespace) = self.namespace_aliases.get(name.namespace()) else { + return name.clone(); + }; + + let prefix = if namespace.is_empty() { + String::new() + } else { + name.prefix().to_string() + }; + Name::new(name.local_name().to_string(), namespace.clone(), prefix) + } + + fn register_xslt_function_names( + &mut self, + declarations: &[ast::Declaration], + ) -> error::SpannedResult<()> { + for declaration in declarations { + let ast::Declaration::Function(function) = declaration else { + continue; + }; + + let arity = u8::try_from(function.params.len()).map_err(|_| { + error::Error::Unsupported("Too many XSLT function parameters".to_string()) + })?; + + let hidden_name = OwnedName::new( + format!("function-{}", self.xslt_functions.len()), + "urn:xee:internal:function".to_string(), + "xee-internal".to_string(), + ); + self.xslt_functions + .insert((function.name.clone(), arity), hidden_name.clone()); + self.variables.new_var_name(&hidden_name); + } + + Ok(()) + } + fn declaration( &mut self, declarations: &mut ir::Declarations, @@ -136,12 +416,247 @@ impl<'a> IrConverter<'a> { Template(template) => self.template(declarations, template), Mode(mode) => self.mode(declarations, mode), Output(output) => self.output(declarations, output), - _ => Err(error::Error::Unsupported(format!( - "Declaration not supported: {:?}", - declaration - )) - .into()), + // Import/Include already handled during pre-processing in parse_with_base_dir + Import(_) | Include(_) => Ok(()), + // These declarations are parsed but not yet compiled - skip gracefully + // to allow stylesheets containing them to still process templates + Function(_) | Variable(_) | Param(_) | Key(_) | StripSpace(_) | PreserveSpace(_) + | DecimalFormat(_) | CharacterMap(_) | NamespaceAlias(_) | ImportSchema(_) + | UsePackage(_) | GlobalContextItem(_) | Accumulator(_) => Ok(()), + } + } + + fn collect_global_variables( + &mut self, + declarations: &[ast::Declaration], + ) -> error::SpannedResult> { + for decl in declarations { + match decl { + ast::Declaration::Variable(var) => { + self.variables.new_var_name(&var.name); + } + ast::Declaration::Param(param) => { + self.variables.new_var_name(¶m.name); + } + ast::Declaration::Function(function) => { + let arity = u8::try_from(function.params.len()).map_err(|_| { + error::Error::Unsupported("Too many XSLT function parameters".to_string()) + })?; + let hidden_name = self + .xslt_functions + .get(&(function.name.clone(), arity)) + .ok_or_else(|| { + error::Error::Unsupported("Unregistered XSLT function name".to_string()) + })?; + self.variables.new_var_name(hidden_name); + } + _ => {} + } + } + + let mut globals = Vec::new(); + for decl in declarations { + match decl { + ast::Declaration::Variable(var) => { + let name = self.variables.lookup_var_name(&var.name).unwrap(); + let expr = self.with_hidden_global_name(&var.name, |this| { + let context_names = this.variables.push_context(); + let params = Self::context_params(&context_names); + let expr = this.global_variable_expr( + var.select.as_ref(), + &var.sequence_constructor, + var.as_.as_ref(), + )?; + this.variables.pop_context(); + Ok((params, expr)) + })?; + globals.push(ir::GlobalVariable { + name, + original_name: None, + required: false, + params: expr.0, + expr: expr.1, + }); + } + ast::Declaration::Param(param) => { + self.validate_param(param)?; + let name = self.variables.lookup_var_name(¶m.name).unwrap(); + let expr = self.with_hidden_global_name(¶m.name, |this| { + let context_names = this.variables.push_context(); + let params = Self::context_params(&context_names); + let expr = this.global_param_expr( + param.select.as_ref(), + ¶m.sequence_constructor, + )?; + this.variables.pop_context(); + Ok((params, expr)) + })?; + globals.push(ir::GlobalVariable { + name, + original_name: Some(param.name.clone()), + required: param.required, + params: expr.0, + expr: expr.1, + }); + } + ast::Declaration::Function(function) => { + let arity = u8::try_from(function.params.len()).map_err(|_| { + error::Error::Unsupported("Too many XSLT function parameters".to_string()) + })?; + let hidden_name = self + .xslt_functions + .get(&(function.name.clone(), arity)) + .ok_or_else(|| { + error::Error::Unsupported("Unregistered XSLT function name".to_string()) + })?; + let name = self.variables.lookup_var_name(hidden_name).unwrap(); + let context_names = self.variables.push_context(); + let params = Self::context_params(&context_names); + let function_definition = self.xslt_function_definition(function)?; + self.variables.pop_context(); + let expr = Spanned::new( + ir::Expr::FunctionDefinition(function_definition), + (function.span.start..function.span.end).into(), + ); + globals.push(ir::GlobalVariable { + name, + original_name: None, + required: false, + params, + expr, + }); + } + _ => {} + } + } + Ok(globals) + } + + fn with_hidden_global_name(&mut self, name: &ast::Name, f: F) -> error::SpannedResult + where + F: FnOnce(&mut Self) -> error::SpannedResult, + { + let hidden_name = self.variables.remove_var_name_in_current_scope(name); + let result = f(self); + if let Some(hidden_name) = hidden_name { + self.variables + .insert_var_name_in_current_scope(name.clone(), hidden_name); } + result + } + + fn validate_param(&self, param: &ast::Param) -> error::SpannedResult<()> { + if param.required && (param.select.is_some() || !param.sequence_constructor.is_empty()) { + return Err(error::Error::XTSE0010.into()); + } + Ok(()) + } + + fn global_variable_expr( + &mut self, + select: Option<&ast::Expression>, + sequence_constructor: &ast::SequenceConstructor, + sequence_type: Option<&xpath_ast::SequenceType>, + ) -> error::SpannedResult { + let expr = if let Some(select) = select { + self.expression(select)?.expr() + } else if sequence_type.is_some() { + self.sequence_constructor(sequence_constructor)?.expr() + } else if !sequence_constructor.is_empty() { + self.temporary_tree(sequence_constructor)?.expr() + } else { + Spanned::new(ir::Expr::Atom(self.empty_string()), (0..0).into()) + }; + self.convert_expr(expr, sequence_type, RaisedError::XTTE0570) + } + + fn global_param_expr( + &mut self, + select: Option<&ast::Expression>, + sequence_constructor: &ast::SequenceConstructor, + ) -> error::SpannedResult { + let expr = if let Some(select) = select { + self.expression(select)?.expr() + } else if !sequence_constructor.is_empty() { + self.sequence_constructor(sequence_constructor)?.expr() + } else { + self.empty_sequence() + }; + Ok(expr) + } + + fn convert_expr( + &mut self, + expr: ir::ExprS, + sequence_type: Option<&xpath_ast::SequenceType>, + error: RaisedError, + ) -> error::SpannedResult { + let Some(sequence_type) = sequence_type else { + return Ok(expr); + }; + + let binding = self.variables.new_binding(expr.value, expr.span); + let (atom, bindings) = Bindings::new(binding).atom_bindings(); + Ok(bindings + .bind_expr_no_span( + &mut self.variables, + ir::Expr::ConvertSequence(ir::ConvertSequence { + atom, + sequence_type: sequence_type.clone(), + error, + }), + ) + .expr()) + } + + fn convert_bindings( + &mut self, + bindings: Bindings, + sequence_type: Option<&xpath_ast::SequenceType>, + error: RaisedError, + ) -> error::SpannedResult { + let Some(sequence_type) = sequence_type else { + return Ok(bindings); + }; + + let (atom, bindings) = bindings.atom_bindings(); + Ok(bindings.bind_expr_no_span( + &mut self.variables, + ir::Expr::ConvertSequence(ir::ConvertSequence { + atom, + sequence_type: sequence_type.clone(), + error, + }), + )) + } + + fn context_params(context_names: &ir::ContextNames) -> Vec { + vec![ + ir::Param { + name: context_names.item.clone(), + type_: None, + default: None, + required: false, + original_name: None, + tunnel: false, + }, + ir::Param { + name: context_names.position.clone(), + type_: None, + default: None, + required: false, + original_name: None, + tunnel: false, + }, + ir::Param { + name: context_names.last.clone(), + type_: None, + default: None, + required: false, + original_name: None, + tunnel: false, + }, + ] } fn template( @@ -149,40 +664,210 @@ impl<'a> IrConverter<'a> { declarations: &mut ir::Declarations, template: &ast::Template, ) -> error::SpannedResult<()> { + for param in &template.params { + self.validate_param(param)?; + } + // Determine type of template first before creating function definition if let Some(pattern) = &template.match_ { - let priority = if let Some(priority) = &template.priority { - *priority - } else { - let default_priorities = default_priority(&pattern.pattern).collect::>(); - if default_priorities.len() > 1 { - // for now, we can't deal with multiple registration yet - return Err(error::Error::Unsupported( - "Default priorities splitting not supported".to_string(), - ) - .into()); - } else { - default_priorities.first().unwrap().1 - } - }; - let function_definition = - self.sequence_constructor_function(&template.sequence_constructor)?; - + let function_definition = self.matched_template_function(template)?; let modes = template .mode .iter() .map(Self::ast_mode_value_to_ir_mode_value) - .collect(); + .collect::>(); + + if let Some(priority) = &template.priority { + declarations.rules.push(ir::Rule { + priority: *priority, + modes, + pattern: transform_pattern(&pattern.pattern, |expr| { + self.pattern_predicate(expr) + })?, + function_definition, + }); + return Ok(()); + } - declarations.rules.push(ir::Rule { - priority, - modes, - pattern: transform_pattern(&pattern.pattern, |expr| self.pattern_predicate(expr))?, - function_definition, + let default_priorities = default_priority(&pattern.pattern).collect::>(); + for (split_pattern, priority) in default_priorities { + declarations.rules.push(ir::Rule { + priority, + modes: modes.clone(), + pattern: transform_pattern(&split_pattern, |expr| { + self.pattern_predicate(expr) + })?, + function_definition: function_definition.clone(), + }); + } + Ok(()) + } else if let Some(name) = &template.name { + // Named template - compile with parameters in function signature + let function_definition = self.template_with_params_function(template)?; + declarations.functions.push(ir::FunctionBinding { + name: ir::Name::new(name.local_name().to_string()), + main: function_definition, }); Ok(()) } else { - Err(error::Error::Unsupported("Named templates not supported".to_string()).into()) + Err(error::Error::Unsupported( + "Template must have either match or name attribute".to_string(), + ) + .into()) + } + } + + fn template_with_params_function( + &mut self, + template: &ast::Template, + ) -> error::SpannedResult { + let context_names = self.variables.push_context(); + self.variables.push_scope(); + let param_names = self.register_template_param_names(template)?; + + let bindings = self.sequence_constructor(&template.sequence_constructor)?; + let mut params = Self::context_params(&context_names); + params.extend(self.template_params(template, param_names)?); + self.variables.pop_scope(); + self.variables.pop_context(); + + Ok(ir::FunctionDefinition { + params, + return_type: None, + body: Box::new(bindings.expr()), + }) + } + + fn matched_template_function( + &mut self, + template: &ast::Template, + ) -> error::SpannedResult { + let context_names = self.variables.push_context(); + self.variables.push_scope(); + let param_names = self.register_template_param_names(template)?; + let bindings = self.sequence_constructor(&template.sequence_constructor)?; + + let mut params = Self::context_params(&context_names); + params.extend(self.template_params(template, param_names)?); + self.variables.pop_scope(); + self.variables.pop_context(); + + Ok(ir::FunctionDefinition { + params, + return_type: None, + body: Box::new(bindings.expr()), + }) + } + + fn xslt_function_definition( + &mut self, + function: &ast::Function, + ) -> error::SpannedResult { + self.variables.push_absent_context(); + self.variables.push_scope(); + + let mut params = Vec::new(); + let mut seen_names = HashSet::new(); + for param in &function.params { + let param_key = ( + param.name.namespace().to_string(), + param.name.local_name().to_string(), + ); + if !seen_names.insert(param_key) { + return Err(error::Error::Unsupported( + "Duplicate XSLT function parameters are not supported".to_string(), + ) + .into()); + } + + let name = self.variables.declare_var_name(¶m.name); + params.push(ir::Param { + name, + type_: param.as_.clone(), + default: None, + required: true, + original_name: None, + tunnel: false, + }); + } + + let bindings = self.sequence_constructor(&function.sequence_constructor)?; + + self.variables.pop_scope(); + self.variables.pop_context(); + + Ok(ir::FunctionDefinition { + params, + return_type: function.as_.clone(), + body: Box::new(bindings.expr()), + }) + } + + fn register_template_param_names( + &mut self, + template: &ast::Template, + ) -> error::SpannedResult> { + let mut param_names = Vec::new(); + let mut seen_names = HashSet::new(); + for param in &template.params { + let param_key = ( + param.name.namespace().to_string(), + param.name.local_name().to_string(), + ); + if !seen_names.insert(param_key) { + return Err(error::SpannedError { + error: error::Error::XTSE0580, + span: Some((param.span.start..param.span.end).into()), + }); + } + let var_name = self.variables.declare_var_name(¶m.name); + param_names.push((param.name.local_name().to_string(), var_name)); } + Ok(param_names) + } + + fn template_params( + &mut self, + template: &ast::Template, + param_names: Vec<(String, ir::Name)>, + ) -> error::SpannedResult> { + let mut params = Vec::new(); + for (original_name, runtime_name) in param_names { + let ast_param = template + .params + .iter() + .find(|param| param.name.local_name() == original_name); + let required = ast_param.map(|param| param.required).unwrap_or(false); + + let default = if let Some(ast_param) = ast_param { + if !ast_param.sequence_constructor.is_empty() { + let expr_s = self + .sequence_constructor(&ast_param.sequence_constructor)? + .expr(); + Some(Box::new(expr_s.value)) + } else if let Some(select_expr) = &ast_param.select { + let expr_s = self.expression(select_expr)?.expr(); + Some(Box::new(expr_s.value)) + } else { + None + } + } else { + None + }; + + params.push(ir::Param { + name: runtime_name, + type_: template + .params + .iter() + .find(|param| param.name.local_name() == original_name) + .and_then(|param| param.as_.clone()), + default, + required, + original_name: Some(original_name), + tunnel: ast_param.map(|param| param.tunnel).unwrap_or(false), + }); + } + Ok(params) } fn mode( @@ -190,7 +875,28 @@ impl<'a> IrConverter<'a> { declarations: &mut ir::Declarations, mode: &ast::Mode, ) -> error::SpannedResult<()> { - declarations.modes.insert(mode.name.clone(), ir::Mode {}); + declarations.modes.insert( + mode.name.clone(), + ir::Mode { + on_no_match: match mode.on_no_match.as_ref() { + Some(ast::OnNoMatch::DeepCopy) => ir::ModeOnNoMatch::DeepCopy, + Some(ast::OnNoMatch::ShallowCopy) => ir::ModeOnNoMatch::ShallowCopy, + Some(ast::OnNoMatch::DeepSkip) => ir::ModeOnNoMatch::DeepSkip, + Some(ast::OnNoMatch::ShallowSkip) => ir::ModeOnNoMatch::ShallowSkip, + Some(ast::OnNoMatch::Fail) => ir::ModeOnNoMatch::Fail, + Some(ast::OnNoMatch::TextOnlyCopy) | None => ir::ModeOnNoMatch::TextOnlyCopy, + }, + warning_on_no_match: mode.warning_on_no_match, + typed: match mode.typed.as_ref() { + Some(ast::Typed::Yes) => ir::ModeTyped::Yes, + Some(ast::Typed::Strict) => ir::ModeTyped::Strict, + Some(ast::Typed::Lax) => ir::ModeTyped::Lax, + Some(ast::Typed::No) | Some(ast::Typed::Unspecified) | None => { + ir::ModeTyped::No + } + }, + }, + ); Ok(()) } @@ -326,14 +1032,26 @@ impl<'a> IrConverter<'a> { ir::Param { name: context_names.item, type_: None, + default: None, + required: false, + original_name: None, + tunnel: false, }, ir::Param { name: context_names.position, type_: None, + default: None, + required: false, + original_name: None, + tunnel: false, }, ir::Param { name: context_names.last, type_: None, + default: None, + required: false, + original_name: None, + tunnel: false, }, ]; Ok(ir::FunctionDefinition { @@ -346,6 +1064,16 @@ impl<'a> IrConverter<'a> { fn sequence_constructor( &mut self, sequence_constructor: &[ast::SequenceConstructorItem], + ) -> error::SpannedResult { + self.variables.push_scope(); + let result = self.sequence_constructor_in_scope(sequence_constructor); + self.variables.pop_scope(); + result + } + + fn sequence_constructor_in_scope( + &mut self, + sequence_constructor: &[ast::SequenceConstructorItem], ) -> error::SpannedResult { let mut items = sequence_constructor.iter(); let left = items.next(); @@ -354,31 +1082,20 @@ impl<'a> IrConverter<'a> { let expr = ir::Expr::Let(ir::Let { name, var_expr: Box::new(var_bindings.expr()), - return_expr: Box::new(self.sequence_constructor(items.as_slice())?.expr()), + return_expr: Box::new( + self.sequence_constructor_in_scope(items.as_slice())?.expr(), + ), }); return Ok(Bindings::new( self.variables.new_binding(expr, (0..0).into()), )); } - self.sequence_constructor_concat(left, items) - } else { - let empty_sequence = self.empty_sequence(); - Ok(Bindings::new( - self.variables - .new_binding(empty_sequence.value, empty_sequence.span), - )) - } - } - fn sequence_constructor_concat<'b>( - &mut self, - left: &ast::SequenceConstructorItem, - items: impl Iterator, - ) -> error::SpannedResult { - let left_bindings = Ok(self.sequence_constructor_item(left)?); - items.fold(left_bindings, |left, right| { - let mut left_bindings = left?; - let mut right_bindings = self.sequence_constructor_item(right)?; + let mut left_bindings = self.sequence_constructor_item(left)?; + if items.as_slice().is_empty() { + return Ok(left_bindings); + } + let mut right_bindings = self.sequence_constructor_in_scope(items.as_slice())?; let expr = ir::Expr::Binary(ir::Binary { left: left_bindings.atom(), op: ir::BinaryOperator::Comma, @@ -386,7 +1103,13 @@ impl<'a> IrConverter<'a> { }); let binding = self.variables.new_binding_no_span(expr); Ok(left_bindings.concat(right_bindings).bind(binding)) - }) + } else { + let empty_sequence = self.empty_sequence(); + Ok(Bindings::new( + self.variables + .new_binding(empty_sequence.value, empty_sequence.span), + )) + } } fn sequence_constructor_item( @@ -410,16 +1133,22 @@ impl<'a> IrConverter<'a> { use ast::SequenceConstructorInstruction::*; match instruction { ApplyTemplates(apply_templates) => self.apply_templates(apply_templates), + ApplyImports(apply_imports) => self.apply_imports(apply_imports), + CallTemplate(call_template) => self.call_template(call_template), ValueOf(value_of) => self.value_of(value_of), If(if_) => self.if_(if_), Choose(choose) => self.choose(choose), ForEach(for_each) => self.for_each(for_each), + ForEachGroup(for_each_group) => self.for_each_group(for_each_group), Iterate(iterate) => self.iterate(iterate), NextIteration(next_iteration) => self.next_iteration(next_iteration), + NextMatch(next_match) => self.next_match(next_match), Break(break_) => self.break_(break_), Copy(copy) => self.copy(copy), CopyOf(copy_of) => self.copy_of(copy_of), + Message(message) => self.message(message), Sequence(sequence) => self.sequence(sequence), + Document(document) => self.document(document), Element(element) => self.element(element), Text(text) => self.text(text), Attribute(attribute) => self.attribute(attribute), @@ -441,6 +1170,22 @@ impl<'a> IrConverter<'a> { } } + fn message(&mut self, message: &ast::Message) -> error::SpannedResult { + let empty_sequence = self.empty_sequence(); + let message_bindings = if let Some(select) = &message.select { + self.expression(select)? + } else if !message.sequence_constructor.is_empty() { + self.sequence_constructor(&message.sequence_constructor)? + } else { + Bindings::new( + self.variables + .new_binding_no_span(empty_sequence.value.clone()), + ) + }; + + Ok(message_bindings.bind_expr(&mut self.variables, empty_sequence)) + } + fn sequence_constructor_content( &mut self, content: &ast::Content, @@ -474,90 +1219,474 @@ impl<'a> IrConverter<'a> { } } - fn sequence_constructor_content_element( - &mut self, - element_node: &ast::ElementNode, - ) -> error::SpannedResult { - let (name_atom, bindings) = self.xml_name(&element_node.name)?.atom_bindings(); - let name_expr = ir::Expr::XmlElement(ir::XmlElement { name: name_atom }); - let (element_atom, mut bindings) = bindings - .bind_expr_no_span(&mut self.variables, name_expr) - .atom_bindings(); - for (name, value) in &element_node.attributes { - let (value_atom, value_bindings) = - self.attribute_value_template(value)?.atom_bindings(); - let (attribute_name_atom, attribute_bindings) = self.xml_name(name)?.atom_bindings(); - let value_bindings = value_bindings.concat(attribute_bindings); - let attribute_expr = ir::Expr::XmlAttribute(ir::XmlAttribute { - name: attribute_name_atom, - value: value_atom, - }); - let (attribute_atom, attribute_bindings) = value_bindings - .bind_expr_no_span(&mut self.variables, attribute_expr) - .atom_bindings(); - let append_expr = ir::Expr::XmlAppend(ir::XmlAppend { - parent: element_atom.clone(), - child: attribute_atom, - }); - let append_bindings = - attribute_bindings.bind_expr_no_span(&mut self.variables, append_expr); - bindings = bindings.concat(append_bindings); + fn sequence_constructor_content_element( + &mut self, + element_node: &ast::ElementNode, + ) -> error::SpannedResult { + let element_name = self.apply_namespace_alias(&element_node.name); + let aliased_attributes = element_node + .attributes + .iter() + .map(|(name, value)| (self.apply_namespace_alias(name), value.clone())) + .collect::>(); + + let (name_atom, bindings) = self.xml_name(&element_name)?.atom_bindings(); + let name_expr = ir::Expr::XmlElement(ir::XmlElement { name: name_atom }); + let (element_atom, mut bindings) = bindings + .bind_expr_no_span(&mut self.variables, name_expr) + .atom_bindings(); + let mut namespaces = element_node.namespaces.clone(); + Self::ensure_name_namespace(&mut namespaces, &element_name); + for (name, _) in &aliased_attributes { + Self::ensure_name_namespace(&mut namespaces, name); + } + for namespace in &namespaces { + let prefix_atom = Spanned::new( + ir::Atom::Const(ir::Const::String(namespace.prefix.clone())), + (0..0).into(), + ); + let namespace_atom = Spanned::new( + ir::Atom::Const(ir::Const::String(namespace.uri.clone())), + (0..0).into(), + ); + let namespace_expr = ir::Expr::XmlNamespace(ir::XmlNamespace { + prefix: prefix_atom, + namespace: namespace_atom, + }); + let (namespace_atom, namespace_bindings) = Bindings::empty() + .bind_expr_no_span(&mut self.variables, namespace_expr) + .atom_bindings(); + let append_expr = ir::Expr::XmlAppend(ir::XmlAppend { + parent: element_atom.clone(), + child: namespace_atom, + }); + let append_bindings = + namespace_bindings.bind_expr_no_span(&mut self.variables, append_expr); + bindings = bindings.concat(append_bindings); + } + for (name, value) in &aliased_attributes { + let (value_atom, value_bindings) = + self.attribute_value_template(value)?.atom_bindings(); + let (attribute_name_atom, attribute_bindings) = self.xml_name(name)?.atom_bindings(); + let value_bindings = value_bindings.concat(attribute_bindings); + let attribute_expr = ir::Expr::XmlAttribute(ir::XmlAttribute { + name: attribute_name_atom, + value: value_atom, + }); + let (attribute_atom, attribute_bindings) = value_bindings + .bind_expr_no_span(&mut self.variables, attribute_expr) + .atom_bindings(); + let append_expr = ir::Expr::XmlAppend(ir::XmlAppend { + parent: element_atom.clone(), + child: attribute_atom, + }); + let append_bindings = + attribute_bindings.bind_expr_no_span(&mut self.variables, append_expr); + bindings = bindings.concat(append_bindings); + } + let sequence_constructor_bindings = self.sequence_constructor_append( + element_atom.clone(), + &element_node.sequence_constructor, + )?; + let bindings = bindings.concat(sequence_constructor_bindings); + Ok(bindings) + } + + fn ensure_name_namespace(namespaces: &mut Vec, name: &ast::Name) { + if name.namespace().is_empty() { + return; + } + + let prefix = name.prefix().to_string(); + let uri = name.namespace().to_string(); + let already_declared = namespaces + .iter() + .any(|namespace| namespace.prefix == prefix && namespace.uri == uri); + if !already_declared { + namespaces.push(ast::LiteralNamespace { prefix, uri }); + } + } + + fn sequence_constructor_append( + &mut self, + element_atom: ir::AtomS, + sequence_constructor: &ast::SequenceConstructor, + ) -> error::SpannedResult { + if !sequence_constructor.is_empty() { + let (atom, bindings) = self + .sequence_constructor(sequence_constructor)? + .atom_bindings(); + let append = ir::Expr::XmlAppend(ir::XmlAppend { + parent: element_atom, + child: atom, + }); + let bindings = bindings.bind_expr_no_span(&mut self.variables, append); + Ok(bindings) + } else { + Ok(Bindings::empty()) + } + } + + fn space_separator_atom(&self) -> ir::AtomS { + Spanned::new( + ir::Atom::Const(ir::Const::String(" ".to_string())), + (0..0).into(), + ) + } + + fn apply_templates( + &mut self, + apply_templates: &ast::ApplyTemplates, + ) -> error::SpannedResult { + let (select_atom, bindings) = self.expression(&apply_templates.select)?.atom_bindings(); + let mut sorts = Vec::new(); + let mut params = Vec::new(); + let mut param_bindings = Bindings::empty(); + + for content in &apply_templates.content { + match content { + ast::ApplyTemplatesContent::Sort(sort) => sorts.push(sort), + ast::ApplyTemplatesContent::WithParam(with_param) => { + let (select_atom, select_bindings) = if let Some(select) = &with_param.select { + let (atom, bindings) = self.expression(select)?.atom_bindings(); + (Some(atom), bindings) + } else { + let sc_bindings = + self.sequence_constructor(&with_param.sequence_constructor)?; + let (atom, bindings) = sc_bindings.atom_bindings(); + (Some(atom), bindings) + }; + + param_bindings = param_bindings.concat(select_bindings); + + params.push(ir::WithParam { + name: ir::Name::new(with_param.name.local_name().to_string()), + select: select_atom, + sequence_constructor: None, + tunnel: with_param.tunnel, + }); + } + } + } + + let (select_atom, sort_bindings) = self.apply_template_sorts(select_atom, &sorts)?; + + let mode = match &apply_templates.mode { + ast::ApplyTemplatesModeValue::EqName(name) => { + ir::ApplyTemplatesModeValue::Named(name.clone()) + } + ast::ApplyTemplatesModeValue::Unnamed => ir::ApplyTemplatesModeValue::Unnamed, + ast::ApplyTemplatesModeValue::Current => { + if self.variables.current_context_names().is_none() { + ir::ApplyTemplatesModeValue::Unnamed + } else { + ir::ApplyTemplatesModeValue::Current + } + } + }; + + let bindings = bindings.concat(sort_bindings).concat(param_bindings); + + Ok(bindings.bind_expr_no_span( + &mut self.variables, + ir::Expr::ApplyTemplates(ir::ApplyTemplates { + mode, + select: select_atom, + builtin_template_params_passthrough: apply_templates + .builtin_template_params_passthrough, + params, + }), + )) + } + + fn apply_imports( + &mut self, + apply_imports: &ast::ApplyImports, + ) -> error::SpannedResult { + self.continue_template(apply_imports.with_params.iter()) + } + + fn next_match(&mut self, next_match: &ast::NextMatch) -> error::SpannedResult { + self.continue_template( + next_match + .content + .iter() + .filter_map(|content| match content { + ast::NextMatchContent::WithParam(with_param) => Some(with_param), + ast::NextMatchContent::Fallback(_) => None, + }), + ) + } + + fn continue_template<'b>( + &mut self, + with_params: impl Iterator, + ) -> error::SpannedResult { + let mut params = Vec::new(); + let mut param_bindings = Bindings::empty(); + + for with_param in with_params { + let (select_atom, select_bindings) = if let Some(select) = &with_param.select { + let (atom, bindings) = self.expression(select)?.atom_bindings(); + (Some(atom), bindings) + } else { + let sc_bindings = self.sequence_constructor(&with_param.sequence_constructor)?; + let (atom, bindings) = sc_bindings.atom_bindings(); + (Some(atom), bindings) + }; + + param_bindings = param_bindings.concat(select_bindings); + + params.push(ir::WithParam { + name: ir::Name::new(with_param.name.local_name().to_string()), + select: select_atom, + sequence_constructor: None, + tunnel: with_param.tunnel, + }); + } + + Ok(param_bindings.bind_expr_no_span( + &mut self.variables, + ir::Expr::ContinueTemplate(ir::ContinueTemplate { params }), + )) + } + + fn apply_template_sorts( + &mut self, + select_atom: ir::AtomS, + sorts: &[&ast::Sort], + ) -> error::SpannedResult<(ir::AtomS, Bindings)> { + let mut current_atom = select_atom; + let mut bindings = Bindings::empty(); + + for sort in sorts.iter().rev() { + self.ensure_supported_sort(sort)?; + + let (key_atom, key_bindings) = self.sort_key_function(sort)?; + let (collation_atom, collation_bindings) = self.sort_collation_atom(sort)?; + + bindings = bindings.concat(key_bindings).concat(collation_bindings); + let sort_expr = self.static_function_call_expr( + "sort", + FN_NAMESPACE, + 3, + vec![current_atom.clone(), collation_atom, key_atom], + ); + let (sorted_atom, sorted_bindings) = bindings + .bind_expr_no_span(&mut self.variables, sort_expr) + .atom_bindings(); + bindings = sorted_bindings; + current_atom = sorted_atom; + + if self.sort_is_descending(sort)? { + let reverse_expr = self.static_function_call_expr( + "reverse", + FN_NAMESPACE, + 1, + vec![current_atom.clone()], + ); + let (reversed_atom, reversed_bindings) = bindings + .bind_expr_no_span(&mut self.variables, reverse_expr) + .atom_bindings(); + bindings = reversed_bindings; + current_atom = reversed_atom; + } + } + + Ok((current_atom, bindings)) + } + + fn sort_key_function( + &mut self, + sort: &ast::Sort, + ) -> error::SpannedResult<(ir::AtomS, Bindings)> { + let param_name = self.variables.new_name(); + let context_names = self.variables.push_context(); + let return_bindings = self.sort_key_return_bindings(sort)?; + self.variables.pop_context(); + + let body = ir::Expr::Map(ir::Map { + context_names, + var_atom: Spanned::new(ir::Atom::Variable(param_name.clone()), (0..0).into()), + return_expr: Box::new(return_bindings.expr()), + }); + let function = ir::Expr::FunctionDefinition(ir::FunctionDefinition { + params: vec![ir::Param { + name: param_name, + type_: None, + default: None, + required: false, + original_name: None, + tunnel: false, + }], + return_type: None, + body: Box::new(Spanned::new(body, (0..0).into())), + }); + + let bindings = Bindings::empty().bind_expr_no_span(&mut self.variables, function); + Ok(bindings.atom_bindings()) + } + + fn sort_key_return_bindings(&mut self, sort: &ast::Sort) -> error::SpannedResult { + let key_bindings = if let Some(select) = &sort.select { + self.expression(select)? + } else if !sort.sequence_constructor.is_empty() { + self.sequence_constructor(&sort.sequence_constructor)? + } else { + self.variables.context_item((0..0).into())? + }; + self.atomized_key_bindings(key_bindings, self.sort_data_type(sort)?) + } + + fn atomized_key_bindings( + &mut self, + key_bindings: Bindings, + data_type: SortDataType, + ) -> error::SpannedResult { + let (key_atom, bindings) = key_bindings.atom_bindings(); + let atomized_expr = self.static_function_call_expr("data", FN_NAMESPACE, 1, vec![key_atom]); + let bindings = bindings.bind_expr_no_span(&mut self.variables, atomized_expr); + + match data_type { + SortDataType::Text => Ok(bindings), + SortDataType::Number => { + let (atomized_atom, bindings) = bindings.atom_bindings(); + let number_expr = + self.static_function_call_expr("number", FN_NAMESPACE, 1, vec![atomized_atom]); + Ok(bindings.bind_expr_no_span(&mut self.variables, number_expr)) + } + } + } + + fn sort_collation_atom( + &mut self, + sort: &ast::Sort, + ) -> error::SpannedResult<(ir::AtomS, Bindings)> { + if let Some(collation) = &sort.collation { + Ok(self.attribute_value_template(collation)?.atom_bindings()) + } else { + Ok(( + Spanned::new(ir::Atom::Const(ir::Const::EmptySequence), (0..0).into()), + Bindings::empty(), + )) + } + } + + fn ensure_supported_sort(&self, sort: &ast::Sort) -> error::SpannedResult<()> { + if sort.lang.is_some() { + return Err(error::Error::Unsupported(String::from( + "xsl:sort lang is not supported yet", + )) + .into()); + } + if sort.case_order.is_some() { + return Err(error::Error::Unsupported(String::from( + "xsl:sort case-order is not supported yet", + )) + .into()); + } + Ok(()) + } + + fn sort_is_descending(&self, sort: &ast::Sort) -> error::SpannedResult { + let Some(order) = &sort.order else { + return Ok(false); + }; + match self + .literal_value_template(order, "xsl:sort order")? + .as_deref() + { + Some("ascending") => Ok(false), + Some("descending") => Ok(true), + Some(value) => Err(error::Error::Unsupported(format!( + "xsl:sort order value {:?} is not supported yet", + value + )) + .into()), + None => Ok(false), } - let sequence_constructor_bindings = self.sequence_constructor_append( - element_atom.clone(), - &element_node.sequence_constructor, - )?; - let bindings = bindings.concat(sequence_constructor_bindings); - Ok(bindings) } - fn sequence_constructor_append( - &mut self, - element_atom: ir::AtomS, - sequence_constructor: &ast::SequenceConstructor, - ) -> error::SpannedResult { - if !sequence_constructor.is_empty() { - let (atom, bindings) = self - .sequence_constructor(sequence_constructor)? - .atom_bindings(); - let append = ir::Expr::XmlAppend(ir::XmlAppend { - parent: element_atom, - child: atom, - }); - let bindings = bindings.bind_expr_no_span(&mut self.variables, append); - Ok(bindings) - } else { - Ok(Bindings::empty()) + fn sort_data_type(&self, sort: &ast::Sort) -> error::SpannedResult { + let Some(data_type) = &sort.data_type else { + return Ok(SortDataType::Text); + }; + match self + .literal_value_template(data_type, "xsl:sort data-type")? + .as_deref() + { + Some("text") => Ok(SortDataType::Text), + Some("number") => Ok(SortDataType::Number), + Some(value) => Err(error::Error::Unsupported(format!( + "xsl:sort data-type value {:?} is not supported yet", + value + )) + .into()), + None => Ok(SortDataType::Text), } } - fn space_separator_atom(&self) -> ir::AtomS { - Spanned::new( - ir::Atom::Const(ir::Const::String(" ".to_string())), - (0..0).into(), - ) + fn literal_value_template( + &self, + value_template: &ast::ValueTemplate, + attribute: &str, + ) -> error::SpannedResult> + where + V: Clone + PartialEq + Eq, + { + let mut value = String::new(); + for item in &value_template.template { + match item { + ast::ValueTemplateItem::String { text, .. } => value.push_str(text), + ast::ValueTemplateItem::Curly { c } => value.push(*c), + ast::ValueTemplateItem::Value { .. } => { + return Err(error::Error::Unsupported(format!( + "{} AVTs are not supported yet", + attribute + )) + .into()) + } + } + } + Ok(Some(value)) } - fn apply_templates( + fn call_template( &mut self, - apply_templates: &ast::ApplyTemplates, + call_template: &ast::CallTemplate, ) -> error::SpannedResult { - let (select_atom, bindings) = self.expression(&apply_templates.select)?.atom_bindings(); - let mode = match &apply_templates.mode { - ast::ApplyTemplatesModeValue::EqName(name) => { - ir::ApplyTemplatesModeValue::Named(name.clone()) - } - ast::ApplyTemplatesModeValue::Unnamed => ir::ApplyTemplatesModeValue::Unnamed, - ast::ApplyTemplatesModeValue::Current => ir::ApplyTemplatesModeValue::Current, - }; + // Compile the with-params for the template invocation + let mut params = Vec::new(); + let mut param_bindings = Bindings::empty(); + + for with_param in &call_template.with_params { + let (select_atom, select_bindings) = if let Some(select) = &with_param.select { + let (atom, bindings) = self.expression(select)?.atom_bindings(); + (Some(atom), bindings) + } else { + let sc_bindings = self.sequence_constructor(&with_param.sequence_constructor)?; + let (atom, bindings) = sc_bindings.atom_bindings(); + (Some(atom), bindings) + }; - Ok(bindings.bind_expr_no_span( - &mut self.variables, - ir::Expr::ApplyTemplates(ir::ApplyTemplates { - mode, + param_bindings = param_bindings.concat(select_bindings); + + params.push(ir::WithParam { + name: ir::Name::new(with_param.name.local_name().to_string()), select: select_atom, - }), - )) + sequence_constructor: None, // Already flattened into select_atom above + tunnel: with_param.tunnel, + }); + } + + let call_template_expr = ir::Expr::CallTemplate(ir::CallTemplate { + name: ir::Name::new(call_template.name.local_name().to_string()), + context: self.variables.current_context_names(), + backwards_compatible: call_template.backwards_compatible, + params, + }); + + Ok(param_bindings.bind_expr_no_span(&mut self.variables, call_template_expr)) } fn select_or_sequence_constructor( @@ -691,18 +1820,49 @@ impl<'a> IrConverter<'a> { ast::SequenceConstructorInstruction::Variable(variable), ) = item { - let name = self.variables.new_var_name(&variable.name); let var_bindings = if let Some(select) = &variable.select { self.expression(select)? - } else { + } else if variable.as_.is_some() { self.sequence_constructor(&variable.sequence_constructor)? + } else if !variable.sequence_constructor.is_empty() { + self.temporary_tree(&variable.sequence_constructor)? + } else { + let empty_string = ir::Expr::Atom(self.empty_string()); + Bindings::empty().bind_expr_no_span(&mut self.variables, empty_string) }; + let var_bindings = + self.convert_bindings(var_bindings, variable.as_.as_ref(), RaisedError::XTTE0570)?; + let name = self.variables.declare_var_name(&variable.name); Ok(Some((name, var_bindings))) } else { Ok(None) } } + fn temporary_tree( + &mut self, + sequence_constructor: &ast::SequenceConstructor, + ) -> error::SpannedResult { + let (document_atom, document_bindings) = Bindings::empty() + .bind_expr_no_span(&mut self.variables, ir::Expr::XmlDocument(ir::XmlRoot {})) + .atom_bindings(); + + if sequence_constructor.is_empty() { + return Ok(document_bindings); + } + + let (child_atom, child_bindings) = self + .sequence_constructor(sequence_constructor)? + .atom_bindings(); + let append_expr = ir::Expr::XmlAppend(ir::XmlAppend { + parent: document_atom, + child: child_atom, + }); + Ok(document_bindings + .concat(child_bindings) + .bind_expr_no_span(&mut self.variables, append_expr)) + } + fn empty_sequence(&mut self) -> ir::ExprS { Spanned::new( ir::Expr::Atom(Spanned::new( @@ -764,7 +1924,10 @@ impl<'a> IrConverter<'a> { } fn for_each(&mut self, for_each: &ast::ForEach) -> error::SpannedResult { - let (var_atom, bindings) = self.expression(&for_each.select)?.atom_bindings(); + let (select_atom, bindings) = self.expression(&for_each.select)?.atom_bindings(); + let sort_refs = for_each.sort.iter().collect::>(); + let (var_atom, sort_bindings) = self.apply_template_sorts(select_atom, &sort_refs)?; + let bindings = bindings.concat(sort_bindings); let context_names = self.variables.push_context(); let return_bindings = self.sequence_constructor(&for_each.sequence_constructor)?; @@ -778,6 +1941,89 @@ impl<'a> IrConverter<'a> { Ok(bindings.bind_expr_no_span(&mut self.variables, expr)) } + fn for_each_group( + &mut self, + for_each_group: &ast::ForEachGroup, + ) -> error::SpannedResult { + if for_each_group.group_adjacent.is_some() + || for_each_group.group_starting_with.is_some() + || for_each_group.group_ending_with.is_some() + || for_each_group.composite + || for_each_group.collation.is_some() + || !for_each_group.sort.is_empty() + { + return Err(error::Error::Unsupported(format!( + "Instruction not supported: {:?}", + for_each_group + )) + .into()); + } + + let group_by = for_each_group.group_by.as_ref().ok_or_else(|| { + error::Error::Unsupported(format!("Instruction not supported: {:?}", for_each_group)) + })?; + + let (select_atom, bindings) = self.expression(&for_each_group.select)?.atom_bindings(); + let (key_function_atom, key_function_bindings) = self.group_key_function(group_by)?; + let grouped_expr = self.static_function_call_expr( + "group-by-first", + FN_NAMESPACE, + 2, + vec![select_atom, key_function_atom], + ); + let (grouped_atom, group_bindings) = key_function_bindings + .bind_expr_no_span(&mut self.variables, grouped_expr) + .atom_bindings(); + let bindings = bindings.concat(group_bindings); + + let context_names = self.variables.push_context(); + let return_bindings = self.sequence_constructor(&for_each_group.sequence_constructor)?; + self.variables.pop_context(); + let expr = ir::Expr::Map(ir::Map { + context_names, + var_atom: grouped_atom, + return_expr: Box::new(return_bindings.expr()), + }); + + Ok(bindings.bind_expr_no_span(&mut self.variables, expr)) + } + + fn group_key_function( + &mut self, + group_by: &ast::Expression, + ) -> error::SpannedResult<(ir::AtomS, Bindings)> { + let param_name = self.variables.new_name(); + let context_names = self.variables.push_context(); + let bindings = self.expression(group_by)?; + let bindings = self.atomized_key_bindings(bindings, SortDataType::Text)?; + self.variables.pop_context(); + + let body = ir::Expr::Map(ir::Map { + context_names, + var_atom: Spanned::new(ir::Atom::Variable(param_name.clone()), (0..0).into()), + return_expr: Box::new(bindings.expr()), + }); + + let function_definition = ir::FunctionDefinition { + params: vec![ir::Param { + name: param_name, + type_: None, + default: None, + required: false, + original_name: None, + tunnel: false, + }], + return_type: None, + body: Box::new(Spanned::new(body, (0..0).into())), + }; + + let function_expr = Bindings::empty().bind_expr_no_span( + &mut self.variables, + ir::Expr::FunctionDefinition(function_definition), + ); + Ok(function_expr.atom_bindings()) + } + fn iterate(&mut self, iterate: &ast::Iterate) -> error::SpannedResult { let (var_atom, bindings) = self.expression(&iterate.select)?.atom_bindings(); @@ -786,7 +2032,7 @@ impl<'a> IrConverter<'a> { .iter() .map(|param| -> error::SpannedResult { let param_bindings = self.select_or_sequence_constructor(param)?; - let name = self.variables.new_var_name(¶m.name); + let name = self.variables.declare_var_name(¶m.name); Ok(ir::IterateParam { name, value: Box::new(param_bindings.expr()), @@ -943,7 +2189,10 @@ impl<'a> IrConverter<'a> { ir::Atom::Const(ir::Const::String(name.local_name().to_string())), (0..0).into(), ); - let namespace = self.empty_string(); + let namespace = Spanned::new( + ir::Atom::Const(ir::Const::String(name.namespace().to_string())), + (0..0).into(), + ); let binding = self .variables @@ -958,8 +2207,40 @@ impl<'a> IrConverter<'a> { &mut self, name: &ast::ValueTemplate, namespace: &Option>, + namespaces: &[ast::LiteralNamespace], ) -> error::SpannedResult { - let (localname_atom, bindings) = self.attribute_value_template(name)?.atom_bindings(); + let literal_name = self.static_value_template(name); + let (localname_atom, bindings) = if let Some((local_name, namespace_uri)) = literal_name + .as_deref() + .and_then(|literal_name| self.resolve_static_qname(literal_name, namespaces)) + { + let local_name_atom = Spanned::new( + ir::Atom::Const(ir::Const::String(local_name)), + (0..0).into(), + ); + let bindings = Bindings::empty() + .bind_expr_no_span(&mut self.variables, ir::Expr::Atom(local_name_atom)); + if namespace.is_none() { + let namespace_atom = Spanned::new( + ir::Atom::Const(ir::Const::String(namespace_uri)), + (0..0).into(), + ); + let namespace_bindings = Bindings::empty() + .bind_expr_no_span(&mut self.variables, ir::Expr::Atom(namespace_atom)); + let (local_name_atom, bindings) = bindings.atom_bindings(); + let (namespace_atom, namespace_bindings) = namespace_bindings.atom_bindings(); + let name = ir::Expr::XmlName(ir::XmlName { + local_name: local_name_atom, + namespace: namespace_atom, + }); + return Ok(bindings + .concat(namespace_bindings) + .bind_expr_no_span(&mut self.variables, name)); + } + bindings.atom_bindings() + } else { + self.attribute_value_template(name)?.atom_bindings() + }; let (namespace_atom, namespace_bindings) = if let Some(namespace) = namespace { self.attribute_value_template(namespace)?.atom_bindings() } else { @@ -981,20 +2262,126 @@ impl<'a> IrConverter<'a> { self.attribute_value_template(name) } + fn static_value_template(&self, value_template: &ast::ValueTemplate) -> Option + where + V: Clone + PartialEq + Eq, + { + let mut value = String::new(); + for item in &value_template.template { + match item { + ast::ValueTemplateItem::String { text, .. } => value.push_str(text), + ast::ValueTemplateItem::Curly { c } => value.push(*c), + ast::ValueTemplateItem::Value { .. } => return None, + } + } + Some(value) + } + + fn resolve_static_qname( + &self, + lexical_qname: &str, + namespaces: &[ast::LiteralNamespace], + ) -> Option<(String, String)> { + let (prefix, local_name) = lexical_qname.split_once(':')?; + let namespace = namespaces + .iter() + .find(|namespace| namespace.prefix == prefix) + .map(|namespace| namespace.uri.as_str()) + .or_else(|| self.static_context.namespaces().by_prefix(prefix))?; + Some((local_name.to_string(), namespace.to_string())) + } + + fn static_name_namespace( + &self, + name: &ast::ValueTemplate, + namespace: &Option>, + namespaces: &[ast::LiteralNamespace], + ) -> Option { + let literal_name = self.static_value_template(name)?; + let (prefix, _) = literal_name.split_once(':')?; + let uri = if let Some(namespace) = namespace { + self.static_value_template(namespace)? + } else { + namespaces + .iter() + .find(|namespace| namespace.prefix == prefix) + .map(|namespace| namespace.uri.clone()) + .or_else(|| { + self.static_context + .namespaces() + .by_prefix(prefix) + .map(str::to_string) + })? + }; + Some(ast::LiteralNamespace { + prefix: prefix.to_string(), + uri, + }) + } + fn element(&mut self, element: &ast::Element) -> error::SpannedResult { let (name_atom, bindings) = self - .xml_name_dynamic(&element.name, &element.namespace)? + .xml_name_dynamic(&element.name, &element.namespace, &element.namespaces)? .atom_bindings(); let expr = ir::Expr::XmlElement(ir::XmlElement { name: name_atom }); let (element_atom, bindings) = bindings .bind_expr_no_span(&mut self.variables, expr) .atom_bindings(); + let (element_atom, bindings) = if let Some(namespace) = + self.static_name_namespace(&element.name, &element.namespace, &element.namespaces) + { + let prefix_atom = Spanned::new( + ir::Atom::Const(ir::Const::String(namespace.prefix)), + (0..0).into(), + ); + let namespace_atom = Spanned::new( + ir::Atom::Const(ir::Const::String(namespace.uri)), + (0..0).into(), + ); + let namespace_expr = ir::Expr::XmlNamespace(ir::XmlNamespace { + prefix: prefix_atom, + namespace: namespace_atom, + }); + let (namespace_atom, namespace_bindings) = Bindings::empty() + .bind_expr_no_span(&mut self.variables, namespace_expr) + .atom_bindings(); + bindings + .concat(namespace_bindings) + .bind_expr_no_span( + &mut self.variables, + ir::Expr::XmlAppend(ir::XmlAppend { + parent: element_atom, + child: namespace_atom, + }), + ) + .atom_bindings() + } else { + (element_atom, bindings) + }; let sequence_constructor_bindings = self.sequence_constructor_append(element_atom, &element.sequence_constructor)?; Ok(bindings.concat(sequence_constructor_bindings)) } + fn document(&mut self, document: &ast::Document) -> error::SpannedResult { + if document.validation.is_some() || document.type_.is_some() { + return Err(error::Error::Unsupported(format!( + "Instruction not supported: {:?}", + document + )) + .into()); + } + + let expr = ir::Expr::XmlDocument(ir::XmlRoot {}); + let (document_atom, bindings) = Bindings::empty() + .bind_expr_no_span(&mut self.variables, expr) + .atom_bindings(); + let sequence_constructor_bindings = + self.sequence_constructor_append(document_atom, &document.sequence_constructor)?; + Ok(bindings.concat(sequence_constructor_bindings)) + } + fn text(&mut self, text: &ast::Text) -> error::SpannedResult { let (atom, bindings) = self .attribute_value_template(&text.content)? @@ -1007,7 +2394,7 @@ impl<'a> IrConverter<'a> { fn attribute(&mut self, attribute: &ast::Attribute) -> error::SpannedResult { let (name_atom, name_bindings) = self - .xml_name_dynamic(&attribute.name, &attribute.namespace)? + .xml_name_dynamic(&attribute.name, &attribute.namespace, &attribute.namespaces)? .atom_bindings(); let (text_atom, text_bindings) = self .select_or_sequence_constructor_simple_content_with_separator( @@ -1082,9 +2469,199 @@ impl<'a> IrConverter<'a> { } fn xpath(&mut self, xpath: &xee_xpath_ast::ast::ExprS) -> error::SpannedResult { + let mut rewritten_xpath = xpath.clone(); + self.rewrite_user_function_references_expr(&mut rewritten_xpath); let mut ir_converter = xee_xpath_compiler::IrConverter::new(&mut self.variables, self.static_context); - ir_converter.expr(xpath) + ir_converter.expr(&rewritten_xpath) + } + + fn lookup_xslt_function_var_name(&self, name: &OwnedName, arity: u8) -> Option { + self.xslt_functions.get(&(name.clone(), arity)).cloned() + } + + fn rewrite_user_function_references_expr(&self, expr: &mut xpath_ast::ExprS) { + for expr_single in &mut expr.value.0 { + self.rewrite_user_function_references_expr_single(expr_single); + } + } + + fn rewrite_user_function_references_expr_or_empty(&self, expr: &mut xpath_ast::ExprOrEmptyS) { + if let Some(expr) = &mut expr.value { + for expr_single in &mut expr.0 { + self.rewrite_user_function_references_expr_single(expr_single); + } + } + } + + fn rewrite_user_function_references_expr_single(&self, expr: &mut xpath_ast::ExprSingleS) { + match &mut expr.value { + xpath_ast::ExprSingle::Path(path_expr) => { + self.rewrite_user_function_references_path_expr(path_expr); + } + xpath_ast::ExprSingle::Apply(apply_expr) => { + self.rewrite_user_function_references_path_expr(&mut apply_expr.path_expr); + if let xpath_ast::ApplyOperator::SimpleMap(path_exprs) = &mut apply_expr.operator { + for path_expr in path_exprs { + self.rewrite_user_function_references_path_expr(path_expr); + } + } + } + xpath_ast::ExprSingle::Let(let_expr) => { + self.rewrite_user_function_references_expr_single(&mut let_expr.var_expr); + self.rewrite_user_function_references_expr_single(&mut let_expr.return_expr); + } + xpath_ast::ExprSingle::If(if_expr) => { + self.rewrite_user_function_references_expr(&mut if_expr.condition); + self.rewrite_user_function_references_expr_single(&mut if_expr.then); + self.rewrite_user_function_references_expr_single(&mut if_expr.else_); + } + xpath_ast::ExprSingle::Binary(binary_expr) => { + self.rewrite_user_function_references_path_expr(&mut binary_expr.left); + self.rewrite_user_function_references_path_expr(&mut binary_expr.right); + } + xpath_ast::ExprSingle::For(for_expr) => { + self.rewrite_user_function_references_expr_single(&mut for_expr.var_expr); + self.rewrite_user_function_references_expr_single(&mut for_expr.return_expr); + } + xpath_ast::ExprSingle::Quantified(quantified_expr) => { + self.rewrite_user_function_references_expr_single(&mut quantified_expr.var_expr); + self.rewrite_user_function_references_expr_single( + &mut quantified_expr.satisfies_expr, + ); + } + } + } + + fn rewrite_user_function_references_path_expr(&self, path_expr: &mut xpath_ast::PathExpr) { + for step in &mut path_expr.steps { + self.rewrite_user_function_references_step_expr(step); + } + } + + fn rewrite_user_function_references_step_expr(&self, step: &mut xpath_ast::StepExprS) { + match &mut step.value { + xpath_ast::StepExpr::PrimaryExpr(primary) => { + let extra_postfixes = self.rewrite_user_function_references_primary_expr(primary); + if !extra_postfixes.is_empty() { + step.value = xpath_ast::StepExpr::PostfixExpr { + primary: primary.clone(), + postfixes: extra_postfixes, + }; + } + } + xpath_ast::StepExpr::PostfixExpr { primary, postfixes } => { + let extra_postfixes = self.rewrite_user_function_references_primary_expr(primary); + for postfix in postfixes.iter_mut() { + self.rewrite_user_function_references_postfix(postfix); + } + if !extra_postfixes.is_empty() { + let mut new_postfixes = extra_postfixes; + new_postfixes.append(postfixes); + *postfixes = new_postfixes; + } + } + xpath_ast::StepExpr::AxisStep(axis_step) => { + for predicate in &mut axis_step.predicates { + self.rewrite_user_function_references_expr(predicate); + } + } + } + } + + fn rewrite_user_function_references_primary_expr( + &self, + primary: &mut xpath_ast::PrimaryExprS, + ) -> Vec { + match &mut primary.value { + xpath_ast::PrimaryExpr::FunctionCall(function_call) => { + for argument in &mut function_call.arguments { + self.rewrite_user_function_references_expr_single(argument); + } + + let arity = match u8::try_from(function_call.arguments.len()) { + Ok(arity) => arity, + Err(_) => return Vec::new(), + }; + + if let Some(hidden_name) = + self.lookup_xslt_function_var_name(&function_call.name.value, arity) + { + let arguments = function_call.arguments.clone(); + primary.value = xpath_ast::PrimaryExpr::VarRef(hidden_name); + vec![xpath_ast::Postfix::ArgumentList(arguments)] + } else { + Vec::new() + } + } + xpath_ast::PrimaryExpr::NamedFunctionRef(named_function_ref) => { + if let Some(hidden_name) = self.lookup_xslt_function_var_name( + &named_function_ref.name.value, + named_function_ref.arity, + ) { + primary.value = xpath_ast::PrimaryExpr::VarRef(hidden_name); + } + Vec::new() + } + xpath_ast::PrimaryExpr::Expr(expr) => { + self.rewrite_user_function_references_expr_or_empty(expr); + Vec::new() + } + xpath_ast::PrimaryExpr::InlineFunction(inline_function) => { + self.rewrite_user_function_references_expr_or_empty(&mut inline_function.body); + Vec::new() + } + xpath_ast::PrimaryExpr::MapConstructor(map_constructor) => { + for entry in &mut map_constructor.entries { + self.rewrite_user_function_references_expr_single(&mut entry.key); + self.rewrite_user_function_references_expr_single(&mut entry.value); + } + Vec::new() + } + xpath_ast::PrimaryExpr::ArrayConstructor(array_constructor) => { + match array_constructor { + xpath_ast::ArrayConstructor::Square(expr) => { + self.rewrite_user_function_references_expr(expr); + } + xpath_ast::ArrayConstructor::Curly(expr) => { + self.rewrite_user_function_references_expr_or_empty(expr); + } + } + Vec::new() + } + xpath_ast::PrimaryExpr::UnaryLookup(key_specifier) => { + self.rewrite_user_function_references_key_specifier(key_specifier); + Vec::new() + } + xpath_ast::PrimaryExpr::Literal(_) + | xpath_ast::PrimaryExpr::VarRef(_) + | xpath_ast::PrimaryExpr::ContextItem => Vec::new(), + } + } + + fn rewrite_user_function_references_postfix(&self, postfix: &mut xpath_ast::Postfix) { + match postfix { + xpath_ast::Postfix::Predicate(expr) => { + self.rewrite_user_function_references_expr(expr); + } + xpath_ast::Postfix::ArgumentList(arguments) => { + for argument in arguments { + self.rewrite_user_function_references_expr_single(argument); + } + } + xpath_ast::Postfix::Lookup(key_specifier) => { + self.rewrite_user_function_references_key_specifier(key_specifier); + } + } + } + + fn rewrite_user_function_references_key_specifier( + &self, + key_specifier: &mut xpath_ast::KeySpecifier, + ) { + if let xpath_ast::KeySpecifier::Expr(expr) = key_specifier { + self.rewrite_user_function_references_expr_or_empty(expr); + } } fn pattern_predicate( @@ -1109,14 +2686,26 @@ impl<'a> IrConverter<'a> { ir::Param { name: context_names.item, type_: None, + default: None, + required: false, + original_name: None, + tunnel: false, }, ir::Param { name: context_names.position, type_: None, + default: None, + required: false, + original_name: None, + tunnel: false, }, ir::Param { name: context_names.last, type_: None, + default: None, + required: false, + original_name: None, + tunnel: false, }, ]; @@ -1127,3 +2716,8 @@ impl<'a> IrConverter<'a> { }) } } + +enum SortDataType { + Text, + Number, +} diff --git a/xee-xslt-compiler/src/default_declarations.rs b/xee-xslt-compiler/src/default_declarations.rs deleted file mode 100644 index 998aeec88..000000000 --- a/xee-xslt-compiler/src/default_declarations.rs +++ /dev/null @@ -1,28 +0,0 @@ -use xee_xslt_ast::{ast, error, parse_transform}; - -// TODO: currently only applies to default mode, no array handling yet -// TODO: we can't do | yet in a a pattern yet so -// define multiple template rules for now -const TEXT_ONLY_COPY: &str = r#" - - - - - - - - - - - - - - - - -"#; - -pub(crate) fn text_only_copy_declarations() -> Result, error::ElementError> { - let transform = parse_transform(TEXT_ONLY_COPY)?; - Ok(transform.declarations) -} diff --git a/xee-xslt-compiler/src/lib.rs b/xee-xslt-compiler/src/lib.rs index 7cdabcb5f..3451e8417 100644 --- a/xee-xslt-compiler/src/lib.rs +++ b/xee-xslt-compiler/src/lib.rs @@ -1,7 +1,6 @@ mod ast_ir; -mod default_declarations; mod priority; mod run; -pub use ast_ir::parse; +pub use ast_ir::{parse, parse_with_base_dir, parse_with_base_dir_and_initial_mode}; pub use run::evaluate; diff --git a/xee-xslt-compiler/tests/test_xslt.rs b/xee-xslt-compiler/tests/test_xslt.rs index 946324eba..ddf3ec531 100644 --- a/xee-xslt-compiler/tests/test_xslt.rs +++ b/xee-xslt-compiler/tests/test_xslt.rs @@ -1,85 +1,864 @@ use std::fmt::Write; +use std::fs; +use std::time::{SystemTime, UNIX_EPOCH}; + +use xee_interpreter::{ + context::{StaticContext, StaticContextBuilder}, + error, + sequence::Sequence, + xml::Documents, +}; +use xee_name::{Namespaces, FN_NAMESPACE}; +use xee_xslt_compiler::{evaluate, parse, parse_with_base_dir}; +use xot::Xot; + +fn xml(xot: &Xot, sequence: Sequence) -> String { + let mut f = String::new(); + + for item in sequence.iter() { + f.write_str(&xot.to_string(item.to_node().unwrap()).unwrap()) + .unwrap(); + } + f +} + +fn evaluate_with_stylesheet_base( + xot: &mut Xot, + xml: &str, + xslt: &str, + stylesheet_path: &std::path::Path, +) -> error::SpannedResult { + let stylesheet_uri = format!("file://{}", stylesheet_path.display()).replace(' ', "%20"); + let mut static_context_builder = StaticContextBuilder::default(); + static_context_builder.static_base_uri(Some(stylesheet_uri.try_into().unwrap())); + let static_context = static_context_builder.build(); + let program = parse_with_base_dir( + static_context, + xslt, + stylesheet_path.parent().map(|parent| parent.to_path_buf()), + ) + .unwrap(); + + let root = xot.parse(xml).unwrap(); + let mut documents = Documents::new(); + let handle = documents.add_root(None, root).unwrap(); + let root = documents.get_node_by_handle(handle).unwrap(); + let mut dynamic_context_builder = program.dynamic_context_builder(); + dynamic_context_builder.context_node(root); + dynamic_context_builder.documents(documents); + let context = dynamic_context_builder.build(); + let runnable = program.runnable(&context); + runnable.many(xot) +} + +#[test] +fn test_transform() { + let mut xot = Xot::new(); + let output = evaluate( + &mut xot, + "", + r#" + + +"#, + ) + .unwrap(); + assert_eq!(xml(&xot, output), ""); +} + +#[test] +fn test_transform_nested() { + let mut xot = Xot::new(); + let output = evaluate( + &mut xot, + "", + r#" + + +"#, + ) + .unwrap(); + assert_eq!(xml(&xot, output), ""); +} + +#[test] +fn test_transform_text_node() { + let mut xot = Xot::new(); + let output = evaluate( + &mut xot, + "", + r#" + + foo +"#, + ) + .unwrap(); + assert_eq!(xml(&xot, output), "foo"); +} + +#[test] +fn test_transform_nested_apply_templates() { + let mut xot = Xot::new(); + let output = evaluate( + &mut xot, + "", + r#" + + + + + + + + + + +"#, + ) + .unwrap(); + assert_eq!(xml(&xot, output), ""); +} + +#[test] +fn test_match_node_pattern_does_not_capture_initial_document_node() { + let mut xot = Xot::new(); + let output = evaluate( + &mut xot, + "\n This is the child number 1.\n", + r#" + + + + + + + + + + + + + This test failed to execute properly. + +"#, + ) + .unwrap(); + + assert_eq!( + xml(&xot, output), + "\n This is the child number 1.\n" + ); +} + +#[test] +fn test_apply_templates_sort_with_param() { + let mut xot = Xot::new(); + let output = evaluate( + &mut xot, + r#" + 3544129828 + 6244029 + 16457833 +"#, + r#" + + + + + + + + + + + + + + + + , + + + +"#, + ) + .unwrap(); + + assert_eq!( + xml(&xot, output), + "16,45,78,33,35,44,12,98,28,62,440,29," + ); +} + +#[test] +fn test_whitespace_padded_required_attribute_values() { + let mut xot = Xot::new(); + let output = evaluate( + &mut xot, + "", + r#" + + + + + + + + + + + + + + + Required parameter; + + + Not required parameter + + +"#, + ) + .unwrap(); + + assert_eq!( + xml(&xot, output), + "Required parameter;Not required parameter" + ); +} + +#[test] +fn test_next_match_with_param_falls_back_to_builtin_rule() { + let mut xot = Xot::new(); + let output = evaluate( + &mut xot, + "abc", + r#" + + + + + + + + + + + + + + + +"#, + ) + .unwrap(); + + assert_eq!(xml(&xot, output), "defaultValue"); +} + +#[test] +fn test_repeated_local_variable_reference_in_union_expression() { + let mut xot = Xot::new(); + let output = evaluate( + &mut xot, + "", + r#" + + + + + + + + + + +"#, + ) + .unwrap(); + + assert_eq!(xml(&xot, output), "2"); +} + +#[test] +fn test_global_variable_can_reference_later_global_param() { + let mut xot = Xot::new(); + let output = evaluate( + &mut xot, + "", + r#" + + + + + + + + + , + + + +"#, + ) + .unwrap(); + + assert_eq!(xml(&xot, output), "templ, titi"); +} + +#[test] +fn test_call_template_unknown_param_is_ignored_in_xslt_1_mode() { + let mut xot = Xot::new(); + let output = evaluate( + &mut xot, + "", + r#" + + + + + + + + + + + + + + It is global! + Not global!!! + + +"#, + ) + .unwrap(); + + assert_eq!(xml(&xot, output), "It is global!"); +} + +#[test] +fn test_union_match_without_explicit_priority_registers_multiple_rules() { + let mut xot = Xot::new(); + let output = evaluate( + &mut xot, + "12", + r#" + + + + + + + + = + + ; + +"#, + ) + .unwrap(); + + assert_eq!(xml(&xot, output), "a=1;d=2;"); +} + +#[test] +fn test_builtin_text_template_rule_constructs_text_nodes() { + let mut xot = Xot::new(); + let output = evaluate( + &mut xot, + "beginmiddleend", + r#" + + + + + + +"#, + ) + .unwrap(); + + assert_eq!(xml(&xot, output), "beginmiddleend"); +} + +#[test] +fn test_xslt_user_defined_function_call_in_select() { + let mut xot = Xot::new(); + let output = evaluate( + &mut xot, + "", + r#" + + + + + + + + + +"#, + ) + .unwrap(); + + assert_eq!(xml(&xot, output), "42"); +} + +#[test] +fn test_for_each_sort_uses_xslt_sort_order() { + let mut xot = Xot::new(); + let output = evaluate( + &mut xot, + "", + r#" + + + + + + + | + + + +"#, + ) + .unwrap(); + + assert_eq!(xml(&xot, output), "id-0|id-10|id-2|"); +} + +#[test] +fn test_for_each_numeric_sort_preserves_order_for_nan_keys() { + let mut xot = Xot::new(); + let output = evaluate( + &mut xot, + "Firstp21.0.900k1.u1-m0.5sLast", + r#" + + + + + + + | + + + +"#, + ) + .unwrap(); + + assert_eq!( + xml(&xot, output), + "First|p2|1.0.9|00k|1.u|1-m|0.5s|Last|" + ); +} + +#[test] +fn test_for_each_descending_numeric_sort_places_nan_last() { + let mut xot = Xot::new(); + let output = evaluate( + &mut xot, + "", + r#" + + + + + + + | + + + +"#, + ) + .unwrap(); + + assert_eq!( + xml(&xot, output), + "12.5|1|0.009|0|-0.05|NaN|NaN|" + ); +} + +#[test] +fn test_mode_all_template_matches_initial_unnamed_mode() { + let mut xot = Xot::new(); + let output = evaluate( + &mut xot, + "", + r##" + + + ok + +"##, + ) + .unwrap(); + + assert_eq!(xml(&xot, output), "ok"); +} + +#[test] +fn test_default_mode_attribute_overrides_nested_apply_templates_mode() { + let mut xot = Xot::new(); + let output = evaluate( + &mut xot, + r#""#, + r##" + + + + + + + + + element-mode-a: + + + + + element-mode-b: + + + + + + + + + attribute-mode-b + +"##, + ) + .unwrap(); + + assert_eq!(xml(&xot, output), "element-mode-a:attribute"); +} + +#[test] +fn test_default_mode_attribute_on_apply_templates_selects_nested_mode() { + let mut xot = Xot::new(); + let output = evaluate( + &mut xot, + r#""#, + r##" + + + + + + + + + element-mode-a: + + + + + element-mode-b: + + + + + + + + + attribute-mode-b + +"##, + ) + .unwrap(); + + assert_eq!( + xml(&xot, output), + "element-mode-a:attribute-mode-b" + ); +} + +#[test] +fn test_apply_templates_current_uses_current_mode_inside_template_rule() { + let mut xot = Xot::new(); + let output = evaluate( + &mut xot, + "", + r##" + + + + + + + + + + + + + + + + + + +"##, + ) + .unwrap(); + + assert_eq!(xml(&xot, output), ""); +} + +#[test] +fn test_apply_templates_current_falls_back_to_unnamed_mode_outside_template_rule() { + let mut xot = Xot::new(); + let output = evaluate( + &mut xot, + "text", + r##" + + + + + + + + + + + + + + + + + + + + + + + +"##, + ) + .unwrap(); + + assert_eq!(xml(&xot, output), ""); +} + +#[test] +fn test_mode_attributes_accept_whitespace_padded_values() { + let mut xot = Xot::new(); + let output = evaluate( + &mut xot, + "", + r##" + + + + + + + + + + + + + + + + +"##, + ) + .unwrap(); + + assert_eq!(xml(&xot, output), ""); +} + +#[test] +fn test_mode_typed_no_is_accepted() { + parse( + StaticContext::default(), + r#" + + +"#, + ) + .unwrap(); +} + +#[test] +fn test_mode_on_no_match_shallow_copy_preserves_attributes() { + let mut xot = Xot::new(); + let output = evaluate( + &mut xot, + "text", + r##" + + + + + + + + +"##, + ) + .unwrap(); -use xee_interpreter::{error, sequence::Sequence}; -use xee_xslt_compiler::evaluate; -use xot::Xot; + assert_eq!( + xml(&xot, output), + "text" + ); +} -fn xml(xot: &Xot, sequence: Sequence) -> String { - let mut f = String::new(); +#[test] +fn test_mode_typed_yes_rejects_untyped_nodes() { + let mut xot = Xot::new(); + let err = evaluate( + &mut xot, + "", + r##" + + - for item in sequence.iter() { - f.write_str(&xot.to_string(item.to_node().unwrap()).unwrap()) - .unwrap(); - } - f + + + +"##, + ) + .unwrap_err(); + + assert_eq!(err.value(), error::Error::XTTE3100); } #[test] -fn test_transform() { +fn test_document_instruction_creates_document_node_variable() { let mut xot = Xot::new(); let output = evaluate( &mut xot, "", r#" - - + + + + hello + + + + + + + + "#, ) .unwrap(); - assert_eq!(xml(&xot, output), ""); + + assert_eq!(xml(&xot, output), "true"); } #[test] -fn test_transform_nested() { +fn test_document_instruction_satisfies_item_return_type() { let mut xot = Xot::new(); let output = evaluate( &mut xot, "", r#" - - + + + + + + + + + + 1 + + "#, ) .unwrap(); - assert_eq!(xml(&xot, output), ""); + + assert_eq!( + xml(&xot, output), + "1" + ); } #[test] -fn test_transform_text_node() { +fn test_sequence_document_loads_relative_to_stylesheet_base_uri() { + let unique = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_nanos(); + let temp_dir = std::env::temp_dir().join(format!( + "xee-sequence-1202-{}-{}", + std::process::id(), + unique + )); + fs::create_dir_all(&temp_dir).unwrap(); + fs::write(temp_dir.join("sequence-1202a.xml"), "").unwrap(); + let mut xot = Xot::new(); - let output = evaluate( + let stylesheet_path = temp_dir.join("sequence-1202.xsl"); + let output = evaluate_with_stylesheet_base( &mut xot, "", r#" - - foo + + + + ((( + + ))) + + "#, + &stylesheet_path, ) .unwrap(); - assert_eq!(xml(&xot, output), "foo"); + + assert_eq!(xml(&xot, output), "((()))"); + + fs::remove_dir_all(&temp_dir).unwrap(); } #[test] -fn test_transform_nested_apply_templates() { +fn test_for_each_group_group_by_keeps_first_item_per_key() { let mut xot = Xot::new(); let output = evaluate( &mut xot, - "", + "", r#" - + - - - - + + + + | + + - - - -"#, +"#, ) .unwrap(); - assert_eq!(xml(&xot, output), ""); + + assert_eq!(xml(&xot, output), "1|3|"); } #[test] @@ -172,6 +951,296 @@ fn test_transform_local_variable_shadow() { assert_eq!(xml(&xot, output), "BAR"); } +#[test] +fn test_duplicate_local_template_params_are_rejected() { + let namespaces = Namespaces::new( + Namespaces::default_namespaces(), + "".to_string(), + FN_NAMESPACE.to_string(), + ); + let static_context = StaticContext::from_namespaces(namespaces); + let output = parse( + static_context, + r#" + + + + + + + + + + + + +"#, + ); + + assert!(matches!( + output, + error::SpannedResult::Err(error::SpannedError { + error: error::Error::XTSE0580, + span: _ + }) + )); +} + +#[test] +fn test_missing_name_attribute_reports_xtse0010() { + let output = parse( + StaticContext::default(), + r#" + + + "#, + ); + + assert!(matches!( + output, + error::SpannedResult::Err(error::SpannedError { + error: error::Error::XTSE0010, + span: _ + }) + )); +} + +#[test] +fn test_disallowed_with_param_attribute_reports_xtse0090() { + let output = parse( + StaticContext::default(), + r#" + + + + + + + + + + + "#, + ); + + assert!(matches!( + output, + error::SpannedResult::Err(error::SpannedError { + error: error::Error::XTSE0090, + span: _ + }) + )); +} + +#[test] +fn test_invalid_required_attribute_value_reports_xtse0020() { + let output = parse( + StaticContext::default(), + r#" + + + + + "#, + ); + + assert!(matches!( + output, + error::SpannedResult::Err(error::SpannedError { + error: error::Error::XTSE0020, + span: _ + }) + )); +} + +#[test] +fn test_pattern_predicate_position_ignores_whitespace_text_nodes() { + let mut xot = Xot::new(); + let output = evaluate( + &mut xot, + r#" + MyServlet + /servlet/MyServlet/* +"#, + r#" + + + + + + {.} + + +"#, + ) + .unwrap(); + + assert_eq!(xml(&xot, output), "/servlet/MyServlet/*"); +} + +#[test] +fn test_message_is_ignored_in_result_sequence() { + let mut xot = Xot::new(); + let output = evaluate( + &mut xot, + "", + r#" + + + + debug + + + +"#, + ) + .unwrap(); + + assert_eq!(xml(&xot, output), ""); +} + +#[test] +fn test_local_variable_as_type_is_enforced() { + let mut xot = Xot::new(); + let output = evaluate( + &mut xot, + "", + r#" + + + + + + "#, + ); + + assert!(matches!( + output, + error::SpannedResult::Err(error::SpannedError { + error: error::Error::XTTE0570, + span: _ + }) + )); +} + +#[test] +fn test_global_variable_sequence_constructor_creates_temporary_tree() { + let mut xot = Xot::new(); + let output = evaluate( + &mut xot, + "", + r#" + + + + + + + + + + + + + +"#, + ) + .unwrap(); + + assert_eq!(xml(&xot, output), "http://p.com/ns"); +} + +#[test] +fn test_global_variable_is_out_of_scope_within_its_own_declaration() { + let namespaces = Namespaces::new( + Namespaces::default_namespaces(), + "".to_string(), + FN_NAMESPACE.to_string(), + ); + let static_context = StaticContext::from_namespaces(namespaces); + let output = parse( + static_context, + r#" + + + + + + +"#, + ); + + assert!(matches!( + output, + error::SpannedResult::Err(error::SpannedError { + error: error::Error::XPST0008, + span: _ + }) + )); +} + +#[test] +fn test_top_level_non_xsl_elements_do_not_break_parse() { + let namespaces = Namespaces::new( + Namespaces::default_namespaces(), + "".to_string(), + FN_NAMESPACE.to_string(), + ); + let static_context = StaticContext::from_namespaces(namespaces); + let output = parse( + static_context, + r#" + + + + + + +"#, + ); + + assert!(output.is_ok()); +} + +#[test] +fn test_builtin_template_rule_passes_params_in_xslt_2_mode() { + let mut xot = Xot::new(); + let output = evaluate( + &mut xot, + "", + r#" + + + + + + + + + + + + + +"#, + ) + .unwrap(); + + assert_eq!(xml(&xot, output), "42"); +} + #[test] fn test_transform_local_variable_from_sequence_constructor() { let mut xot = Xot::new(); @@ -191,6 +1260,36 @@ fn test_transform_local_variable_from_sequence_constructor() { assert_eq!(xml(&xot, output), "B"); } +#[test] +fn test_unused_local_variable_does_not_trigger_global_circularity() { + let mut xot = Xot::new(); + let output = evaluate( + &mut xot, + "", + r#" + + + + + + + + + + + + + + +"#, + ) + .unwrap(); + + assert_eq!(xml(&xot, output), "3"); +} + #[test] fn test_transform_document_order_dynamically_with_variable() { @@ -209,11 +1308,10 @@ fn test_transform_document_order_dynamically_with_variable() { ) .unwrap(); - // TODO: I am not sure whether this is correct; I'd expect $foo//node() to - // also get the root nodes of the sequence, but it doesn't seem to do so - // but the main point of this test is to check that the nodes found - // do have document order (created dynamically) and they do - assert_eq!(xml(&xot, output), ""); + // The variable content builds a temporary document node whose only child is + // the literal element created inside xsl:variable. Therefore + // $foo//node() yields that plus its two children in document order. + assert_eq!(xml(&xot, output), ""); } #[test] @@ -461,11 +1559,12 @@ fn test_copy_function() { "#, ); - // this is an error as we try to atomize a function + // The function item is rejected while constructing the variable's complex + // content, before string($foo) gets a chance to atomize it. assert!(matches!( output, error::SpannedResult::Err(error::SpannedError { - error: error::Error::FOTY0014, + error: error::Error::XTDE0450, span: _ }) )); @@ -819,25 +1918,26 @@ fn test_xsl_element() { assert_eq!(xml(&xot, output), r#"content"#); } -// cannot test this yet as we need namespace prefix handling - -// #[test] -// fn test_xsl_element_with_namespace() { -// let mut xot = Xot::new(); -// let output = evaluate( -// &mut xot, -// r#""#, -// r#" -// -// -// content -// -// "#, -// ) -// .unwrap(); +#[test] +fn test_xsl_element_with_prefixed_name_uses_static_namespace() { + let mut xot = Xot::new(); + let output = evaluate( + &mut xot, + r#""#, + r#" + + + content + +"#, + ) + .unwrap(); -// assert_eq!(xml(&xot, output), r#"content"#); -// } + assert_eq!( + xml(&xot, output), + r#"content"# + ); +} #[test] fn test_xsl_text() {