Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,21 @@
import static fr.opensagres.xdocreport.document.odt.ODTConstants.MIME_MAPPING;
import static fr.opensagres.xdocreport.document.odt.ODTConstants.STYLES_XML_ENTRY;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import com.sun.org.apache.xml.internal.serialize.OutputFormat;
import com.sun.org.apache.xml.internal.serialize.XMLSerializer;
import fr.opensagres.xdocreport.converter.MimeMapping;
import fr.opensagres.xdocreport.core.XDocReportException;
import fr.opensagres.xdocreport.core.document.DocumentKind;
import fr.opensagres.xdocreport.core.io.IEntryOutputStreamProvider;
import fr.opensagres.xdocreport.core.io.IEntryReaderProvider;
import fr.opensagres.xdocreport.core.io.IEntryWriterProvider;
import fr.opensagres.xdocreport.core.io.XDocArchive;
import fr.opensagres.xdocreport.core.io.internal.ByteArrayOutputStream;
import fr.opensagres.xdocreport.document.AbstractXDocReport;
import fr.opensagres.xdocreport.document.images.IImageRegistry;
import fr.opensagres.xdocreport.document.odt.images.ODTImageRegistry;
Expand All @@ -47,6 +53,14 @@
import fr.opensagres.xdocreport.document.odt.template.ODTContextHelper;
import fr.opensagres.xdocreport.document.odt.textstyling.ODTDefaultStyle;
import fr.opensagres.xdocreport.template.IContext;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

/**
* Open Office ODT report. For mime mapping please see {@see
Expand Down Expand Up @@ -119,6 +133,140 @@ protected void onBeforeProcessTemplateEngine( IContext context, XDocArchive outp

}

@Override
protected void doPostprocessIfNeeded( XDocArchive outputArchive )
throws Exception
{
applyParentStyleToChildren(outputArchive);
}

/**
* Text styling will style the nodes with some generated style such as XDocReport_Bold. These styles inherit
* from the default paragraph styles so will ignore the style of the enclosing mail merge field. Another issue
* was that nested styles would not stack. For example <b>bold <i>italic</i></b> would look <b>bold</b> <i>italic</i>
* because unlike HTML and CSS, styling rules aren't inherited from enclosing tags.
*
* This post processing loops through each element in the document body and generate bespoke styles for each element
* with an XDocReport style. This new style will inherit from the parent style but copy all the style rules from the
* XDocReport style.
*
* @param outputArchive the output archive after processing the report. Any changes will be written back into this
* archive.
*/
private static void applyParentStyleToChildren( XDocArchive outputArchive )
throws ParserConfigurationException, IOException, SAXException
{
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setNamespaceAware(true);
DocumentBuilder xmlDocBuilder = documentBuilderFactory.newDocumentBuilder();

// This is a document of the base styles for the document
Document styleXML= xmlDocBuilder
.parse(outputArchive.getEntryInputStream("styles.xml"));

// This document contains the text content as well as a number of "automatic" styles for each element. We generate
// a new automatic style for each text node with a XDocReport style.
Document contentXML = xmlDocBuilder
.parse(outputArchive.getEntryInputStream("content.xml"));


// recursively apply the parent styles to child nodes
applyParentStyleToChildren(contentXML, styleXML);

// write the changes back into the archive
ByteArrayOutputStream contentOutStream = new ByteArrayOutputStream();
new XMLSerializer(contentOutStream, new OutputFormat(contentXML)).serialize(contentXML);
XDocArchive.setEntry(outputArchive, "content.xml", new ByteArrayInputStream(contentOutStream.toByteArray()));
}

private static void applyParentStyleToChildren(Document contentXML,
Document styleXML )
{
// Get the list of styles under the office:styles element and add them to styles
Element officeStyles = (Element) styleXML.getDocumentElement().getElementsByTagName("office:styles").item(0);
NodeList styleNodeList = officeStyles.getElementsByTagName("style:style");
Map<String, Element> styles = new HashMap<String, Element>();

for (int i = 0; i < styleNodeList.getLength(); i++)
{
Element styleElement = (Element) styleNodeList.item(i);
String styleName = styleElement.getAttribute("style:name");
styles.put(styleName, styleElement);
}

// Recurse through all the elements in the document body
Element body = (Element) contentXML.getDocumentElement().getElementsByTagName("office:body").item(0);
Map<String, Element> generatedStyles = new HashMap<String, Element>();
updateChildStyles(body, null, styles, generatedStyles);

// Copy the generated styles to the automatic style list in content.xml
Element automaticStyles = (Element) contentXML.getDocumentElement().getElementsByTagName("office:automatic-styles").item(0);
for (Element style : generatedStyles.values())
{
automaticStyles.appendChild(contentXML.importNode(style, true));
}
}

private static void updateChildStyles(Element element,
String parentStyleName,
Map<String, Element> styles,
Map<String, Element> generatedStyles )
{
String textNS = element.getOwnerDocument().lookupNamespaceURI("text");

// Loop through each child node setting the style appropriately
NodeList children = element.getChildNodes();
for(int i = 0; i < children.getLength(); i++)
{
if (children.item(i) instanceof Element)
{
Element childElement = (Element) children.item(i);
String nextParentStyleName = parentStyleName;
if (childElement.hasAttribute("text:style-name"))
{
String childStyleName = childElement.getAttribute("text:style-name");

// If this node is a XDocReport style, generate a new style with the XDocReport styling rules that inherits
// from parentStyleName
if (childStyleName.startsWith("XDocReport_") && parentStyleName != null)
{
String newStyleName = getOrCreateStyle(parentStyleName, styles.get(childStyleName), generatedStyles);
childElement.setAttributeNS(textNS, "text:style-name", newStyleName);

// Any child should inherit from this new style
nextParentStyleName = newStyleName;
}
else
{
// Any child should inherit from this elements style
nextParentStyleName = childStyleName;
}
}

// Finally we should update any children of this element
updateChildStyles(childElement, nextParentStyleName, styles, generatedStyles);
}
}
}

private static String getOrCreateStyle(String parentStyleName, Element styleToMerge, Map<String, Element> newStyles)
{
String styleNsUri = styleToMerge.getOwnerDocument().lookupNamespaceURI("style");
String styleToMergeName = styleToMerge.getAttribute("style:name");
String mergedStyleName = parentStyleName + "_" + styleToMergeName;

// if we've not already generated this style then copy the XDocReport style making it inherit from parentStyleName
if (!newStyles.containsKey(mergedStyleName))
{
Element newStyle = (Element) styleToMerge.cloneNode(true);
newStyle.setAttributeNS(styleNsUri, "style:name", mergedStyleName);
newStyle.setAttributeNS(styleNsUri, "style:parent-style-name", parentStyleName);
newStyles.put(mergedStyleName, newStyle);
}

return mergedStyleName;
}

@Override
protected String[] getDefaultXMLEntries()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
package fr.opensagres.xdocreport.document.odt;

import fr.opensagres.xdocreport.core.io.XDocArchive;
import org.junit.Test;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.io.InputStream;
import java.util.LinkedList;
import java.util.List;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;

public class TestODTPostProcessing
{
class ElementWrapper
{
private Element mElement;
ElementWrapper(Element pElement)
{
mElement = pElement;
}

private ElementWrapper getElement( String pTagName)
{
return new ElementWrapper((Element) mElement.getElementsByTagName(pTagName).item(0));
}

private ElementWrapper assertStyleName(String pName)
{
assertEquals(pName, getStyleName());
return this;
}

private ElementWrapper assertAttributeEqual(String pAttributeName, String pExpectedValue)
{
assertEquals(pExpectedValue, mElement.getAttribute(pAttributeName));
return this;
}

private String getStyleName()
{
return mElement.getAttribute("text:style-name");
}

private List<ElementWrapper> getChildElements()
{
List<ElementWrapper> lChildren = new LinkedList<ElementWrapper>();
NodeList lChildNodes = mElement.getChildNodes();

for (int i = 0; i < lChildNodes.getLength(); i++)
{
if (lChildNodes.item(i) instanceof Element){
lChildren.add(new ElementWrapper((Element) lChildNodes.item(i)));
}
}

return lChildren;
}
}

private Document parseXMLInputStream(InputStream pInputStream)
throws ParserConfigurationException, IOException, SAXException
{
DocumentBuilderFactory lDocumentBuilderFactory = DocumentBuilderFactory.newInstance();
lDocumentBuilderFactory.setNamespaceAware(true);
DocumentBuilder lDocumentBuilder = lDocumentBuilderFactory.newDocumentBuilder();

return lDocumentBuilder.parse(pInputStream);
}

private XDocArchive loadTestArchive()
throws IOException, ParserConfigurationException
{


XDocArchive lXDocArchive = new XDocArchive();
XDocArchive.setEntry(lXDocArchive, "content.xml", getClass().getResourceAsStream("postprocesstestxml/content.xml"));
XDocArchive.setEntry(lXDocArchive, "styles.xml", getClass().getResourceAsStream("postprocesstestxml/styles.xml"));

return lXDocArchive;
}

@Test
public void testStyleNameGeneration()
throws Exception
{
XDocArchive lXDocArchive = loadTestArchive();

new ODTReport().doPostprocessIfNeeded(lXDocArchive);

Document lContent = parseXMLInputStream(lXDocArchive.getEntryInputStream("content.xml"));

new ElementWrapper(lContent.getDocumentElement())
.getElement("office:body")
.getElement("office:text")
.getElement("text:p")
.getElement("text:span")
.assertStyleName("T2")
.getElement("text:span")
.assertStyleName("T2_XDocReport_Bold")
.getElement("text:span")
.assertStyleName("T2_XDocReport_Bold_XDocReport_Italic");
}

@Test
public void testStyleT2BoldGeneration()
throws Exception
{
XDocArchive lXDocArchive = loadTestArchive();
new ODTReport().doPostprocessIfNeeded(lXDocArchive);

Document lContent = parseXMLInputStream(lXDocArchive.getEntryInputStream("content.xml"));

List<ElementWrapper> automaticStyles = new ElementWrapper(lContent.getDocumentElement())
.getElement("office:automatic-styles")
.getChildElements();

ElementWrapper T2BoldStyle = null;
for (ElementWrapper styleElement : automaticStyles)
{
if("T2_XDocReport_Bold".equals(styleElement.mElement.getAttribute("style:name")))
{
T2BoldStyle = styleElement;
}
}


if (T2BoldStyle != null){
T2BoldStyle
.assertAttributeEqual("style:parent-style-name", "T2")
.assertAttributeEqual("style:name", "T2_XDocReport_Bold")
.getElement("style:text-properties")
.assertAttributeEqual("fo:font-weight", "bold");
}
else
{
fail("Missing style T2_XDocReport_Bold");
}
}

@Test
public void testStyleT2BoldItalicGeneration()
throws Exception
{
XDocArchive lXDocArchive = loadTestArchive();
new ODTReport().doPostprocessIfNeeded(lXDocArchive);

Document lContent = parseXMLInputStream(lXDocArchive.getEntryInputStream("content.xml"));

List<ElementWrapper> automaticStyles = new ElementWrapper(lContent.getDocumentElement())
.getElement("office:automatic-styles")
.getChildElements();

ElementWrapper T2BoldStyle = null;
for (ElementWrapper styleElement : automaticStyles)
{
if("T2_XDocReport_Bold_XDocReport_Italic".equals(styleElement.mElement.getAttribute("style:name")))
{
T2BoldStyle = styleElement;
}
}


if ( T2BoldStyle != null )
{
T2BoldStyle
.assertAttributeEqual("style:parent-style-name", "T2_XDocReport_Bold")
.assertAttributeEqual("style:name", "T2_XDocReport_Bold_XDocReport_Italic")
.getElement("style:text-properties")
.assertAttributeEqual("fo:font-style", "italic");
}
else
{
fail("Missing style T2_XDocReport_Bold_XDocReport_Italic");
}
}

}
Loading