linch_docx_rs/document/
header_footer.rs1use crate::document::Paragraph;
4use crate::error::Result;
5use crate::xml::{RawXmlElement, RawXmlNode};
6use quick_xml::events::{BytesDecl, BytesEnd, BytesStart, Event};
7use quick_xml::{Reader, Writer};
8use std::io::Cursor;
9
10#[derive(Clone, Debug, Default)]
12pub struct HeaderFooter {
13 pub paragraphs: Vec<Paragraph>,
15 pub unknown_children: Vec<RawXmlNode>,
17 pub is_header: bool,
19}
20
21impl HeaderFooter {
22 pub fn from_xml(xml: &str, is_header: bool) -> Result<Self> {
24 let mut reader = Reader::from_str(xml);
25 reader.config_mut().trim_text(true);
26
27 let mut hf = HeaderFooter {
28 is_header,
29 ..Default::default()
30 };
31 let mut buf = Vec::new();
32
33 loop {
34 match reader.read_event_into(&mut buf)? {
35 Event::Start(e) => {
36 let local = e.name().local_name();
37 match local.as_ref() {
38 b"hdr" | b"ftr" => {
39 }
41 b"p" => {
42 let para = Paragraph::from_reader(&mut reader, &e)?;
43 hf.paragraphs.push(para);
44 }
45 _ => {
46 let raw = RawXmlElement::from_reader(&mut reader, &e)?;
47 hf.unknown_children.push(RawXmlNode::Element(raw));
48 }
49 }
50 }
51 Event::Empty(e) => {
52 let local = e.name().local_name();
53 if local.as_ref() == b"p" {
54 let para = Paragraph::from_empty(&e)?;
55 hf.paragraphs.push(para);
56 } else {
57 let raw = RawXmlElement {
58 name: String::from_utf8_lossy(e.name().as_ref()).to_string(),
59 attributes: e
60 .attributes()
61 .filter_map(|a| a.ok())
62 .map(|a| {
63 (
64 String::from_utf8_lossy(a.key.as_ref()).to_string(),
65 String::from_utf8_lossy(&a.value).to_string(),
66 )
67 })
68 .collect(),
69 children: Vec::new(),
70 self_closing: true,
71 };
72 hf.unknown_children.push(RawXmlNode::Element(raw));
73 }
74 }
75 Event::Eof => break,
76 _ => {}
77 }
78 buf.clear();
79 }
80
81 Ok(hf)
82 }
83
84 pub fn to_xml(&self) -> Result<String> {
86 let mut buffer = Cursor::new(Vec::new());
87 let mut writer = Writer::new(&mut buffer);
88
89 writer.write_event(Event::Decl(BytesDecl::new(
90 "1.0",
91 Some("UTF-8"),
92 Some("yes"),
93 )))?;
94
95 let tag = if self.is_header { "w:hdr" } else { "w:ftr" };
96 let mut start = BytesStart::new(tag);
97 start.push_attribute(("xmlns:w", crate::xml::W));
98 start.push_attribute(("xmlns:r", crate::xml::R));
99 writer.write_event(Event::Start(start))?;
100
101 for para in &self.paragraphs {
102 para.write_to(&mut writer)?;
103 }
104
105 for child in &self.unknown_children {
106 child.write_to(&mut writer)?;
107 }
108
109 writer.write_event(Event::End(BytesEnd::new(tag)))?;
110
111 let xml_bytes = buffer.into_inner();
112 String::from_utf8(xml_bytes)
113 .map_err(|e| crate::error::Error::InvalidDocument(e.to_string()))
114 }
115
116 pub fn text(&self) -> String {
118 self.paragraphs
119 .iter()
120 .map(|p| p.text())
121 .collect::<Vec<_>>()
122 .join("\n")
123 }
124
125 pub fn add_paragraph(&mut self, text: impl Into<String>) {
127 self.paragraphs.push(Paragraph::new(text));
128 }
129
130 pub fn new_header() -> Self {
132 HeaderFooter {
133 is_header: true,
134 paragraphs: vec![Paragraph::default()],
135 ..Default::default()
136 }
137 }
138
139 pub fn new_footer() -> Self {
141 HeaderFooter {
142 is_header: false,
143 paragraphs: vec![Paragraph::default()],
144 ..Default::default()
145 }
146 }
147}
148
149#[cfg(test)]
150mod tests {
151 use super::*;
152
153 #[test]
154 fn test_parse_header() {
155 let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
156<w:hdr xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
157 <w:p>
158 <w:r>
159 <w:t>Header Text</w:t>
160 </w:r>
161 </w:p>
162</w:hdr>"#;
163
164 let hf = HeaderFooter::from_xml(xml, true).unwrap();
165 assert!(hf.is_header);
166 assert_eq!(hf.paragraphs.len(), 1);
167 assert_eq!(hf.text(), "Header Text");
168 }
169
170 #[test]
171 fn test_header_roundtrip() {
172 let mut hf = HeaderFooter::new_header();
173 hf.paragraphs.clear();
174 hf.add_paragraph("Test Header");
175
176 let xml = hf.to_xml().unwrap();
177 let hf2 = HeaderFooter::from_xml(&xml, true).unwrap();
178 assert_eq!(hf2.text(), "Test Header");
179 }
180
181 #[test]
182 fn test_footer() {
183 let mut hf = HeaderFooter::new_footer();
184 hf.paragraphs.clear();
185 hf.add_paragraph("Page 1");
186
187 let xml = hf.to_xml().unwrap();
188 assert!(xml.contains("w:ftr"));
189 assert!(!xml.contains("w:hdr"));
190 }
191}