Skip to main content

oxml_core/
xml.rs

1//! Shared OOXML namespace and attribute helpers.
2
3use quick_xml::XmlVersion;
4use quick_xml::events::BytesStart;
5
6use crate::error::Result;
7
8/// Relationships namespace.
9pub const R_NS: &str = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
10
11/// Markup Compatibility namespace.
12pub const MC_NS: &str = "http://schemas.openxmlformats.org/markup-compatibility/2006";
13
14/// Return the local portion of a possibly prefixed XML name.
15pub fn local_name(name: &[u8]) -> &[u8] {
16    match name.iter().position(|&byte| byte == b':') {
17        Some(pos) => &name[pos + 1..],
18        None => name,
19    }
20}
21
22/// Check whether an XML name has the expected local portion.
23pub fn matches_local_name(name: &[u8], expected: &[u8]) -> bool {
24    local_name(name) == expected
25}
26
27/// Return a named attribute value, matching with or without a prefix.
28pub fn get_attr(element: &BytesStart<'_>, name: &[u8]) -> Option<String> {
29    element
30        .attributes()
31        .flatten()
32        .find(|attr| matches_local_name(attr.key.as_ref(), name))
33        .and_then(|attr| std::str::from_utf8(&attr.value).ok().map(str::to_owned))
34}
35
36/// Return non-`vt` prefixed namespace declarations needed by raw XML children.
37pub(crate) fn extra_namespace_declarations(
38    element: &BytesStart<'_>,
39) -> Result<Vec<(String, String)>> {
40    let mut declarations = Vec::new();
41    for attribute in element.attributes() {
42        let attribute = attribute?;
43        let key = attribute.key.as_ref();
44        if key.starts_with(b"xmlns:") && key != b"xmlns:vt" {
45            let name = std::str::from_utf8(key)?.to_owned();
46            let value = attribute
47                .decoded_and_normalized_value(XmlVersion::Implicit1_0, element.decoder())?
48                .into_owned();
49            declarations.push((name, value));
50        }
51    }
52    Ok(declarations)
53}
54
55#[cfg(test)]
56mod tests {
57    use quick_xml::Reader;
58    use quick_xml::events::Event;
59
60    use super::*;
61
62    #[test]
63    fn local_names_match_with_or_without_a_prefix() {
64        assert_eq!(local_name(b"w:document"), b"document");
65        assert_eq!(local_name(b"document"), b"document");
66        assert!(matches_local_name(b"p:sld", b"sld"));
67        assert!(!matches_local_name(b"p:sld", b"slide"));
68    }
69
70    #[test]
71    fn attributes_match_with_or_without_a_prefix() {
72        let mut reader = Reader::from_str(r#"<item r:id="rId7" plain="value"/>"#);
73        let mut buf = Vec::new();
74        let Event::Empty(element) = reader.read_event_into(&mut buf).unwrap() else {
75            panic!("expected empty element");
76        };
77
78        assert_eq!(get_attr(&element, b"id").as_deref(), Some("rId7"));
79        assert_eq!(get_attr(&element, b"plain").as_deref(), Some("value"));
80        assert_eq!(get_attr(&element, b"missing"), None);
81    }
82}