Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_text/src/xml/mod.rs

12.9 KiB, 1 run

created by r1870400018:22556, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1//! XML that remembers where it came from.
2//!
3//! A namespace-aware reader whose every node retains the byte span of the source it was read from,
4//! and a writer that edits a document by *splicing* into those spans rather than by serialising a
5//! model back out.
6//!
7//! # Why the spans are the whole point
8//!
9//! The ordinary shape of an XML editor is: parse into a structure, change the structure, write the
10//! structure out. Everything the structure has no field for is then silently gone. In a `.docx` that
11//! is the comments, the bookmarks, the tracked changes, the content controls, the custom XML, the
12//! theme and the tab stops -- and the person who notices is not the user, it is the colleague they
13//! sent the file to.
14//!
15//! So nothing here is ever written out of the tree. An element with no handler is still a node, and
16//! it serialises by emitting the bytes it was read from. An edit is a [`Splice`]: a byte range and
17//! what replaces it. [`Xml::render`] walks the source, drops in the splices, and copies everything
18//! else. A document nobody edited renders as the bytes it was parsed from, not because that was
19//! tested but because there is no code path that could do otherwise.
20//!
21//! That leaves one thing worth testing, and it is tested: **the spans tile the source exactly**. Every
22//! node's span, concatenated in order, is the source with nothing missing and nothing counted twice.
23//! A lexer that lost a byte would show up there and nowhere else.
24//!
25//! # This is not [`crate::doc`], and the difference matters
26//!
27//! [`crate::doc`] is a neutral document tree that deliberately cannot carry markup it has no node for
28//! -- see [`crate::doc::policy`] on why. That makes it right for *reading* a document into prose and
29//! right for *creating* one from prose, and wrong for editing a file somebody else wrote, because
30//! everything it cannot represent is everything an edit would destroy. Reach for `doc` to read or to
31//! create. Reach for this to edit.
32//!
33//! # It is not only for Office
34//!
35//! `fe2o3_net`'s UPnP client hand-rolls `element_body()` and `first_element()` over strings today.
36//! Anything that reads XML by looking for angle brackets is a candidate for this.
37//!
38//! # Usage
39//!
40//! ```ignore
41//! use oxedyne_fe2o3_text::xml::Xml;
42//!
43//! let mut xml = res!(Xml::parse(&src));
44//! let body = res!(xml.root()).find(&["w:body"]);
45//! // ... locate a paragraph, then replace exactly its bytes.
46//! res!(xml.splice(para.span.clone(), fresh));
47//! let out = xml.render();
48//! ```
49
50pub mod read;
51pub mod write;
52
53use oxedyne_fe2o3_core::prelude::*;
54
55use std::ops::Range;
56
57/// A byte range in the source a document was read from.
58pub type Span = Range<usize>;
59
60/// How deep a document may nest its elements before the reader refuses it.
61///
62/// A `.docx` nests a dozen deep at its worst -- a table in a cell in a table, inside a text box. A
63/// thousand is a document built to exhaust the stack of whatever reads it.
64pub const DEPTH_LIMIT: usize = 256;
65
66/// A replacement of one byte range of the source by fresh text.
67///
68/// The only way this module changes a document. What is outside every splice is copied, so what was
69/// never understood is never touched.
70#[derive(Clone, Debug, PartialEq)]
71pub struct Splice {
72 /// The bytes being replaced.
73 pub span: Span,
74 /// What replaces them.
75 pub text: String,
76}
77
78/// A qualified name, and the namespace it resolved to.
79#[derive(Clone, Debug, PartialEq)]
80pub struct Name {
81 /// The name as it was written, prefix and all: `w:pStyle`.
82 pub qname: String,
83 /// Where the name sits in the source.
84 pub span: Span,
85 /// The namespace URI it resolved to, as an index into the document's table. `None` where the name
86 /// carries no prefix and no default namespace is in scope.
87 pub ns: Option<usize>,
88}
89
90impl Name {
91
92 /// The local part: what follows the colon, or the whole name where there is none.
93 pub fn local(&self) -> &str {
94 match self.qname.find(':') {
95 Some(i) => &self.qname[i + 1..],
96 None => &self.qname,
97 }
98 }
99
100 /// The prefix, empty where the name carries none.
101 pub fn prefix(&self) -> &str {
102 match self.qname.find(':') {
103 Some(i) => &self.qname[..i],
104 None => "",
105 }
106 }
107}
108
109/// One attribute of an element.
110#[derive(Clone, Debug, PartialEq)]
111pub struct Attr {
112 /// The attribute's name.
113 pub name: Name,
114 /// Its value, with entity references resolved.
115 pub value: String,
116 /// The whole `name="value"`, in the source.
117 pub span: Span,
118 /// The value alone, between its quotes.
119 pub val_span: Span,
120}
121
122/// An element, its attributes and what it holds.
123#[derive(Clone, Debug, PartialEq)]
124pub struct Elem {
125 /// The element's name.
126 pub name: Name,
127 /// Its attributes, in the order written. Namespace declarations are among them: they are
128 /// attributes, and dropping them would make the element unwritable.
129 pub attrs: Vec<Attr>,
130 /// What it holds, in order.
131 pub kids: Vec<Node>,
132 /// The whole element in the source, from the `<` of its open tag to the `>` of its close.
133 pub span: Span,
134 /// Its open tag alone.
135 pub open: Span,
136 /// What lies between the tags. `None` where the element was written `<a/>`.
137 pub inner: Option<Span>,
138}
139
140impl Elem {
141
142 /// The value of an attribute, by the name as written.
143 pub fn attr(&self, qname: &str) -> Option<&str> {
144 self.attrs.iter().find(|a| a.name.qname == qname).map(|a| a.value.as_str())
145 }
146
147 /// The value of an attribute, by its namespace and local name.
148 ///
149 /// What to ask where the document's choice of prefix is not yours to assume. A `.docx` written by
150 /// Word and one written by LibreOffice agree on the URIs and need not agree on the prefixes.
151 pub fn attr_ns(&self, uri: Option<usize>, local: &str) -> Option<&str> {
152 self.attrs.iter()
153 .find(|a| a.name.ns == uri && a.name.local() == local)
154 .map(|a| a.value.as_str())
155 }
156
157 /// The child elements, in order.
158 pub fn elems(&self) -> impl Iterator<Item = &Elem> {
159 self.kids.iter().filter_map(|k| match k {
160 Node::Elem(e) => Some(e),
161 _ => None,
162 })
163 }
164
165 /// The first child element of that name, as written.
166 pub fn child(&self, qname: &str) -> Option<&Elem> {
167 self.elems().find(|e| e.name.qname == qname)
168 }
169
170 /// Every child element of that name, as written.
171 pub fn children(&self, qname: &str) -> Vec<&Elem> {
172 self.elems().filter(|e| e.name.qname == qname).collect()
173 }
174
175 /// The element at the end of a path of child names, where each step is the first match.
176 pub fn find(&self, path: &[&str]) -> Option<&Elem> {
177 let mut at = self;
178 for step in path {
179 at = at.child(step)?;
180 }
181 Some(at)
182 }
183
184 /// Every descendant of that name, in document order, the element itself included where it matches.
185 pub fn all(&self, qname: &str) -> Vec<&Elem> {
186 let mut out = Vec::new();
187 self.gather(qname, &mut out);
188 out
189 }
190
191 /// Adds this element and its descendants of that name to a list, in document order.
192 fn gather<'a>(&'a self, qname: &str, out: &mut Vec<&'a Elem>) {
193 if self.name.qname == qname {
194 out.push(self);
195 }
196 for kid in self.elems() {
197 kid.gather(qname, out);
198 }
199 }
200
201 /// Whether the element carries a descendant of any of those names.
202 ///
203 /// What an edit asks before it touches a span: a paragraph holding a bookmark, a comment anchor or
204 /// a footnote reference is one whose deletion would leave a dangling reference in another part of
205 /// the document, and no check on the bytes of *this* part would catch it.
206 pub fn holds_any(&self, qnames: &[&str]) -> bool {
207 if qnames.iter().any(|n| self.name.qname == *n) {
208 return true;
209 }
210 self.elems().any(|k| k.holds_any(qnames))
211 }
212}
213
214/// A node of the document: an element, or one of the things that are not elements and are still
215/// bytes somebody wrote.
216#[derive(Clone, Debug, PartialEq)]
217pub enum Node {
218 /// An element.
219 Elem(Elem),
220 /// Character data, as its span. Undecoded, because most of it is never looked at.
221 Text(Span),
222 /// A comment, whole, `<!--` to `-->`.
223 Comment(Span),
224 /// A processing instruction, the XML declaration among them.
225 Pi(Span),
226 /// A `<![CDATA[ ... ]]>` section, whole.
227 CData(Span),
228 /// A document type declaration, whole.
229 DocType(Span),
230}
231
232impl Node {
233
234 /// The node's span in the source, whatever kind it is.
235 pub fn span(&self) -> Span {
236 match self {
237 Self::Elem(e) => e.span.clone(),
238 Self::Text(s)
239 | Self::Comment(s)
240 | Self::Pi(s)
241 | Self::CData(s)
242 | Self::DocType(s) => s.clone(),
243 }
244 }
245}
246
247/// A parsed XML document, holding the source it was read from.
248#[derive(Clone, Debug, Default)]
249pub struct Xml {
250 /// The source. Every span addresses into it and every unedited byte is written back out of it.
251 src: String,
252 /// The nodes at the top of the document, in order.
253 pub nodes: Vec<Node>,
254 /// Every namespace URI the document declared, once each. A [`Name`] refers to one by index.
255 uris: Vec<String>,
256 /// The edits, kept in order of where they fall and never overlapping.
257 edits: Vec<Splice>,
258}
259
260impl Xml {
261
262 /// The source the document was read from.
263 pub fn source(&self) -> &str {
264 &self.src
265 }
266
267 /// The raw source of a span, exactly as written.
268 pub fn raw(&self, span: &Span) -> &str {
269 self.src.get(span.clone()).unwrap_or("")
270 }
271
272 /// The text of a span with entity references resolved.
273 pub fn text(&self, span: &Span) -> String {
274 write::decode(self.raw(span))
275 }
276
277 /// The namespace URIs the document declared.
278 pub fn uris(&self) -> &[String] {
279 &self.uris
280 }
281
282 /// Where a namespace URI sits in the document's table, if it declared one.
283 pub fn uri_index(&self, uri: &str) -> Option<usize> {
284 self.uris.iter().position(|u| u == uri)
285 }
286
287 /// The document's root element.
288 pub fn root(&self) -> Outcome<&Elem> {
289 for node in &self.nodes {
290 if let Node::Elem(e) = node {
291 return Ok(e);
292 }
293 }
294 Err(err!("The document has no root element."; Invalid, Input, Missing))
295 }
296
297 /// Whether nothing has been spliced, so rendering gives the source back.
298 pub fn is_pristine(&self) -> bool {
299 self.edits.is_empty()
300 }
301
302 /// The splices waiting to be rendered.
303 pub fn edits(&self) -> &[Splice] {
304 &self.edits
305 }
306
307 /// Replaces a byte range of the source with fresh text.
308 ///
309 /// The range must lie within the source and must not overlap a splice already made, both of which
310 /// are refused rather than resolved: two edits that overlap have no defined result, and guessing
311 /// one would be a corruption nobody asked for.
312 pub fn splice(&mut self, span: Span, text: String) -> Outcome<()> {
313 if span.start > span.end || span.end > self.src.len() {
314 return Err(err!(
315 "An edit was asked for over bytes {}..{} of a document of {} bytes.",
316 span.start, span.end, self.src.len(); Invalid, Input, Range));
317 }
318 if !self.src.is_char_boundary(span.start) || !self.src.is_char_boundary(span.end) {
319 return Err(err!(
320 "An edit was asked for over bytes {}..{}, which cut a character in half.",
321 span.start, span.end; Invalid, Input, Range));
322 }
323 let at = self.edits.partition_point(|e| e.span.end <= span.start);
324 if let Some(next) = self.edits.get(at) {
325 if next.span.start < span.end {
326 return Err(err!(
327 "An edit over bytes {}..{} overlaps one already made over {}..{}.",
328 span.start, span.end, next.span.start, next.span.end; Invalid, Input));
329 }
330 }
331 self.edits.insert(at, Splice { span, text });
332 Ok(())
333 }
334
335 /// Undoes every splice, so the document renders as its source again.
336 pub fn revert(&mut self) {
337 self.edits.clear();
338 }
339
340 /// The document as it now stands: the source, with the splices dropped in.
341 ///
342 /// Everything outside a splice is copied, so a construct this never understood is written back
343 /// exactly as it arrived.
344 pub fn render(&self) -> String {
345 let mut out = String::with_capacity(self.src.len());
346 let mut i = 0;
347 for e in &self.edits {
348 out.push_str(&self.src[i..e.span.start]);
349 out.push_str(&e.text);
350 i = e.span.end;
351 }
352 out.push_str(&self.src[i..]);
353 out
354 }
355
356 /// The plain text an element holds, its descendants included, with entities resolved.
357 ///
358 /// Elements contribute nothing of themselves, so `<w:t>a</w:t><w:t>b</w:t>` gives `ab`. A caller
359 /// that wants a space between runs puts one there; this does not invent characters the document
360 /// does not hold.
361 pub fn text_of(&self, elem: &Elem) -> String {
362 let mut out = String::new();
363 self.gather_text(elem, &mut out);
364 out
365 }
366
367 /// Adds an element's character data to a string, descending as it goes.
368 fn gather_text(&self, elem: &Elem, out: &mut String) {
369 for kid in &elem.kids {
370 match kid {
371 Node::Text(s) => out.push_str(&self.text(s)),
372 Node::CData(s) => {
373 // The content of a section, without its `<![CDATA[` and `]]>`, and undecoded --
374 // that is what a CDATA section is for.
375 let raw = self.raw(s);
376 let body = raw.strip_prefix("<![CDATA[")
377 .and_then(|r| r.strip_suffix("]]>"))
378 .unwrap_or(raw);
379 out.push_str(body);
380 }
381 Node::Elem(e) => self.gather_text(e, out),
382 _ => {}
383 }
384 }
385 }
386
387 /// Every element of that name anywhere in the document, in document order.
388 pub fn all(&self, qname: &str) -> Vec<&Elem> {
389 let mut out = Vec::new();
390 for node in &self.nodes {
391 if let Node::Elem(e) = node {
392 e.gather(qname, &mut out);
393 }
394 }
395 out
396 }
397}