1. 程式人生 > >對現有docx/pdf檔案轉pdf,並新增超連結

對現有docx/pdf檔案轉pdf,並新增超連結

需求:上傳一個檔案(可能關聯絡統中已有的公文),要求能自動識別並替換為超連結,顯示在網頁中。

目標:以PDF的形式進行展示,儘可能支援更多的檔案種類。

定義:以[公文名]的形式定義替換

實現:暫支援docx,對pdf支援不太友好,doc就涼涼

首先,我們先獲取我們需要處理的物件(檔案的上傳以及型別的限制就不多說了):

String fileInPath = "這裡應該是前臺上傳檔案的路徑";

String fileOutPath = "這裡應該是最終存放檔案的路徑";

//這是我們需要替換的公文及其連結鍵值對

Map<String, String> gwMap = new HashMap<String, String>();
for (GwFileInfo gwFileInfo : gwFileDaoS.getNormalGwFileList()) {
      gwMap.put(gwFileInfo.getFile_name(), gwFileInfo.getOponer_path());

}

其次,判定檔案型別,進行操作

pdf如下

FileInputStream is = null;
File file = new File(fileInPath);
file.getParentFile().mkdirs();
PdfReader reader;
try {
    reader = new PdfReader(fileInPath);
    PdfStamper stamper;
    stamper = new PdfStamper(reader, new FileOutputStream(fileOutPath));
    List<float[]> floatList = getKeyWords(fileInPath);//獲取所有出現檔案的座標
    for (int i = 0; i < floatList.size(); i++) {
        System.err.println(floatList.get(i)[0]);
        System.err.println(floatList.get(i)[1]);
        System.err.println(floatList.get(i)[2]);
        System.err.println(floatList.get(i)[3]);
        PdfContentByte canvas = stamper.getOverContent((int) floatList.get(i)[4]);//當前所處頁及層級
        canvas.saveState();
        canvas.setColorFill(BaseColor.WHITE);//設定覆蓋層北京顏色
        canvas.rectangle(floatList.get(i)[0], floatList.get(i)[1]-2, floatList.get(i)[3], floatList.get(i)[2]);//覆蓋層,-2是圖形偏移量
        canvas.fill();
        canvas.restoreState();
        canvas.beginText(); //開始在覆蓋層寫公文名字(應該在floatList中去除,沒有做)以及超連結樣式,這邊缺失下劃線需要補上
        BaseFont bf = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.EMBEDDED);
        Font font = new Font(bf, 12, Font.BOLD);
        font.setColor(BaseColor.RED);
        //設定字型和大小
        canvas.setFontAndSize(bf, 12);
        canvas.setColorFill(BaseColor.BLUE);
        //設定字型的輸出位置
        canvas.setTextMatrix(floatList.get(i)[0], floatList.get(i)[1]);
        //要輸出的text
        canvas.showText("多退少補" );  
        canvas.endText();
        PdfContentByte overContent = stamper.getUnderContent(1);//設定超連結層級
        Rectangle rectangle = new Rectangle(floatList.get(i)[0], floatList.get(i)[1], floatList.get(i)[0]+floatList.get(i)[3], floatList.get(i)[1]+floatList.get(i)[2]);//設定超連結區域(和上面的不一樣哦)
        overContent.rectangle(rectangle);
        PdfAnnotation annotation = PdfAnnotation.createLink(
        stamper.getWriter(), rectangle, PdfAnnotation.HIGHLIGHT_INVERT,
        new PdfAction("http://itextpdf.com/")
    );
    stamper.addAnnotation(annotation, (int) floatList.get(i)[4]);
    }
    stamper.close();
    reader.close();
}catch (IOException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
} catch (DocumentException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
}

//返回關鍵字所在的座標和頁數,注意pdf左邊取值是從左下角開始

    private static List<float[]> getKeyWords(String filePath){
        final List<float[]> arrays = new ArrayList<float[]>();
        PdfReader pdfReader;
        try{
            pdfReader = new PdfReader(filePath);
            int pageNum = pdfReader.getNumberOfPages();
            PdfReaderContentParser pdfReaderContentParser = new PdfReaderContentParser(pdfReader);
 
            // 下標從1開始
            for (int i = 1; i <= pageNum; i++) {
                final int finalI = i;
                pdfReaderContentParser.processContent(i, new RenderListener() {
                    //此方法是監聽PDF裡的文字內容,有重複情況會都把座標和頁碼資訊都存入arrays裡
                    @Override
                    public void renderText(TextRenderInfo textRenderInfo) {
                        String text = textRenderInfo.getText(); // 整頁內容
                        System.err.println(textRenderInfo.getText());
                        if (null != text && text.contains("費用管理暫行辦法")) {
                            Rectangle2D.Float boundingRectange = textRenderInfo
                                    .getBaseline().getBoundingRectange();
                            float [] resu = new float[5];
                            System.out.println("======="+text);
                            System.out.println("h:"+boundingRectange.getHeight());
                            System.out.println("w:"+boundingRectange.width);
                            System.out.println("centerX:"+boundingRectange.getCenterX());
                            System.out.println("centerY:"+boundingRectange.getCenterY());
                            System.out.println("x:"+boundingRectange.getX());
                            System.out.println("y:"+boundingRectange.getY());
                            System.out.println("maxX:"+boundingRectange.getMaxX());
                            System.out.println("maxY:"+boundingRectange.getMaxY());
                            System.out.println("minX:"+boundingRectange.getMinX());
                            System.out.println("minY:"+boundingRectange.getMinY());
                            resu[0] = (float)boundingRectange.getX();
                            resu[1] = (float)boundingRectange.getY();
                            resu[2] = (float)(boundingRectange.getHeight() == 0 ? 12 : boundingRectange.getHeight());
                            resu[3] = (float)boundingRectange.width;
                            resu[4] = finalI;
                            arrays.add(resu);
                        }
                    }
                    
                    @Override
                    public void renderImage(ImageRenderInfo arg0)
                    {
                    }
 
                    @Override
                    public void endTextBlock()
                    {
 
                    }
 
                    @Override
                    public void beginTextBlock()
                    {
                    }
                });
            }
        } catch (IOException e){
            e.printStackTrace();
        }
        return arrays;
    }

小結:無法識別pdf中文字大小,只能用resu[2] = (float)(boundingRectange.getHeight() == 0 ? 12 : boundingRectange.getHeight());來設定預設高度,無法識別pdf背景顏色,預設使用的white來設計,所以看上去可能還是不錯的,但不具備通用性。這邊的公文什麼的迴圈比較也沒做,需要使用的話需要自行修改部分程式碼!

docx如下

關鍵點:文字替換,增加超連結,docx轉pdf

          try {
                example(fileInPath,gwMap);
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            doc2pdf(fileInPath, fileOutPath);

public void example(String inFile,Map<String, String> gwMap) throws Exception {
        InputStream is = new FileInputStream(inFile);
        XWPFDocument document = new XWPFDocument(OPCPackage.open(is));
        this.replaceInPara(document, gwMap);
        document.write(new FileOutputStream(inFile));

    }

private void replaceInPara(XWPFDocument doc, Map<String, String> params) {
        Iterator<XWPFParagraph> iterator = doc.getParagraphsIterator();
        XWPFParagraph para;
        while (iterator.hasNext()) {
            para = iterator.next();
            this.replaceInPara(para, params);
        }
    }

    //文字

    private void replaceInPara(XWPFParagraph para, Map<String, String> params) {
        List<XWPFRun> runs;
        Matcher matcher;
        String link = null;
        if (this.matcher(para.getParagraphText()).find()) {
            runs = para.getRuns();
            for (int i=0; i<runs.size(); i++) {
                XWPFRun run = runs.get(i);
                String runText = run.toString();
                matcher = this.matcher(runText);
                if (matcher.find()) {
                    System.err.println(matcher);
                    System.err.println((matcher = this.matcher(runText)).find());
//下面這個迭代替換會造成死迴圈,我也不是太清楚
//                    while ((matcher = this.matcher(runText)).find()) {
//                        System.err.println(runText.substring(1, runText.length()-1));
//                        System.out.println(params.get(runText.substring(1, runText.length()-1)));
//                        link = matcher.replaceFirst(String.valueOf(params.get(matcher.group(1))));
//                    }
                    link = matcher.replaceFirst(String.valueOf(params.get(matcher.group(1))));
                    //直接呼叫XWPFRun的setText()方法設定文字時,在底層會重新建立一個XWPFRun,把文字附加在當前文字後面,  
                    //所以我們不能直接設值,需要先刪除當前run,然後再自己手動插入一個新的run。
                    if(params.get(matcher.group(1))!=null){
                        para.removeRun(i);
                        appendExternalHyperlink("../../"+link, runText.substring(1, runText.length()-1), para);
                    }
//                    para.insertNewRun(i).setText(runText.substring(1, runText.length()-1));
                }
            }
        }
    }

    private Matcher matcher(String str) {
        Pattern pattern = Pattern.compile("\\[([^\\]]+)\\]", Pattern.CASE_INSENSITIVE);
        Matcher matcher = pattern.matcher(str);
        return matcher;
    }

    //新增超連結   

    public static void appendExternalHyperlink(String url, String text,XWPFParagraph paragraph) {

        // Add the link as External relationship
        String id = paragraph.getDocument().getPackagePart().addExternalRelationship(url,XWPFRelation.HYPERLINK.getRelation()).getId();
        // Append the link and bind it to the relationship
        CTHyperlink cLink = paragraph.getCTP().addNewHyperlink();
        cLink.setId(id);
        
        // Create the linked text
        CTText ctText = CTText.Factory.newInstance();
        ctText.setStringValue(text);
        CTR ctr = CTR.Factory.newInstance();
        CTRPr rpr = ctr.addNewRPr();
        
        //設定超連結樣式
        CTColor color = CTColor.Factory.newInstance();
        color.setVal("0000FF");
        rpr.setColor(color);
        rpr.addNewU().setVal(STUnderline.SINGLE);
        
//        //設定字型
//        CTFonts fonts = rpr.isSetRFonts() ? rpr.getRFonts() : rpr.addNewRFonts();
//        fonts.setAscii("微軟雅黑");
//        fonts.setEastAsia("微軟雅黑");
//        fonts.setHAnsi("微軟雅黑");

//        //設定字型大小
//        CTHpsMeasure sz = rpr.isSetSz() ? rpr.getSz() : rpr.addNewSz();
//        sz.setVal(new BigInteger("24"));

        ctr.setTArray(new CTText[] { ctText });
        // Insert the linked text into the link
        cLink.setRArray(new CTR[] { ctr });
        
//        //設定段落居中
//        paragraph.setAlignment(ParagraphAlignment.CENTER);
//        paragraph.setVerticalAlignment(TextAlignment.CENTER);
    }

    public void doc2pdf(String inPath, String outPath) {
        if (!getLicense()) { // 驗證License 若不驗證則轉化出的pdf文件會有水印產生
            return;
        }
        try {  
            long old = System.currentTimeMillis();
            File file = new File(outPath); // 新建一個空白pdf文件
            FileOutputStream os = new FileOutputStream(file);
            Document doc = new Document(inPath); // Address是將要被轉化的word文件
            doc.save(os, SaveFormat.PDF);// 全面支援DOC, DOCX, OOXML, RTF HTML, OpenDocument, PDF,
                                         // EPUB, XPS, SWF 相互轉換
            long now = System.currentTimeMillis();
            System.out.println("共耗時:" + ((now - old) / 1000.0) + "秒"); // 轉化用時
            os.close();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

小結:這邊唯一存在的問題是[檔名]在建立的時候要是整體的形式,要不識別出來是

[

公文名

],這樣無法識別替換。

doc我就不贅述了,只做了個文字替換,無法直接新增超連結,其實可以互相轉換之後再實現,這是後話

jar包:我還是列出來吧,這個東西emmm,可能也是相當的坑的!

itextpdf-5.5.13.jar         itext5處理核心

itext-asian-5.2.0.jar       用於支援pdf新增中文

aspose-words-15.8.0-jdk16.jar    doc/docx等檔案轉pdf

還有就是poi有關jar包

一個License

<License>
  <Data>
    <Products>
      <Product>Aspose.Total for Java</Product>
      <Product>Aspose.Words for Java</Product>
    </Products>
    <EditionType>Enterprise</EditionType>
    <SubscriptionExpiry>20991231</SubscriptionExpiry>
    <LicenseExpiry>20991231</LicenseExpiry>
    <SerialNumber>8bfe198c-7f0c-4ef8-8ff0-acc3237bf0d7</SerialNumber>
  </Data>
  <Signature>sNLLKGMUdF0r8O1kKilWAGdgfs2BvJb/2Xp8p5iuDVfZXmhppo+d0Ran1P9TKdjV4ABwAgKXxJ3jcQTqE/2IRfqwnPf8itN8aFZlV3TJPYeD3yWE7IT55Gz6EijUpC7aKeoohTb4w2fpox58wWoF3SNp6sK6jDfiAUGEHYJ9pjU=</Signature>
</License>

總結:這個功能不能算完成了吧,但是也做的7788的了,參考了很多網上的資料,然後組合起來,也是挺不容易的,也看到了很多提一樣的問題的人,好像很多都沒有一個好的解決方案。查資料的時候看見一句簽名,不僅是索取者,也是分享者,與大家共勉。

題外話:然後又有新需求啦,把一個文件拆成好幾個文件,emmm,只知道用docx4j.jar,還需要慢慢研究,今天就到這裡了!