Hlavní navigace

Úprava DOCX dokumentu v Apache Camel

16. 1. 2021 13:21 Jiří Raška

Tak jsem narazil na jeden zajímavý problém, a sice úprava DOCX dokumentu v Java aplikaci. 

V podstatě dostanu dokument jako byte stream, potřebuji v něm upravit text (v tomto případě vyhodit vadné reference), a opět jej odeslat jako byte stream. 

Vzhledem k tomu, že v rámci implementace často používám Apache Camel, volba padla na něj.

A tohle je tedy výsledek mého snažení:

Jak tedy na to 

On ten DOCX dokument je v podstatě ZIP kontejner se soubory primárně ve formátu XML, a případně dalších formátů, pokud jsou do dokumentu vloženy např. obrázky.
Proto vlastní postup zahrnuje tyhle kroky:
  • rozbalení ZIP kontejneru na jednotlivé soubory (se zachováním adresářové struktury a jmen souborů)
  • výběr jen těch souborů, které budu upravovat (v mém případě beru všechny XML soubory v adresáři ./word)
  • úprava každého souboru dle potřeby
  • sbalení všech souborů do ZIP kontejneru A takto vypadá řešení

A takto vypadá řešení

Řešení je zapsáno ve formě JUnit testu, kdy obsah vlastního dokumentu načítám z lokálních resource. Ve vlastní aplikaci je to pochopitelně napojeno jinak.

Výsledek pak očekávám jako výstup route.

public class DocHandoverDOCXFixTest extends CamelTestSupport {

    @Override
    protected RouteBuilder createRouteBuilder() {
        return new RouteBuilder() {
            public void configure() {
/* 1 */         from("direct:start")
/* 2 */             .split(new ZipSplitter(), new ZipAggregationStrategy(true, true))
/* 3 */                 .streaming()
                        .choice()
/* 4 */                     .when(simple("${in.header.CamelFileName} regex 'word/.*\\.xml'"))
                                .process(new Processor() {
                                @Override
                                public void process(Exchange exchange) throws Exception {
                                    String msg = exchange.getIn().getBody(String.class);
/* 5 */                             // tady je kód pro úpravu jednoho dokumentu   
                                    exchange.getIn().setBody(msg.getBytes(), byte[].class);
                                }
                                })
                            .endChoice()
                            .otherwise()
/* 6 */                         .convertBodyTo(byte[].class)
                            .endChoice()
                        .end()
                    .end()
                    .to("mock:result");
            }
        };
    }
    
    @Test
    public void test1() throws Exception {

/* 7 */ String sourceUrl = "classpath:cz/i/isac/resource/libreoffice/mpa01.docx";
        InputStream is = new FileInputStream(ResourceUtils.getFile(sourceUrl));

/* 8 */ MockEndpoint resultEndpoint = resolveMandatoryEndpoint("mock:result", MockEndpoint.class);
        resultEndpoint.expectedMessageCount(1);

/* 9 */ template.sendBodyAndHeader("direct:start", is, Exchange.FILE_NAME, "mpa01.docx");
       
/* 10 */resultEndpoint.assertIsSatisfied();
    }   
}

A ještě vysvětlení k jednotlivým krokům:

/* 1 */     - začátek definice route v Camel; pro napojení na zbytek světa používám přímé volání, ale to je závisle na kontextu použití

/* 2 */     - a tady je ta nejzajímavější část

Pro rozdělení ZIP streamu na jednotlivé soubory používám instanci ZipSplitter

Výsledek je, že v rámci directivy split dostanu samostatné exchange pro každý soubor, který je v kontejneru zabalený. Současně je v hlavičkách zachován název souboru.

Po zpracování ale potřebuji, aby se zase vše sbalilo do ZIP streamu. 

Pro to je v rámci directivy ještě použita instance ZipAggregationStrategy. Ta dělá přesně opačný postup oproti ZipSplitter. Bere jednotlivé exchange z directivy split a spojuje je do jednoho ZIP kontejneru. Ty dva parametry true říkají, že u spojených souborů má být zachována adresářová struktura a jméno souboru.

/* 3 */     - streaming znamená, že se budou soubory zpracovávat jeden po druhém, a není tedy potřeba je všechny najednou načíst do pameti

/* 4 */     - na tomhle řádku je jen ukázka toho, že na základě adresáře a jména souboru udělám nějakou akci. V tomto případě hledám všechny soubory v adresáři ./word s příponou .xml.

/* 5 */     - no a tady s tím něco udělám … ale to není předmětem tohoto povídání

/* 6 */     - jiné soubory, kterými se nezabývám, si také převedu do bytového streamu (možná to není potřeba, ale pro jistotu …)

Dále již jsou pouze kroky, které se týkají vyvolání JUnit testu:

/* 7 */     - načtení obsahu souboru z resource

/* 8 */     - nastavení mock, abych si ověřil, že dostanu zpět jeden dokument

/* 9 */     - vyvolání vlastní route

/* 10 */   – a závěrečná kontrola mock

A to je vše. Třeba vám to k něčemu bude.

Sdílet