1use libxml::tree::{Node, NodeType};
9use rustc_hash::FxHashMap as HashMap;
10
11use crate::{
12 document::PostDocument,
13 object_db::{ObjectDB, Value},
14 processor::{ProcessResult, Processor},
15};
16
17pub struct Scan {
21 name: String,
22 pub db: ObjectDB,
24 page_id: Option<String>,
26 db_status_due_at: usize,
32}
33
34struct ScannedProps {
36 props: Vec<(String, Value)>,
38 labels: Vec<String>,
40 id: Option<String>,
42}
43
44impl ScannedProps {
45 fn push(&mut self, key: &str, val: Value) { self.props.push((key.to_string(), val)); }
46}
47
48impl Scan {
49 pub fn new(db: ObjectDB) -> Self {
50 Scan {
51 name: "Scan".to_string(),
52 db,
53 page_id: None,
54 db_status_due_at: 1,
55 }
56 }
57
58 pub fn scan(&mut self, doc: &PostDocument, node: &Node, parent_id: Option<&str>) {
62 if let Some(qname) = doc.get_qname(node) {
63 self.dispatch(doc, node, &qname, parent_id);
64 }
65 }
66
67 fn dispatch(&mut self, doc: &PostDocument, node: &Node, tag: &str, parent_id: Option<&str>) {
69 match tag {
70 "ltx:document" | "ltx:part" | "ltx:chapter" | "ltx:section" | "ltx:appendix"
71 | "ltx:subsection" | "ltx:subsubsection" | "ltx:paragraph" | "ltx:subparagraph"
72 | "ltx:bibliography" | "ltx:index" | "ltx:glossary" | "ltx:theorem" | "ltx:proof" => {
73 self.section_handler(doc, node, tag, parent_id)
74 },
75 "ltx:abstract" => self.named_handler(doc, node, tag, parent_id, "Abstract"),
79 "ltx:acknowledgements" => self.named_handler(doc, node, tag, parent_id, "Acknowledgements"),
80 "ltx:table" | "ltx:figure" | "ltx:float" | "ltx:listing" => {
81 self.captioned_handler(doc, node, tag, parent_id)
82 },
83 "ltx:equation" | "ltx:equationgroup" | "ltx:item" | "ltx:listingline" => {
84 self.labelled_handler(doc, node, tag, parent_id)
85 },
86 "ltx:anchor" => self.anchor_handler(doc, node, tag, parent_id),
87 "ltx:Math" => {
101 let id = get_xml_id(node);
102 if let Some(ref id_str) = id {
103 let sp = self.collect_common(doc, node, tag, parent_id);
104 let key = format!("ID:{}", id_str);
105 self.register_scanned(&key, sp);
106 self.add_as_child(id_str, parent_id);
107 }
108 },
110 "ltx:note" => self.note_handler(doc, node, tag, parent_id),
111 "ltx:bibitem" => self.bibitem_handler(doc, node, parent_id),
112 "ltx:bibentry" => {},
113 "ltx:indexmark" => self.indexmark_handler(doc, node, parent_id),
114 "ltx:glossaryentry" | "ltx:glossarydefinition" => {
115 self.glossaryentry_handler(doc, node, tag, parent_id)
116 },
117 "ltx:ref" => self.ref_handler(doc, node, tag, parent_id),
118 "ltx:bibref" => self.bibref_handler(doc, node, tag, parent_id),
119 "ltx:glossaryref" => self.glossaryref_handler(doc, node, tag, parent_id),
120 "ltx:navigation" | "ltx:rawhtml" => {},
121 "ltx:rdf" => self.rdf_handler(node, parent_id),
122 "ltx:declare" => self.declare_handler(doc, node, tag, parent_id),
123 _ => self.default_handler(doc, node, tag, parent_id),
124 }
125 }
126
127 pub fn scan_children(&mut self, doc: &PostDocument, node: &Node, parent_id: Option<&str>) {
129 let children = collect_element_children(node);
130 for child in &children {
131 self.scan(doc, child, parent_id);
132 }
133 }
134
135 fn page_id(&self, doc: &PostDocument) -> Option<String> {
137 self.page_id.clone().or_else(|| {
138 doc
139 .get_document_element()
140 .and_then(|root| get_xml_id(&root))
141 })
142 }
143
144 fn in_page_id(&self, doc: &PostDocument, node: &Node) -> Option<String> {
146 let id = get_xml_id(node)?;
147 let base_id = self.page_id(doc).unwrap_or_default();
148
149 if id == base_id {
150 None
151 } else if !base_id.is_empty() {
152 if let Some(rest) = id.strip_prefix(&base_id).and_then(|r| r.strip_prefix('.')) {
153 Some(rest.to_string())
154 } else {
155 Some(id)
156 }
157 } else {
158 Some(id)
159 }
160 }
161
162 fn collect_common(
165 &self,
166 doc: &PostDocument,
167 node: &Node,
168 tag: &str,
169 parent_id: Option<&str>,
170 ) -> ScannedProps {
171 let id = get_xml_id(node);
172 let labels_str = node.get_attribute("labels");
173 let labels: Vec<String> = labels_str
174 .map(|s| s.split_whitespace().map(String::from).collect())
175 .unwrap_or_default();
176
177 let mut sp = ScannedProps { props: Vec::new(), labels, id };
178
179 sp.push("type", Value::from(tag));
180 if let Some(ref id_str) = sp.id {
181 sp.push("id", Value::from(id_str.as_str()));
182 }
183 if let Some(pid) = parent_id {
184 sp.push("parent", Value::from(pid));
185 }
186 if !sp.labels.is_empty() {
187 sp.push("labels", Value::from(sp.labels.clone()));
188 }
189 if let Some(loc) = doc.site_relative_destination() {
190 sp.push("location", Value::from(loc));
191 }
192 if let Some(pageid) = self.page_id(doc) {
193 sp.push("pageid", Value::from(pageid));
194 }
195 if sp.id.is_some() {
196 if let Some(fragid) = self.in_page_id(doc, node) {
197 sp.push("fragid", Value::from(fragid));
198 }
199 }
200
201 if let Some(listnames) = node.get_attribute("inlist") {
203 let mut inlist = HashMap::default();
204 for name in listnames.split_whitespace() {
205 inlist.insert(name.to_string(), Value::Bool(true));
206 }
207 sp.push("inlist", Value::Hash(inlist));
208 }
209
210 let mut has_refnum = false;
214 for tagnode in child_tag_nodes(node) {
215 let key = if let Some(role) = tagnode.get_attribute("role") {
216 if role.ends_with("refnum") {
217 role
218 } else {
219 format!("tag:{}", role)
220 }
221 } else {
222 "refnum".to_string()
223 };
224 if key == "refnum" {
225 has_refnum = true;
226 }
227 let text = tagnode.get_content();
228 sp.push(&key, Value::from(text));
229 }
230
231 if tag == "ltx:equation" && !sp.labels.is_empty() && !has_refnum {
243 if let Some(inherited) = group_sibling_refnum(node) {
244 sp.push("refnum", Value::from(inherited));
245 }
246 }
247
248 sp
249 }
250
251 fn register_scanned(&mut self, db_key: &str, sp: ScannedProps) {
253 if let Some(ref id) = sp.id {
255 for label in &sp.labels {
256 self
257 .db
258 .register(label, vec![("id", Value::from(id.as_str()))]);
259 }
260 }
261 let owned_props: Vec<(&str, Value)> = Vec::new();
263 let entry = self.db.register(db_key, owned_props);
264 for (k, v) in sp.props {
265 entry.set_value(&k, v);
266 }
267 }
268
269 fn add_as_child(&mut self, id: &str, parent_id: Option<&str>) {
271 let mut current_parent = parent_id.map(String::from);
272 while let Some(ref pid) = current_parent {
273 let key = format!("ID:{}", pid);
274 let has_children = self
275 .db
276 .lookup(&key)
277 .map(|e| e.has_value("children"))
278 .unwrap_or(false);
279 if has_children {
280 if let Some(entry_mut) = self.db.lookup_mut(&key) {
281 entry_mut.push_new("children", vec![Value::from(id)]);
282 }
283 return;
284 }
285 let parent = self
286 .db
287 .lookup(&key)
288 .and_then(|e| e.get_string("parent").map(String::from));
289 current_parent = parent;
290 }
291 }
292
293 fn default_handler(
297 &mut self,
298 doc: &PostDocument,
299 node: &Node,
300 tag: &str,
301 parent_id: Option<&str>,
302 ) {
303 let id = get_xml_id(node);
311 if let Some(ref id_str) = id {
312 let sp = self.collect_common(doc, node, tag, parent_id);
313 let key = format!("ID:{}", id_str);
314 self.register_scanned(&key, sp);
315 }
320 let effective_id = id.as_deref().or(parent_id);
321 self.scan_children(doc, node, effective_id);
322 }
323
324 fn section_handler(
325 &mut self,
326 doc: &PostDocument,
327 node: &Node,
328 tag: &str,
329 parent_id: Option<&str>,
330 ) {
331 let mut sp = self.collect_common(doc, node, tag, parent_id);
332 let id = sp.id.clone();
333 if let Some(ref id_str) = id {
334 sp.push("primary", Value::Bool(true));
335 sp.push("children", Value::List(Vec::new()));
336 if let Some(title_node) = doc.findnode_at("ltx:title", node) {
346 let value = self
347 .db
348 .adopt_xml(&title_node)
349 .unwrap_or_else(|| Value::from(title_text_content(&title_node).as_str()));
350 sp.push("title", value);
351 }
352 if let Some(toctitle_node) = doc.findnode_at("ltx:toctitle", node) {
353 let value = self
354 .db
355 .adopt_xml(&toctitle_node)
356 .unwrap_or_else(|| Value::from(title_text_content(&toctitle_node).as_str()));
357 sp.push("toctitle", value);
358 }
359 if let Some(stub) = node.get_attribute("stub") {
360 sp.push("stub", Value::from(stub));
361 }
362 let key = format!("ID:{}", id_str);
363 self.register_scanned(&key, sp);
364 self.add_as_child(id_str, parent_id);
365 }
366 let effective_id = id.as_deref().or(parent_id);
367 self.scan_children(doc, node, effective_id);
368 }
369
370 fn named_handler(
377 &mut self,
378 doc: &PostDocument,
379 node: &Node,
380 tag: &str,
381 parent_id: Option<&str>,
382 default_name: &str,
383 ) {
384 let mut sp = self.collect_common(doc, node, tag, parent_id);
385 let id = sp.id.clone();
386 if let Some(ref id_str) = id {
387 let name = node
388 .get_attribute("name")
389 .unwrap_or_else(|| default_name.to_string());
390 sp.push("primary", Value::Bool(true));
391 sp.push("children", Value::List(Vec::new()));
392 sp.push("title", Value::from(name.as_str()));
393 sp.push("toctitle", Value::from(name.as_str()));
394 let key = format!("ID:{}", id_str);
395 self.register_scanned(&key, sp);
396 self.add_as_child(id_str, parent_id);
397 }
398 let effective_id = id.as_deref().or(parent_id);
399 self.scan_children(doc, node, effective_id);
400 }
401
402 fn captioned_handler(
403 &mut self,
404 doc: &PostDocument,
405 node: &Node,
406 tag: &str,
407 parent_id: Option<&str>,
408 ) {
409 let mut sp = self.collect_common(doc, node, tag, parent_id);
410 let id = sp.id.clone();
411 if let Some(ref id_str) = id {
412 if let Some(role) = node.get_attribute("role") {
413 sp.push("role", Value::from(role));
414 }
415 let caption = doc
416 .findnode_at("child::ltx:caption", node)
417 .or_else(|| doc.findnode_at("descendant::ltx:caption", node));
418 if let Some(ref cap) = caption {
419 sp.push("caption", Value::from(cap.get_content()));
420 }
421 let toccaption = doc
422 .findnode_at("child::ltx:toccaption", node)
423 .or_else(|| doc.findnode_at("descendant::ltx:toccaption", node));
424 if let Some(ref tc) = toccaption {
425 sp.push("toccaption", Value::from(tc.get_content()));
426 }
427 let key = format!("ID:{}", id_str);
428 self.register_scanned(&key, sp);
429 self.add_as_child(id_str, parent_id);
430 }
431 let effective_id = id.as_deref().or(parent_id);
432 self.scan_children(doc, node, effective_id);
433 }
434
435 fn labelled_handler(
436 &mut self,
437 doc: &PostDocument,
438 node: &Node,
439 tag: &str,
440 parent_id: Option<&str>,
441 ) {
442 let mut sp = self.collect_common(doc, node, tag, parent_id);
443 let id = sp.id.clone();
444 if let Some(ref id_str) = id {
445 if let Some(role) = node.get_attribute("role") {
446 sp.push("role", Value::from(role));
447 }
448 let key = format!("ID:{}", id_str);
449 self.register_scanned(&key, sp);
450 self.add_as_child(id_str, parent_id);
451 }
452 let effective_id = id.as_deref().or(parent_id);
453 self.scan_children(doc, node, effective_id);
454 }
455
456 fn anchor_handler(
457 &mut self,
458 doc: &PostDocument,
459 node: &Node,
460 tag: &str,
461 parent_id: Option<&str>,
462 ) {
463 let mut sp = self.collect_common(doc, node, tag, parent_id);
464 let id = sp.id.clone();
465 if let Some(ref id_str) = id {
466 sp.push("title", Value::from(node.get_content()));
467 let key = format!("ID:{}", id_str);
468 self.register_scanned(&key, sp);
469 self.add_as_child(id_str, parent_id);
470 }
471 let effective_id = id.as_deref().or(parent_id);
472 self.scan_children(doc, node, effective_id);
473 }
474
475 fn note_handler(&mut self, doc: &PostDocument, node: &Node, tag: &str, parent_id: Option<&str>) {
476 let mut sp = self.collect_common(doc, node, tag, parent_id);
477 let id = sp.id.clone();
478 if let Some(ref id_str) = id {
479 if let Some(role) = node.get_attribute("role") {
480 sp.push("role", Value::from(role));
481 }
482 sp.push("note", Value::from(node.get_content()));
484 let key = format!("ID:{}", id_str);
485 self.register_scanned(&key, sp);
486 self.add_as_child(id_str, parent_id);
487 }
488 let effective_id = id.as_deref().or(parent_id);
489 self.scan_children(doc, node, effective_id);
490 }
491
492 fn bibitem_handler(&mut self, doc: &PostDocument, node: &Node, parent_id: Option<&str>) {
493 let id = match get_xml_id(node) {
494 Some(id) => id,
495 None => {
496 self.scan_children(doc, node, parent_id);
497 return;
498 },
499 };
500
501 let key = node.get_attribute("key");
502 let bib = doc.findnode_at("ancestor-or-self::ltx:bibliography", node);
503 let lists_str = bib
504 .and_then(|b| b.get_attribute("lists"))
505 .unwrap_or_else(|| "bibliography".to_string());
506
507 if let Some(ref bibkey) = key {
509 for list in lists_str.split_whitespace() {
510 let label_key = format!("BIBLABEL:{}:{}", list, bibkey);
511 self
512 .db
513 .register(&label_key, vec![("id", Value::from(id.as_str()))]);
514 }
515 }
516
517 let mut props: Vec<(String, Value)> = Vec::new();
519 props.push(("id".to_string(), Value::from(id.as_str())));
520 props.push(("type".to_string(), Value::from("ltx:bibitem")));
521 if let Some(pid) = parent_id {
522 props.push(("parent".to_string(), Value::from(pid)));
523 }
524 if let Some(ref k) = key {
525 props.push(("bibkey".to_string(), Value::from(k.as_str())));
526 }
527 if let Some(loc) = doc.site_relative_destination() {
528 props.push(("location".to_string(), Value::from(loc)));
529 }
530 if let Some(pageid) = self.page_id(doc) {
531 props.push(("pageid".to_string(), Value::from(pageid)));
532 }
533 if let Some(fragid) = self.in_page_id(doc, node) {
534 props.push(("fragid".to_string(), Value::from(fragid)));
535 }
536
537 props.extend(bibitem_tag_props(doc, node));
538
539 let db_key = format!("ID:{}", id);
540 let entry = self.db.register(&db_key, vec![]);
541 for (k, v) in props {
542 entry.set_value(&k, v);
543 }
544
545 self.scan_children(doc, node, Some(&id));
546 }
547
548 fn ref_handler(&mut self, doc: &PostDocument, node: &Node, tag: &str, parent_id: Option<&str>) {
549 if let Some(label) = node.get_attribute("labelref") {
550 let in_toc = !doc
551 .findnodes_at(
552 "ancestor::ltx:tocentry | ancestor::ltx:bibblock[contains(@class,'ltx_bib_cited')]",
553 Some(node),
554 )
555 .is_empty();
556 if !in_toc {
557 self.db.register(&label, vec![]);
558 if let Some(pid) = parent_id {
559 if let Some(entry) = self.db.lookup_mut(&label) {
560 entry.note_association(&["referrers", pid]);
561 }
562 }
563 }
564 }
565 self.default_handler(doc, node, tag, parent_id);
566 }
567
568 fn bibref_handler(
569 &mut self,
570 doc: &PostDocument,
571 node: &Node,
572 tag: &str,
573 parent_id: Option<&str>,
574 ) {
575 let in_cited = !doc
576 .findnodes_at(
577 "ancestor::ltx:bibblock[contains(@class,'ltx_bib_cited')]",
578 Some(node),
579 )
580 .is_empty();
581 if !in_cited {
582 if let Some(keys) = node.get_attribute("bibrefs") {
583 let inlist = node.get_attribute("inlist").unwrap_or_default();
584 let mut lists: Vec<&str> = inlist.split_whitespace().collect();
585 lists.push("bibliography");
586 let label_keys: Vec<String> = keys
587 .split(',')
588 .filter(|k| !k.is_empty())
589 .flat_map(|bibkey| {
590 lists
591 .iter()
592 .map(move |list| format!("BIBLABEL:{}:{}", list, bibkey))
593 })
594 .collect();
595 for label_key in &label_keys {
596 self.db.register(label_key, vec![]);
597 }
598 if let Some(pid) = parent_id {
599 for label_key in &label_keys {
600 if let Some(entry) = self.db.lookup_mut(label_key) {
601 entry.note_association(&["referrers", pid]);
602 }
603 }
604 }
605 }
606 }
607 self.default_handler(doc, node, tag, parent_id);
608 }
609
610 fn glossaryref_handler(
611 &mut self,
612 doc: &PostDocument,
613 node: &Node,
614 tag: &str,
615 parent_id: Option<&str>,
616 ) {
617 if let (Some(k), Some(l)) = (node.get_attribute("key"), node.get_attribute("inlist")) {
618 let gkey = format!("GLOSSARY:{}:{}", l, k);
619 self.db.register(&gkey, vec![]);
620 if let Some(pid) = parent_id {
621 if let Some(entry) = self.db.lookup_mut(&gkey) {
622 entry.note_association(&["referrers", pid]);
623 }
624 }
625 }
626 self.default_handler(doc, node, tag, parent_id);
627 }
628
629 fn indexmark_handler(&mut self, doc: &PostDocument, node: &Node, parent_id: Option<&str>) {
630 let phrases = doc.findnodes_at("ltx:indexphrase", Some(node));
631 let see_also = doc.findnodes_at("ltx:indexsee", Some(node));
632
633 let key_parts: Vec<String> = phrases
634 .iter()
635 .filter_map(|p| p.get_attribute("key"))
636 .collect();
637 let key = format!("INDEX:{}", key_parts.join(":"));
638
639 let inlist = node.get_attribute("inlist").map(|listnames| {
640 let mut h = HashMap::default();
641 for name in listnames.split_whitespace() {
642 h.insert(name.to_string(), Value::Bool(true));
643 }
644 Value::Hash(h)
645 });
646
647 let exists = self.db.lookup(&key).is_some();
648 if !exists {
649 let adopted: Vec<Value> = phrases
657 .iter()
658 .map(|n| {
659 self
660 .db
661 .adopt_xml(n)
662 .unwrap_or_else(|| Value::from(n.get_content().as_str()))
663 })
664 .collect();
665 let mut props = vec![("phrases", Value::List(adopted))];
666 if let Some(il) = inlist {
667 props.push(("inlist", il));
668 }
669 self.db.register(&key, props);
670 }
671
672 if !see_also.is_empty() {
673 let nodes: Vec<Value> = see_also
678 .iter()
679 .map(|n| {
680 self
681 .db
682 .adopt_xml(n)
683 .unwrap_or_else(|| Value::from(n.get_content().as_str()))
684 })
685 .collect();
686 if let Some(entry) = self.db.lookup_mut(&key) {
687 entry.push_new("see_also", nodes);
688 }
689 } else if let Some(pid) = parent_id {
690 let style = node
691 .get_attribute("style")
692 .unwrap_or_else(|| "normal".to_string());
693 if let Some(entry) = self.db.lookup_mut(&key) {
694 entry.note_association(&["referrers", pid, &style]);
695 }
696 }
697 }
698
699 fn glossaryentry_handler(
700 &mut self,
701 doc: &PostDocument,
702 node: &Node,
703 tag: &str,
704 parent_id: Option<&str>,
705 ) {
706 let id = if tag == "ltx:glossaryentry" {
707 get_xml_id(node)
708 } else {
709 None
710 };
711 let lists = node.get_attribute("inlist").unwrap_or_else(|| {
712 doc
713 .findnode_at(
714 "ancestor::ltx:glossarylist[@lists] | ancestor::ltx:glossary[@lists]",
715 node,
716 )
717 .and_then(|p| p.get_attribute("lists"))
718 .unwrap_or_else(|| "glossary".to_string())
719 });
720 let key = node.get_attribute("key").unwrap_or_default();
721 let phrases = doc.findnodes_at("ltx:glossaryphrase", Some(node));
722
723 for list in lists.split_whitespace() {
724 let gkey = format!("GLOSSARY:{}:{}", list, key);
725 let entry = self.db.register(&gkey, vec![]);
726 for phrase in &phrases {
727 let role = phrase
728 .get_attribute("role")
729 .unwrap_or_else(|| "label".to_string());
730 let prop_key = format!("phrase:{}", role);
731 entry.set_value(&prop_key, Value::from(phrase.get_content()));
732 }
733 if let Some(ref id_str) = id {
734 entry.set_value("id", Value::from(id_str.as_str()));
735 }
736 }
737
738 if let Some(ref id_str) = id {
739 let sp = self.collect_common(doc, node, tag, parent_id);
740 let db_key = format!("ID:{}", id_str);
741 self.register_scanned(&db_key, sp);
742 }
743 let effective_id = id.as_deref().or(parent_id);
744 self.scan_children(doc, node, effective_id);
745 }
746
747 fn rdf_handler(&mut self, node: &Node, parent_id: Option<&str>) {
748 let mut id = node.get_attribute("about");
749 if let Some(ref about) = id {
750 if let Some(stripped) = about.strip_prefix('#') {
751 id = Some(stripped.to_string());
752 }
753 }
754 let id = id.or_else(|| parent_id.map(String::from));
755 let property = node.get_attribute("property");
756 let value = node
757 .get_attribute("resource")
758 .or_else(|| node.get_attribute("content"));
759
760 if let (Some(prop), Some(val), Some(id_str)) = (property, value, id) {
761 let db_key = format!("ID:{}", id_str);
762 let entry = self.db.register(&db_key, vec![]);
763 entry.set_value(&prop, Value::from(val));
764 }
765 }
766
767 fn declare_handler(
768 &mut self,
769 doc: &PostDocument,
770 node: &Node,
771 tag: &str,
772 parent_id: Option<&str>,
773 ) {
774 let decl_type = node.get_attribute("type");
775 let sort = node.get_attribute("sortkey");
776 let decl_id = get_xml_id(node);
777 let definiens = node.get_attribute("definiens");
778
779 let term = doc.findnode_at("child::ltx:tags/ltx:tag[@role='term']", node);
780 let description = doc.findnode_at("child::ltx:text", node);
781
782 if decl_type.as_deref() == Some("definition") {
783 let mut def = definiens.clone();
784 if def.is_none() {
785 if let Some(ref term_node) = term {
786 let syms = doc.findnodes_at("descendant-or-self::ltx:XMTok[@meaning]", Some(term_node));
787 let mut non_rel = Vec::new();
788 let mut rel = Vec::new();
789 for sym in &syms {
790 let meaning = sym.get_attribute("meaning").unwrap_or_default();
791 if meaning.starts_with("delimited-") {
792 continue;
793 }
794 if sym.get_attribute("role").as_deref() == Some("RELOP") {
795 rel.push(meaning);
796 } else {
797 non_rel.push(meaning);
798 }
799 }
800 non_rel.extend(rel);
801 def = non_rel.into_iter().next();
802 }
803 }
804 if let Some(ref def_name) = def {
805 let dkey = format!("DECLARATION:global:{}", def_name);
806 let mut sp = self.collect_common(doc, node, tag, parent_id);
807 if let Some(ref desc) = description {
808 sp.push("description", Value::from(desc.get_content()));
809 }
810 self.register_scanned(&dkey, sp);
811 }
812 } else if decl_type.is_none() && parent_id.is_some() {
813 if let Some(ref did) = decl_id {
814 let has_content =
815 description.is_some() || doc.findnode_at("ltx:tags/ltx:tag", node).is_some();
816 if has_content {
817 let dkey = format!("DECLARATION:local:{}", did);
818 let mut sp = self.collect_common(doc, node, tag, parent_id);
819 if let Some(ref desc) = description {
820 sp.push("description", Value::from(desc.get_content()));
821 }
822 self.register_scanned(&dkey, sp);
823 }
824 }
825 }
826
827 if let Some(ref sk) = sort {
828 let name = definiens.as_deref().or(decl_id.as_deref()).unwrap_or(sk);
829 let nkey = format!("NOTATION:{}", name);
830 let mut sp = self.collect_common(doc, node, tag, parent_id);
831 sp.push("sortkey", Value::from(sk.as_str()));
832 if let Some(ref desc) = description {
833 sp.push("description", Value::from(desc.get_content()));
834 }
835 self.register_scanned(&nkey, sp);
836 }
837 }
838}
839
840impl Processor for Scan {
841 fn get_name(&self) -> &str { &self.name }
842
843 fn process(&mut self, doc: PostDocument, _nodes: Vec<Node>) -> ProcessResult {
844 let root = match doc.get_document_element() {
845 Some(r) => r,
846 None => return Ok(vec![doc]),
847 };
848
849 let id = get_xml_id(&root).unwrap_or_else(|| {
850 let mut root_mut = root.clone();
851 root_mut.set_attribute("xml:id", "Document").ok();
852 "Document".to_string()
853 });
854
855 if self.db.lookup("SITE_ROOT").is_none() {
856 self
857 .db
858 .register("SITE_ROOT", vec![("id", Value::from(id.as_str()))]);
859 }
860
861 self.page_id = Some(id.clone());
862 self.scan(&doc, &root, None);
863 self.page_id = None;
864
865 let loc = doc.site_relative_destination().unwrap_or_default();
866 let doc_key = format!("DOCUMENT:{}", loc);
867 self
868 .db
869 .register(&doc_key, vec![("id", Value::from(id.as_str()))]);
870
871 let site_id = self
880 .db
881 .lookup("SITE_ROOT")
882 .and_then(|e| e.get_string("id").map(String::from))
883 .unwrap_or_default();
884 let id_key = format!("ID:{}", id);
885 let needs_parent = self
886 .db
887 .lookup(&id_key)
888 .map(|e| !e.has_value("parent"))
889 .unwrap_or(false);
890 if !site_id.is_empty() && id != site_id && needs_parent {
891 let mut parent_id: Option<String> = None;
893 let mut upid = id.clone();
894 while let Some(dot) = upid.rfind('.') {
895 upid.truncate(dot);
896 if !upid.is_empty() && self.db.lookup(&format!("ID:{}", upid)).is_some() {
897 parent_id = Some(upid.clone());
898 break;
899 }
900 }
901 if parent_id.is_none() {
903 parent_id = Some(site_id);
904 }
905 if let Some(pid) = parent_id {
906 if pid != id {
907 if let Some(entry_mut) = self.db.lookup_mut(&id_key) {
908 entry_mut.set_values(vec![("parent", Value::from(pid.as_str()))]);
909 }
910 self.add_as_child(&id, Some(&pid));
911 }
912 }
913 }
914
915 if self.db.len() >= self.db_status_due_at {
918 Info!("scan", "db_status", "Scan: DBStatus: {}", self.db.status());
919 self.db_status_due_at = (self.db.len() + 1).next_power_of_two();
920 }
921 Ok(vec![doc])
922 }
923}
924
925pub fn bibitem_tag_props(doc: &PostDocument, node: &Node) -> Vec<(String, Value)> {
938 let mut props = Vec::new();
939 for role in &[
940 "authors",
941 "fullauthors",
942 "year",
943 "number",
944 "refnum",
945 "title",
946 "key",
947 "bibtype",
948 ] {
949 let xpath = format!("ltx:tags/ltx:tag[@role='{}']", role);
950 if let Some(tagnode) = doc.findnode_at(&xpath, node) {
951 let prop_name = match *role {
952 "key" => "keytag",
953 "bibtype" => "typetag",
954 _ => *role,
955 };
956 props.push((prop_name.to_string(), Value::from(tagnode.get_content())));
957 }
958 }
959 props
960}
961
962fn collect_element_children(node: &Node) -> Vec<Node> {
963 let mut result = Vec::new();
964 if let Some(child) = node.get_first_child() {
965 let mut current = Some(child);
966 while let Some(ref c) = current {
967 if c.get_type() == Some(NodeType::ElementNode) {
968 result.push(c.clone());
969 }
970 current = c.get_next_sibling();
971 }
972 }
973 result
974}
975
976fn equation_refnum_text(node: &Node) -> Option<String> {
980 for t in child_tag_nodes(node) {
981 if t.get_attribute("role").as_deref() == Some("refnum") {
982 let txt = t.get_content();
983 if !txt.trim().is_empty() {
984 return Some(txt);
985 }
986 }
987 }
988 None
989}
990
991fn group_sibling_refnum(node: &Node) -> Option<String> {
998 let parent = node.get_parent()?;
999 if parent.get_name() != "equationgroup" {
1000 return None;
1001 }
1002 let is_equation =
1003 |n: &Node| n.get_type() == Some(NodeType::ElementNode) && n.get_name() == "equation";
1004 let mut fwd = node.get_next_sibling();
1005 while let Some(sib) = fwd {
1006 if is_equation(&sib) {
1007 if let Some(r) = equation_refnum_text(&sib) {
1008 return Some(r);
1009 }
1010 }
1011 fwd = sib.get_next_sibling();
1012 }
1013 let mut back = node.get_prev_sibling();
1014 while let Some(sib) = back {
1015 if is_equation(&sib) {
1016 if let Some(r) = equation_refnum_text(&sib) {
1017 return Some(r);
1018 }
1019 }
1020 back = sib.get_prev_sibling();
1021 }
1022 None
1023}
1024
1025fn child_tag_nodes(node: &Node) -> Vec<Node> {
1026 let mut result = Vec::new();
1027 let mut child = node.get_first_child();
1028 while let Some(c) = child {
1029 if c.get_type() == Some(NodeType::ElementNode) && c.get_name() == "tags" {
1030 let mut tag_child = c.get_first_child();
1031 while let Some(t) = tag_child {
1032 if t.get_type() == Some(NodeType::ElementNode) && t.get_name() == "tag" {
1033 result.push(t.clone());
1034 }
1035 tag_child = t.get_next_sibling();
1036 }
1037 }
1038 child = c.get_next_sibling();
1039 }
1040 result
1041}
1042
1043fn get_xml_id(node: &Node) -> Option<String> {
1045 node
1046 .get_attribute("xml:id")
1047 .or_else(|| node.get_attribute_ns("id", "http://www.w3.org/XML/1998/namespace"))
1048}
1049
1050pub(crate) fn title_text_content(node: &Node) -> String {
1063 let mut result = String::new();
1064 let mut child = node.get_first_child();
1065 while let Some(c) = child {
1066 match c.get_type() {
1067 Some(NodeType::TextNode) => {
1068 result.push_str(&c.get_content());
1069 },
1070 Some(NodeType::ElementNode) => {
1071 let name = c.get_name();
1072 if name == "tag" {
1073 if let Some(open) = c.get_attribute("open") {
1075 result.push_str(&open);
1076 }
1077 result.push_str(&title_text_content(&c));
1078 if let Some(close) = c.get_attribute("close") {
1079 result.push_str(&close);
1080 }
1081 } else {
1082 result.push_str(&title_text_content(&c));
1083 }
1084 },
1085 _ => {},
1086 }
1087 child = c.get_next_sibling();
1088 }
1089 result
1090}
1091
1092