summaryrefslogtreecommitdiffstats
path: root/third_party/rust/embed-manifest/src/manifest/xml.rs
blob: a247f42a618f6be6784be072328cd8d1b40f64ce (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
use std::fmt::{self, Display, Formatter, Write};

/// Simple but still over-engineered XML generator for a [`Formatter`], for generating
/// Windows Manifest XML. This can easily generate invalid XML.
///
/// When used correctly, this should generate the same output as MT’s `-canonicalize`
/// option.
pub struct XmlFormatter<'a, 'f> {
    f: &'f mut Formatter<'a>,
    state: State,
    depth: usize,
}

#[derive(Eq, PartialEq)]
enum State {
    Init,
    StartTag,
    Text,
}

impl<'a, 'f> XmlFormatter<'a, 'f> {
    pub fn new(f: &'f mut Formatter<'a>) -> Self {
        Self {
            f,
            state: State::Init,
            depth: 0,
        }
    }

    fn pretty(&mut self) -> fmt::Result {
        if self.f.alternate() {
            self.f.write_str("\r\n")?;
            for _ in 0..self.depth {
                self.f.write_str("    ")?;
            }
        }
        Ok(())
    }

    pub fn start_document(&mut self) -> fmt::Result {
        if !self.f.alternate() {
            self.f.write_char('\u{FEFF}')?;
        }
        self.f
            .write_str("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\r\n")
    }

    pub fn element<F: FnOnce(&mut Self) -> fmt::Result>(&mut self, name: &str, attrs: &[(&str, &str)], f: F) -> fmt::Result {
        self.start_element(name, attrs)?;
        f(self)?;
        self.end_element(name)
    }

    pub fn empty_element(&mut self, name: &str, attrs: &[(&str, &str)]) -> fmt::Result {
        self.start_element(name, attrs)?;
        self.end_element(name)
    }

    pub fn start_element(&mut self, name: &str, attrs: &[(&str, &str)]) -> fmt::Result {
        if self.state == State::StartTag {
            self.f.write_char('>')?;
        }
        if self.depth != 0 {
            self.pretty()?;
        }
        write!(self.f, "<{}", name)?;
        for (name, value) in attrs {
            write!(self.f, " {}=\"{}\"", name, Xml(value))?;
        }
        self.depth += 1;
        self.state = State::StartTag;
        Ok(())
    }

    pub fn end_element(&mut self, name: &str) -> fmt::Result {
        self.depth -= 1;
        match self.state {
            State::Init => {
                self.pretty()?;
                write!(self.f, "</{}>", name)
            }
            State::Text => {
                self.state = State::Init;
                write!(self.f, "</{}>", name)
            }
            State::StartTag => {
                self.state = State::Init;
                if self.f.alternate() {
                    self.f.write_str("/>")
                } else {
                    write!(self.f, "></{}>", name)
                }
            }
        }
    }

    pub fn text(&mut self, s: &str) -> fmt::Result {
        if self.state == State::StartTag {
            self.state = State::Text;
            self.f.write_char('>')?;
        }
        Xml(s).fmt(self.f)
    }
}

/// Temporary wrapper for outputting a string with XML attribute encoding.
/// This does not do anything with the control characters which are not
/// valid in XML, encoded or not.
struct Xml<'a>(&'a str);

impl<'a> Display for Xml<'a> {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        // Process the string in blocks separated by special characters, so that the parts that
        // don't need encoding can be written all at once, not character by character, and with
        // no checks for whether string slices are aligned on character boundaries.
        for s in self.0.split_inclusive(&['<', '&', '>', '"', '\r'][..]) {
            // Check whether the last character in the substring needs encoding. This will be
            // `None` at the end of the input string.
            let mut iter = s.chars();
            let ch = match iter.next_back() {
                Some('<') => Some("&lt;"),
                Some('&') => Some("&amp;"),
                Some('>') => Some("&gt;"),
                Some('"') => Some("&quot;"),
                Some('\r') => Some("&#13;"),
                _ => None,
            };
            // Write the substring except the last character, then the encoded character;
            // or the entire substring if it is not terminated by a special character.
            match ch {
                Some(enc) => {
                    f.write_str(iter.as_str())?;
                    f.write_str(enc)?;
                }
                None => f.write_str(s)?,
            }
        }
        Ok(())
    }
}