text-document-io 1.4.1

Import/export for text-document: plain text, Markdown, HTML, LaTeX, DOCX
Documentation
// Generated by Qleany v1.4.8 from feature_use_case.tera
use crate::ImportPlainTextDto;
use anyhow::{Result, anyhow};
use common::database::CommandUnitOfWork;
use common::entities::{Block, Document, Frame, InlineContent, InlineElement, Root};
use common::types::{EntityId, ROOT_ENTITY_ID};

pub trait ImportPlainTextUnitOfWorkFactoryTrait: Send + Sync {
    fn create(&self) -> Box<dyn ImportPlainTextUnitOfWorkTrait>;
}

#[macros::uow_action(entity = "Root", action = "Get")]
#[macros::uow_action(entity = "Root", action = "GetRelationship")]
#[macros::uow_action(entity = "Document", action = "Get")]
#[macros::uow_action(entity = "Document", action = "Update")]
#[macros::uow_action(entity = "Document", action = "GetRelationship")]
#[macros::uow_action(entity = "Frame", action = "Get")]
#[macros::uow_action(entity = "Frame", action = "Create")]
#[macros::uow_action(entity = "Frame", action = "Update")]
#[macros::uow_action(entity = "Frame", action = "Remove")]
#[macros::uow_action(entity = "Frame", action = "GetRelationship")]
#[macros::uow_action(entity = "Block", action = "Create")]
#[macros::uow_action(entity = "Block", action = "CreateMulti")]
#[macros::uow_action(entity = "InlineElement", action = "Create")]
#[macros::uow_action(entity = "InlineElement", action = "CreateMulti")]
pub trait ImportPlainTextUnitOfWorkTrait: CommandUnitOfWork {}

pub struct ImportPlainTextUseCase {
    uow_factory: Box<dyn ImportPlainTextUnitOfWorkFactoryTrait>,
}

impl ImportPlainTextUseCase {
    pub fn new(uow_factory: Box<dyn ImportPlainTextUnitOfWorkFactoryTrait>) -> Self {
        ImportPlainTextUseCase { uow_factory }
    }

    pub fn execute(&mut self, dto: &ImportPlainTextDto) -> Result<()> {
        let mut uow = self.uow_factory.create();
        uow.begin_transaction()?;

        // Step 1: Get Root (id=1) and its Document via relationship
        let root = uow
            .get_root(&ROOT_ENTITY_ID)?
            .ok_or_else(|| anyhow!("Root entity not found"))?;

        let doc_ids = uow.get_root_relationship(
            &root.id,
            &common::direct_access::root::RootRelationshipField::Document,
        )?;
        let doc_id = *doc_ids
            .first()
            .ok_or_else(|| anyhow!("Root has no associated Document"))?;

        // Step 2: Remove all existing frames (cascade deletes blocks and elements)
        let frame_ids = uow.get_document_relationship(
            &doc_id,
            &common::direct_access::document::DocumentRelationshipField::Frames,
        )?;
        for frame_id in &frame_ids {
            uow.remove_frame(frame_id)?;
        }

        // Step 3: Create a new root Frame owned by the Document
        let new_frame = Frame::default();
        let created_frame = uow.create_frame(&new_frame, doc_id, -1)?;

        // Step 4: Split input text into lines and create blocks with inline elements
        // Normalize line endings: \r\n -> \n, lone \r -> \n
        let normalized = dto.plain_text.replace("\r\n", "\n").replace('\r', "\n");
        let lines: Vec<&str> = normalized.split('\n').collect();
        let num_blocks = lines.len() as i64;
        let mut total_chars: i64 = 0;
        let mut document_position: i64 = 0;
        let mut block_ids: Vec<i64> = Vec::new();

        for (i, line) in lines.iter().enumerate() {
            let line_len = line.chars().count() as i64;

            // Create a Block owned by the Frame
            let block = Block {
                plain_text: line.to_string(),
                text_length: line_len,
                document_position,
                ..Block::default()
            };

            let created_block = uow.create_block(&block, created_frame.id, -1)?;

            // Create an InlineElement owned by the Block
            let element = InlineElement {
                content: InlineContent::Text(line.to_string()),
                ..InlineElement::default()
            };

            uow.create_inline_element(&element, created_block.id, -1)?;

            block_ids.push(created_block.id as i64);
            total_chars += line_len;

            // Update document_position: each block is separated by 1 (block separator)
            document_position += line_len;
            if i < lines.len() - 1 {
                document_position += 1; // block separator
            }
        }

        // Step 5: Update Frame child_order
        let mut updated_frame = uow
            .get_frame(&created_frame.id)?
            .ok_or_else(|| anyhow!("Created frame not found"))?;
        updated_frame.child_order = block_ids;
        uow.update_frame(&updated_frame)?;

        // Step 6: Update Document cached fields
        let mut updated_doc = uow
            .get_document(&doc_id)?
            .ok_or_else(|| anyhow!("Document not found after import"))?;
        updated_doc.character_count = total_chars;
        updated_doc.block_count = num_blocks;
        uow.update_document(&updated_doc)?;

        uow.commit()?;
        Ok(())
    }
}