Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 97 additions & 0 deletions xee-xpath-lexer/src/explicit_whitespace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -305,4 +305,101 @@ mod tests {
assert_eq!(iter.next(), Some((Token::CommentStart, 6..13)));
assert_eq!(iter.next(), Some((Token::NCName("ncname"), 13..19)));
}

// Axis names and other keywords must be valid as local names in QNames.
// For example, "ex:child" must produce a PrefixedQName, not separate tokens.
#[test]
fn test_prefixed_qname_with_axis_name_local_names() {
let cases = [
("ex:child", "child", 8),
("ex:parent", "parent", 9),
("ex:self", "self", 7),
("ex:attribute", "attribute", 12),
("ex:descendant", "descendant", 13),
("ex:descendant-or-self", "descendant-or-self", 21),
("ex:ancestor", "ancestor", 11),
("ex:ancestor-or-self", "ancestor-or-self", 19),
("ex:following", "following", 12),
("ex:following-sibling", "following-sibling", 20),
("ex:preceding", "preceding", 12),
("ex:preceding-sibling", "preceding-sibling", 20),
("ex:namespace", "namespace", 12),
];
for (input, local_name, len) in cases {
let mut iter = ExplicitWhitespace::from_str(input);
assert_eq!(
iter.next(),
Some((
Token::PrefixedQName(PrefixedQName {
prefix: "ex",
local_name
}),
0..len
)),
"failed for input: {input}"
);
assert_eq!(iter.next(), None, "unexpected trailing token for: {input}");
}
}

#[test]
fn test_prefixed_qname_with_keyword_local_names() {
let cases = [
("ex:and", "and", 6),
("ex:or", "or", 5),
("ex:div", "div", 6),
("ex:mod", "mod", 6),
("ex:for", "for", 6),
("ex:let", "let", 6),
("ex:some", "some", 7),
("ex:every", "every", 8),
("ex:is", "is", 5),
("ex:to", "to", 5),
("ex:union", "union", 8),
("ex:intersect", "intersect", 12),
("ex:except", "except", 9),
("ex:instance", "instance", 11),
("ex:treat", "treat", 8),
("ex:cast", "cast", 7),
("ex:castable", "castable", 11),
("ex:as", "as", 5),
("ex:in", "in", 5),
("ex:return", "return", 9),
("ex:satisfies", "satisfies", 12),
("ex:then", "then", 7),
("ex:else", "else", 7),
];
for (input, local_name, len) in cases {
let mut iter = ExplicitWhitespace::from_str(input);
assert_eq!(
iter.next(),
Some((
Token::PrefixedQName(PrefixedQName {
prefix: "ex",
local_name
}),
0..len
)),
"failed for input: {input}"
);
assert_eq!(iter.next(), None, "unexpected trailing token for: {input}");
}
}

// Keywords should also work as prefixes in QNames
#[test]
fn test_prefixed_qname_with_keyword_prefix() {
let mut iter = ExplicitWhitespace::from_str("child:foo");
assert_eq!(
iter.next(),
Some((
Token::PrefixedQName(PrefixedQName {
prefix: "child",
local_name: "foo"
}),
0..9
))
);
assert_eq!(iter.next(), None);
}
}
62 changes: 58 additions & 4 deletions xee-xpath-lexer/src/reserved.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,18 @@ use crate::Token;
impl<'a> Token<'a> {
// tokens that can count as an ncname as a local name or as a prefix
pub(crate) fn ncname(&self) -> Option<&'a str> {
// in section A.3 of the XPath 3.1 specification
// a bunch of tokens are listed as reserved functions.
// They can be used as a valid prefix or local name, just like
// an ncname
// Per XPath 3.1 spec (https://www.w3.org/TR/xpath-31/#terminal-symbols):
//
// "Keywords in XPath 3.1 use lower-case characters and are not
// reserved—that is, names in XPath 3.1 expressions are allowed
// to be the same as language keywords, except for certain
// unprefixed function-names listed in A.3 Reserved Function Names."
//
// All keyword tokens can therefore be used as valid NCNames (prefixes
// or local names in QNames). For example, "ex:child" is a valid
// qualified name where "child" is the local name, not the child axis.
match self {
// reserved function names (XPath 3.1 spec A.3)
Token::Array => Some("array"),
Token::Attribute => Some("attribute"),
Token::Comment => Some("comment"),
Expand All @@ -27,6 +34,53 @@ impl<'a> Token<'a> {
Token::Text => Some("text"),
Token::Typeswitch => Some("typeswitch"),

// axis names
Token::Ancestor => Some("ancestor"),
Token::AncestorOrSelf => Some("ancestor-or-self"),
Token::Child => Some("child"),
Token::Descendant => Some("descendant"),
Token::DescendantOrSelf => Some("descendant-or-self"),
Token::Following => Some("following"),
Token::FollowingSibling => Some("following-sibling"),
Token::Namespace => Some("namespace"),
Token::Parent => Some("parent"),
Token::Preceding => Some("preceding"),
Token::PrecedingSibling => Some("preceding-sibling"),
Token::Self_ => Some("self"),

// other keywords
Token::And => Some("and"),
Token::As => Some("as"),
Token::Cast => Some("cast"),
Token::Castable => Some("castable"),
Token::Div => Some("div"),
Token::Else => Some("else"),
Token::Eq => Some("eq"),
Token::Every => Some("every"),
Token::Except => Some("except"),
Token::For => Some("for"),
Token::Ge => Some("ge"),
Token::Gt => Some("gt"),
Token::Idiv => Some("idiv"),
Token::In => Some("in"),
Token::Instance => Some("instance"),
Token::Intersect => Some("intersect"),
Token::Is => Some("is"),
Token::Le => Some("le"),
Token::Let => Some("let"),
Token::Lt => Some("lt"),
Token::Mod => Some("mod"),
Token::Ne => Some("ne"),
Token::Of => Some("of"),
Token::Or => Some("or"),
Token::Return => Some("return"),
Token::Satisfies => Some("satisfies"),
Token::Some => Some("some"),
Token::Then => Some("then"),
Token::To => Some("to"),
Token::Treat => Some("treat"),
Token::Union => Some("union"),

// an NCName of course can also be a prefix or a local name
Token::NCName(name) => Some(name),
_ => None,
Expand Down
16 changes: 16 additions & 0 deletions xee-xpath/tests/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,22 @@ where
Ok(())
}

pub(crate) fn run_xml_with_ns(
xml: &str,
xpath: &str,
namespaces: &[(&str, &str)],
) -> error::Result<Sequence> {
let mut documents = Documents::new();
let handle = documents.add_string_without_uri(xml).unwrap();
let mut static_context_builder = StaticContextBuilder::default();
for (prefix, uri) in namespaces {
static_context_builder.add_namespace(prefix, uri);
}
let queries = Queries::new(static_context_builder);
let q = queries.sequence(xpath)?;
q.execute(&mut documents, handle)
}

fn xot_nodes_to_items(node: &[xot::Node]) -> Sequence {
Sequence::from(
node.iter()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
source: xee-xpath/tests/xpath.rs
assertion_line: 1355
expression: "run_xml(\"<data><child>found it</child></data>\", \"//child/string()\",)"
---
Ok(
One(
One {
item: Atomic(
String(
String,
"found it",
),
),
},
),
)
17 changes: 17 additions & 0 deletions xee-xpath/tests/snapshots/xpath__keyword_qname_ancestor.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
source: xee-xpath/tests/xpath.rs
assertion_line: 1389
expression: "run_xml_with_ns(r#\"<data xmlns:ex=\"http://example.com/ex\"><ex:ancestor>found it</ex:ancestor></data>\"#,\n\"//ex:ancestor/string()\", &[(\"ex\", \"http://example.com/ex\")])"
---
Ok(
One(
One {
item: Atomic(
String(
String,
"found it",
),
),
},
),
)
17 changes: 17 additions & 0 deletions xee-xpath/tests/snapshots/xpath__keyword_qname_and.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
source: xee-xpath/tests/xpath.rs
assertion_line: 1407
expression: "run_xml_with_ns(r#\"<data xmlns:ex=\"http://example.com/ex\"><ex:and>found it</ex:and></data>\"#,\n\"//ex:and/string()\", &[(\"ex\", \"http://example.com/ex\")])"
---
Ok(
One(
One {
item: Atomic(
String(
String,
"found it",
),
),
},
),
)
17 changes: 17 additions & 0 deletions xee-xpath/tests/snapshots/xpath__keyword_qname_child.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
source: xee-xpath/tests/xpath.rs
assertion_line: 1353
expression: "run_xml_with_ns(r#\"<data xmlns:ex=\"http://example.com/ex\"><ex:child>found it</ex:child></data>\"#,\n\"//ex:child/string()\", &[(\"ex\", \"http://example.com/ex\")])"
---
Ok(
One(
One {
item: Atomic(
String(
String,
"found it",
),
),
},
),
)
17 changes: 17 additions & 0 deletions xee-xpath/tests/snapshots/xpath__keyword_qname_descendant.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
source: xee-xpath/tests/xpath.rs
assertion_line: 1380
expression: "run_xml_with_ns(r#\"<data xmlns:ex=\"http://example.com/ex\"><ex:descendant>found it</ex:descendant></data>\"#,\n\"//ex:descendant/string()\", &[(\"ex\", \"http://example.com/ex\")])"
---
Ok(
One(
One {
item: Atomic(
String(
String,
"found it",
),
),
},
),
)
17 changes: 17 additions & 0 deletions xee-xpath/tests/snapshots/xpath__keyword_qname_or.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
source: xee-xpath/tests/xpath.rs
assertion_line: 1398
expression: "run_xml_with_ns(r#\"<data xmlns:ex=\"http://example.com/ex\"><ex:or>found it</ex:or></data>\"#,\n\"//ex:or/string()\", &[(\"ex\", \"http://example.com/ex\")])"
---
Ok(
One(
One {
item: Atomic(
String(
String,
"found it",
),
),
},
),
)
17 changes: 17 additions & 0 deletions xee-xpath/tests/snapshots/xpath__keyword_qname_parent.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
source: xee-xpath/tests/xpath.rs
assertion_line: 1362
expression: "run_xml_with_ns(r#\"<data xmlns:ex=\"http://example.com/ex\"><ex:parent>found it</ex:parent></data>\"#,\n\"//ex:parent/string()\", &[(\"ex\", \"http://example.com/ex\")])"
---
Ok(
One(
One {
item: Atomic(
String(
String,
"found it",
),
),
},
),
)
17 changes: 17 additions & 0 deletions xee-xpath/tests/snapshots/xpath__keyword_qname_self.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
source: xee-xpath/tests/xpath.rs
assertion_line: 1371
expression: "run_xml_with_ns(r#\"<data xmlns:ex=\"http://example.com/ex\"><ex:self>found it</ex:self></data>\"#,\n\"//ex:self/string()\", &[(\"ex\", \"http://example.com/ex\")])"
---
Ok(
One(
One {
item: Atomic(
String(
String,
"found it",
),
),
},
),
)
Loading
Loading