diff --git a/crates/nu_plugin_query/src/query_xml.rs b/crates/nu_plugin_query/src/query_xml.rs
index ffe0caa92c..0d69a28cb2 100644
--- a/crates/nu_plugin_query/src/query_xml.rs
+++ b/crates/nu_plugin_query/src/query_xml.rs
@@ -66,14 +66,14 @@ Output of the nodeset results depends on the flags used:
Example {
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
- | 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,
},
// scalar output
Example {
- description: "Query number of stylesheets SVG file has",
- example: r#"http get --raw https://www.w3.org/TR/SVG/images/conform/smiley.svg
- | query xml 'count(//svg:style)' --namespaces {svg: "http://www.w3.org/2000/svg"}"#,
+ description: "Query the language of Nushell blog (`xml:` prefix is always available)",
+ example: r#"http get --raw https://www.nushell.sh/atom.xml
+ | query xml 'string(/*/@xml:lang)'"#,
result: None,
},
// query attributes
@@ -85,9 +85,9 @@ Output of the nodeset results depends on the flags used:
},
// default output
Example {
- description: "Get recent Debian news",
- example: r#"http get --raw https://www.debian.org/News/news
- | query xml '//item/title|//item/link'
+ description: "Get recent Nushell news",
+ example: r#"http get --raw https://www.nushell.sh/atom.xml
+ | query xml '//atom:entry/atom:title|//atom:entry/atom:link/@href' --namespaces {atom: "http://www.w3.org/2005/Atom"}
| window 2 --stride 2
| each { {title: $in.0.string_value, link: $in.1.string_value} }"#,
result: None,
@@ -141,7 +141,22 @@ pub fn execute_xpath_query(
let document = package.as_document();
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());
}
@@ -547,4 +562,26 @@ mod tests {
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#"hello"#,
+ );
+
+ let spanned_str: Spanned = 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"));
+ }
}