1mod conflict_log;
16mod edit_log;
17mod merge_list;
18mod merge_pair;
19
20pub use conflict_log::{ConflictLog, ConflictType};
21pub use edit_log::{EditLog, EditType};
22pub use merge_list::{MergeEntry, MergeList};
23pub use merge_pair::{MergePair, MergePairList};
24
25use std::io::Write;
26
27use crate::matching::TriMatching;
28use crate::node::base::BaseNode;
29use crate::node::branch::BranchNode;
30use crate::node::{MatchType, NodeRef, XmlContent, XmlElement};
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
34pub enum Operation {
35 Nop,
37 MoveI,
39 MoveF,
41 Delete,
43}
44
45pub struct Merge {
47 matching: TriMatching,
49 pub conflict_log: ConflictLog,
51 pub edit_log: EditLog,
53 indent: usize,
55}
56
57impl Merge {
58 pub fn new(matching: TriMatching) -> Self {
60 Merge {
61 matching,
62 conflict_log: ConflictLog::new(),
63 edit_log: EditLog::new(),
64 indent: 0,
65 }
66 }
67
68 pub fn matching(&self) -> &TriMatching {
70 &self.matching
71 }
72
73 pub fn merge<W: Write>(&mut self, writer: &mut W) -> std::io::Result<()> {
77 writeln!(writer, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>")?;
79
80 self.tree_merge(
82 Some(self.matching.left_root().clone()),
83 Some(self.matching.right_root().clone()),
84 writer,
85 )?;
86
87 Ok(())
88 }
89
90 fn tree_merge<W: Write>(
94 &mut self,
95 a: Option<NodeRef>,
96 b: Option<NodeRef>,
97 writer: &mut W,
98 ) -> std::io::Result<()> {
99 let mlist_a = a.as_ref().map(|node| self.make_merge_list(node));
101 let mlist_b = b.as_ref().map(|node| self.make_merge_list(node));
102
103 let merged = match (&mlist_a, &mlist_b) {
105 (Some(ma), Some(mb)) => self.make_merge_pair_list(ma.clone(), mb.clone()),
106 (Some(ma), None) => self.merge_list_to_pair_list(ma, None),
107 (None, Some(mb)) => self.merge_list_to_pair_list(mb, None),
108 (None, None) => MergePairList::new(),
109 };
110
111 for pair in merged.pairs() {
113 let merged_node = self.merge_node_content(pair);
114
115 if let Some(content) = &merged_node {
116 match content {
117 XmlContent::Text(text) => {
118 let text_str: String = text.text().iter().collect();
120 write!(
121 writer,
122 "{}{}",
123 Self::indent_str_for(self.indent),
124 escape_xml_text(&text_str)
125 )?;
126 }
127 XmlContent::Comment(comment) => {
128 let comment_text: String = comment.text().iter().collect();
130 writeln!(
131 writer,
132 "{}<!-- {} -->",
133 Self::indent_str_for(self.indent),
134 comment_text
135 )?;
136 }
137 XmlContent::Element(elem) => {
138 if elem.qname() == "$ROOT$" {
140 let partners = self.get_recursion_partners(pair);
141 self.tree_merge(partners.first, partners.second, writer)?;
142 } else {
143 self.write_start_element(writer, elem, self.indent)?;
145
146 self.indent += 1;
148
149 let partners = self.get_recursion_partners(pair);
151 self.tree_merge(partners.first, partners.second, writer)?;
152
153 self.indent -= 1;
155
156 writeln!(
158 writer,
159 "{}</{}>",
160 Self::indent_str_for(self.indent),
161 elem.qname()
162 )?;
163 }
164 }
165 }
166 }
167 }
168
169 Ok(())
170 }
171
172 fn make_merge_list(&mut self, parent: &NodeRef) -> MergeList {
174 MergeList::from_branch(parent, &mut self.edit_log)
175 }
176
177 fn make_merge_pair_list(&mut self, mlist_a: MergeList, mlist_b: MergeList) -> MergePairList {
179 let mut mlist_a = mlist_a;
180 let mut mlist_b = mlist_b;
181
182 self.remove_deleted_or_moved(&mut mlist_a, &mut mlist_b);
184
185 self.edit_log.checkpoint();
187
188 if mlist_a.entry_count() != mlist_b.entry_count() {
190 self.edit_log.rewind();
192 let is_left = mlist_a
193 .entry_parent()
194 .map(|p| BranchNode::is_left_tree(&p))
195 .unwrap_or(true);
196 return if is_left {
197 self.merge_list_to_pair_list(&mlist_a, Some(&mlist_b))
198 } else {
199 self.merge_list_to_pair_list(&mlist_b, Some(&mlist_a))
200 };
201 }
202
203 let mut merged = MergePairList::new();
204 let mut pos_a = 0usize;
205 let mut pos_b = 0usize;
206
207 loop {
208 let ea = mlist_a.entry(pos_a);
209 let eb = mlist_b.entry(pos_b);
210
211 if ea.is_none() || eb.is_none() {
212 break;
213 }
214
215 let ea = ea.unwrap();
216 let eb = eb.unwrap();
217
218 for hangon in ea.hangons() {
220 let partner = hangon.borrow().first_partner(MatchType::FULL);
221 merged.append(Some(hangon.clone()), partner);
222 self.log_hangon_struct_ops(hangon);
223 }
224
225 if !self.check_hangon_combine(ea, eb) {
227 for hangon in eb.hangons() {
228 let partner = hangon.borrow().first_partner(MatchType::FULL);
229 merged.append(Some(hangon.clone()), partner);
230 self.log_hangon_struct_ops(hangon);
231 }
232 }
233
234 let next_a: Option<usize>;
236 let next_b: Option<usize>;
237
238 let ea_locked = ea.locked && mlist_a.entry(pos_a + 1).is_some_and(|e| e.locked);
239 let eb_locked = eb.locked && mlist_b.entry(pos_b + 1).is_some_and(|e| e.locked);
240
241 if !ea_locked && !eb_locked {
242 next_a = Some(pos_a + 1);
244 next_b = Some(pos_b + 1);
245 } else if ea_locked && !eb_locked {
246 next_a = Some(pos_a + 1);
247 next_b = mlist_b.find_partner_index(mlist_a.entry(pos_a + 1));
248 } else if !ea_locked && eb_locked {
249 next_b = Some(pos_b + 1);
250 next_a = mlist_a.find_partner_index(mlist_b.entry(pos_b + 1));
251 } else {
252 let partner_b = mlist_b.find_partner_index(mlist_a.entry(pos_a + 1));
254 if partner_b != Some(pos_b + 1) {
255 self.conflict_log.add_list_conflict(
257 ConflictType::Move,
258 "Conflicting moves inside child list",
259 ea.node().and_then(|n| BranchNode::base_match(&n)),
260 ea.node(),
261 eb.node(),
262 );
263 self.edit_log.rewind();
264 let is_left = mlist_a
265 .entry_parent()
266 .map(|p| BranchNode::is_left_tree(&p))
267 .unwrap_or(true);
268 return if is_left {
269 self.merge_list_to_pair_list(&mlist_a, Some(&mlist_b))
270 } else {
271 self.merge_list_to_pair_list(&mlist_b, Some(&mlist_a))
272 };
273 }
274 next_a = Some(pos_a + 1);
275 next_b = partner_b;
276 }
277
278 pos_a = next_a.unwrap_or(pos_a + 1);
279 pos_b = next_b.unwrap_or(pos_b + 1);
280
281 if mlist_a.is_end(pos_a) || mlist_b.is_end(pos_b) {
283 break;
284 }
285
286 let ea = mlist_a.entry(pos_a);
288 let eb = mlist_b.entry(pos_b);
289 if let (Some(ea), Some(eb)) = (ea, eb) {
290 merged.append(ea.node(), eb.node());
291 self.log_entry_struct_ops(ea, eb);
292 }
293 }
294
295 self.edit_log.commit();
296 merged
297 }
298
299 fn merge_list_to_pair_list(
301 &mut self,
302 mlist_a: &MergeList,
303 mlist_b: Option<&MergeList>,
304 ) -> MergePairList {
305 let mut merged = MergePairList::new();
306
307 for (i, entry) in mlist_a.entries().iter().enumerate() {
308 if i > 0 && !mlist_a.is_end(i) {
310 let node = entry.node();
311 let partner = node
312 .as_ref()
313 .and_then(|n| n.borrow().first_partner(MatchType::FULL));
314 merged.append(node, partner);
315 if let Some(n) = entry.node() {
316 self.log_hangon_struct_ops(&n);
317 }
318 }
319
320 for hangon in entry.hangons() {
322 let partner = hangon.borrow().first_partner(MatchType::FULL);
323 merged.append(Some(hangon.clone()), partner);
324 self.log_hangon_struct_ops(hangon);
325 }
326
327 if let Some(mb) = mlist_b {
329 if let Some(partner_entry) = mb.find_partner(entry) {
330 if !self.check_hangon_combine(entry, partner_entry) {
331 for hangon in partner_entry.hangons() {
332 let partner = hangon.borrow().first_partner(MatchType::FULL);
333 merged.append(Some(hangon.clone()), partner);
334 self.log_hangon_struct_ops(hangon);
335 }
336 }
337 }
338 }
339 }
340
341 merged
342 }
343
344 fn remove_deleted_or_moved(&mut self, mlist_a: &mut MergeList, mlist_b: &mut MergeList) {
346 let base_parent = mlist_a
347 .entry_parent()
348 .and_then(|p| BranchNode::base_match(&p));
349
350 if base_parent.is_none() {
351 return;
352 }
353
354 let base_parent = base_parent.unwrap();
355 let child_count = base_parent.borrow().child_count();
356
357 for i in 0..child_count {
358 let bn = base_parent.borrow().child(i).cloned();
359 if bn.is_none() {
360 continue;
361 }
362 let bn = bn.unwrap();
363
364 let op1 = self.get_operation(&bn, mlist_a);
365 let op2 = self.get_operation(&bn, mlist_b);
366
367 if op1 <= op2 {
370 self.handle_operation_pair(op1, op2, &bn, mlist_a, mlist_b);
371 } else {
372 self.handle_operation_pair(op2, op1, &bn, mlist_b, mlist_a);
373 }
374 }
375 }
376
377 fn handle_operation_pair(
379 &mut self,
380 op1: Operation,
381 op2: Operation,
382 bn: &NodeRef,
383 ml1: &mut MergeList,
384 _ml2: &mut MergeList,
385 ) {
386 use Operation::*;
387
388 match (op1, op2) {
389 (Nop, Nop) | (Nop, MoveI) | (MoveI, MoveI) | (Delete, Delete) => {
390 }
392 (Nop, MoveF) | (Nop, Delete) => {
393 if let Some(ix) = ml1.match_in_list(bn) {
395 if op2 == Delete {
396 if let Some(entry) = ml1.entry(ix) {
398 if let Some(node) = entry.node() {
399 if self.is_deletia_modified(&node, ml1) {
400 self.conflict_log.add_list_warning(
401 ConflictType::Delete,
402 "Modifications in deleted subtree",
403 Some(bn.clone()),
404 entry.node(),
405 None,
406 );
407 }
408 }
409 }
410 self.edit_log.delete(Some(bn.clone()));
411 }
412
413 ml1.move_hangons_to_predecessor(ix);
415 ml1.remove_entry_at(ix);
416 }
417 }
418 (MoveI, MoveF) => {
419 if let Some(ix) = ml1.match_in_list(bn) {
421 let node = ml1.entry(ix).and_then(|e| e.node());
422 let partner = node
423 .as_ref()
424 .and_then(|n| n.borrow().first_partner(MatchType::FULL));
425 self.conflict_log.add_list_conflict(
426 ConflictType::Move,
427 "Node moved to different locations",
428 Some(bn.clone()),
429 node,
430 partner,
431 );
432 ml1.remove_entry_at(ix);
433 }
434 }
435 (MoveI, Delete) => {
436 if let Some(ix) = ml1.match_in_list(bn) {
438 let node = ml1.entry(ix).and_then(|e| e.node());
439 self.conflict_log.add_list_conflict(
440 ConflictType::Move,
441 "Node moved and deleted",
442 Some(bn.clone()),
443 node,
444 None,
445 );
446 self.edit_log.delete(Some(bn.clone()));
447 ml1.remove_entry_at(ix);
448 }
449 }
450 (MoveF, MoveF) => {
451 if self.is_movef_movef_conflict(bn) {
453 self.conflict_log.add_list_conflict(
454 ConflictType::Move,
455 "Node moved to different locations",
456 Some(bn.clone()),
457 BaseNode::left(bn).borrow().full_match(),
458 BaseNode::right(bn).borrow().full_match(),
459 );
460 }
461 }
462 (MoveF, Delete) => {
463 self.conflict_log.add_list_conflict(
465 ConflictType::Move,
466 "Node moved and deleted",
467 Some(bn.clone()),
468 BaseNode::left(bn).borrow().full_match(),
469 BaseNode::right(bn).borrow().full_match(),
470 );
471 }
472 _ => {}
473 }
474 }
475
476 fn get_operation(&self, bn: &NodeRef, ml: &MergeList) -> Operation {
478 if let Some(pos) = ml.match_in_list(bn) {
479 if ml.entry(pos).is_some_and(|e| e.moved) {
480 Operation::MoveI
481 } else {
482 Operation::Nop
483 }
484 } else {
485 let is_left = ml
487 .entry_parent()
488 .map(|p| BranchNode::is_left_tree(&p))
489 .unwrap_or(true);
490
491 let copies = if is_left {
492 BaseNode::left(bn)
493 } else {
494 BaseNode::right(bn)
495 };
496
497 if copies.borrow().match_count() == 0 {
498 Operation::Delete
499 } else {
500 Operation::MoveF
501 }
502 }
503 }
504
505 fn is_deletia_modified(&self, n: &NodeRef, ml: &MergeList) -> bool {
507 let base_match = BranchNode::base_match(n);
508 if base_match.is_none() {
509 return true; }
511
512 let base_match = base_match.unwrap();
513 if self.get_operation(&base_match, ml) != Operation::Nop {
514 return true; }
516
517 if BranchNode::base_match_type(n) != MatchType::FULL {
518 return true; }
520
521 if let Some(partners) = BranchNode::partners(n) {
523 if partners.borrow().match_count() == 0 {
524 if !self.matches(n, &base_match) {
525 return true; }
527 for i in 0..n.borrow().child_count() {
529 if let Some(child) = n.borrow().child(i).cloned() {
530 if BranchNode::base_match_type(&child) != MatchType::FULL {
532 return true;
533 }
534 }
535 }
536 }
537 }
538
539 false
540 }
541
542 fn is_movef_movef_conflict(&self, _bn: &NodeRef) -> bool {
544 false
547 }
548
549 fn merge_node_content(&mut self, pair: &MergePair) -> Option<XmlContent> {
551 let n1 = pair.first.as_ref();
552 let n2 = pair.second.as_ref();
553
554 match (n1, n2) {
555 (None, None) => None,
556 (Some(n), None) | (None, Some(n)) => n.borrow().content().cloned(),
557 (Some(n1), Some(n2)) => {
558 let n1_content = n1.borrow().is_match(MatchType::CONTENT);
559 let n2_content = n2.borrow().is_match(MatchType::CONTENT);
560
561 if n1_content {
562 if !n2_content {
563 self.edit_log.update(Some(n2.clone()));
564 n2.borrow().content().cloned()
565 } else {
566 self.content_merge(n1, n2)
567 }
568 } else if n2_content {
569 self.edit_log.update(Some(n1.clone()));
570 n1.borrow().content().cloned()
571 } else {
572 self.content_merge(n1, n2)
574 }
575 }
576 }
577 }
578
579 fn content_merge(&mut self, a: &NodeRef, b: &NodeRef) -> Option<XmlContent> {
581 let base_a = BranchNode::base_match(a);
582 let base_b = BranchNode::base_match(b);
583
584 let a_updated = base_a.as_ref().is_none_or(|ba| !self.matches(a, ba));
585 let b_updated = base_b.as_ref().is_none_or(|bb| !self.matches(b, bb));
586
587 if a_updated && b_updated {
588 if self.matches(a, b) {
590 self.conflict_log.add_node_warning(
591 ConflictType::Update,
592 "Node updated in both branches, but updates are equal",
593 base_a.clone(),
594 Some(a.clone()),
595 Some(b.clone()),
596 );
597 self.edit_log.update(Some(a.clone()));
598 return a.borrow().content().cloned();
599 }
600
601 if let Some(merged) = self.merge_elements(base_a.as_ref(), a, b) {
603 return Some(merged);
604 }
605
606 self.conflict_log.add_node_conflict(
608 ConflictType::Update,
609 "Node updated in both branches, using branch 1",
610 base_a,
611 Some(a.clone()),
612 Some(b.clone()),
613 );
614 self.edit_log.update(Some(a.clone()));
615 a.borrow().content().cloned()
616 } else if b_updated {
617 self.edit_log.update(Some(b.clone()));
618 b.borrow().content().cloned()
619 } else if a_updated {
620 self.edit_log.update(Some(a.clone()));
621 a.borrow().content().cloned()
622 } else {
623 a.borrow().content().cloned()
624 }
625 }
626
627 fn merge_elements(
629 &self,
630 base: Option<&NodeRef>,
631 a: &NodeRef,
632 b: &NodeRef,
633 ) -> Option<XmlContent> {
634 let base_content = base.and_then(|n| n.borrow().content().cloned());
635 let a_content = a.borrow().content().cloned();
636 let b_content = b.borrow().content().cloned();
637
638 match (&base_content, &a_content, &b_content) {
639 (
640 Some(XmlContent::Element(base_elem)),
641 Some(XmlContent::Element(a_elem)),
642 Some(XmlContent::Element(b_elem)),
643 ) => {
644 let tag_name = if base_elem.qname() == b_elem.qname() {
646 a_elem.qname().to_string()
647 } else if base_elem.qname() == a_elem.qname() {
648 b_elem.qname().to_string()
649 } else {
650 return None; };
652
653 self.merge_attributes(
655 base_elem.attributes(),
656 a_elem.attributes(),
657 b_elem.attributes(),
658 )
659 .map(|merged_attrs| XmlContent::Element(XmlElement::new(tag_name, merged_attrs)))
660 }
661 _ => None,
662 }
663 }
664
665 fn merge_attributes(
667 &self,
668 base: &std::collections::HashMap<String, String>,
669 a: &std::collections::HashMap<String, String>,
670 b: &std::collections::HashMap<String, String>,
671 ) -> Option<std::collections::HashMap<String, String>> {
672 let mut merged = std::collections::HashMap::new();
673 let mut deletia = std::collections::HashSet::new();
674
675 for (key, base_val) in base {
677 let in_a = a.get(key);
678 let in_b = b.get(key);
679
680 match (in_a, in_b) {
681 (None, Some(b_val)) if base_val != b_val => return None, (Some(a_val), None) if base_val != a_val => return None, (None, _) | (_, None) => {
684 deletia.insert(key.clone());
685 }
686 _ => {}
687 }
688 }
689
690 for (key, a_val) in a {
692 if deletia.contains(key) {
693 continue;
694 }
695
696 if let Some(b_val) = b.get(key) {
697 let base_val = base.get(key);
698 if base_val == Some(b_val) {
699 merged.insert(key.clone(), a_val.clone());
701 } else if base_val == Some(a_val) {
702 merged.insert(key.clone(), b_val.clone());
704 } else if a_val != b_val {
705 return None;
707 } else {
708 merged.insert(key.clone(), a_val.clone());
709 }
710 } else {
711 merged.insert(key.clone(), a_val.clone());
713 }
714 }
715
716 for (key, b_val) in b {
718 if !deletia.contains(key) && !a.contains_key(key) {
719 merged.insert(key.clone(), b_val.clone());
720 }
721 }
722
723 Some(merged)
724 }
725
726 fn get_recursion_partners(&self, pair: &MergePair) -> RecursionPartners {
728 let n1 = pair.first.as_ref();
729 let n2 = pair.second.as_ref();
730
731 match (n1, n2) {
732 (None, _) | (_, None) => RecursionPartners {
733 first: pair.first.clone(),
734 second: pair.second.clone(),
735 },
736 (Some(n1), Some(n2)) => {
737 let n1_children = n1.borrow().is_match(MatchType::CHILDREN);
738 let n2_children = n2.borrow().is_match(MatchType::CHILDREN);
739
740 if n1_children && n2_children {
741 RecursionPartners {
742 first: Some(n1.clone()),
743 second: Some(n2.clone()),
744 }
745 } else if n1_children && n2.borrow().is_match(MatchType::CONTENT) {
746 RecursionPartners {
747 first: None,
748 second: Some(n2.clone()),
749 }
750 } else if n1.borrow().is_match(MatchType::CONTENT) && n2_children {
751 RecursionPartners {
752 first: Some(n1.clone()),
753 second: None,
754 }
755 } else {
756 RecursionPartners {
757 first: Some(n1.clone()),
758 second: Some(n2.clone()),
759 }
760 }
761 }
762 }
763 }
764
765 fn matches(&self, a: &NodeRef, b: &NodeRef) -> bool {
767 let a_borrowed = a.borrow();
768 let b_borrowed = b.borrow();
769 let a_content = a_borrowed.content();
770 let b_content = b_borrowed.content();
771
772 match (a_content, b_content) {
773 (Some(ac), Some(bc)) => ac.content_equals(bc),
774 _ => false,
775 }
776 }
777
778 fn log_hangon_struct_ops(&mut self, n: &NodeRef) {
780 if !BranchNode::has_base_match(n) {
781 self.edit_log.insert(Some(n.clone()));
782 } else {
783 let base = BranchNode::base_match(n).unwrap();
784 let is_left = BranchNode::is_left_tree(n);
785
786 let copies = if is_left {
787 BaseNode::left(&base)
788 } else {
789 BaseNode::right(&base)
790 };
791
792 if copies.borrow().match_count() > 1 {
793 self.edit_log.copy(Some(n.clone()));
794 } else {
795 self.edit_log.r#move(Some(n.clone()));
796 }
797 }
798 }
799
800 fn log_entry_struct_ops(&mut self, e1: &MergeEntry, e2: &MergeEntry) {
802 if e1.moved {
803 if let Some(n) = e1.node() {
804 self.edit_log.r#move(Some(n));
805 }
806 } else if e2.moved {
807 if let Some(n) = e2.node() {
808 self.edit_log.r#move(Some(n));
809 }
810 }
811 }
812
813 fn check_hangon_combine(&mut self, ea: &MergeEntry, eb: &MergeEntry) -> bool {
817 if ea.hangons().is_empty() {
819 return false;
820 }
821
822 if ea.hangons().len() != eb.hangons().len() {
823 if !ea.hangons().is_empty() && !eb.hangons().is_empty() {
824 self.conflict_log.add_list_warning(
825 ConflictType::Insert,
826 "Insertions/copies in both branches",
827 ea.node().and_then(|n| BranchNode::base_match(&n)),
828 ea.node(),
829 eb.node(),
830 );
831 }
832 return false;
833 }
834
835 let mut equal = true;
837 for (ha, hb) in ea.hangons().iter().zip(eb.hangons().iter()) {
838 if !self.tree_matches(ha, hb) {
839 equal = false;
840 break;
841 }
842 }
843
844 if equal {
845 self.conflict_log.add_list_warning(
846 ConflictType::Insert,
847 "Equal insertions/copies in both branches",
848 ea.node().and_then(|n| BranchNode::base_match(&n)),
849 ea.node(),
850 eb.node(),
851 );
852 } else if !ea.hangons().is_empty() {
853 self.conflict_log.add_list_warning(
854 ConflictType::Insert,
855 "Insertions/copies in both branches, sequencing",
856 ea.node().and_then(|n| BranchNode::base_match(&n)),
857 ea.node(),
858 eb.node(),
859 );
860 }
861
862 equal
863 }
864
865 fn tree_matches(&self, a: &NodeRef, b: &NodeRef) -> bool {
867 if !self.matches(a, b) {
868 return false;
869 }
870
871 let a_count = a.borrow().child_count();
872 let b_count = b.borrow().child_count();
873
874 if a_count != b_count {
875 return false;
876 }
877
878 for i in 0..a_count {
879 let a_child = a.borrow().child(i).cloned();
880 let b_child = b.borrow().child(i).cloned();
881
882 match (a_child, b_child) {
883 (Some(ac), Some(bc)) => {
884 if !self.tree_matches(&ac, &bc) {
885 return false;
886 }
887 }
888 _ => return false,
889 }
890 }
891
892 true
893 }
894
895 fn write_start_element<W: Write>(
897 &self,
898 writer: &mut W,
899 elem: &XmlElement,
900 indent: usize,
901 ) -> std::io::Result<()> {
902 write!(writer, "{}<{}", Self::indent_str_for(indent), elem.qname())?;
903
904 let mut attrs: Vec<(&String, &String)> = elem.attributes().iter().collect();
906 attrs.sort_by(|a, b| a.0.cmp(b.0));
907
908 for (name, value) in attrs {
909 write!(writer, " {}=\"{}\"", name, escape_xml_attr(value))?;
910 }
911
912 writeln!(writer, ">")?;
913 Ok(())
914 }
915
916 fn indent_str_for(level: usize) -> String {
918 " ".repeat(level)
919 }
920}
921
922struct RecursionPartners {
924 first: Option<NodeRef>,
925 second: Option<NodeRef>,
926}
927
928fn escape_xml_text(s: &str) -> String {
930 s.replace('&', "&")
931 .replace('<', "<")
932 .replace('>', ">")
933}
934
935fn escape_xml_attr(s: &str) -> String {
937 s.replace('&', "&")
938 .replace('<', "<")
939 .replace('>', ">")
940 .replace('"', """)
941 .replace('\'', "'")
942}
943
944impl crate::node::NodeInner {
945 pub fn first_partner(&self, type_flags: MatchType) -> Option<NodeRef> {
947 if let crate::node::NodeKind::Branch {
948 match_type,
949 partners,
950 ..
951 } = self.kind()
952 {
953 if !match_type.intersects(type_flags) {
954 return None;
955 }
956
957 if let Some(partners) = partners {
958 let borrowed = partners.borrow();
959 for candidate in borrowed.matches() {
960 if BranchNode::base_match_type(candidate).intersects(type_flags) {
961 return Some(candidate.clone());
962 }
963 }
964 }
965 }
966 None
967 }
968
969 pub fn is_match(&self, type_flags: MatchType) -> bool {
971 if let crate::node::NodeKind::Branch { match_type, .. } = self.kind() {
972 match_type.intersects(type_flags)
973 } else {
974 false
975 }
976 }
977}