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 | |
| 50 | pub mod read; |
| 51 | pub mod write; |
| 52 | |
| 53 | use oxedyne_fe2o3_core::prelude::*; |
| 54 | |
| 55 | use std::ops::Range; |
| 56 | |
| 57 | /// A byte range in the source a document was read from. |
| 58 | pub 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. |
| 64 | pub 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)] |
| 71 | pub 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)] |
| 80 | pub 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 | |
| 90 | impl 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)] |
| 111 | pub 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)] |
| 124 | pub 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 | |
| 140 | impl 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)] |
| 217 | pub 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 | |
| 232 | impl 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)] |
| 249 | pub 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 | |
| 260 | impl 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 | } |