From 874805b7a5fb93e66b6cd0b35a95b0a6e82dd372 Mon Sep 17 00:00:00 2001 From: "Dylan M. Taylor" Date: Fri, 13 Nov 2026 06:08:59 -0500 Subject: [PATCH] Parse saved cross-reference text in shared model --- crates/idml/src/model/mod.rs | 4 +-- crates/idml/src/model/story.rs | 55 ++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 2 deletions(-) diff --git a/crates/idml/src/model/mod.rs b/crates/idml/src/model/mod.rs index 419c4b70c..663551e04 100644 --- a/crates/idml/src/model/mod.rs +++ b/crates/idml/src/model/mod.rs @@ -28,8 +28,8 @@ pub use spread::{ TextWrapPreference, spreads, }; pub use story::{ - Ace, CONTAINERS, Cell, CellOptions, Container, ContainerKind, ContentPart, PageReference, Range, Story, StoryAnchor, StoryPreference, Table, - TableChild, TableColumn, TableLayout, TableRow, TextNode, TrackedChangeKind, stories, + Ace, CONTAINERS, Cell, CellOptions, Container, ContainerKind, ContentPart, PageReference, Range, SavedCrossReference, SavedCrossReferencePart, + Story, StoryAnchor, StoryPreference, Table, TableChild, TableColumn, TableLayout, TableRow, TextNode, TrackedChangeKind, stories, }; use crate::value::{matrix, nums}; diff --git a/crates/idml/src/model/story.rs b/crates/idml/src/model/story.rs index 399e4e241..7ea28b51e 100644 --- a/crates/idml/src/model/story.rs +++ b/crates/idml/src/model/story.rs @@ -78,6 +78,61 @@ pub struct PageReference { pub values: Values, } +/// Text saved inside an IDML `CrossReferenceSource` before the application regenerates it from +/// the cross-reference format. Page-number variables are separate segments so callers can retain +/// the last displayed page while mapping to their own text model. +#[derive(Clone, Debug, Default, PartialEq)] +pub struct SavedCrossReference { + pub self_id: Option, + pub applied_format: Option, + pub parts: Vec, +} + +#[derive(Clone, Debug, PartialEq)] +pub enum SavedCrossReferencePart { + Text(String), + PageNumber { result_text: Option }, +} + +impl SavedCrossReference { + /// Reads the bounded saved-text grammar. `page_variables` contains IDs whose variable type is + /// the cross-reference page number; when the document omits variable definitions, InDesign's + /// conventional `TV XRefPageNumber` suffix is recognized. + pub fn from_el(el: &El, page_variables: &[String]) -> SavedCrossReference { + let mut parsed = SavedCrossReference { + self_id: el.attr("Self").map(str::to_string), + applied_format: el.attr("AppliedFormat").map(str::to_string), + parts: Vec::new(), + }; + saved_xref_parts(el, page_variables, &mut parsed.parts, 0); + parsed + } +} + +fn saved_xref_parts(el: &El, page_variables: &[String], parts: &mut Vec, depth: usize) { + if depth > 16 { + return; + } + for child in el.elements() { + match child.local() { + "Content" => parts.push(SavedCrossReferencePart::Text(child.deep_text())), + "TextVariableInstance" => { + let id = child.attr("AssociatedTextVariable").unwrap_or_default(); + let page_number = + page_variables.iter().any(|variable| variable == id) || (page_variables.is_empty() && id.ends_with("TV XRefPageNumber")); + if page_number { + parts.push(SavedCrossReferencePart::PageNumber { result_text: child.attr("ResultText").map(str::to_string) }); + } else { + parts.push(SavedCrossReferencePart::Text(child.attr("ResultText").unwrap_or_default().to_string())); + } + } + "Change" if child.attr("ChangeType") == Some("DeletedText") => {} + "CharacterStyleRange" | "Change" => saved_xref_parts(child, page_variables, parts, depth + 1), + _ => {} + } + } +} + impl PageReference { pub fn referenced_topic(&self) -> Option<&str> { self.values.get("ReferencedTopic")