Apache POI, изменение типа файла Mime. Это возможно исправить?

#java #apache-poi #docx

#java #apache-poi #docx

Вопрос:

У меня проблемы с Apache POI и типом файла Mime. Я использую шаблон файла (Microsoft Word DOCX) для изменения некоторых значений с помощью Apache Poi. Исходный файл имеет mime-тип «application / vnd.openxmlformats-officedocument.wordprocessingml.document» (в Linux: file -i {filename}), но я обрабатываю файл с помощью POI и сохраняю, затем снова получаю «application / octet-stream» и хочу сохранить файл с исходным типом mime.

Я открываю файл с помощью шестнадцатеричного редактора, оба файла исходные и модифицированные, и оба имеют одинаковые «магические числа» (50 4B 03 04), но размер файла отличается, даже если тексты одинаковые. Так это возможно исправить? У кого-нибудь такая же проблема? Я проверяю это в LibreOffice и, похоже, имеет то же поведение, что и Apache POI.

Любая помощь, любая информация помогут.

Комментарии:

1. Удается ли Apache Tika правильно определять тип файла? Если да, то похоже на ошибку в вашей версии file инструмента

2. Я анализирую документ и отправляю в другой сторонний API, и они не принимают docx с другим типом mime, должно быть «application / vnd.openxmlformats-officedocument.wordprocessingml.document». Но как можно манипулировать документом и сохранять этот тип mime? Я тоже пытаюсь в Windows, и у меня такая же проблема.

3. Тип mime файла OOXML задается [Content_Types.xml] файлом, встроенным в структуру zip. Apache POI устанавливает это правильно. Любое программное обеспечение, которое не проверяет, что делает это неправильно, и нуждается в указании на спецификацию Microsoft!

4. Очень интересно! Я проверяю файлы, оба похожи, но не равны. Порядок, отступы элементов отличаются. Я действительно не уверен, почему stop работает в файловой системе Linux и Windows.

5. Я провел здесь тест. Мы распаковываем файл DOCX и снова переупаковываем в Windows с помощью 7zip, и тип mime в порядке (используя метод DEFLATE). Мы делаем тот же самый репак, используя 7zip, используя BZIP2, и тип mime, похоже, «application / zip». Итак, проблема заключается в методе, который Apache POI и LibreOffice используют для упаковки файла.

Ответ №1:

Как вы уже указывали в комментарии, то, как Apache POI перестраивает пакет Office Open XML ZIP , приводит к неправильной интерпретации типа содержимого некоторыми инструментами. Файл Office Open XML ( *.docx , *.xlsx , *.pptx ) является ZIP архивом, но способ Microsoft Office упаковки этого архива должен быть особенным. Я не нашел, что именно это такое.

Пример:

Начните с Document.docx простого содержимого, которое было сохранено Microsoft Word.

Для этого file -i создается:

 axel@arichter:~/Dokumente/JAVA/poi/poi-4.0.1$ file -i Document.docx 
Document.docx: application/vnd.openxmlformats-officedocument.wordprocessingml.document; charset=binary
  

Теперь запустите этот код:

 import java.io.FileOutputStream;
import java.io.FileInputStream;

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

public class WordReadAndReWrite {

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

  String inFilePath = "Document.docx";
  String outFilePath = "NewDocument.docx";

  XWPFDocument doc = new XWPFDocument(new FileInputStream(inFilePath));

  doc.createParagraph().createRun().setText("new text inserted");

  FileOutputStream out = new FileOutputStream(outFilePath); 
  doc.write(out);
  out.close();
  doc.close();
 }

}
  

Для результирующего NewDocument.docx , file -i выдает:

 axel@arichter:~/Dokumente/JAVA/poi/poi-4.0.1$ file -i NewDocument.docx 
NewDocument.docx: application/octet-stream; charset=binary
  

Но если мы делаем то же самое, не используя ZipPackage Apache POI, а вместо этого используем файловую систему для получения XML из пакета Office Open XML ZIP , используя следующий код:

 import java.nio.file.Files;
import java.nio.file.FileSystems;
import java.nio.file.FileSystem;
import java.nio.file.Paths;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;

import org.w3c.dom.Document;
import org.w3c.dom.Node;

import javax.xml.transform.TransformerFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.stream.StreamResu<
import javax.xml.transform.dom.DOMSource;

public class WordReadAndReWriteFileSystem {

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

  String inFilePath = "Document.docx";
  String outFilePath = "NewDocument.docx";

  FileSystem fileSystem = FileSystems.newFileSystem(Paths.get(inFilePath), null);
  Path wordDocumentXml = fileSystem.getPath("/word/document.xml");

  DocumentBuilder documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
  Document xmlDocument = documentBuilder.parse(Files.newInputStream(wordDocumentXml, StandardOpenOption.READ));

  Node p = xmlDocument.createElement("w:p");
  Node r = xmlDocument.createElement("w:r");
  p.appendChild(r);
  Node t = xmlDocument.createElement("w:t");
  r.appendChild(t);
  Node text = xmlDocument.createTextNode("new text inserted");
  t.appendChild(text);

  Node body = xmlDocument.getElementsByTagName("w:body").item(0);
  Node sectPr = xmlDocument.getElementsByTagName("w:sectPr").item(0);
  body.insertBefore(p, sectPr);

  TransformerFactory transformerFactory = TransformerFactory.newInstance();
  Transformer transformer = transformerFactory.newTransformer();
  DOMSource domSource = new DOMSource(xmlDocument);
  Path tmpDoc = Files.createTempFile("wordDocument", "tmp");
  tmpDoc.toFile().deleteOnExit();
  StreamResult streamResult = new StreamResult(Files.newOutputStream(tmpDoc, StandardOpenOption.WRITE));
  transformer.transform(domSource, streamResult);

  fileSystem.close();

  Path tmpZip = Files.createTempFile("zipDocument", "tmp");
  tmpZip.toFile().deleteOnExit();
  Path path = Files.copy(Paths.get(inFilePath), tmpZip, StandardCopyOption.REPLACE_EXISTING);
  fileSystem = FileSystems.newFileSystem(path, null);
  wordDocumentXml = fileSystem.getPath("/word/document.xml");

  Files.copy(tmpDoc, wordDocumentXml, StandardCopyOption.REPLACE_EXISTING);
  fileSystem.close();

  Files.copy(tmpZip, Paths.get(outFilePath), StandardCopyOption.REPLACE_EXISTING);

 }

}
  

Затем для результирующего NewDocument.docx , file -i выдает:

 axel@arichter:~/Dokumente/JAVA/poi/poi-4.0.1$ file -i NewDocument.docx 
NewDocument.docx: application/vnd.openxmlformats-officedocument.wordprocessingml.document; charset=binary
  

Ответ №2:

Этот код показывает правильный тип файла mime для всех файлов, которые я тестирую:

 public static void main(String[] args) {
    String fileName = "model_libreoffice.docx";
//        String fileName = "model_poi.docx";
//        String fileName = "model_msoffice.docx";
//        String fileName = "model_repacked_bz2.docx";

    try {
        InputStream is = Main.class.getResourceAsStream("/"   fileName);
        Tika t = new Tika();
        String mime = t.detect(is, fileName);
        System.out.println("----> "    mime);
    } catch (IOException e) {
        e.printStackTrace();
    }
}
  

После долгой отладки и тестирования я думаю, что это проблема с сторонней проверкой файлов.
Этот простой код показывает мне правильный тип mime для всех файлов, которые я пробую, модифицированных MicrosoftOffice, LibreOffice, Apache Poi, Распаковывает и снова архивирует (переименованный в DOCX) файлы содержимого DOCX…

Поэтому я думаю, что эту проблему вообще можно пометить как «решаемую».