橋接Microsoft Word和瀏覽器

jopen 8年前發布 | 22K 次閱讀 Microsoft


在軟件世界中,已經有大量所見即所得的HTML編輯器,絕大多數的編輯器都是用JavaScript及基于JavaScript的庫構建而成。這些編輯器在處理各種與HTML相關的格式化以及生成HTML源數據時運轉良好,但并不具備我們在商業報告中所需要的各種能力。例如,在一個具有典型的審閱/批準生命周期的發布中,創建圖形、圖表、跟蹤變化以及插入注釋是非常實用的。加之,Word天生就是離線工作的。創建新的文檔或編輯現有文檔時,無需網絡連接。而瀏覽器/JavaScript的離線能力仍然有限。考慮到這些能力,有效利用為此目的專門開發的原生應用看起來是更好的解決方案。

Apache Poor Obfuscation Implementation (POI)是一個以加強版面向商業的報告和預覽為目的,讀取MS Word和MS Excel等Microsoft文檔的杰出Java框架。它通過將Word文檔轉化為易讀的HTML格式,提供了增強的數據讀取能力。

</div>

設計

POI庫支持Office Open XML文件格式——OOXML(文字處理和電子表格的一種表示形式)。它包含了用于讀取文檔中各種區塊的API。在將文檔加載到POI內存中時,它會獲取文檔所有的元數據和內容。我們可以通過遍歷文檔中的各個區塊(例如,段落、表格、單元格等。),很容易就讀取到這些信息。不過,僅僅使用POI,我們還無法實現HTML等效元素的生成。

例如,一段帶有背景顏色的文本會被渲染為帶有字體類型、背景顏色等格式化樣式的HTML span元素。我們應該可以根據從POI中讀取到的該文檔區塊的所有相關屬性或樣式,用Simple API for XML (SAX) API創建HTML元素。為了實現讀取低級別文檔區塊的功能,應該有一個訪問者模式風格的API順序地讀取文檔中各個區塊的內容和屬性(格式化風格等)。

Xdocreport是在POI內核和POI-OOXML基礎上用通用的OOXML-SCHEMA構建而成。它會在POI內核的幫助下加載文檔,然后在poi-ooxml和ooxml-schemas的幫助下讀取內容和元數據。由于使用了模式庫,Xdocreport可以很方便地瀏覽文檔中的元素。Xdocreport提供了訪問者風格的API用于讀取文檔中的每個區塊并以HTML格式生成內容。通過擴展該庫,就可以處理各種格式的HTML樣式并且克服對表結構和編號渲染的限制。下面的代碼片段展示了如何使用所見即所得的方法完成這項工作。

橋接Microsoft Word和瀏覽器

關于如何使用擴展的xdocreport控制HTML渲染,請參考下方的體系架構圖

橋接Microsoft Word和瀏覽器

現在,我們開始嘗試將包含各種組件的擴展docx文檔,例如段落、表格、編號以及圖片等,轉換為HTML格式。

Docx到HTML轉換的實現

加載docx文件流用于創建XWPFDocument對象

FileInputStream fstream = new FileInputStream("Example.docx");
XWPFDocument document = new XWPFDocument (fstream);

//創建選項。這些選項用于控制圖片的渲染等,
XHTMLOptions options = XHTMLOptions.create();

// 創建輸出流用于存儲生成的HTML源文件
ByteArrayOutputStream out = new ByteArrayOutputStream();
IContentHandlerFactory factory = DefaultContentHandlerFactory.INSTANCE;
options.setIgnoreStylesIfUnused(false);
XHTMLMapper mapper = new XHTMLMapper (document, factory.create(out, null, options), options);
mapper.start();
out.close(); 

我們可以將輸出流轉化成String對象并創建HTML文件。

String html = new String(out.toByteArray(), “UTF-8”); 

我們可以很容易地將生成的HTML源數據附在servlet響應輸出流當中。

自定義格式化樣式和組件

我們可以擴展xdocreport中的XHTMLMapper類自定義從MS Word轉化而來的默認的組件樣式。我們還可以自定義HTML組件的渲染行為。例如,上標/下標是作為附加在span元素上的CSS樣式生成的。但是,如果某個較早版本的瀏覽器無法理解這些CSS樣式,而只能理解sup和sub標簽該怎么辦?所以,作為渲染的一部分,會強制生成sup/sub HTML標簽,而不是CSS樣式。下面的例子展示了如何重寫visitRun方法以生成sup/sub HTML標簽:

@Override
protected void visitRun(XWPFRun run, boolean pageNumber, String url, Object paragraphContainer) throws Exception {
           boolean isSuper = false;
    boolean isSub = false;
if (rPr.getVertAlign() != null) {
        int align = rPr.getVertAlign().getVal().intValue();
        if (STVerticalAlignRun.INT_SUPERSCRIPT == align) {
            isSuper = true;
        } else if (STVerticalAlignRun.INT_SUBSCRIPT == align) {
            isSub = true;
        }
    }
    .
.
.
if (isSuper || isSub) {
        startElement(isSuper ? "SUP" : "SUB", null);
    }
    .
    .
    .
    if (isSuper || isSub) {
        endElement(isSuper ? "SUP" : "SUB");
    } 

我們還可以自定義組件。例如,默認情況下,所有生成的超鏈接都會直接打開目標,不會在鏈接上附加任何屬性。雖然我們無法將所有的配置都轉化成HTML,我們仍需要支持在新的窗口中打開所有的超鏈接。我們可以通過重寫visitRun方法達成這一目的:

@Override
protected void visitRun(XWPFRun run, boolean pageNumber, String url, Object 
paragraphContainer) throws Exception {
    boolean isUrl = url != null;
    if (isUrl) {
        AttributesImpl hyperlinkAttributes = new AttributesImpl();
        SAXHelper.addAttrValue(hyperlinkAttributes, "href", url);
        SAXHelper.addAttrValue(hyperlinkAttributes, "target", "_blank");
        startElement("a", hyperlinkAttributes);
    }
    .
    .
    .
    if (isUrl) {
        characters(" ");
        endElement("a");
    } 

橋接Microsoft Word和瀏覽器

項目編號到HTML列表的轉換

對于項目編號,對應生成的HTML可以使ul和li元素。不過,并不是所有的MS Word編號類型或樣式都可以轉化成等效的HTML列表項,因為MS Word有自己的渲染能力和圖片或剪貼畫庫。簡單的ul和li HTML元素無法滿足這個要求。這時我們可以利用span元素。第一個span元素中將包含實際的編號字符,第二個包含具體數據。因此,實現基本的項目編號,我們可以重寫“startVisitParagraph”方法。下面的代碼片段展示了如何渲染基本的ASCII字符項目編號(一個小圓點)。

startElement(SPAN_ELEMENT, attributes);
String text = itemContext.getText();
if (StringUtils.isNotEmpty(text)) {
    text = text.replace('\u2020', '\u2022'); //loop to replace all
    text = text + " ";
    SAXHelper.characters(contentHandler, StringEscapeUtils.escapeHtml(text));
}
endElement(SPAN_ELEMENT); 

橋接Microsoft Word和瀏覽器

在HTML中,制表符沒有直接的表示法。我們可以輸出固定數量的空格字符作為制表符的近似替代品。代碼片段如下:

@Override
protected void visitTabs(CTTabs o, Object paragraphContainer) throws Exception {
    if (paragraph != null && o == null) {
        startElement(SPAN_ELEMENT, null);
        characters("    ");//所需添加一定數量的空格
        endElement(SPAN_ELEMENT);
        return;
    }
    super.visitTabs(o, paragraphContainer);
} 

圖片抽取

到現在為止,我們已經將具有格式化樣式的文本數據轉化為HTML源。不過,文檔中可能還有一些內嵌或外鏈一些圖片在其中。這些圖片需要被抽取出來并在瀏覽器中展示。

內嵌圖片

我們可以用默認的附帶XHTMLOptions選項的URIResolver解析內嵌圖片。默認的解析器會掃描“word/media/”下的docx壓縮文件夾,所有的內嵌圖片都存儲在這里。渲染后的HTML圖片代碼如下所示

<img src="word/media/image1.jpeg" width="189pt" height="141pt"/> 

但是,瀏覽器無法理解src的值(源路徑),這會導致圖片無法訪問。我們可以通過自定義的解析器解決這個問題——構建一個URL傳給要加載這個圖片的servlet

final String imgUrl = "/MyImageLoader?imgeId=";
XHTMLOptions options = XHTMLOptions.create().URIResolver(new IURIResolver(){

        @Override
        public String resolve(String uri) {
            if (imgUrl == null)
                return "/no_image.gif"; 
            int ls = uri.lastIndexOf('/');
            if (ls >= 0)
                uri = uri.substring(ls+1);
                return imgUrl+uri;
            }}); 

上述代碼所創建的選項解析并渲染的圖片代碼如下:

<img src="/MyImageLoader/imageId=image1.jpeg" width="189pt" height="141pt"/> 

典型的serclet實現會包含內聯的java注釋:

resp.setContentType("image/jpeg");// 根據文件類型設置正確的內容類型 
ServletOutputStream img = resp.getOutputStream();
InputStream fis = getFileInputStream(); // docx文件輸入流
if(fis != null) {
    String imageId = req.getParameter("imageId");
    XWPFDocument document = new XWPFDocument(fis); // 加載文檔              
    XWPFPictureData pic = document.getPictureDataByID(imageId);// 獲取圖片
    if (pic != null)
        img.write(pic.getData());
} 

實現自定義的圖片解析器的一個主要優點是我們可以控制需要渲染的圖片格式。轉換的HTML在瀏覽器中的呈現效果如下:

橋接Microsoft Word和瀏覽器

外部鏈接圖片

如果需要在多個文檔中復用同一個圖片,外部圖片(通過菜單插入-圖片-文件名(指定圖片URL)并選擇“鏈接到文件”插入到MS Word中)更加實用。這樣我們只有一個該圖片的拷貝而且因為不需要將其內嵌到每個文檔中還能夠節省磁盤空間。這些圖片會被渲染成如url所指定的HTML圖片標簽和源。如果我們要自定義渲染器,如渲染前的防病毒掃描,渲染的圖片是否在圖片格式的白名單中,我們可以重寫XHTMLMapper中的“visitPicture”方法。

// 插入的外部鏈接圖片
String link = picture.getBlipFill().getBlip().getLink();
PackageRelationship rel = 
document.getPackagePart().getRelationships().getRelationshipByID(link);
if (rel != null) {
    String src = rel.getTargetURI().toString(); // 圖片url 

一旦獲取到圖片的URL,我們就可以基于渲染前的執行操作,如掃描、認證、授權以及允許的圖片格式檢查等,決定渲染行為。

下一步研究及限制

我們可以進一步擴展Xdocreport,以支持更多的組件或區塊。MS Word可以展示或保存在不同的布局視圖中(如Web版式視圖、打印視圖等)。對于Web布局,HTML不應該有頁邊界。而且,頁面背景色和頁邊界都可以轉化成等效的HTML元素或格式。不過,仍有一些組件或區塊沒有等效的HTML元素,例如頁碼和多段落分列。

總結

我們已經看到了如何將MS Word用作所見即所得的HTML生成器。MS Word提供了許多功能,包括可用在業務審閱和批準周期中的變更追蹤。因為有可以在客戶端完成編輯工作的原生應用,我們還可以離線工作。這種方式的一大優勢在于通過Java可以很容易控制HTML源碼的生成。從 Github 上可以找到相關的示例源代碼。

關于作者

橋接Microsoft Word和瀏覽器 Prasadu Babu Dandu 是MetricStream公司平臺部門的技術主管,MetricStream是在企業GRC軟件業內領先的公司。其主要工作是基于J2EE技術構建企業級軟件。他是開源社區的狂熱貢獻者。

查看英文原文: Bridging Microsoft Word and the Browser

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!