1use quick_xml::Reader;
13use quick_xml::escape::unescape;
14use quick_xml::events::{BytesRef, BytesText, Event};
15use quick_xml::name::QName;
16
17pub fn decode_escaped(text: &BytesText<'_>) -> String {
22 let Ok(raw) = text.decode() else {
23 return String::new();
24 };
25 match unescape(&raw) {
26 Ok(unescaped) => unescaped.into_owned(),
27 Err(_) => raw.into_owned(),
29 }
30}
31
32pub fn decode_plain(text: &BytesText<'_>) -> String {
38 text.decode().map(|c| c.into_owned()).unwrap_or_default()
39}
40
41pub fn resolve_entity(entity: &BytesRef<'_>) -> String {
47 let Ok(name) = entity.decode() else {
48 return String::new();
49 };
50 match unescape(&format!("&{name};")) {
51 Ok(resolved) => resolved.into_owned(),
52 Err(_) => format!("&{name};"),
53 }
54}
55
56pub fn read_element_text(reader: &mut Reader<&[u8]>, start_name: QName<'_>) -> String {
67 let end = start_name.as_ref().to_vec();
68 let mut out = String::new();
69 let mut buf = Vec::new();
70 let mut depth = 1u32;
71
72 let (trim_start, trim_end) = {
77 let config = reader.config_mut();
78 let previous = (config.trim_text_start, config.trim_text_end);
79 config.trim_text(false);
80 previous
81 };
82
83 loop {
84 match reader.read_event_into(&mut buf) {
85 Ok(Event::Start(ref e)) => {
86 if e.name().as_ref() == end {
87 depth += 1;
88 }
89 }
90 Ok(Event::End(ref e)) => {
91 if e.name().as_ref() == end {
92 depth -= 1;
93 if depth == 0 {
94 break;
95 }
96 }
97 }
98 Ok(Event::Text(ref e)) => out.push_str(&decode_plain(e)),
99 Ok(Event::CData(ref e)) => {
100 if let Ok(decoded) = e.decode() {
101 out.push_str(&decoded);
102 }
103 }
104 Ok(Event::GeneralRef(ref e)) => out.push_str(&resolve_entity(e)),
105 Ok(Event::Eof) | Err(_) => break,
106 _ => {}
107 }
108 buf.clear();
109 }
110
111 let config = reader.config_mut();
112 config.trim_text_start = trim_start;
113 config.trim_text_end = trim_end;
114
115 out
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121
122 fn text_of(xml: &str) -> String {
123 let mut reader = Reader::from_str(xml);
124 let mut buf = Vec::new();
125 loop {
126 match reader.read_event_into(&mut buf) {
127 Ok(Event::Start(ref e)) if e.name().as_ref() == b"t" => {
128 return read_element_text(&mut reader, e.name());
129 }
130 Ok(Event::Eof) => return String::new(),
131 _ => {}
132 }
133 }
134 }
135
136 #[test]
137 fn entities_survive_text_accumulation() {
138 assert_eq!(text_of("<t>a & b</t>"), "a & b");
139 assert_eq!(text_of("<t><tag></t>"), "<tag>");
140 assert_eq!(text_of("<t>AB</t>"), "AB");
141 assert_eq!(text_of("<t>plain</t>"), "plain");
142 }
143
144 #[test]
145 fn whitespace_around_entities_is_kept_even_when_trimming() {
146 let mut reader = Reader::from_str("<t>Title & Co. <tagged></t>");
147 reader.config_mut().trim_text(true);
148 let mut buf = Vec::new();
149 loop {
150 match reader.read_event_into(&mut buf) {
151 Ok(Event::Start(ref e)) => {
152 assert_eq!(
153 read_element_text(&mut reader, e.name()),
154 "Title & Co. <tagged>"
155 );
156 assert!(reader.config().trim_text_start);
158 return;
159 }
160 Ok(Event::Eof) => panic!("no start tag"),
161 _ => {}
162 }
163 }
164 }
165
166 #[test]
167 fn unknown_entity_is_preserved_verbatim() {
168 assert_eq!(text_of("<t>a b</t>"), "a b");
169 }
170
171 #[test]
172 fn read_text_span_is_unescaped() {
173 let xml = "<t>a & b</t>";
174 let mut reader = Reader::from_str(xml);
175 let mut buf = Vec::new();
176 loop {
177 match reader.read_event_into(&mut buf) {
178 Ok(Event::Start(ref e)) => {
179 let span = reader.read_text(e.name()).unwrap();
180 assert_eq!(decode_escaped(&span), "a & b");
181 return;
182 }
183 Ok(Event::Eof) => panic!("no start tag"),
184 _ => {}
185 }
186 }
187 }
188
189 #[test]
190 fn xml_text_handles_cdata_mixed_nested_and_general_refs() {
191 assert_eq!(
192 text_of("<t>start<![CDATA[<raw>]]><n>nested & text</n>end </t>"),
193 "start<raw>nested & textend "
194 );
195 }
196}