Search code examples
c#odtodftoolkitodt.net

How to maintain style formatting when merging two ODT documents together


I am working with the AODL library for C#. So far I have been able to wholesale import the text of the second document into the first. The issue is I can't quite figure out what I need to grab to make sure the styling is also moved over to the merged document. Below is the simple code I'm using to test. The closest answer I can find is Merging two .odt files from code, which somewhat answers my question, but it still doesn't tell me where I need to put the styling/ where to get it from. It at least lets me know that I need to go through the styles in the second document and make sure there are not matching names in the first otherwise there will be conflicts. I'm not sure exactly what to do, and documentation has been very slim. Before you suggest anything I would like to let you know that, yes, odt is the filetype I need to work with, and doing any kind of interop stuff like Microsoft does with Word is not what I'm after. If there is another library out there that works similarly to AODL I'm all ears.

TextDocument mergeTemplateDoc = ReadContentsOfFile(mergeTemplateFileName);
TextDocument vehicleTemplateDoc = ReadContentsOfFile(vehicleTemplateFileName);

foreach (IContent piece in vehicleTemplateDoc.Content)
{
    XmlNode newNode = mergeTemplateDoc.XmlDoc.ImportNode(piece.Node,true);

    Paragraph p = ParagraphBuilder.CreateParagraphWithExistingNode(mergeTemplateDoc, newNode);

    mergeTemplateDoc.Content.Add(p);
}

mergeTemplateDoc.SaveTo("MergComplete.odt");

Solution

  • Here is what I ended up doing to solve my issue. Keep in mind I have since migrated to using Java since this question was asked, as the library appears to work a little better in that language.

    Essentially what the methods below are doing is Grabbing the Automatic Styles that are generated in each document. It iterates through the second document and finds each style node, checking for the name attribute. That name is then tagged with an extra identifier that is unique to that document, so when they are merged together they won't conflict name wise.

    The mergeFontTypesToPrimaryDoc just grabs the fonts that don't already exist in the primary doc since all the fonts are referenced in the same way in the documents there is no editing to be done.

    The updateNodeChildrenStyleNames is just a recursive method that I used to make sure I get all the in line style nodes updated to remove any conflicting names between the two documents.

    This similar idea should work in C# as well.

    private static void mergeStylesToPrimaryDoc(OdfTextDocument primaryDoc, OdfTextDocument secondaryDoc) throws Exception {
        OdfFileDom primaryContentDom = primaryDoc.getContentDom();
        OdfOfficeAutomaticStyles primaryDocAutomaticStyles = primaryDoc.getContentDom().getAutomaticStyles();
        OdfOfficeAutomaticStyles secondaryDocAutomaticStyles = secondaryDoc.getContentDom().getAutomaticStyles();
        //Adopt style nodes from secondary doc
        for(int i =0; i<secondaryDocAutomaticStyles.getLength();i++){
            Node style = secondaryDocAutomaticStyles.item(i).cloneNode(true);
            if(style.hasAttributes()){
                NamedNodeMap attributes = style.getAttributes();
                for(int j=0; j< attributes.getLength();j++){
                    Node a = attributes.item(j);
                    if(a.getLocalName().equals("name")){
                        a.setNodeValue(a.getNodeValue()+_stringToAddToStyle);
                    }
                }
            }
            if(style.hasChildNodes()){
                updateNodeChildrenStyleNames(style, _stringToAddToStyle, "name");
            }
    
    
            primaryDocAutomaticStyles.appendChild(primaryContentDom.adoptNode(style));
    
        }
    }
    
    private static void mergeFontTypesToPrimaryDoc(OdfTextDocument primaryDoc, OdfTextDocument secondaryDoc) throws Exception {
        //Insert referenced font types that are not in the primary document you are merging into
        NodeList sdDomNodes = secondaryDoc.getContentDom().getChildNodes().item(0).getChildNodes();
        NodeList pdDomNodes = primaryDoc.getContentDom().getChildNodes().item(0).getChildNodes();
        OdfFileDom primaryContentDom = primaryDoc.getContentDom();
        Node sdFontNode=null;
        Node pdFontNode=null;
        for(int i =0; i<sdDomNodes.getLength();i++){
            if(sdDomNodes.item(i).getNodeName().equals("office:font-face-decls")){
                sdFontNode = sdDomNodes.item(i);
                break;
            }
        }
        for(int i =0; i<pdDomNodes.getLength();i++){
            Node n =pdDomNodes.item(i); 
            if(n.getNodeName().equals("office:font-face-decls")){
                pdFontNode = pdDomNodes.item(i);
                break;
            }
        }
        if(sdFontNode !=null && pdFontNode != null){
            NodeList sdFontNodeChildList = sdFontNode.getChildNodes();
            NodeList pdFontNodeChildList = pdFontNode.getChildNodes();
            List<String> fontNames = new ArrayList<String>();
            //Get list of existing fonts in primary doc
            for(int i=0; i<pdFontNodeChildList.getLength();i++){
                NamedNodeMap attributes = pdFontNodeChildList.item(i).getAttributes(); 
                for(int j=0; j<attributes.getLength();j++){
                    if(attributes.item(j).getLocalName().equals("name")){
                        fontNames.add(attributes.item(j).getNodeValue());
                    }
                }
            }
            //Check each font in the secondary doc to make sure it gets added if the primary doesn't have it
            for(int i=0; i<sdFontNodeChildList.getLength();i++){
                Node fontNode = sdFontNodeChildList.item(i).cloneNode(true); 
                NamedNodeMap attributes = fontNode.getAttributes();
                String fontName="";
                for(int j=0; j< attributes.getLength();j++){
                    if(attributes.item(j).getLocalName().equals("name")){
                        fontName = attributes.item(j).getNodeValue();
                        break;
                    }
                }
                if(!fontName.equals("") && !fontNames.contains(fontName)){
                    pdFontNode.appendChild(primaryContentDom.adoptNode(fontNode));
                }
    
            }
        }
    }
    
    private static void updateNodeChildrenStyleNames(Node n, String stringToAddToStyle, String nodeLocalName){
        NodeList childNodes = n.getChildNodes();
        for (int i=0; i< childNodes.getLength(); i++){
    
            Node currentChild = childNodes.item(i);
    
            if(currentChild.hasAttributes()){
                NamedNodeMap attributes = currentChild.getAttributes();
                for(int j =0; j < attributes.getLength(); j++){
                    Node a = attributes.item(j);
                    if(a.getLocalName().equals(nodeLocalName)){
                        a.setNodeValue(a.getNodeValue() + stringToAddToStyle);
                    }
                }
            }
            if(currentChild.hasChildNodes()){
                updateNodeChildrenStyleNames(currentChild, stringToAddToStyle, nodeLocalName);
            }
        }
    } 
    

    }