All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.deepoove.poi.NiceXWPFDocument Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2014-2015 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.deepoove.poi;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.xwpf.usermodel.NumberingWrapper;
import org.apache.poi.xwpf.usermodel.XWPFAbstractNum;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFNum;
import org.apache.poi.xwpf.usermodel.XWPFNumbering;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFPictureData;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import org.apache.poi.xwpf.usermodel.XWPFStyle;
import org.apache.poi.xwpf.usermodel.XWPFStyles;
import org.apache.poi.xwpf.usermodel.XWPFTable;
import org.apache.poi.xwpf.usermodel.XWPFTableCell;
import org.apache.poi.xwpf.usermodel.XWPFTableRow;
import org.apache.xmlbeans.XmlCursor;
import org.apache.xmlbeans.XmlObject;
import org.apache.xmlbeans.XmlOptions;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTAbstractNum;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTBody;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTLvl;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTP;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTbl;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STJc;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STNumberFormat;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STNumberFormat.Enum;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.deepoove.poi.util.TableTools;

/**
 * 对原生poi的扩展
 * 
 * @author Sayi
 * @version 0.0.1
 *
 */
public class NiceXWPFDocument extends XWPFDocument {

    private static Logger logger = LoggerFactory.getLogger(NiceXWPFDocument.class);

    protected List allTables = new ArrayList();

    public NiceXWPFDocument() {
        super();
    }

    public NiceXWPFDocument(InputStream in) throws IOException {
        super(in);
        buildAllTables();
    }

    private void buildAllTables() {
        List tables = this.getTables();
        if (null == tables) return;
        else allTables.addAll(tables);

        List rows = null;
        List cells = null;
        List cellTables = null;
        for (XWPFTable table : tables) {
            rows = table.getRows();
            if (null == rows) continue;
            for (XWPFTableRow row : rows) {
                cells = row.getTableCells();
                if (null == cells) continue;
                for (XWPFTableCell cell : cells) {
                    cellTables = cell.getTables();
                    if (null != cellTables) allTables.addAll(cellTables);
                }
            }
        }
    }

    public XWPFTable getTableByCTTbl(CTTbl ctTbl) {
        for (int i = 0; i < allTables.size(); i++) {
            if (allTables.get(i).getCTTbl() == ctTbl) { return allTables.get(i); }
        }
        return null;
    }

    /**
     * 在某个段落起始处插入表格
     * 
     * @param run
     * @param row
     * @param col
     * @return
     */
    public XWPFTable insertNewTable(XWPFRun run, int row, int col) {
        XmlCursor cursor = ((XWPFParagraph) run.getParent()).getCTP().newCursor();

        // XmlCursor cursor = run.getCTR().newCursor();
        if (isCursorInBody(cursor)) {
            String uri = CTTbl.type.getName().getNamespaceURI();
            String localPart = "tbl";
            cursor.beginElement(localPart, uri);
            cursor.toParent();

            CTTbl t = (CTTbl) cursor.getObject();
            XWPFTable newT = new XWPFTable(t, this, row, col);
            XmlObject o = null;
            while (!(o instanceof CTTbl) && (cursor.toPrevSibling())) {
                o = cursor.getObject();
            }
            if (!(o instanceof CTTbl)) {
                tables.add(0, newT);
            } else {
                int pos = tables.indexOf(getTable((CTTbl) o)) + 1;
                tables.add(pos, newT);
            }
            int i = 0;
            XmlCursor tableCursor = t.newCursor();
            try {
                cursor.toCursor(tableCursor);
                while (cursor.toPrevSibling()) {
                    o = cursor.getObject();
                    if (o instanceof CTP || o instanceof CTTbl) {
                        i++;
                    }
                }
                bodyElements.add(i > bodyElements.size() ? bodyElements.size() : i, newT);
                // bodyElements.add(i, newT);
                cursor.toCursor(tableCursor);
                cursor.toEndToken();
                return newT;
            }
            finally {
                tableCursor.dispose();
            }
        }
        return null;
    }

    /**
     * 设置表格的宽度
     * 
     * @param table
     * @param width
     *            单位cm
     * @param rows
     * @param cols
     */
    public void widthTable(XWPFTable table, float widthCM, int rows, int cols) {
        TableTools.widthTable(table, widthCM, cols);
        TableTools.borderTable(table, 4);
    }

    /**
     * 在某个段落起始处插入段落
     * 
     * @param run
     * @return
     */
    public XWPFParagraph insertNewParagraph(XWPFRun run) {
        // XmlCursor cursor = run.getCTR().newCursor();
        XmlCursor cursor = ((XWPFParagraph) run.getParent()).getCTP().newCursor();
        if (isCursorInBody(cursor)) {
            String uri = CTP.type.getName().getNamespaceURI();
            /*
             * TODO DO not use a coded constant, find the constant in the OOXML
             * classes instead, as the child of type CT_Paragraph is defined in
             * the OOXML schema as 'p'
             */
            String localPart = "p";
            // creates a new Paragraph, cursor is positioned inside the new
            // element
            cursor.beginElement(localPart, uri);
            // move the cursor to the START token to the paragraph just created
            cursor.toParent();
            CTP p = (CTP) cursor.getObject();
            XWPFParagraph newP = new XWPFParagraph(p, this);
            XmlObject o = null;
            /*
             * move the cursor to the previous element until a) the next
             * paragraph is found or b) all elements have been passed
             */
            while (!(o instanceof CTP) && (cursor.toPrevSibling())) {
                o = cursor.getObject();
            }
            /*
             * if the object that has been found is a) not a paragraph or b) is
             * the paragraph that has just been inserted, as the cursor in the
             * while loop above was not moved as there were no other siblings,
             * then the paragraph that was just inserted is the first paragraph
             * in the body. Otherwise, take the previous paragraph and calculate
             * the new index for the new paragraph.
             */
            if ((!(o instanceof CTP)) || (CTP) o == p) {
                paragraphs.add(0, newP);
            } else {
                int pos = paragraphs.indexOf(getParagraph((CTP) o)) + 1;
                paragraphs.add(pos, newP);
            }

            /*
             * create a new cursor, that points to the START token of the just
             * inserted paragraph
             */
            XmlCursor newParaPos = p.newCursor();
            try {
                /*
                 * Calculate the paragraphs index in the list of all body
                 * elements
                 */
                int i = 0;
                cursor.toCursor(newParaPos);
                while (cursor.toPrevSibling()) {
                    o = cursor.getObject();
                    if (o instanceof CTP || o instanceof CTTbl) i++;
                }
                bodyElements.add(i > bodyElements.size() ? bodyElements.size() : i, newP);
                cursor.toCursor(newParaPos);
                cursor.toEndToken();
                return newP;
            }
            finally {
                newParaPos.dispose();
            }
        }
        return null;
    }

    private boolean isCursorInBody(XmlCursor cursor) {
        XmlCursor verify = cursor.newCursor();
        verify.toParent();
        try {
            return true;// (verify.getObject() == this.getDocument().getBody());
        }
        finally {
            verify.dispose();
        }
    }

    public BigInteger addNewNumbericId(Pair numFmt) {
        XWPFNumbering numbering = this.getNumbering();
        if (null == numbering) {
            numbering = this.createNumbering();
        }

        NumberingWrapper numberingWrapper = new NumberingWrapper(numbering);
        CTAbstractNum cTAbstractNum = CTAbstractNum.Factory.newInstance();
        // if we have an existing document, we must determine the next
        // free number first.
        cTAbstractNum
                .setAbstractNumId(BigInteger.valueOf(numberingWrapper.getAbstractNumsSize() + 10));

        Enum fmt = numFmt.getLeft();
        String val = numFmt.getRight();
        CTLvl cTLvl = cTAbstractNum.addNewLvl();
        cTLvl.addNewNumFmt().setVal(fmt);
        cTLvl.addNewLvlText().setVal(val);
        cTLvl.addNewStart().setVal(BigInteger.valueOf(1));
        cTLvl.setIlvl(BigInteger.valueOf(0));
        if (fmt == STNumberFormat.BULLET) {
            cTLvl.addNewLvlJc().setVal(STJc.LEFT);
        } else {
            // cTLvl.setIlvl(BigInteger.valueOf(0));
        }

        XWPFAbstractNum abstractNum = new XWPFAbstractNum(cTAbstractNum);
        BigInteger abstractNumID = numbering.addAbstractNum(abstractNum);

        return numbering.addNum(abstractNumID);
    }

    /**
     * 生成一个新的流文档
     * 
     * @return
     * @throws IOException
     * @since 1.3.0
     */
    public NiceXWPFDocument generate() throws IOException {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        this.write(byteArrayOutputStream);
        this.close();
        return new NiceXWPFDocument(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));
    }

    /**
     * 指定位置合并Word文档
     * 
     * @param docMerges
     *            待合并的文档
     * @param run
     *            合并的位置
     * @return 合并后的文档
     * @throws Exception
     * @since 1.3.0
     */
    public NiceXWPFDocument merge(List docMerges, XWPFRun run) throws Exception {
        if (null == docMerges || docMerges.isEmpty() || null == run) return this;
        // XWPFParagraph paragraph = insertNewParagraph(run);
        XWPFParagraph paragraph = (XWPFParagraph) run.getParent();
        CTP ctp = paragraph.getCTP();

        CTBody body = this.getDocument().getBody();
        String srcString = body.xmlText();
        String prefix = srcString.substring(0, srcString.indexOf(">") + 1);
        String sufix = srcString.substring(srcString.lastIndexOf("<"));
        List addParts = new ArrayList();
        for (NiceXWPFDocument docMerge : docMerges) {
            addParts.add(extractMergePart(docMerge));
        }

        CTP makeBody = CTP.Factory.parse(prefix + StringUtils.join(addParts, "") + sufix);
        ctp.set(makeBody);

        String xmlText = body.xmlText();
        xmlText = xmlText.replaceAll("", "").replaceAll("", "").replaceAll("", "")
                .replaceAll("", "").replaceAll("", "")
                .replaceAll("", "");

        // System.out.println(xmlText);
        body.set(CTBody.Factory.parse(xmlText));

        return generate();
    }

    /**
     * 文档末尾合并另一个Word文档
     * 
     * @param docMerge
     *            待合并文档
     * @return 合并后的文档
     * @throws Exception
     * @since 1.3.0
     */
    public NiceXWPFDocument merge(NiceXWPFDocument docMerge) throws Exception {
        if (null == docMerge) return this;
        CTBody body = this.getDocument().getBody();
        String srcString = body.xmlText();
        String prefix = srcString.substring(0, srcString.indexOf(">") + 1);
        String mainPart = srcString.substring(srcString.indexOf(">") + 1,
                srcString.lastIndexOf("<"));
        String sufix = srcString.substring(srcString.lastIndexOf("<"));

        String addPart = extractMergePart(docMerge);

        CTBody makeBody = CTBody.Factory.parse(prefix + mainPart + addPart + sufix);
        body.set(makeBody);
        return generate();
    }

    private String extractMergePart(NiceXWPFDocument docMerge) throws InvalidFormatException {
        CTBody bodyMerge = docMerge.getDocument().getBody();
        Map styleIdsMap = mergeStyles(docMerge);
        Map numIdsMap = mergeNumbering(docMerge);
        Map blipIdsMap = mergePicture(docMerge);

        XmlOptions optionsOuter = new XmlOptions();
        optionsOuter.setSaveOuter();
        String appendString = bodyMerge.xmlText(optionsOuter);
        String addPart = ridSectPr(appendString);

        for (String styleId : styleIdsMap.keySet()) {
            addPart = addPart
                    .replaceAll(" placeHolderblipIdsMap = new HashMap();
        for (String relaId : blipIdsMap.keySet()) {
            placeHolderblipIdsMap.put(relaId, blipIdsMap.get(relaId) + "@PoiTL@");
        }
        for (String relaId : placeHolderblipIdsMap.keySet()) {
            addPart = addPart.replaceAll("r:embed=\"" + relaId + "\"",
                    "r:embed=\"" + placeHolderblipIdsMap.get(relaId) + "\"");
        }
        addPart = addPart.replaceAll("@PoiTL@", "");

        for (BigInteger numId : numIdsMap.keySet()) {
            addPart = addPart.replaceAll("") + 1,
                    appendString.lastIndexOf("") + 11,
                    appendString.lastIndexOf("<"));
            return prefix + sufix;
        } else {
            addPart = appendString.substring(appendString.indexOf(">") + 1,
                    appendString.lastIndexOf("<"));
        }
        return addPart;
    }

    private Map mergePicture(NiceXWPFDocument docMerge)
            throws InvalidFormatException {
        Map blipIdsMap = new HashMap();
        List allPictures = docMerge.getAllPictures();
        for (XWPFPictureData xwpfPictureData : allPictures) {
            String relationId = docMerge.getRelationId(xwpfPictureData);
            String blidId = this.addPictureData(xwpfPictureData.getData(),
                    xwpfPictureData.getPictureType());
            blipIdsMap.put(relationId, blidId);
        }
        return blipIdsMap;
    }

    private Map mergeNumbering(NiceXWPFDocument docMerge) {
        Map numIdsMap = new HashMap();
        XWPFNumbering numberingMerge = docMerge.getNumbering();
        if (null == numberingMerge) return numIdsMap;
        NumberingWrapper wrapperMerge = new NumberingWrapper(numberingMerge);
        List nums = wrapperMerge.getNums();
        if (null == nums) return numIdsMap;

        XWPFNumbering numbering = this.getNumbering();
        if (null == numbering) numbering = this.createNumbering();
        NumberingWrapper wrapper = new NumberingWrapper(numbering);

        XWPFAbstractNum xwpfAbstractNum;
        CTAbstractNum cTAbstractNum;
        Map cache = new HashMap();
        for (XWPFNum xwpfNum : nums) {
            BigInteger mergeNumId = xwpfNum.getCTNum().getNumId();

            cTAbstractNum = cache.get(xwpfNum.getCTNum().getAbstractNumId().getVal());
            if (null == cTAbstractNum) {
                xwpfAbstractNum = numberingMerge
                        .getAbstractNum(xwpfNum.getCTNum().getAbstractNumId().getVal());
                if (null == xwpfAbstractNum) {
                    logger.warn("cannot find cTAbstractNum by XWPFNum.");
                    continue;
                }
                cTAbstractNum = xwpfAbstractNum.getCTAbstractNum();
                cTAbstractNum
                        .setAbstractNumId(BigInteger.valueOf(wrapper.getAbstractNumsSize() + 20));
                cache.put(xwpfNum.getCTNum().getAbstractNumId().getVal(), cTAbstractNum);
            }

            BigInteger numID = numbering
                    .addNum(numbering.addAbstractNum(new XWPFAbstractNum(cTAbstractNum)));

            numIdsMap.put(mergeNumId, numID);
        }
        return numIdsMap;
    }

    @SuppressWarnings("unchecked")
    private Map mergeStyles(NiceXWPFDocument docMerge) {
        Map styleIdsMap = new HashMap();
        XWPFStyles styles = this.getStyles();
        if (null == styles) styles = createStyles();
        XWPFStyles stylesMerge = docMerge.getStyles();
        if (null == stylesMerge) return styleIdsMap;
        try {
            Field listStyleField = XWPFStyles.class.getDeclaredField("listStyle");
            listStyleField.setAccessible(true);
            List lists = (List) listStyleField.get(stylesMerge);
            for (XWPFStyle xwpfStyle : lists) {
                if (styles.styleExist(xwpfStyle.getStyleId())) {
                    String id = xwpfStyle.getStyleId();
                    xwpfStyle.setStyleId(UUID.randomUUID().toString());
                    styleIdsMap.put(id, xwpfStyle.getStyleId());
                }
                styles.addStyle(xwpfStyle);
            }
        } catch (Exception e) {
            logger.error("merge style error", e);
        }
        return styleIdsMap;
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy