Multiple output types for query xml (#16459)

This is a breaking change.

Refs
https://discord.com/channels/601130461678272522/615329862395101194/1406413134402551869

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

Previously, `query xml` always returned a table, even for scalar
results. Now scalar results will be returned as scalars.

For example

```nushell
open -r tests/fixtures/formats/jt.xml
| query xml 'false()'
```

used to return

```
╭───┬─────────╮
│ # │ false() │
├───┼─────────┤
│ 0 │ false   │
╰───┴─────────╯
```

and now it will return just `false`.

## 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-18 13:34:57 +02:00
committed by GitHub
parent 7d5adf0a42
commit 1ddae02da7

View File

@@ -84,43 +84,27 @@ pub fn execute_xpath_query(
// build_namespaces(&arguments, &mut context); // build_namespaces(&arguments, &mut context);
let res = xpath.evaluate(&context, document.root()); let res = xpath.evaluate(&context, document.root());
// Some xpath statements can be long, so let's truncate it with ellipsis
let mut key = query_string.clone();
if query_string.len() >= 20 {
key.truncate(17);
key += "...";
} else {
key = query_string.to_string();
};
match res { match res {
Ok(r) => { Ok(sxd_xpath::Value::Boolean(b)) => Ok(Value::bool(b, call.head)),
let mut record = Record::new(); Ok(sxd_xpath::Value::Number(n)) => Ok(Value::float(n, call.head)),
let mut records: Vec<Value> = vec![]; Ok(sxd_xpath::Value::String(s)) => Ok(Value::string(s, call.head)),
Ok(sxd_xpath::Value::Nodeset(ns)) => {
match r { // Some xpath statements can be long, so let's truncate it with ellipsis
sxd_xpath::Value::Nodeset(ns) => { let mut key = query_string.clone();
for n in ns.document_order() { if query_string.len() >= 20 {
record.push(key.clone(), Value::string(n.string_value(), call.head)); key.truncate(17);
} key += "...";
} } else {
sxd_xpath::Value::Boolean(b) => { key = query_string.to_string();
record.push(key, Value::bool(b, call.head));
}
sxd_xpath::Value::Number(n) => {
record.push(key, Value::float(n, call.head));
}
sxd_xpath::Value::String(s) => {
record.push(key, Value::string(s, call.head));
}
}; };
// convert the cols and vecs to a table by creating individual records let mut records: Vec<Value> = vec![];
// for each item so we can then use a list to make a table for n in ns.document_order() {
for (k, v) in record { records.push(Value::record(
records.push(Value::record(record! { k => v }, call.head)) record! {key.clone() => Value::string(n.string_value(), call.head)},
call.head,
));
} }
Ok(Value::list(records, call.head)) Ok(Value::list(records, call.head))
} }
Err(err) => { Err(err) => {
@@ -166,14 +150,7 @@ mod tests {
let actual = query(&call, &text, Some(spanned_str), None).expect("test should not fail"); let actual = query(&call, &text, Some(spanned_str), None).expect("test should not fail");
let expected = Value::list( assert_eq!(actual, Value::test_float(1.0));
vec![Value::test_record(record! {
"count(//a/*[posit..." => Value::test_float(1.0),
})],
Span::test_data(),
);
assert_eq!(actual, expected);
} }
#[test] #[test]
@@ -196,14 +173,7 @@ mod tests {
let actual = query(&call, &text, Some(spanned_str), None).expect("test should not fail"); let actual = query(&call, &text, Some(spanned_str), None).expect("test should not fail");
let expected = Value::list( assert_eq!(actual, Value::test_float(1.0));
vec![Value::test_record(record! {
"count(//*[contain..." => Value::test_float(1.0),
})],
Span::test_data(),
);
assert_eq!(actual, expected);
} }
#[test] #[test]
@@ -243,4 +213,91 @@ mod tests {
// and yet it should work regardless // and yet it should work regardless
assert_eq!(actual, expected); assert_eq!(actual, expected);
} }
#[test]
fn number_returns_float() {
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/>"#);
let spanned_str: Spanned<String> = Spanned {
item: "count(/elt)".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_float(1.0));
}
#[test]
fn boolean_returns_bool() {
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/>"#);
let spanned_str: Spanned<String> = Spanned {
item: "false()".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_bool(false));
}
#[test]
fn string_returns_string() {
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/>"#);
let spanned_str: Spanned<String> = Spanned {
item: "local-name(/elt)".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("elt"));
}
#[test]
fn nodeset_returns_table() {
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>hello</elt>"#);
let spanned_str: Spanned<String> = Spanned {
item: "/elt".to_string(),
span: Span::test_data(),
};
let actual = query(&call, &text, Some(spanned_str), None).expect("test should not fail");
let expected = Value::list(
vec![Value::test_record(record! {
"/elt" => Value::string("hello", Span::test_data()),
})],
Span::test_data(),
);
assert_eq!(actual, expected);
}
} }