Как добавить элемент altChunk в XWPFDocument с помощью Apache POI

Я хотел бы добавить HTML как altChunk в файл DOCX, используя Apache POI. Я знаю, что doc4jx может сделать это с более простым API, но по техническим причинам мне нужно использовать Apache POI.

Использование классов CT для выполнения низкоуровневых вещей с xml немного сложно. Я могу создать altChunk с помощью следующего кода:

import java.io.File;
import java.io.FileOutputStream;

import javax.xml.namespace.QName;

import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import org.apache.xmlbeans.impl.values.XmlComplexContentImpl;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTDocument1;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.impl.CTBodyImpl;

public class AltChunkTest {
    public static void main(String[] args) throws Exception  {
        XWPFDocument doc = new XWPFDocument();
        doc.createParagraph().createRun().setText("AltChunk below:");
        QName ALTCHUNK =  new QName ( "http://schemas.openxmlformats.org/wordprocessingml/2006/main" ,  "altChunk" ) ; 
        CTDocument1 ctDoc = doc.getDocument() ; 
        CTBodyImpl ctBody =  (CTBodyImpl) ctDoc. getBody(); 
        XmlComplexContentImpl xcci =  ( XmlComplexContentImpl ) ctBody.get_store().add_element_user(ALTCHUNK); 
        // what's need to now add "<b>Hello World!</b>"
        FileOutputStream out = new FileOutputStream(new File("test.docx"));
        doc.write(out);
    }
}

Но как мне добавить html контент в 'xcci' сейчас?

2 ответа

Решение

В Office Open XML за Word (*.docx) altChunk предоставляет метод для использования чистого HTML описать части документа.

Два важных замечания о altChunk :

Первый: он используется только для импорта контента. Если вы открываете документ с помощью Word и сохраните его, новый сохраненный документ не будет содержать часть содержимого альтернативного формата или разметку altChunk, которая ссылается на него. Word сохраняет весь импортированный контент по умолчанию Office Open XML элементы.

Второе: большинство приложений, кроме Word которые умеют читать *.docx тоже не буду читать altChunk содержание вообще. Например Libreoffice или же OpenOfficeWriter не будет читать altChunk содержание, а также apache poi не будет читать altChunk содержание при открытии *.docx файл.

Как altChunk реализовано в *.docxZIP файловая структура?

Есть /word/*.html файлы в *.docxZIP файл. На которые ссылается Id в /word/document.xml как <w:altChunk r:id="htmlDoc1"/> например. Соотношение между идентификаторами и /word/*.html файлы приведены в /word/_rels/document.xml.rels как <Relationship Id="htmlDoc1" Target="htmlDoc1.html" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/aFChunk"/> например.

Итак, нам нужно сначала POIXMLDocumentPartс для /word/*.html файлы и POIXMLRelation s для связи между идентификаторами и /word/*.html файлы. Следующий код обеспечивает это, имея класс-оболочку, который расширяет POIXMLDocumentPart для /word/htmlDoc#.html файлы в ZIP-архиве *.docx. Это также предоставляет методы для управления HTML. Также он предоставляет метод для создания /word/htmlDoc#.html файлы в ZIP-архиве *.docx и создание отношений к нему.

Код:

import java.io.*;

import org.apache.poi.*;
import org.apache.poi.ooxml.*;
import org.apache.poi.openxml4j.opc.*;

import org.apache.poi.xwpf.usermodel.*;

public class CreateWordWithHTMLaltChunk {

 //a method for creating the htmlDoc /word/htmlDoc#.html in the *.docx ZIP archive  
 //String id will be htmlDoc#.
 private static MyXWPFHtmlDocument createHtmlDoc(XWPFDocument document, String id) throws Exception {
  OPCPackage oPCPackage = document.getPackage();
  PackagePartName partName = PackagingURIHelper.createPartName("/word/" + id + ".html");
  PackagePart part = oPCPackage.createPart(partName, "text/html");
  MyXWPFHtmlDocument myXWPFHtmlDocument = new MyXWPFHtmlDocument(part, id);
  document.addRelation(myXWPFHtmlDocument.getId(), new XWPFHtmlRelation(), myXWPFHtmlDocument);
  return myXWPFHtmlDocument;
 }

 public static void main(String[] args) throws Exception {

  XWPFDocument document = new XWPFDocument();

  XWPFParagraph paragraph;
  XWPFRun run;
  MyXWPFHtmlDocument myXWPFHtmlDocument;

  paragraph = document.createParagraph();
  run = paragraph.createRun();
  run.setText("Default paragraph followed by first HTML chunk.");

  myXWPFHtmlDocument = createHtmlDoc(document, "htmlDoc1");
  myXWPFHtmlDocument.setHtml(myXWPFHtmlDocument.getHtml().replace("<body></body>",
   "<body><p>Simple <b>HTML</b> <i>formatted</i> <u>text</u></p></body>"));
  document.getDocument().getBody().addNewAltChunk().setId(myXWPFHtmlDocument.getId());  

  paragraph = document.createParagraph();
  run = paragraph.createRun();
  run.setText("Default paragraph followed by second HTML chunk.");

  myXWPFHtmlDocument = createHtmlDoc(document, "htmlDoc2");
  myXWPFHtmlDocument.setHtml(myXWPFHtmlDocument.getHtml().replace("<body></body>",
   "<body>" +
   "<table>"+
   "<caption>A table></caption>" +
   "<tr><th>Name</th><th>Date</th><th>Amount</th></tr>" +
   "<tr><td>John Doe</td><td>2018-12-01</td><td>1,234.56</td></tr>" +
   "</table>" +
   "</body>"
   ));
  document.getDocument().getBody().addNewAltChunk().setId(myXWPFHtmlDocument.getId());  

  FileOutputStream out = new FileOutputStream("CreateWordWithHTMLaltChunk.docx");
  document.write(out);
  out.close();
  document.close();

 }

 //a wrapper class for the  htmlDoc /word/htmlDoc#.html in the *.docx ZIP archive
 //provides methods for manipulating the HTML
 //TODO: We should *not* using String methods for manipulating HTML!
 private static class MyXWPFHtmlDocument extends POIXMLDocumentPart {

  private String html;
  private String id;

  private MyXWPFHtmlDocument(PackagePart part, String id) throws Exception {
   super(part);
   this.html = "<!DOCTYPE html><html><head><style></style><title>HTML import</title></head><body></body>";
   this.id = id;
  }

  private String getId() {
   return id;
  }

  private String getHtml() {
   return html;
  }

  private void setHtml(String html) {
   this.html = html;
  }

  @Override
  protected void commit() throws IOException {
   PackagePart part = getPackagePart();
   OutputStream out = part.getOutputStream();
   Writer writer = new OutputStreamWriter(out, "UTF-8");
   writer.write(html);
   writer.close();
   out.close();
  }

 }

 //the XWPFRelation for /word/htmlDoc#.html
 private final static class XWPFHtmlRelation extends POIXMLRelation {
  private XWPFHtmlRelation() {
   super(
    "text/html", 
    "http://schemas.openxmlformats.org/officeDocument/2006/relationships/aFChunk", 
    "/word/htmlDoc#.html");
  }
 }
}

Результат:

введите описание изображения здесь

Основываясь на ответе Акселя Рихтера, я заменил вызов CTBody.addNewAltChunk() на CTBodyImpl.get_store(). Add_element_user(QName), который устраняет добавленную зависимость в 15 МБ от ooxml-схем. Поскольку это используется в настольном приложении, мы стараемся сохранить его как можно меньший размер. В случае, если это может быть полезно кому-либо еще:

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;

import javax.xml.namespace.QName;

import org.apache.poi.ooxml.POIXMLDocumentPart;
import org.apache.poi.ooxml.POIXMLRelation;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.openxml4j.opc.PackagePart;
import org.apache.poi.openxml4j.opc.PackagePartName;
import org.apache.poi.openxml4j.opc.PackagingURIHelper;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.xmlbeans.SimpleValue;
import org.apache.xmlbeans.impl.values.XmlComplexContentImpl;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.impl.CTBodyImpl;

public class AltChunkTest {
    public static void main(String[] args) throws Exception  {
        XWPFDocument doc = new XWPFDocument();
        doc.createParagraph().createRun().setText("AltChunk below:");
        addHtml(doc,"chunk1","<!DOCTYPE html><html><head><style></style><title></title></head><body><b>Hello World!</b></body></html>");
        FileOutputStream out = new FileOutputStream(new File("test.docx"));
        doc.write(out);
    }

    static void addHtml(XWPFDocument doc, String id,String html) throws Exception {
        OPCPackage oPCPackage = doc.getPackage();
        PackagePartName partName = PackagingURIHelper.createPartName("/word/" + id + ".html");
        PackagePart part = oPCPackage.createPart(partName, "text/html");
        class HtmlRelation extends POIXMLRelation {
            private HtmlRelation() {
                super(  "text/html",
                        "http://schemas.openxmlformats.org/officeDocument/2006/relationships/aFChunk",
                        "/word/htmlDoc#.html");
            }
        }
        class HtmlDocumentPart extends POIXMLDocumentPart {
            private HtmlDocumentPart(PackagePart part) throws Exception {
                super(part);
            }

            @Override
            protected void commit() throws IOException {
                try (OutputStream out = part.getOutputStream()) {
                    try (Writer writer = new OutputStreamWriter(out, "UTF-8")) {
                        writer.write(html);
                    }
                }
            }
        };
        HtmlDocumentPart documentPart = new HtmlDocumentPart(part);
        doc.addRelation(id, new HtmlRelation(), documentPart);
        CTBodyImpl b = (CTBodyImpl) doc.getDocument().getBody();
        QName ALTCHUNK = new QName("http://schemas.openxmlformats.org/wordprocessingml/2006/main", "altChunk");
        XmlComplexContentImpl altchunk = (XmlComplexContentImpl) b.get_store().add_element_user(ALTCHUNK);
        QName ID = new QName("http://schemas.openxmlformats.org/officeDocument/2006/relationships", "id");
        SimpleValue target = (SimpleValue)altchunk.get_store().add_attribute_user(ID);
        target.setStringValue(id);
    }
}

Эта функция работает в poi-ooxml 4.0.0, где классы POIXMLDocumentPart и POIXMLRelation находятся в пакете org.apache.poi.ooxml.*

import org.apache.poi.ooxml.POIXMLDocumentPart;
import org.apache.poi.ooxml.POIXMLRelation;

Но как мы можем использовать poi-ooxml 3.9, где классы немного отличаются и в org.apache.poi.*

import org.apache.poi.POIXMLDocumentPart;
import org.apache.poi.POIXMLRelation;
Другие вопросы по тегам