1mod block;
21mod format;
22
23use std::fmt;
24use std::ops::Range;
25
26use crate::xml::{Attribute, Element, Name, Node, Ns};
27
28pub use block::{block_state, heading_level, set_heading, set_list};
29pub use format::{Mark, format, marked};
30
31#[derive(Debug, Clone, PartialEq, Eq)]
34pub enum Refused {
35 Covered,
37 Formula,
39 Namespace,
41 NotFound,
43 Structure,
46}
47
48impl fmt::Display for Refused {
49 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50 match self {
51 Self::Covered => write!(f, "the cell is covered by a neighbour's span"),
52 Self::Formula => write!(f, "the cell holds a formula"),
53 Self::Namespace => write!(f, "the document does not declare the namespace needed"),
54 Self::NotFound => write!(f, "nothing is there to edit"),
55 Self::Structure => write!(f, "the range crosses a table or a frame"),
56 }
57 }
58}
59
60impl std::error::Error for Refused {}
61
62#[derive(Debug, Clone, Copy, PartialEq, Eq)]
64enum Kind {
65 Text(usize),
67 Spaces(usize),
69 Tab,
71 LineBreak,
73 Marker,
75}
76
77impl Kind {
78 fn len(self) -> usize {
79 match self {
80 Self::Text(n) | Self::Spaces(n) => n,
81 Self::Tab | Self::LineBreak => 1,
82 Self::Marker => 0,
83 }
84 }
85}
86
87#[derive(Debug)]
89struct Segment {
90 path: Vec<usize>,
92 start: usize,
94 kind: Kind,
95}
96
97fn is_inline_container(element: &Element) -> bool {
102 element.name.ns == Ns::Text
103 && !element.children.is_empty()
104 && matches!(
105 &*element.name.local,
106 "span" | "a" | "bibliography-mark" | "ruby" | "ruby-base" | "meta" | "meta-field"
107 )
108}
109
110pub fn is_paragraph(element: &Element) -> bool {
112 element.is(&Ns::Text, "p") || element.is(&Ns::Text, "h")
113}
114
115pub fn holds_text(element: &Element) -> bool {
119 is_inline_container_name(element)
120}
121
122fn is_inline_container_name(element: &Element) -> bool {
125 element.name.ns == Ns::Text
126 && matches!(
127 &*element.name.local,
128 "span" | "a" | "bibliography-mark" | "ruby" | "ruby-base" | "meta" | "meta-field"
129 )
130}
131
132fn segments(paragraph: &Element) -> Vec<Segment> {
133 let mut out = Vec::new();
134 let mut path = Vec::new();
135 let mut at = 0;
136 collect(paragraph, &mut path, &mut at, &mut out);
137 out
138}
139
140fn collect(parent: &Element, path: &mut Vec<usize>, at: &mut usize, out: &mut Vec<Segment>) {
141 for (index, child) in parent.children.iter().enumerate() {
142 let kind = match child {
143 Node::Text(t) | Node::CData(t) => Kind::Text(t.chars().count()),
144 Node::Comment(_) | Node::ProcessingInstruction(_) => continue,
145 Node::Element(e) if e.is(&Ns::Text, "s") => {
146 Kind::Spaces(e.attr_usize(&Ns::Text, "c").unwrap_or(1))
147 }
148 Node::Element(e) if e.is(&Ns::Text, "tab") => Kind::Tab,
149 Node::Element(e) if e.is(&Ns::Text, "line-break") => Kind::LineBreak,
150 Node::Element(e) if is_inline_container(e) => {
151 path.push(index);
152 collect(e, path, at, out);
153 path.pop();
154 continue;
155 }
156 Node::Element(_) => Kind::Marker,
157 };
158 path.push(index);
159 out.push(Segment {
160 path: path.clone(),
161 start: *at,
162 kind,
163 });
164 path.pop();
165 *at += kind.len();
166 }
167}
168
169pub fn text(paragraph: &Element) -> String {
173 let mut out = String::new();
174 write_text(paragraph, &mut out);
175 out
176}
177
178fn write_text(parent: &Element, out: &mut String) {
179 for child in &parent.children {
180 match child {
181 Node::Text(t) | Node::CData(t) => out.push_str(t),
182 Node::Element(e) if e.is(&Ns::Text, "s") => {
183 for _ in 0..e.attr_usize(&Ns::Text, "c").unwrap_or(1) {
184 out.push(' ');
185 }
186 }
187 Node::Element(e) if e.is(&Ns::Text, "tab") => out.push('\t'),
188 Node::Element(e) if e.is(&Ns::Text, "line-break") => out.push('\n'),
189 Node::Element(e) if is_inline_container(e) => write_text(e, out),
190 Node::Element(_) | Node::Comment(_) | Node::ProcessingInstruction(_) => {}
191 }
192 }
193}
194
195pub fn links(paragraph: &Element) -> Vec<(Range<usize>, &str)> {
199 let mut found = Vec::new();
200 collect_links(paragraph, &mut 0, &mut found);
201 found
202}
203
204fn collect_links<'a>(
205 parent: &'a Element,
206 at: &mut usize,
207 found: &mut Vec<(Range<usize>, &'a str)>,
208) {
209 for child in &parent.children {
210 match child {
211 Node::Text(t) | Node::CData(t) => *at += t.chars().count(),
212 Node::Element(e) if e.is(&Ns::Text, "s") => {
213 *at += e.attr_usize(&Ns::Text, "c").unwrap_or(1);
214 }
215 Node::Element(e) if e.is(&Ns::Text, "tab") || e.is(&Ns::Text, "line-break") => {
216 *at += 1;
217 }
218 Node::Element(e) if is_inline_container(e) => {
219 let start = *at;
220 collect_links(e, at, found);
221 if e.is(&Ns::Text, "a")
222 && let Some(href) = e.attr(&Ns::Xlink, "href")
223 {
224 found.push((start..*at, href));
225 }
226 }
227 Node::Element(_) | Node::Comment(_) | Node::ProcessingInstruction(_) => {}
228 }
229 }
230}
231
232pub fn replace(paragraph: &mut Element, range: Range<usize>, with: &str) {
243 let len = text(paragraph).chars().count();
244 let start = range.start.min(len);
245 let end = range.end.clamp(start, len);
246 let added = with.chars().count();
247 let holder = segments(paragraph).into_iter().find(|s| {
251 matches!(s.kind, Kind::Text(_)) && s.start <= start && start < s.start + s.kind.len()
252 });
253 if start < end
254 && added > 0
255 && let Some(segment) = holder
256 {
257 insert_into(paragraph, &segment.path, start - segment.start, with);
258 cut(paragraph, start + added..end + added, |_| false);
259 } else {
260 cut(paragraph, start..end, |_| false);
261 insert(paragraph, start, with);
262 }
263 normalize(paragraph);
264}
265
266pub fn split(paragraph: &Element, at: usize) -> (Element, Element) {
273 let len = text(paragraph).chars().count();
274 let at = at.min(len);
275 let mut first = paragraph.clone();
276 cut(&mut first, at..len, |start| start > at);
277 let mut second = paragraph.clone();
278 cut(&mut second, 0..at, |start| start <= at);
279 second.attrs.retain(|a| {
280 !(a.name.is(&Ns::Text, "id")
281 || a.name.local.as_ref() == "id" && a.name.prefix.as_deref() == Some("xml"))
282 });
283 normalize(&mut first);
284 normalize(&mut second);
285 (first, second)
286}
287
288pub fn join(first: &mut Element, second: Element) {
291 first.children.extend(second.children);
292 first.self_closing = false;
293 normalize(first);
294}
295
296pub fn rewrite(paragraph: &Element, edited: &str) -> Vec<Element> {
307 let before = text(paragraph);
308 let old: Vec<char> = before.chars().collect();
309 let new: Vec<char> = edited.chars().collect();
310 let prefix = old.iter().zip(&new).take_while(|(a, b)| a == b).count();
311 let suffix = old[prefix..]
312 .iter()
313 .rev()
314 .zip(new[prefix..].iter().rev())
315 .take_while(|(a, b)| a == b)
316 .count();
317 let inserted: String = new[prefix..new.len() - suffix].iter().collect();
318
319 let mut whole = paragraph.clone();
320 replace(&mut whole, prefix..old.len() - suffix, &inserted);
321
322 let mut breaks: Vec<usize> = inserted
325 .chars()
326 .enumerate()
327 .filter(|(_, c)| *c == '\n')
328 .map(|(i, _)| prefix + i)
329 .collect();
330 breaks.reverse();
331 let mut after = Vec::new();
332 for at in breaks {
333 let (first, mut second) = split(&whole, at);
334 replace(&mut second, 0..1, "");
336 after.push(second);
337 whole = first;
338 }
339 after.push(whole);
340 after.reverse();
341 after
342}
343
344pub fn apply(root: &mut Element, path: &[usize], edited: &str) -> Result<usize, Refused> {
351 let (last, above) = path.split_last().ok_or(Refused::NotFound)?;
352 let parent = root.at_mut(above).ok_or(Refused::NotFound)?;
353 let Some(Node::Element(paragraph)) = parent.children.get(*last) else {
354 return Err(Refused::NotFound);
355 };
356 if !is_paragraph(paragraph) {
357 return Err(Refused::NotFound);
358 }
359 let paragraphs = rewrite(paragraph, edited);
360 let count = paragraphs.len();
361 parent
362 .children
363 .splice(*last..=*last, paragraphs.into_iter().map(Node::Element));
364 Ok(count)
365}
366
367pub fn split_at(root: &mut Element, path: &[usize], at: usize) -> Result<Vec<usize>, Refused> {
381 let (last, above) = path.split_last().ok_or(Refused::NotFound)?;
382 let parent = root.at_mut(above).ok_or(Refused::NotFound)?;
383 let Some(Node::Element(paragraph)) = parent.children.get(*last) else {
384 return Err(Refused::NotFound);
385 };
386 if !is_paragraph(paragraph) {
387 return Err(Refused::NotFound);
388 }
389 if parent.is(&Ns::Text, "list-item")
390 && parent.children.len() == 1
391 && paragraph.children.is_empty()
392 {
393 return take_out_of_list(root, above);
394 }
395 let (first, second) = split(paragraph, at);
396 let parent = root.at_mut(above).ok_or(Refused::NotFound)?;
397 if !parent.is(&Ns::Text, "list-item") {
398 parent
399 .children
400 .splice(*last..=*last, [Node::Element(first), Node::Element(second)]);
401 let mut second_at = above.to_vec();
402 second_at.push(last + 1);
403 return Ok(second_at);
404 }
405
406 let mut item = Element {
410 name: parent.name.clone(),
411 attrs: Vec::new(),
412 children: vec![Node::Element(second)],
413 self_closing: false,
414 };
415 item.children.extend(parent.children.drain(last + 1..));
416 parent.children[*last] = Node::Element(first);
417 let (item_at, list_path) = above.split_last().ok_or(Refused::NotFound)?;
418 let enclosing = root.at_mut(list_path).ok_or(Refused::NotFound)?;
419 enclosing.children.insert(item_at + 1, Node::Element(item));
420 let mut second_at = list_path.to_vec();
421 second_at.extend([item_at + 1, 0]);
422 Ok(second_at)
423}
424
425pub(crate) fn take_out_of_list(
433 root: &mut Element,
434 item_path: &[usize],
435) -> Result<Vec<usize>, Refused> {
436 let (item_at, list_path) = item_path.split_last().ok_or(Refused::NotFound)?;
437 let (list_at, container_path) = list_path.split_last().ok_or(Refused::NotFound)?;
438 let continue_numbering = root.name_for(&Ns::Text, "continue-numbering");
439 let list = root.at(list_path).ok_or(Refused::NotFound)?;
440 let Some(Node::Element(item)) = list.children.get(*item_at) else {
441 return Err(Refused::NotFound);
442 };
443 let contents = item.children.clone();
444 let mut before = list.clone();
445 before.children.truncate(*item_at);
446 let mut after = list.clone();
447 after.children.drain(..=*item_at);
448 let before_holds_items = before.elements().next().is_some();
449 let after_holds_items = after.elements().next().is_some();
450 after.attrs.retain(|a| {
451 !(a.name.is(&Ns::Text, "id")
452 || a.name.local.as_ref() == "id" && a.name.prefix.as_deref() == Some("xml"))
453 });
454 after.set_attr(continue_numbering, "true");
455
456 let container = root.at_mut(container_path).ok_or(Refused::NotFound)?;
457 let mut replacement = Vec::new();
458 if before_holds_items {
459 replacement.push(Node::Element(before));
460 }
461 let first_at = *list_at + replacement.len();
462 replacement.extend(contents);
463 if after_holds_items {
464 replacement.push(Node::Element(after));
465 }
466 container.children.splice(*list_at..=*list_at, replacement);
467 let mut at = container_path.to_vec();
468 at.push(first_at);
469 Ok(at)
470}
471
472pub fn replace_range(
489 root: &mut Element,
490 from: (&[usize], usize),
491 to: (&[usize], usize),
492 with: &str,
493) -> Result<(), Refused> {
494 let ((from, start), (to, end)) = (from, to);
495 if !root.at(from).is_some_and(is_paragraph) || !root.at(to).is_some_and(is_paragraph) {
496 return Err(Refused::NotFound);
497 }
498 if from == to {
499 let paragraph = root.at_mut(from).ok_or(Refused::NotFound)?;
500 replace(paragraph, start..end, with);
501 return Ok(());
502 }
503 let common = from.iter().zip(to).take_while(|(a, b)| a == b).count();
506 if common >= from.len() || common >= to.len() || from[common] >= to[common] {
507 return Err(Refused::NotFound);
508 }
509 if end_inside_structure(root, common, from, to) {
510 return Err(Refused::Structure);
511 }
512 let (first_child, last_child) = (from[common], to[common]);
513
514 let last = root.at_mut(to).ok_or(Refused::NotFound)?;
517 replace(last, 0..end, "");
518 let tail = std::mem::take(&mut last.children);
519
520 let holder = root.at_mut(&from[..common]).ok_or(Refused::NotFound)?;
521 let emptied = match holder.children.get_mut(last_child) {
522 Some(Node::Element(child)) if common + 1 < to.len() => {
523 strip_before(child, &to[common + 1..])
524 }
525 _ => true,
526 };
527 if emptied {
528 holder.children.remove(last_child);
529 }
530 holder.children.drain(first_child + 1..last_child);
531 if let Some(Node::Element(child)) = holder.children.get_mut(first_child) {
532 strip_after(child, &from[common + 1..]);
533 }
534
535 let first = root.at_mut(from).ok_or(Refused::NotFound)?;
536 let len = text(first).chars().count();
537 replace(first, start..len, with);
538 let mut rest = Element::new("text", "p", Ns::Text);
539 rest.children = tail;
540 join(first, rest);
541 Ok(())
542}
543
544fn end_inside_structure(root: &Element, common: usize, from: &[usize], to: &[usize]) -> bool {
548 let inside = |path: &[usize]| {
549 (common + 1..path.len()).any(|depth| root.at(&path[..depth]).is_some_and(is_structure))
550 };
551 inside(from) || inside(to)
552}
553
554fn is_structure(element: &Element) -> bool {
555 element.is(&Ns::Table, "table")
556 || element.is(&Ns::Table, "table-row")
557 || element.is(&Ns::Table, "table-cell")
558 || element.is(&Ns::Table, "covered-table-cell")
559 || element.is(&Ns::Draw, "frame")
560 || element.is(&Ns::Draw, "text-box")
561}
562
563fn strip_before(element: &mut Element, path: &[usize]) -> bool {
567 let Some((&at, rest)) = path.split_first() else {
568 return true;
569 };
570 let emptied = match element.children.get_mut(at) {
571 Some(Node::Element(child)) if !rest.is_empty() => strip_before(child, rest),
572 _ => true,
573 };
574 let through = if emptied { at + 1 } else { at };
575 element
576 .children
577 .drain(..through.min(element.children.len()));
578 !element
579 .children
580 .iter()
581 .any(|n| matches!(n, Node::Element(_)))
582}
583
584fn strip_after(element: &mut Element, path: &[usize]) {
587 let Some((&at, rest)) = path.split_first() else {
588 return;
589 };
590 if let Some(Node::Element(child)) = element.children.get_mut(at) {
591 strip_after(child, rest);
592 }
593 element.children.truncate(at + 1);
594}
595
596pub fn join_with_previous(root: &mut Element, path: &[usize]) -> Result<Vec<usize>, Refused> {
605 let (last, above) = path.split_last().ok_or(Refused::NotFound)?;
606 let parent = root.at_mut(above).ok_or(Refused::NotFound)?;
607 let paragraph_node = |node: &Node| matches!(node, Node::Element(e) if is_paragraph(e));
608 if !parent.children.get(*last).is_some_and(paragraph_node) {
609 return Err(Refused::NotFound);
610 }
611 let previous = parent.children[..*last]
612 .iter()
613 .rposition(|node| matches!(node, Node::Element(_)))
614 .filter(|&index| paragraph_node(&parent.children[index]))
615 .ok_or(Refused::NotFound)?;
616 let Node::Element(second) = parent.children.remove(*last) else {
619 return Err(Refused::NotFound);
620 };
621 parent.children.drain(previous + 1..*last);
622 let Some(Node::Element(first)) = parent.children.get_mut(previous) else {
623 return Err(Refused::NotFound);
624 };
625 join(first, second);
626 let mut at = above.to_vec();
627 at.push(previous);
628 Ok(at)
629}
630
631fn cut(paragraph: &mut Element, range: Range<usize>, drop_marker: impl Fn(usize) -> bool) {
637 for segment in segments(paragraph).into_iter().rev() {
638 let end = segment.start + segment.kind.len();
639 let overlap_start = range.start.max(segment.start);
640 let overlap_end = range.end.min(end);
641 let overlaps = overlap_start < overlap_end;
642 let remove = match segment.kind {
643 Kind::Marker => drop_marker(segment.start),
644 Kind::Tab | Kind::LineBreak => overlaps,
645 Kind::Spaces(n) => {
646 if !overlaps {
647 continue;
648 }
649 let left = n - (overlap_end - overlap_start);
650 if left > 0
651 && let Some(e) = paragraph.at_mut(&segment.path)
652 {
653 set_count(e, left);
654 }
655 left == 0
656 }
657 Kind::Text(_) => {
658 if !overlaps {
659 continue;
660 }
661 let Some((parent, index)) = parent_of(paragraph, &segment.path) else {
662 continue;
663 };
664 let Some(Node::Text(t) | Node::CData(t)) = parent.children.get_mut(index) else {
665 continue;
666 };
667 let kept: String = t
668 .chars()
669 .enumerate()
670 .filter(|(i, _)| {
671 let at = segment.start + i;
672 !(overlap_start..overlap_end).contains(&at)
673 })
674 .map(|(_, c)| c)
675 .collect();
676 *t = kept;
677 t.is_empty()
678 }
679 };
680 if remove && let Some((parent, index)) = parent_of(paragraph, &segment.path) {
681 parent.children.remove(index);
682 prune_emptied(paragraph, &segment.path[..segment.path.len() - 1]);
683 }
684 }
685}
686
687fn prune_emptied(paragraph: &mut Element, container: &[usize]) {
695 let mut path = container.to_vec();
696 while !path.is_empty() {
697 let Some((parent, index)) = parent_of(paragraph, &path) else {
698 return;
699 };
700 match parent.children.get(index) {
701 Some(Node::Element(e)) if is_inline_container_name(e) && e.children.is_empty() => {
702 parent.children.remove(index);
703 path.pop();
704 }
705 _ => return,
706 }
707 }
708}
709
710fn insert(paragraph: &mut Element, at: usize, with: &str) {
717 if with.is_empty() {
718 return;
719 }
720 let segments = segments(paragraph);
721 let text_at = |predicate: &dyn Fn(&Segment, usize) -> bool| {
722 segments
723 .iter()
724 .find(|s| matches!(s.kind, Kind::Text(_)) && predicate(s, s.start + s.kind.len()))
725 };
726 let target = text_at(&|s, end| s.start < at && at < end)
727 .or_else(|| text_at(&|_, end| end == at))
728 .or_else(|| text_at(&|s, _| s.start == at));
729 if let Some(segment) = target {
730 insert_into(paragraph, &segment.path, at - segment.start, with);
731 return;
732 }
733 let after = segments
737 .iter()
738 .rev()
739 .find(|s| s.start + s.kind.len() == at)
740 .map(|s| s.path.clone());
741 let before = segments
742 .iter()
743 .find(|s| s.start >= at)
744 .map(|s| s.path.clone());
745 let node = Node::Text(with.to_owned());
746 if let Some(path) = after
747 && let Some((parent, index)) = parent_of(paragraph, &path)
748 {
749 parent.children.insert(index + 1, node);
750 } else if let Some(path) = before
751 && let Some((parent, index)) = parent_of(paragraph, &path)
752 {
753 parent.children.insert(index, node);
754 } else {
755 paragraph.children.push(node);
756 paragraph.self_closing = false;
757 }
758}
759
760fn insert_into(paragraph: &mut Element, path: &[usize], offset: usize, with: &str) {
762 if let Some((parent, index)) = parent_of(paragraph, path)
763 && let Some(Node::Text(t) | Node::CData(t)) = parent.children.get_mut(index)
764 {
765 let byte = t.char_indices().nth(offset).map_or(t.len(), |(b, _)| b);
766 t.insert_str(byte, with);
767 }
768}
769
770fn parent_of<'a>(paragraph: &'a mut Element, path: &[usize]) -> Option<(&'a mut Element, usize)> {
772 let (last, above) = path.split_last()?;
773 Some((paragraph.at_mut(above)?, *last))
774}
775
776fn set_count(space: &mut Element, count: usize) {
777 if count <= 1 {
778 space.remove_attr(&Ns::Text, "c");
779 } else {
780 let name = space
781 .attrs
782 .iter()
783 .find(|a| a.name.is(&Ns::Text, "c"))
784 .map_or_else(
785 || {
786 Name::new(
787 space.name.prefix.as_deref().unwrap_or("text"),
788 "c",
789 Ns::Text,
790 )
791 },
792 |a| a.name.clone(),
793 );
794 space.set_attr(name, count.to_string());
795 }
796}
797
798fn normalize(paragraph: &mut Element) {
807 let prefix = paragraph
808 .name
809 .prefix
810 .as_deref()
811 .unwrap_or("text")
812 .to_owned();
813 let mut previous_was_space = true;
814 normalize_in(paragraph, &prefix, &mut previous_was_space);
815}
816
817fn normalize_in(parent: &mut Element, prefix: &str, previous_was_space: &mut bool) {
818 merge_text(parent);
819 let mut index = 0;
820 while index < parent.children.len() {
821 match &mut parent.children[index] {
822 Node::Text(t) | Node::CData(t) => {
823 let replacement = encode(t, prefix, previous_was_space);
824 match replacement {
825 None => index += 1,
826 Some(nodes) => {
827 let count = nodes.len();
828 parent.children.splice(index..=index, nodes);
829 index += count;
830 }
831 }
832 }
833 Node::Element(e) if is_inline_container(e) => {
834 normalize_in(e, prefix, previous_was_space);
835 index += 1;
836 }
837 Node::Element(e) => {
838 if e.is(&Ns::Text, "s") || e.is(&Ns::Text, "tab") || e.is(&Ns::Text, "line-break") {
841 *previous_was_space = true;
842 }
843 index += 1;
844 }
845 Node::Comment(_) | Node::ProcessingInstruction(_) => index += 1,
846 }
847 }
848}
849
850fn merge_text(parent: &mut Element) {
857 let mut index = 1;
858 while index < parent.children.len() {
859 let joinable = matches!(
860 (&parent.children[index - 1], &parent.children[index]),
861 (Node::Text(_), Node::Text(_))
862 );
863 if joinable {
864 let Node::Text(tail) = parent.children.remove(index) else {
865 unreachable!("matched a text node");
866 };
867 if let Node::Text(head) = &mut parent.children[index - 1] {
868 head.push_str(&tail);
869 }
870 } else {
871 index += 1;
872 }
873 }
874}
875
876fn encode(text: &str, prefix: &str, previous_was_space: &mut bool) -> Option<Vec<Node>> {
879 let needs_work = text.contains(['\t', '\n', '\r'])
880 || text.contains(" ")
881 || (*previous_was_space && text.starts_with(' '));
882 if !needs_work {
883 if let Some(last) = text.chars().last() {
884 *previous_was_space = last == ' ';
885 }
886 return None;
887 }
888 let mut nodes = Vec::new();
889 let mut run = String::new();
890 let mut spaces = 0usize;
891 let flush_spaces = |nodes: &mut Vec<Node>,
892 run: &mut String,
893 spaces: &mut usize,
894 previous_was_space: &mut bool| {
895 if *spaces == 0 {
896 return;
897 }
898 let mut counted = *spaces;
901 if !*previous_was_space {
902 run.push(' ');
903 counted -= 1;
904 }
905 if counted > 0 {
906 if !run.is_empty() {
907 nodes.push(Node::Text(std::mem::take(run)));
908 }
909 let mut s = Element::new(prefix, "s", Ns::Text);
910 if counted > 1 {
911 s.attrs.push(Attribute {
912 name: Name::new(prefix, "c", Ns::Text),
913 value: counted.to_string(),
914 });
915 }
916 nodes.push(Node::Element(s));
917 }
918 *spaces = 0;
919 *previous_was_space = true;
920 };
921 for c in text.chars() {
922 match c {
923 ' ' => spaces += 1,
924 '\r' => {}
927 '\t' | '\n' => {
928 flush_spaces(&mut nodes, &mut run, &mut spaces, previous_was_space);
929 if !run.is_empty() {
930 nodes.push(Node::Text(std::mem::take(&mut run)));
931 }
932 let local = if c == '\t' { "tab" } else { "line-break" };
933 nodes.push(Node::Element(Element::new(prefix, local, Ns::Text)));
934 *previous_was_space = true;
935 }
936 other => {
937 flush_spaces(&mut nodes, &mut run, &mut spaces, previous_was_space);
938 run.push(other);
939 *previous_was_space = false;
940 }
941 }
942 }
943 flush_spaces(&mut nodes, &mut run, &mut spaces, previous_was_space);
944 if !run.is_empty() {
945 nodes.push(Node::Text(run));
946 }
947 Some(nodes)
948}