How to maintain style formatting when merging two ODT documents together

500 views Asked by At

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");
2

There are 2 answers

0
PTaladay On BEST ANSWER

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);
        }
    }
} 

}

1
cxs On

I do not know how precisely it should be coded, but using 7zip i have been able to just copy the whole styles.xml from one file to another. Programatically it should be just as easy. I always format my files with styles and never with direct formatting. So just replacing any file is prone to eliminate the local styles.

I found this answer (to the question "Cleaning a stylesheet of unused styles") https://www.mobileread.com/forums/showpost.php?s=cbbee08a1204df71ec5cd88bcf222253&p=2100914&postcount=13 which iterates through all the styles in one document. It doesn't show how to incorporate one into the other, but the backbone is clear.

'---------------------------------------------------------- 03/02/2012
' Supprimer les styles personnalisés inutilisés
' d'un document texte ou d'un classeur
'---------------------------------------------------------------------
sub stylesPersoInutiles()
dim coStylesFamilles as object, oStyleFamille as object
dim oStyle as object, nomFamille as string
dim f as long, x as long
dim ts(), buf as string, iRet as integer
const SEP = ", "

    coStylesFamilles = thisComponent.StyleFamilies
    for f = 0 to coStylesFamilles.count -1
        ' Pour chaque famille
        nomFamille = coStylesFamilles.elementNames(f)
        oStyleFamille = coStylesFamilles.getByName(nomFamille)
        buf = ""
        for x = 0 to oStyleFamille.Count -1
            ' Pour chaque style
            oStyle = oStyleFamille(x)
            'xray oStyle            
            if (oStyle.isUserDefined) and (not oStyle.isInUse) then
                buf = buf & oStyle.name & SEP
            end if
        next x

        if len(buf) > len(SEP) then
            buf = left(buf, len(buf) - len(SEP))
            iRet = msgBox("Styles personnalisés non utilisés : " _
                & chr(13) & buf & chr(13) & chr(13) _
                & "Faut-il les détruire ?", 4+32+256, nomFamille)
            if iRet = 6 then
                ts = split(buf, SEP)
                for x = 0 to uBound(ts) 
                    oStyleFamille.removeByName(ts(x))
                next x
            end if
        end if
    next f
end sub