rust-docx 0.2.14

Rust parser, document model, and serializer for DOCX and legacy Word documents
Documentation
use crate::model::{
    DocModel, DocNode, ImageRunNode, ParagraphChildNode, ParagraphNode, TableCellContentNode,
    TableNode,
};
use crate::{OoxmlPackage, ParseOptions};
use rust_office_art::model3d::{parse_model3d, Model3DAsset};

struct Models<'a> {
    package: &'a OoxmlPackage,
    options: &'a ParseOptions,
    content_types: super::ContentTypeLookup,
    missing_assets: bool,
}

impl Models<'_> {
    fn image(&mut self, image: &mut ImageRunNode) {
        if let Some(xml) = image.source_xml.as_deref() {
            image.model_3d = parse_model3d(xml, self.options, |id| {
                let target = image
                    .presentation
                    .as_ref()?
                    .assets
                    .iter()
                    .find(|asset| asset.relationship_id.as_deref() == Some(id))?
                    .target
                    .as_deref()?;
                let data = self.package.binary_assets.get(target)?;
                Some(Model3DAsset {
                    part_name: target.to_owned(),
                    content_type: super::content_type_for_part(target, &self.content_types)
                        .unwrap_or_else(|| "application/octet-stream".into()),
                    data: Some(data.clone().into()),
                })
            })
            .map(Box::new);
            if let Some(model) = &image.model_3d {
                image.semantic_kind = Some("model_3d".into());
                self.missing_assets |=
                    model.model_relationship_id.is_some() && model.asset.is_none();
            }
        }
        if let Some(nodes) = &mut image.content_nodes {
            self.nodes(nodes);
        }
    }

    fn children(&mut self, children: &mut [ParagraphChildNode]) {
        for child in children {
            match child {
                ParagraphChildNode::Image(image) => self.image(image),
                ParagraphChildNode::FormField(field) => {
                    if let Some(preview) = &mut field.source_preview {
                        self.image(&mut preview.image);
                    }
                }
                ParagraphChildNode::Text(_) => {}
            }
        }
    }

    fn paragraph(&mut self, paragraph: &mut ParagraphNode) {
        self.children(&mut paragraph.children);
        for fragment in paragraph.review_content.iter_mut().flatten() {
            self.children(&mut fragment.children);
        }
    }

    fn table(&mut self, table: &mut TableNode) {
        for row in table.rows.iter_mut().chain(
            table
                .review_rows
                .iter_mut()
                .flatten()
                .map(|fragment| &mut fragment.row),
        ) {
            for cell in &mut row.cells {
                for node in &mut cell.nodes {
                    match node {
                        TableCellContentNode::Paragraph(paragraph) => self.paragraph(paragraph),
                        TableCellContentNode::Table(table) => self.table(table),
                    }
                }
            }
        }
    }

    fn nodes(&mut self, nodes: &mut [DocNode]) {
        for node in nodes {
            match node {
                DocNode::Paragraph(paragraph) => self.paragraph(paragraph),
                DocNode::Table(table) => self.table(table),
            }
        }
    }
}

pub(super) fn attach_models(model: &mut DocModel, package: &OoxmlPackage, options: &ParseOptions) {
    let mut models = Models {
        package,
        options,
        content_types: super::parse_content_types(package),
        missing_assets: false,
    };
    models.nodes(&mut model.nodes);
    let metadata = &mut model.metadata;
    for header in &mut metadata.header_sections {
        models.nodes(&mut header.nodes);
    }
    for footer in &mut metadata.footer_sections {
        models.nodes(&mut footer.nodes);
    }
    for section in metadata.sections.iter_mut().flatten() {
        for header in &mut section.header_sections {
            models.nodes(&mut header.nodes);
        }
        for footer in &mut section.footer_sections {
            models.nodes(&mut footer.nodes);
        }
    }
    for notes in [&mut metadata.footnotes, &mut metadata.endnotes] {
        for note in notes.iter_mut().flatten() {
            if let Some(nodes) = &mut note.nodes {
                models.nodes(nodes);
            }
        }
    }
    for comment in metadata.comments.iter_mut().flatten() {
        if let Some(nodes) = &mut comment.nodes {
            models.nodes(nodes);
        }
    }
    if models.missing_assets {
        metadata
            .warnings
            .push("An embedded 3D model asset could not be resolved".into());
    }
}