1use std::path::Path;
8
9use libxml::tree::Node;
10use rustc_hash::FxHashMap as HashMap;
11
12use crate::{
13 document::{NodeData, PostDocument, get_xml_id},
14 processor::{ProcessResult, Processor},
15};
16
17#[derive(Debug, Clone)]
19pub enum SplitNaming {
20 Id,
22 IdRelative,
24 Label,
26 LabelRelative,
28}
29
30struct PageEntry {
32 node: Node,
33 id: Option<String>,
34 upid: Option<String>,
35 name: String,
36 children: Vec<PageEntry>,
37 document: Option<PostDocument>,
38}
39
40pub struct Split {
44 name: String,
45 split_xpath: String,
47 split_naming: SplitNaming,
49 no_navigation: bool,
51 unnamed_page_counter: u32,
53}
54
55impl Split {
56 pub fn new(split_xpath: &str, split_naming: SplitNaming, no_navigation: bool) -> Self {
57 Split {
58 name: "Split".to_string(),
59 split_xpath: split_xpath.to_string(),
60 split_naming,
61 no_navigation,
62 unnamed_page_counter: 0,
63 }
64 }
65
66 fn get_pages(&self, doc: &PostDocument) -> Vec<Node> { doc.find_split_pages(&self.split_xpath) }
70
71 fn generate_unnamed_page_name(&mut self) -> String {
73 self.unnamed_page_counter += 1;
74 format!("FOO{}", self.unnamed_page_counter)
75 }
76
77 fn presort_pages(
82 tree: &mut PageEntry,
83 haschildren: &mut HashMap<String, bool>,
84 pages: Vec<Node>,
85 ) {
86 let mut path: Vec<usize> = Vec::new(); for page in pages {
92 loop {
94 let current_node = Self::get_node_at(tree, &path);
95 if is_child(&page, ¤t_node) {
96 break;
97 }
98 if path.is_empty() {
99 break;
100 }
101 path.pop();
102 }
103
104 let current_node = Self::get_node_at(tree, &path);
105 let current_id = get_xml_id(¤t_node);
106 let localname = current_node.get_name();
107 haschildren.insert(localname, true);
108
109 let page_id = get_xml_id(&page);
110 let entry = PageEntry {
111 node: page,
112 id: page_id,
113 upid: current_id,
114 name: String::new(),
115 children: Vec::new(),
116 document: None,
117 };
118
119 let parent = Self::get_entry_at_mut(tree, &path);
121 parent.children.push(entry);
122 let new_idx = parent.children.len() - 1;
123
124 path.push(new_idx);
126 }
127 }
128
129 fn get_node_at(tree: &PageEntry, path: &[usize]) -> Node {
131 let mut current = tree;
132 for &idx in path {
133 current = ¤t.children[idx];
134 }
135 current.node.clone()
136 }
137
138 fn get_entry_at_mut<'a>(tree: &'a mut PageEntry, path: &[usize]) -> &'a mut PageEntry {
140 let mut current = tree;
141 for &idx in path {
142 current = &mut current.children[idx];
143 }
144 current
145 }
146
147 fn prename_pages(
151 &mut self,
152 doc: &PostDocument,
153 tree: &mut PageEntry,
154 haschildren: &HashMap<String, bool>,
155 ) {
156 for i in 0..tree.children.len() {
157 let (parent_name, parent_node) = (tree.name.clone(), tree.node.clone());
158 let child = &tree.children[i];
159 let child_localname = child.node.get_name();
160 let recursive = haschildren.get(&child_localname).copied().unwrap_or(false);
161 let name = self.get_page_name(doc, &child.node, &parent_node, &parent_name, recursive);
162 tree.children[i].name = name;
163 }
164 for child in &mut tree.children {
166 self.prename_pages(doc, child, haschildren);
167 }
168 }
169
170 fn process_pages(
175 &mut self,
176 doc: &mut PostDocument,
177 entries: &mut Vec<PageEntry>,
178 ) -> Vec<PostDocument> {
179 let mut intoc = false;
181 for entry in entries.iter() {
182 let node = &entry.node;
183 if let Some(inlist) = node.get_attribute("inlist") {
184 if inlist.contains("toc") {
185 intoc = true;
186 }
187 }
188 for attr in &["xml:lang", "backgroundcolor"] {
196 let xpath = format!("ancestor-or-self::*[@{}][1]", attr);
197 if let Some(anc) = doc.findnode_at(&xpath, node) {
198 let val = anc.get_attribute(attr).or_else(|| {
199 attr
200 .strip_prefix("xml:")
201 .and_then(|local| anc.get_attribute_ns(local, "http://www.w3.org/XML/1998/namespace"))
202 });
203 if let Some(val) = val {
204 let mut node_mut = node.clone();
205 node_mut.set_attribute(attr, &val).ok();
206 }
207 }
208 }
209 }
210
211 let mut docs = Vec::new();
212 while !entries.is_empty() {
213 let parent = match entries[0].node.get_parent() {
214 Some(p) => p,
215 None => {
216 entries.remove(0);
217 continue;
218 },
219 };
220
221 let mut removed: Vec<Node> = Vec::new();
223 loop {
224 let last = parent.get_last_child();
225 match last {
226 Some(mut sib) => {
227 sib.unlink_node();
228 removed.insert(0, sib.clone());
229 if sib == entries[0].node {
230 break;
231 }
232 },
233 None => break,
234 }
235 }
236
237 let mut toc: Vec<NodeData> = Vec::new();
239
240 while !entries.is_empty() && !removed.is_empty() && entries[0].node == removed[0] {
242 let mut entry = entries.remove(0);
243 let page = entry.node.clone();
244
245 if intoc && page.get_attribute("inlist").is_none() {
247 let mut page_mut = page.clone();
248 page_mut.set_attribute("inlist", "toc").ok();
249 }
250
251 let removed_node = removed.remove(0);
253 doc.remove_nodes(&[removed_node]);
254
255 if let Some(id) = get_xml_id(&page) {
257 let mut toc_attrs = HashMap::default();
258 toc_attrs.insert("idref".to_string(), id);
259 toc_attrs.insert("show".to_string(), "toctitle".to_string());
260 let tocentry = NodeData::Element {
261 tag: "ltx:tocentry".to_string(),
262 attributes: None,
263 children: vec![NodeData::Element {
264 tag: "ltx:ref".to_string(),
265 attributes: Some(toc_attrs),
266 children: vec![],
267 }],
268 };
269 toc.push(tocentry);
270 }
271
272 let mut child_docs = self.process_pages(doc, &mut entry.children);
274
275 let subdoc = doc.new_document(page, &entry.name);
277 entry.document = Some(subdoc);
278 docs.push(entry.document.take().unwrap());
280 docs.append(&mut child_docs);
281 }
282
283 if !toc.is_empty() {
285 let has_toc = !doc
287 .findnodes_at("descendant::ltx:TOC[@lists='toc']", Some(&parent))
288 .is_empty();
289 if !has_toc {
290 let parent_type = parent.get_name();
291 let mut toclist_attrs = HashMap::default();
292 toclist_attrs.insert("class".to_string(), format!("ltx_toclist_{}", parent_type));
293 let toc_node = NodeData::Element {
294 tag: "ltx:TOC".to_string(),
295 attributes: None,
296 children: vec![NodeData::Element {
297 tag: "ltx:toclist".to_string(),
298 attributes: Some(toclist_attrs),
299 children: toc,
300 }],
301 };
302 let mut parent_mut = parent.clone();
303 doc.add_nodes(&mut parent_mut, &[toc_node]);
304 }
305 }
306
307 let mut parent_mut = parent;
309 for mut child in removed {
310 parent_mut.add_child(&mut child).ok();
311 }
312 }
313 docs
314 }
315
316 fn add_navigation(entry: &mut PageEntry, nav_nodes: &[Node]) {
320 if let Some(ref mut doc) = entry.document {
321 if let Some(mut root) = doc.get_document_element() {
322 let nav_data: Vec<NodeData> = nav_nodes
323 .iter()
324 .map(|n| NodeData::XmlNode(n.clone()))
325 .collect();
326 doc.add_nodes(&mut root, &nav_data);
327 }
328 }
329 for child in &mut entry.children {
330 Self::add_navigation(child, nav_nodes);
331 }
332 }
333
334 fn get_page_name(
338 &mut self,
339 doc: &PostDocument,
340 page: &Node,
341 parent: &Node,
342 parent_path: &str,
343 recursive: bool,
344 ) -> String {
345 let attr = match self.split_naming {
346 SplitNaming::Id | SplitNaming::IdRelative => "xml:id",
347 SplitNaming::Label | SplitNaming::LabelRelative => "labels",
348 };
349
350 let mut name = if attr == "xml:id" {
351 get_xml_id(page).unwrap_or_default()
352 } else {
353 page.get_attribute(attr).unwrap_or_default()
354 };
355
356 if let Some(first) = name.split_whitespace().next() {
358 name = first.to_string();
359 }
360 if let Some(stripped) = name.strip_prefix("LABEL:") {
361 name = stripped.to_string();
362 }
363
364 if name.is_empty() {
365 if attr == "labels" {
366 if let Some(id) = get_xml_id(page) {
367 Info!(
368 "split",
369 "pathname",
370 "Using '{}' to create page pathname, instead of missing '{}'",
371 id,
372 attr
373 );
374 name = id;
375 } else {
376 name = self.generate_unnamed_page_name();
377 Info!(
378 "split",
379 "pathname",
380 "Using '{}' to create page pathname, instead of missing '{}'",
381 name,
382 attr
383 );
384 }
385 } else {
386 name = self.generate_unnamed_page_name();
387 Info!(
388 "split",
389 "pathname",
390 "Using '{}' to create page pathname, instead of missing '{}'",
391 name,
392 attr
393 );
394 }
395 }
396
397 let as_dir = match self.split_naming {
399 SplitNaming::IdRelative | SplitNaming::LabelRelative => {
400 let parent_attr = if attr == "xml:id" {
401 get_xml_id(parent)
402 } else {
403 parent.get_attribute(attr)
404 };
405 if let Some(pname) = parent_attr {
406 let pname = pname.split_whitespace().next().unwrap_or("");
407 let pname = pname.strip_prefix("LABEL:").unwrap_or(pname);
408 if let Some(rest) = name.strip_prefix(pname) {
409 let rest = rest.trim_start_matches(['.', '_', ':']);
410 if !rest.is_empty() {
411 name = rest.to_string();
412 }
413 }
414 }
415 recursive
416 },
417 _ => false,
418 };
419
420 name = name.replace(':', "_");
422
423 let ext = doc
424 .get_destination_extension()
425 .unwrap_or_else(|| "xml".to_string());
426 let parent_dir = Path::new(parent_path)
427 .parent()
428 .and_then(|p| p.to_str())
429 .unwrap_or(".");
430
431 let parent_dir = if parent_dir.is_empty() {
433 "."
434 } else {
435 parent_dir
436 };
437
438 if as_dir {
439 format!("{}/{}/index.{}", parent_dir, name, ext)
440 } else {
441 format!("{}/{}.{}", parent_dir, name, ext)
442 }
443 }
444}
445
446impl Processor for Split {
447 fn get_name(&self) -> &str { &self.name }
448
449 fn process(&mut self, mut doc: PostDocument, nodes: Vec<Node>) -> ProcessResult {
450 let root = match nodes.into_iter().next() {
451 Some(r) => r,
452 None => return Ok(vec![doc]),
453 };
454
455 let mut root_mut = root;
457 if get_xml_id(&root_mut).is_none() {
458 root_mut
459 .set_attribute("xml:id", "TEMPORARY_DOCUMENT_ID")
460 .ok();
461 }
462
463 let pages = self.get_pages(&doc);
464 let pages: Vec<Node> = pages
466 .into_iter()
467 .filter(|p| p.get_parent().and_then(|pp| pp.get_parent()).is_some())
468 .collect();
469
470 if pages.is_empty() {
471 Info!("split", "result", "[not split]");
472 return Ok(vec![doc]);
473 }
474
475 let nav_nodes: Vec<Node> = doc.findnodes("descendant::ltx:navigation");
477 if !nav_nodes.is_empty() {
478 doc.remove_nodes(&nav_nodes);
479 }
480
481 let root_id = get_xml_id(&root_mut);
483 let root_dest = doc.get_destination().unwrap_or("").to_string();
484 let mut tree = PageEntry {
485 node: root_mut,
486 id: root_id,
487 upid: None,
488 name: root_dest,
489 children: Vec::new(),
490 document: Some(doc),
491 };
492
493 let mut haschildren = HashMap::default();
494 Self::presort_pages(&mut tree, &mut haschildren, pages);
495
496 let doc_tmp = tree.document.take().unwrap();
498 self.prename_pages(&doc_tmp, &mut tree, &haschildren);
499 tree.document = Some(doc_tmp);
500
501 let mut doc = tree.document.take().unwrap();
503 let mut docs = vec![];
504 let mut child_docs = self.process_pages(&mut doc, &mut tree.children);
505
506 if !nav_nodes.is_empty() && !self.no_navigation {
508 tree.document = Some(doc);
510 Self::add_navigation(&mut tree, &nav_nodes);
511 doc = tree.document.take().unwrap();
512
513 for child_doc in &mut child_docs {
515 if let Some(mut root) = child_doc.get_document_element() {
516 let nav_data: Vec<NodeData> = nav_nodes
517 .iter()
518 .map(|n| NodeData::XmlNode(n.clone()))
519 .collect();
520 child_doc.add_nodes(&mut root, &nav_data);
521 }
522 }
523 }
524
525 docs.insert(0, doc);
526 docs.append(&mut child_docs);
527
528 let n = docs.len();
529 Info!(
530 "split",
531 "result",
532 "{}",
533 if n > 1 {
534 format!(" [Split into {} pages]", n)
535 } else {
536 "[not split]".to_string()
537 }
538 );
539
540 Ok(docs)
541 }
542}
543
544fn is_child(child: &Node, ancestor: &Node) -> bool {
546 let mut parent = child.get_parent();
547 while let Some(ref p) = parent {
548 if *p == *ancestor {
549 return true;
550 }
551 parent = p.get_parent();
552 }
553 false
554}