2012-02-21 2 views
8

मैं एक .doc/.dot/.docx/.dotx (मैं picky नहीं हूँ, मैं सिर्फ यह काम करना चाहता हूँ) दस्तावेज़ को खोलने के प्लेसहोल्डर (या कुछ इसी तरह) के लिए यह पार्स की जरूरत है, मेरे अपने डेटा, रखा और फिर .doc/.docx/.pdf दस्तावेज़ उत्पन्न वापसी।जावा में वर्ड दस्तावेज़/टेम्पलेट को कैसे खोलें और कुशलतापूर्वक उपयोग करें?

और उन सभी के शीर्ष पर, मुझे इसे मुक्त करने के लिए टूल की आवश्यकता है।

मैंने अपनी जरूरतों को पूरा करने वाली किसी चीज़ के लिए खोज की है, लेकिन मुझे कुछ भी नहीं मिला। डॉक्मोसिस, जावाडोकैक्स, एस्पेप इत्यादि जैसे उपकरण वाणिज्यिक हैं। जो मैंने पढ़ा है उससे अपाचे पीओआई सफलतापूर्वक इसे कार्यान्वित करने के करीब कहीं नहीं है (वर्तमान में उनके पास फ्रेमवर्क के वर्ड भाग पर काम करने वाला कोई आधिकारिक डेवलपर नहीं है)।

एकमात्र चीज जो दिखती है कि चाल चल सकती है ओपनऑफिस यूएनओ एपीआई है। लेकिन यह किसी ऐसे व्यक्ति के लिए एक बहुत बड़ा बाइट है जिसने कभी भी इस एपीआई (मेरे जैसे) का उपयोग नहीं किया है।

तो अगर मैं इसमें कूदने जा रहा हूं, तो मुझे यह सुनिश्चित करना होगा कि मैं सही रास्ते पर हूं।

क्या कोई मुझे इस पर कुछ सलाह दे सकता है?

+0

¿यदि यह कुछ प्लेसहोल्डर्स को बदलने का मामला है, तो जावा क्यों? – Alfabravo

+1

क्या आपने docx4j को आजमाया था? – JasonPlutext

+0

जावा क्योंकि यह वास्तव में एक बड़ी परियोजना का एक छोटा सा हिस्सा है। मैं docx4j के साथ जाऊंगा। – kensvebary

उत्तर

23

मुझे पता है कि यह एक लंबा समय रहा है क्योंकि मैंने यह प्रश्न पोस्ट किया है, और मैंने कहा कि जब मैं समाप्त कर दूंगा तो मैं अपना समाधान पोस्ट करूंगा। तो यहां यह है।

मुझे उम्मीद है कि यह किसी दिन किसी की मदद करेगा। यह एक पूर्ण मजदूर वर्ग है, और आपको बस इतना करना है कि इसे अपने आवेदन में रखा जाए, और अपनी रूट निर्देशिका में .docx टेम्पलेट्स के साथ TEMPLATE_DIRECTORY_ROOT निर्देशिका रखें।

उपयोग बहुत आसान है। आप अपनी .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(); 
     } 
    } 

} 
+1

धन्यवाद, @kensvebary। अफसोस की बात है, यह दृष्टिकोण भरोसेमंद काम नहीं करेगा। शब्द शब्दों के बीच में टैग के सभी प्रकारों को इंजेक्ट करता है, जिससे प्लेसहोल्डर्स को ढूंढना मुश्किल हो जाता है। मान लें कि आपके पास %% कर्मचारी आईडी %% नामक प्लेसहोल्डर है। '%% EmployeeID%' ही आरटीएफ के साथ क्या होगा। – PeterToTheThird

+0

मैंने कोशिश की और यह बहुत अच्छा काम करता है। सामग्री को प्रतिस्थापित करने के लिए कोई [[ReplaceContent]] का उपयोग कर सकता है। यह ठीक काम करता है –

+0

यह एक अच्छी शुरुआत है, भले ही यह बहुत मजबूत न हो।यहां तक ​​कि जब आप साधारण प्लेसहोल्डर्स का उपयोग करते हैं, तो मुझे आश्चर्य होता है कि अंत में क्या होता है: मुझे लगता है कि वर्ड कैरिज रिटर्न कोडिंग कर रहा है, हाइफ़न ... एक्सएमएल टैग के साथ जो प्रतिस्थापन स्ट्रिंग को तोड़ सकता है? – xtof54

0

मैं आपके जैसी स्थिति में कम या ज्यादा कम रहा हूं, मुझे एक बार में एमएस वर्ड मर्ज टेम्पलेट्स का एक पूरा समूह संशोधित करना पड़ा। जावा समाधान खोजने की कोशिश करने के लिए बहुत कुछ करने के बाद मैंने आखिरकार विजुअल स्टूडियो 2010 एक्सप्रेस स्थापित किया जो मुफ्त है और सी # में काम किया।

+0

मदद लोगों के लिए धन्यवाद! आपने मुझे कुछ गाइडलाइन दी हैं। मुझे लगता है कि मैं docx4j लाइब्रेरी से शुरू करूंगा। और जहां आवश्यक हो वहां कुछ कस्टम कोड जोड़ें। जब मैं समाप्त करता हूं तो मैं समाधान पोस्ट करूंगा। – kensvebary

+0

@kensvebary अंत में आपका समाधान कैसा है? मुझे आपके जैसा ही समाधान चाहिए, कृपया मुझे एक प्रतिक्रिया दें जैसा आप देखते हैं। अग्रिम धन्यवाद! – malajisi

4

चूंकि एक डॉक्स फ़ाइल केवल एक्सएमएल फाइलों का एक ज़िप-संग्रह है (साथ ही छवियों जैसे एम्बेडेड ऑब्जेक्ट्स के लिए कोई बाइनरी फाइल), हम ज़िप फ़ाइल को अनपॅक करके उस आवश्यकता को पूरा करते हैं, document.xml को टेम्पलेट इंजन में खिलाते हैं (हमने freemarker का उपयोग किया) जो हमारे लिए विलय करता है, और उसके बाद आउटपुट दस्तावेज़ को नई डॉक्स फ़ाइल प्राप्त करने के लिए ज़िप करता है।

टेम्पलेट दस्तावेज़ तब एम्बेडेड फ्रीमार्कर अभिव्यक्ति/निर्देशों के साथ एक साधारण डॉक्स है, और इसे Word में संपादित किया जा सकता है।

चूंकि (संयुक्त) जेडीके के साथ ज़िपिंग किया जा सकता है, और फ्रीमार्कर ओपन सोर्स है, तो आपको किसी भी लाइसेंस शुल्क नहीं लेते हैं, न कि शब्द के लिए भी।

सीमा यह है कि यह दृष्टिकोण केवल डॉक्क्स या आरटीएफ फाइलों को उत्सर्जित कर सकता है, और आउटपुट दस्तावेज़ में टेम्पलेट के समान फ़ाइल प्रकार होगा। यदि आपको दस्तावेज़ को किसी अन्य प्रारूप (जैसे पीडीएफ) में कनवर्ट करने की आवश्यकता है तो आपको उस समस्या को अलग से हल करना होगा।

+0

एक और सीमा यह है कि आप आसानी से ऐसा कुछ भी नहीं कर सकते जो पार्ट रिलेशनशिप बनाता है (उदाहरण के लिए छवियां, हाइपरलिंक्स) – JasonPlutext

+0

हां, यदि आपको गतिशील छवियां डालने की आवश्यकता है तो आपको छवि संदर्भ डालने के लिए एक फ्रीमार्कर टैग विकसित करना होगा और छवि शामिल करना होगा ज़िप में डेटा। हाथ से, मुझे हाइपरलिंक के साथ समस्या नहीं दिख रही है। – meriton

3

मैं अपाचे पोई 3 पर भरोसा करता हूं।12 और पैराग्राफ प्रसंस्करण (अलग-अलग पैराग्राफ को टेबल, हेडर/पाद लेख, और फुटनोट्स से निकालने के लिए, क्योंकि इस पैराग्राफ को XWPFDocument.getParagraphs() द्वारा वापस नहीं किया जाता है)।

प्रसंस्करण कोड (~100 lines) और इकाई परीक्षण here on github हैं।

0

मैंने हाल ही में इसी तरह की समस्या का सामना किया है: "एक उपकरण जो टेम्पलेट स्वीकार करता है '.docx' फ़ाइल, पास पैरामीटर संदर्भ के मूल्यांकन द्वारा फ़ाइल को संसाधित करता है और प्रक्रिया के परिणामस्वरूप '.docx' फ़ाइल आउटपुट करता है। "

आखिर में भगवान ने हमें scriptlet4dox लाया :)। इस उत्पाद के लिए प्रमुख विशेषताएं है: 1. (आदि पैरामीटर इंजेक्शन,) टेम्पलेट फ़ाइल में स्क्रिप्ट तालिका

और बहुत से अन्य सुविधाओं में संग्रह से अधिक आइटम 2. लूप के रूप में ग्रूवी कोड इंजेक्शन। लेकिन जैसा कि मैंने प्रोजेक्ट पर आखिरी प्रतिबद्धता की जांच की है, एक साल पहले किया जाता है, इसलिए एक संभावना है कि परियोजना नई सुविधाओं और नए बग फिक्स के लिए समर्थित नहीं है। इसका उपयोग करने के लिए यह आपकी पसंद है या नहीं।

संबंधित मुद्दे