Как открыть и управлять документом / шаблоном Word в Java? - PullRequest
16 голосов
/ 21 февраля 2012

Мне нужно открыть документ .doc/.dot/.docx/.dotx (я не привередливый, я просто хочу, чтобы он работал), разобрать его для заполнителей (или что-то подобное), поставить свои данные, и затем верните сгенерированный .doc/.docx/.dotx/.pdf документ.

И вдобавок ко всему, мне нужны инструменты, чтобы сделать это бесплатно.

Я искал что-то, что соответствовало бы моим потребностям, но я ничего не могу найти. Такие инструменты, как Docmosis, Javadocx, Aspose и т. Д. Являются коммерческими. Из того, что я прочитал, Apache POI далеко не успешно реализовал это (у них в настоящее время нет официального разработчика, работающего над частью Word платформы).

Единственное, что может сработать, - это OpenOffice UNO API. Но это довольно большой байт для тех, кто никогда не использовал этот API (как я).

Так что, если я собираюсь прыгнуть в это, мне нужно убедиться, что я на правильном пути.

Может кто-нибудь дать мне совет по этому поводу?

Ответы [ 5 ]

28 голосов
/ 12 апреля 2012

Я знаю, что прошло много времени с тех пор, как я разместил этот вопрос, и я сказал, что я опубликую свое решение, когда я закончу.Итак, вот оно.

Я надеюсь, что когда-нибудь это кому-нибудь поможет.Это полный рабочий класс, и все, что вам нужно сделать, это поместить его в ваше приложение и поместить каталог TEMPLATE_DIRECTORY_ROOT с шаблонами .docx в ваш корневой каталог.

Использование очень просто.Вы помещаете заполнители (ключ) в файл .docx, а затем передаете имя файла и карту, содержащую соответствующие пары ключ-значение для этого файла.

Наслаждайтесь!

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.URI;
import java.util.Deque;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.UUID;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;

import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.servlet.http.HttpServletResponse;

public class DocxManipulator {

    private static final String MAIN_DOCUMENT_PATH = "word/document.xml";
    private static final String TEMPLATE_DIRECTORY_ROOT = "TEMPLATES_DIRECTORY/";


    /*    PUBLIC METHODS    */

    /**
     * Generates .docx document from given template and the substitution data
     * 
     * @param templateName
     *            Template data
     * @param substitutionData
     *            Hash map with the set of key-value pairs that represent
     *            substitution data
     * @return
     */
    public static Boolean generateAndSendDocx(String templateName, Map<String,String> substitutionData) {

        String templateLocation = TEMPLATE_DIRECTORY_ROOT + templateName;

        String userTempDir = UUID.randomUUID().toString();
        userTempDir = TEMPLATE_DIRECTORY_ROOT + userTempDir + "/";

        try {

            // Unzip .docx file
            unzip(new File(templateLocation), new File(userTempDir));       

            // Change data
            changeData(new File(userTempDir + MAIN_DOCUMENT_PATH), substitutionData);

            // Rezip .docx file
            zip(new File(userTempDir), new File(userTempDir + templateName));

            // Send HTTP response
            sendDOCXResponse(new File(userTempDir + templateName), templateName);

            // Clean temp data
            deleteTempData(new File(userTempDir));
        } 
        catch (IOException ioe) {
            System.out.println(ioe.getMessage());
            return false;
        }

        return true;
    }


    /*    PRIVATE METHODS    */

    /**
     * Unzipps specified ZIP file to specified directory
     * 
     * @param zipfile
     *            Source ZIP file
     * @param directory
     *            Destination directory
     * @throws IOException
     */
    private static void unzip(File zipfile, File directory) throws IOException {

        ZipFile zfile = new ZipFile(zipfile);
        Enumeration<? extends ZipEntry> entries = zfile.entries();

        while (entries.hasMoreElements()) {
          ZipEntry entry = entries.nextElement();
          File file = new File(directory, entry.getName());
          if (entry.isDirectory()) {
            file.mkdirs();
          } 
          else {
            file.getParentFile().mkdirs();
            InputStream in = zfile.getInputStream(entry);
            try {
              copy(in, file);
            } 
            finally {
              in.close();
            }
          }
        }
      }


    /**
     * Substitutes keys found in target file with corresponding data
     * 
     * @param targetFile
     *            Target file
     * @param substitutionData
     *            Map of key-value pairs of data
     * @throws IOException
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    private static void changeData(File targetFile, Map<String,String> substitutionData) throws IOException{

        BufferedReader br = null;
        String docxTemplate = "";
        try {
            br = new BufferedReader(new InputStreamReader(new FileInputStream(targetFile), "UTF-8"));
            String temp;
            while( (temp = br.readLine()) != null)
                docxTemplate = docxTemplate + temp; 
            br.close();
            targetFile.delete();
        } 
        catch (IOException e) {
            br.close();
            throw e;
        }

        Iterator substitutionDataIterator = substitutionData.entrySet().iterator();
        while(substitutionDataIterator.hasNext()){
            Map.Entry<String,String> pair = (Map.Entry<String,String>)substitutionDataIterator.next();
            if(docxTemplate.contains(pair.getKey())){
                if(pair.getValue() != null)
                    docxTemplate = docxTemplate.replace(pair.getKey(), pair.getValue());
                else
                    docxTemplate = docxTemplate.replace(pair.getKey(), "NEDOSTAJE");
            }
        }

        FileOutputStream fos = null;
        try{
            fos = new FileOutputStream(targetFile);
            fos.write(docxTemplate.getBytes("UTF-8"));
            fos.close();
        }
        catch (IOException e) {
            fos.close();
            throw e;
        }
    }

    /**
     * Zipps specified directory and all its subdirectories
     * 
     * @param directory
     *            Specified directory
     * @param zipfile
     *            Output ZIP file name
     * @throws IOException
     */
    private static void zip(File directory, File zipfile) throws IOException {

        URI base = directory.toURI();
        Deque<File> queue = new LinkedList<File>();
        queue.push(directory);
        OutputStream out = new FileOutputStream(zipfile);
        Closeable res = out;

        try {
          ZipOutputStream zout = new ZipOutputStream(out);
          res = zout;
          while (!queue.isEmpty()) {
            directory = queue.pop();
            for (File kid : directory.listFiles()) {
              String name = base.relativize(kid.toURI()).getPath();
              if (kid.isDirectory()) {
                queue.push(kid);
                name = name.endsWith("/") ? name : name + "/";
                zout.putNextEntry(new ZipEntry(name));
              } 
              else {
                if(kid.getName().contains(".docx"))
                    continue;  
                zout.putNextEntry(new ZipEntry(name));
                copy(kid, zout);
                zout.closeEntry();
              }
            }
          }
        } 
        finally {
          res.close();
        }
      }

    /**
     * Sends HTTP Response containing .docx file to Client
     * 
     * @param generatedFile
     *            Path to generated .docx file
     * @param fileName
     *            File name of generated file that is being presented to user
     * @throws IOException
     */
    private static void sendDOCXResponse(File generatedFile, String fileName) throws IOException {

        FacesContext facesContext = FacesContext.getCurrentInstance();
        ExternalContext externalContext = facesContext.getExternalContext();
        HttpServletResponse response = (HttpServletResponse) externalContext
                .getResponse();

        BufferedInputStream input = null;
        BufferedOutputStream output = null;

        response.reset();
        response.setHeader("Content-Type", "application/msword");
        response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
        response.setHeader("Content-Length",String.valueOf(generatedFile.length()));

        input = new BufferedInputStream(new FileInputStream(generatedFile), 10240);
        output = new BufferedOutputStream(response.getOutputStream(), 10240);

        byte[] buffer = new byte[10240];
        for (int length; (length = input.read(buffer)) > 0;) {
            output.write(buffer, 0, length);
        }

        output.flush();
        input.close();
        output.close();

        // Inform JSF not to proceed with rest of life cycle
        facesContext.responseComplete();
    }


    /**
     * Deletes directory and all its subdirectories
     * 
     * @param file
     *            Specified directory
     * @throws IOException
     */
    public static void deleteTempData(File file) throws IOException {

        if (file.isDirectory()) {

            // directory is empty, then delete it
            if (file.list().length == 0)
                file.delete();
            else {
                // list all the directory contents
                String files[] = file.list();

                for (String temp : files) {
                    // construct the file structure
                    File fileDelete = new File(file, temp);
                    // recursive delete
                    deleteTempData(fileDelete);
                }

                // check the directory again, if empty then delete it
                if (file.list().length == 0)
                    file.delete();
            }
        } else {
            // if file, then delete it
            file.delete();
        }
    }

    private static void copy(InputStream in, OutputStream out) throws IOException {

        byte[] buffer = new byte[1024];
        while (true) {
          int readCount = in.read(buffer);
          if (readCount < 0) {
            break;
          }
          out.write(buffer, 0, readCount);
        }
      }

      private static void copy(File file, OutputStream out) throws IOException {
        InputStream in = new FileInputStream(file);
        try {
          copy(in, out);
        } finally {
          in.close();
        }
      }

      private static void copy(InputStream in, File file) throws IOException {
        OutputStream out = new FileOutputStream(file);
        try {
          copy(in, out);
        } finally {
          out.close();
        }
     }

}
5 голосов
/ 21 февраля 2012

Поскольку docx-файл представляет собой просто zip-архив xml-файлов (плюс любые бинарные файлы для встроенных объектов, таких как изображения), мы выполнили это требование, распаковав zip-файл и направив document.xml в механизм шаблонов (мыиспользовал freemarker ), который выполняет слияние для нас, а затем архивирует выходной документ, чтобы получить новый файл docx.

Документ шаблона тогда представляет собой просто обычный docx со встроенными выражениями / директивами freemarkerи может быть отредактировано в Word.

Поскольку (не) архивирование может быть выполнено с помощью JDK, а Freemarker имеет открытый исходный код, вы не несете никаких лицензионных платежей, даже за само слово.

Ограничение состоит в том, что этот подход может создавать только файлы docx или rtf, а выходной документ будет иметь тот же тип файла, что и шаблон.Если вам нужно преобразовать документ в другой формат (например, pdf), вам придется решить эту проблему отдельно.

3 голосов
/ 07 августа 2015

В конечном итоге я полагался на Apache Poi 3.12 и обрабатывал абзацы (отдельно извлекал абзацы из таблиц, верхних и нижних колонтитулов и сносок, так как такие абзацы не возвращались XWPFDocument.getParagraphs () ).

Код обработки ( ~ 100 строк ) и юнит-тесты здесь на github .

0 голосов
/ 06 февраля 2017

Недавно я столкнулся с подобной проблемой: «Инструмент, который принимает файл шаблона .docx, обрабатывает файл путем оценки переданного контекста параметра и выводит файл« .docx »как результат процесса.»

наконец Бог принес нам scriptlet4dox :).Ключевыми функциями этого продукта являются: 1. внедрение кода в виде скриптов в файле шаблона (внедрение параметров и т. д.) 2. циклическое переключение элементов коллекции в таблице

и многие другие функции.но, как я проверил, последний коммит в проекте выполняется около года назад, поэтому есть вероятность, что проект не будет поддерживаться новыми функциями и новыми исправлениями ошибок.это ваш выбор, использовать его или нет.

0 голосов
/ 21 февраля 2012

Я был в более или менее такой же ситуации, как и вы, мне пришлось изменить целую кучу шаблонов слияния MS Word одновременно. После долгих поисков в Google, чтобы найти решение Java, я наконец установил Visual Studio 2010 Express, которая бесплатна, и выполнил эту работу на C #.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...