2012-02-21 32 views
8

Tôi cần mở tài liệu .doc/.dot/.docx/.dotx (Tôi không cầu kỳ, tôi chỉ muốn nó hoạt động), phân tích cú pháp cho trình giữ chỗ (hoặc nội dung tương tự), đặt dữ liệu của riêng tôi, và sau đó trả về tài liệu .doc/.docx/.pdf.Làm thế nào để mở và thao tác tài liệu Word/mẫu trong Java?

Và trên hết, tôi cần các công cụ để hoàn thành miễn phí.

Tôi đã tìm kiếm một thứ gì đó có thể làm hài lòng nhu cầu của tôi, nhưng tôi không thể tìm thấy bất kỳ thứ gì. Các công cụ như Docmosis, Javadocx, Aspose vv là thương mại. Từ những gì tôi đã đọc Apache POI là hư không gần thành công thực hiện điều này (họ hiện không có bất kỳ nhà phát triển chính thức làm việc trên phần Word của khuôn khổ).

Điều duy nhất trông có thể làm là lừa OpenOffice UNO API. Nhưng đó là một byte khá lớn cho một người chưa bao giờ sử dụng API này (như tôi).

Vì vậy, nếu tôi định chuyển sang điều này, tôi cần đảm bảo rằng tôi đang đi đúng hướng.

Ai đó có thể cho tôi một số lời khuyên về điều này không?

+0

¿Nếu đó chỉ là vấn đề thay thế một số trình giữ chỗ, tại sao lại là Java? – Alfabravo

+1

Bạn đã thử docx4j chưa? – JasonPlutext

+0

Java vì đây chỉ là một phần nhỏ của một dự án thực sự lớn. Tôi sẽ đi với docx4j. – kensvebary

Trả lời

23

Tôi biết đã lâu rồi tôi mới đăng câu hỏi này và tôi đã nói rằng tôi sẽ đăng giải pháp của mình khi hoàn thành. Vì vậy, ở đây nó được.

Tôi hy vọng rằng nó sẽ giúp ai đó một ngày nào đó. Đây là một lớp học đầy đủ, và tất cả những gì bạn phải làm là đặt nó vào ứng dụng của bạn, và đặt thư mục TEMPLATE_DIRECTORY_ROOT với các mẫu .docx trong thư mục gốc của bạn.

Cách sử dụng rất đơn giản. Bạn đặt phần giữ chỗ (khóa) trong tệp .docx, rồi chuyển tên tệp và Bản đồ chứa các cặp khóa-giá trị tương ứng cho tệp đó.

Tận hưởng!

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(); 
     } 
    } 

} 
+1

Cảm ơn, @kensvebary. Đáng buồn thay, cách tiếp cận này sẽ không hoạt động đáng tin cậy. Word chèn tất cả các loại thẻ bổ sung ở giữa các từ, khiến việc tìm kiếm trình giữ chỗ trở nên khó khăn. Giả sử bạn có trình giữ chỗ có tên %% employeeId %%. Từ có thể (hoặc có thể không) hiển thị nó như vậy: '%% employeeId% ' cùng sẽ xảy ra với RTF. – PeterToTheThird

+0

Tôi đã thử nó và nó hoạt động tuyệt vời. Để thay thế nội dung, người dùng có thể sử dụng [[ReplaceContent]]. Điều này hoạt động tốt –

+0

Đó là một khởi đầu tốt, mặc dù nó có thể không phải là rất mạnh mẽ.Ngay cả khi bạn sử dụng trình giữ chỗ đơn giản, tôi tự hỏi điều gì xảy ra ở cuối dòng: Tôi đoán Word đang mã hóa trả về, dấu gạch ngang ... với thẻ xml có thể phá vỡ chuỗi thay thế? – xtof54

0

Tôi đã ở nhiều hơn hoặc ít hơn tình huống tương tự như bạn, tôi đã phải sửa đổi toàn bộ một loạt các mẫu MS Word hợp nhất cùng một lúc. Sau khi đã googled rất nhiều để cố gắng tìm một giải pháp Java cuối cùng tôi đã cài đặt Visual Studio 2010 Express đó là miễn phí và đã làm công việc trong C#.

+0

Cảm ơn các bạn đã giúp đỡ! Bạn đã cho tôi một số hướng dẫn. Tôi nghĩ tôi sẽ bắt đầu với thư viện docx4j. Và thêm một số mã tùy chỉnh nếu cần. Tôi sẽ đăng giải pháp khi hoàn tất. – kensvebary

+0

@kensvebary làm thế nào là giải pháp của bạn ở cuối? Tôi cần giải pháp tương tự như của bạn, hãy cho tôi một phản hồi như bạn thấy. Cảm ơn trước! – malajisi

4

Vì tệp docx chỉ đơn thuần là tệp nén xml (cộng với bất kỳ tệp nhị phân nào cho các đối tượng nhúng như hình ảnh), chúng tôi đã đáp ứng yêu cầu đó bằng cách giải nén tệp zip, cấp tệp document.xml cho một công cụ mẫu. chúng tôi đã sử dụng freemarker) thực hiện việc hợp nhất cho chúng tôi và sau đó nén tài liệu đầu ra để nhận tệp docx mới.

Tài liệu mẫu sau đó chỉ đơn giản là một docx thông thường với các biểu thức/chỉ thị freemarker được nhúng và có thể được chỉnh sửa trong Word.

Vì (bỏ) nén có thể được thực hiện với JDK, và Freemarker là mã nguồn mở, bạn không phải chịu bất kỳ khoản phí giấy phép nào, thậm chí không phải cho chính từ đó.

Giới hạn là cách tiếp cận này chỉ có thể phát ra tệp docx hoặc rtf và tài liệu đầu ra sẽ có cùng một loại tệp như mẫu. Nếu bạn cần chuyển đổi tài liệu sang định dạng khác (chẳng hạn như pdf), bạn sẽ phải giải quyết vấn đề đó một cách riêng biệt.

+0

Một hạn chế khác là bạn không thể làm bất cứ điều gì tạo mối quan hệ một phần (ví dụ hình ảnh, siêu liên kết) – JasonPlutext

+0

Có, nếu bạn cần chèn hình ảnh động, bạn sẽ phải phát triển thẻ freemarker để chèn tham chiếu hình ảnh và bao gồm hình ảnh dữ liệu trong zip. Off-hand, tôi không thấy vấn đề với siêu liên kết. – meriton

3

Tôi đã kết thúc dựa vào Apache Poi 3.12 và xử lý các đoạn văn (trích xuất riêng các đoạn văn cũng từ các bảng, đầu trang/chân trang và chú thích, vì các đoạn văn đó không được trả về bởi XWPFDocument.getParagraphs()).

Mã xử lý (~100 lines) và kiểm tra đơn vị là here on github.

0

Gần đây, tôi đã xử lý vấn đề tương tự: "Công cụ chấp nhận tệp '.docx' mẫu, xử lý tệp bằng cách đánh giá ngữ cảnh tham số đã truyền và xuất tệp '.docx' là kết quả của quá trình. "

cuối cùng thần đã mang lại cho chúng tôi scriptlet4dox :). các tính năng quan trọng đối với sản phẩm này là: 1. Mã tiêm groovy như kịch bản trong tập tin mẫu (tiêm tham số, vv) 2. lặp trên mục bộ sưu tập trong bảng

và rất nhiều tính năng khác. nhưng khi tôi kiểm tra cam kết cuối cùng về dự án được thực hiện khoảng một năm trước, do đó, có khả năng dự án không được hỗ trợ cho các tính năng mới và sửa lỗi mới. đây là sự lựa chọn của bạn để sử dụng nó hay không.

Các vấn đề liên quan