How to Correctly Merge Numbering in DOCX Files [updated code]?

87 views Asked by At

I'm working on a function to merge two DOCX files using docx4j. I'm currently facing an issue with the mergeNumbering method. Despite following suggestions from a related Stack Overflow question, I'm unable to translate the solutions to my specific case.

The problem arises when merging lists from two documents. The original list maintains its structure, but the merged list does not preserve the correct numbering format, especially for nested items.

    public byte[] unifyDocx(MultipartFile originalFile, String parentFilePath) {

        try {

            Document parentDoc = cmis.getFile(parentFilePath);

            WordprocessingMLPackage wordMLPackageParent = WordprocessingMLPackage.load(parentDoc.getContentStream().getStream());

            WordprocessingMLPackage wordMLPackageOriginal = WordprocessingMLPackage.load(originalFile.getInputStream());

            resolveRelationshipIdConflicts(wordMLPackageParent, wordMLPackageOriginal);

            wordMLPackageParent.getMainDocumentPart().getContent().addAll(wordMLPackageOriginal.getMainDocumentPart().getContent());

            mergeHyperlinkRelationships(wordMLPackageParent, wordMLPackageOriginal);

            mergeImagesRelationships(wordMLPackageParent, wordMLPackageOriginal);

            mergeStyles(wordMLPackageParent, wordMLPackageOriginal);

            mergeNumberingDefinitions(wordMLPackageParent, wordMLPackageOriginal);
            
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();

            wordMLPackageParent.save(byteArrayOutputStream);

            return byteArrayOutputStream.toByteArray();

        } catch (Exception ex) {
            logger.error("Exception: ", ex);
            throw new GenericErrorException(erroPadrao);
        }

    }

      public static void mergeNumberingDefinitions(WordprocessingMLPackage parentPkg, WordprocessingMLPackage originalPkg) throws Exception {
        NumberingDefinitionsPart parentNumberingPart = parentPkg.getMainDocumentPart().getNumberingDefinitionsPart();
        NumberingDefinitionsPart originalNumberingPart = originalPkg.getMainDocumentPart().getNumberingDefinitionsPart();

        if (originalNumberingPart == null) {
            return; 
        }

        if (parentNumberingPart == null) {
            parentNumberingPart = new NumberingDefinitionsPart();
            parentPkg.getMainDocumentPart().addTargetPart(parentNumberingPart);
        }

        Numbering originalNumbering = originalNumberingPart.getJaxbElement();
        Map<BigInteger, BigInteger> numIdMapping = new HashMap<>();

        for (Numbering.AbstractNum originalAbstractNum : originalNumbering.getAbstractNum()) {
            Numbering.AbstractNum clonedAbstractNum = XmlUtils.deepCopy(originalAbstractNum, Context.jc);
            Numbering.Num newNum = parentNumberingPart.addAbstractListNumberingDefinition(clonedAbstractNum);
            BigInteger newAbstractNumId = newNum.getAbstractNumId().getVal();
            numIdMapping.put(originalAbstractNum.getAbstractNumId(), newAbstractNumId);
        }

        for (Numbering.Num originalNum : originalNumbering.getNum()) {
            BigInteger originalAbstractNumId = originalNum.getAbstractNumId().getVal();
            if (numIdMapping.containsKey(originalAbstractNumId)) {
                BigInteger newAbstractNumId = numIdMapping.get(originalAbstractNumId);
                long newNumId = parentNumberingPart.restart(originalNum.getNumId().longValue(), 0, newAbstractNumId.longValue());
                numIdMapping.put(originalNum.getNumId(), BigInteger.valueOf(newNumId));
            }
        }

        updateNumberingReferences(originalPkg, numIdMapping);
    }

    private static void updateNumberingReferences(WordprocessingMLPackage originalPkg, Map<BigInteger, BigInteger> numIdMapping) throws Exception {
        List<Object> mainDocumentContent = originalPkg.getMainDocumentPart().getContent();

        for (Object o : mainDocumentContent) {
            if (o instanceof org.docx4j.wml.P) {
                org.docx4j.wml.P paragraph = (org.docx4j.wml.P) o;
                if (paragraph.getPPr() != null && paragraph.getPPr().getNumPr() != null && paragraph.getPPr().getNumPr().getNumId() != null) {
                    BigInteger oldNumId = paragraph.getPPr().getNumPr().getNumId().getVal();
                    BigInteger newNumId = numIdMapping.get(oldNumId);

                    if (newNumId != null) {
                        paragraph.getPPr().getNumPr().getNumId().setVal(newNumId);
                    }
                }
            }
        }
    }

    
 public static void resolveRelationshipIdConflicts(WordprocessingMLPackage model, WordprocessingMLPackage thesis) throws Exception {
        RelationshipsPart modelRelsPart = model.getMainDocumentPart().getRelationshipsPart();
        RelationshipsPart thesisRelsPart = thesis.getMainDocumentPart().getRelationshipsPart();

        Set<String> modelIds = new HashSet<>();
        for (Relationship rel : modelRelsPart.getRelationships().getRelationship()) {
            modelIds.add(rel.getId());
        }

        Relationships thesisRelationships = thesisRelsPart.getRelationships();
        for (Relationship rel : thesisRelationships.getRelationship()) {
            if (modelIds.contains(rel.getId())) {
                String newId = generateRandomRelId(modelRelsPart, thesisRelsPart);
                updateRelationshipId(thesis, rel.getId(), newId);
                rel.setId(newId);
            }
        }
    }

    private static String generateRandomRelId(RelationshipsPart modelRelsPart, RelationshipsPart thesisRelsPart) {
        Random random = new Random();
        String newId;
        do {
            newId = "rId" + random.nextInt(10000);
        } while (modelRelsPart.getRelationshipByID(newId) != null || thesisRelsPart.getRelationshipByID(newId) != null);
        return newId;
    }

Attached are the images showing the original list and the list after merging:

Original list:

enter image description here

Merging list:

enter image description here

Additionally, I have a query regarding the resolveRelationshipIdConflicts function: Is this function sufficient for resolving ID conflicts in this context?

0

There are 0 answers