Make xml: prefix always available in query xml (#16472)

## Release notes summary - What our users need to know

`query xml` now has `xml:` namespace prefix available by default,
without the need to specify it via `--namespaces`.

## Details and motivation

`xml:` prefix is rarely (if ever) explicitly declared in XML sources, so
its associated namespace is a bit obscure. `sxd_xpath` seems to assume
it's `http://www.w3.org/XML/1998/namespace`, so we register it by
default.

## Tasks after submitting
<!-- Remove any tasks which aren't relevant for your PR, or add your own
-->
- [ ] Update the
[documentation](https://github.com/nushell/nushell.github.io)
This commit is contained in:
Bruce Weirdan
2025-08-19 23:39:30 +02:00
committed by GitHub
parent dd664e3cdb
commit 258a7f6e97

View File

@@ -66,14 +66,14 @@ Output of the nodeset results depends on the flags used:
Example { Example {
description: "Query namespaces on the root element of an SVG file", description: "Query namespaces on the root element of an SVG file",
example: r#"http get --raw https://www.w3.org/TR/SVG/images/conform/smiley.svg example: r#"http get --raw https://www.w3.org/TR/SVG/images/conform/smiley.svg
| query xml '/svg:svg/namespace::*' --output-string-value --output-names --namespaces {svg: "http://www.w3.org/2000/svg"}"#, | query xml '/svg:svg/namespace::*' --output-string-value --output-names --output-type --namespaces {svg: "http://www.w3.org/2000/svg"}"#,
result: None, result: None,
}, },
// scalar output // scalar output
Example { Example {
description: "Query number of stylesheets SVG file has", description: "Query the language of Nushell blog (`xml:` prefix is always available)",
example: r#"http get --raw https://www.w3.org/TR/SVG/images/conform/smiley.svg example: r#"http get --raw https://www.nushell.sh/atom.xml
| query xml 'count(//svg:style)' --namespaces {svg: "http://www.w3.org/2000/svg"}"#, | query xml 'string(/*/@xml:lang)'"#,
result: None, result: None,
}, },
// query attributes // query attributes
@@ -85,9 +85,9 @@ Output of the nodeset results depends on the flags used:
}, },
// default output // default output
Example { Example {
description: "Get recent Debian news", description: "Get recent Nushell news",
example: r#"http get --raw https://www.debian.org/News/news example: r#"http get --raw https://www.nushell.sh/atom.xml
| query xml '//item/title|//item/link' | query xml '//atom:entry/atom:title|//atom:entry/atom:link/@href' --namespaces {atom: "http://www.w3.org/2005/Atom"}
| window 2 --stride 2 | window 2 --stride 2
| each { {title: $in.0.string_value, link: $in.1.string_value} }"#, | each { {title: $in.0.string_value, link: $in.1.string_value} }"#,
result: None, result: None,
@@ -141,7 +141,22 @@ pub fn execute_xpath_query(
let document = package.as_document(); let document = package.as_document();
let mut context = Context::new(); let mut context = Context::new();
for (prefix, uri) in namespaces.unwrap_or_default().into_iter() { let mut namespaces = namespaces.unwrap_or_default();
if namespaces.get("xml").is_none() {
// XML namespace is always present, so we add it explicitly
// it's used in attributes like `xml:lang`, `xml:base`, etc.
namespaces.insert(
"xml",
Value::string("http://www.w3.org/XML/1998/namespace", call.head),
);
}
// NB: `xmlns:whatever=` or `xmlns=` may look like an attribute, but XPath doesn't treat it as such.
// Those are namespaces, and they are available through a separate axis (`namespace::`)
// Thus we don't need to register a namespace for `xmlns` prefix
for (prefix, uri) in namespaces.into_iter() {
context.set_namespace(prefix.as_str(), uri.into_string()?.as_str()); context.set_namespace(prefix.as_str(), uri.into_string()?.as_str());
} }
@@ -547,4 +562,26 @@ mod tests {
assert_eq!(actual, expected); assert_eq!(actual, expected);
} }
#[test]
fn xml_namespace_is_always_present() {
let call = EvaluatedCall {
head: Span::test_data(),
positional: vec![],
named: vec![],
};
let text = Value::test_string(
r#"<?xml version="1.0" encoding="UTF-8"?><elt xml:lang="en">hello</elt>"#,
);
let spanned_str: Spanned<String> = Spanned {
item: "string(/elt/@xml:lang)".to_string(),
span: Span::test_data(),
};
let actual = query(&call, &text, Some(spanned_str), None).expect("test should not fail");
assert_eq!(actual, Value::test_string("en"));
}
} }