org.pptx4j.convert.out.svginhtml.SvgExporter Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of docx4j Show documentation
Show all versions of docx4j Show documentation
docx4j is a library which helps you to work with the Office Open
XML file format as used in docx
documents, pptx presentations, and xlsx spreadsheets.
package org.pptx4j.convert.out.svginhtml;
import java.io.ByteArrayOutputStream;
import java.io.StringReader;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Templates;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import org.apache.log4j.Logger;
import org.docx4j.XmlUtils;
import org.docx4j.convert.out.AbstractConversionSettings;
import org.docx4j.convert.out.html.AbstractHtmlExporter;
import org.docx4j.convert.out.html.HTMLConversionImageHandler;
import org.docx4j.convert.out.html.AbstractHtmlExporter.HtmlSettings;
import org.docx4j.dml.CTTextCharacterProperties;
import org.docx4j.dml.CTTextParagraphProperties;
import org.docx4j.dml.CTTransform2D;
import org.docx4j.model.styles.StyleTree;
import org.docx4j.model.styles.Tree;
import org.docx4j.model.styles.StyleTree.AugmentedStyle;
import org.docx4j.openpackaging.exceptions.Docx4JException;
import org.docx4j.openpackaging.exceptions.InvalidFormatException;
import org.docx4j.openpackaging.packages.PresentationMLPackage;
import org.docx4j.openpackaging.parts.PresentationML.SlidePart;
import org.docx4j.wml.PPr;
import org.docx4j.wml.RPr;
import org.docx4j.wml.Style;
import org.plutext.jaxb.svg11.Line;
import org.plutext.jaxb.svg11.ObjectFactory;
import org.plutext.jaxb.svg11.Svg;
import org.pptx4j.Box;
import org.pptx4j.Point;
import org.pptx4j.jaxb.Context;
import org.pptx4j.model.ResolvedLayout;
import org.pptx4j.model.TextStyles;
import org.pptx4j.pml.CxnSp;
import org.pptx4j.pml.GroupShape;
import org.w3c.dom.Document;
import org.w3c.dom.DocumentFragment;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
import org.w3c.dom.traversal.NodeIterator;
import org.xml.sax.InputSource;
public class SvgExporter {
public static class SvgSettings extends AbstractConversionSettings {
public static final String IMAGE_TARGET_URI = "imageTargetUri";
public void setImageTargetUri(String imageTargetUri) {
settings.put(IMAGE_TARGET_URI, imageTargetUri);
}
public String getImageTargetUri() {
return (String)settings.get(IMAGE_TARGET_URI);
}
}
// NB: file suffix must end with .xhtml in order to see the SVG in a browser
protected static Logger log = Logger.getLogger(SvgExporter.class);
public static JAXBContext jcSVG;
static ObjectFactory oFactory;
static Templates xslt;
static {
try {
jcSVG = JAXBContext.newInstance("org.plutext.jaxb.svg11");
oFactory = new ObjectFactory();
Source xsltSource = new StreamSource(
org.docx4j.utils.ResourceUtils.getResource(
"org/pptx4j/convert/out/svginhtml/pptx2svginhtml.xslt"));
xslt = XmlUtils.getTransformerTemplate(xsltSource);
} catch (Exception e) {
e.printStackTrace();
}
}
private static String imageDirPath;
public static void setImageDirPath(String _imageDirPath) {
imageDirPath = _imageDirPath;
}
/**
* Create an HTML (with SVG) page representing the slide.
* @param presentationMLPackage
* @param slide
* @return
* @throws Exception
*/
public static String svg(PresentationMLPackage presentationMLPackage,
SlidePart slide) throws Exception {
return svg(presentationMLPackage, slide, null);
}
/**
* Create an HTML (with SVG) page representing the slide.
* @param presentationMLPackage
* @param slide
* @param settings
* @return
* @throws Exception
*/
public static String svg(PresentationMLPackage presentationMLPackage,
SlidePart slide, SvgSettings settings) throws Exception {
ResolvedLayout rl = ((SlidePart)slide).getResolvedLayout();
// System.out.println( XmlUtils.marshaltoString(rl.getShapeTree(), false, true, Context.jcPML,
// "http://schemas.openxmlformats.org/presentationml/2006/main", "spTree", GroupShape.class) );
return SvgExporter.svg(presentationMLPackage, rl, settings);
}
/**
* @param presentationMLPackage
* @param layout
* @return
* @throws Exception
*/
private static String svg(PresentationMLPackage presentationMLPackage,
ResolvedLayout layout, SvgSettings settings) throws Exception {
ByteArrayOutputStream intermediate = new ByteArrayOutputStream();
Result intermediateResult = new StreamResult( intermediate );
svg(presentationMLPackage, layout, intermediateResult, settings);
return intermediate.toString("UTF-8");
//log.info(svg);
}
private static void svg(PresentationMLPackage presentationMLPackage,
ResolvedLayout layout, javax.xml.transform.Result result,
SvgSettings settings) throws Exception {
org.w3c.dom.Document doc = XmlUtils.marshaltoW3CDomDocument(
layout.getShapeTree(),
Context.jcPML,
"http://schemas.openxmlformats.org/presentationml/2006/main", "spTree", GroupShape.class);
if (settings == null) {
settings = new SvgSettings();
}
settings.setWmlPackage(presentationMLPackage);
settings.getSettings().put("resolvedLayout", layout);
if ((settings.getImageDirPath() == null) && (imageDirPath != null)) {
settings.setImageDirPath(imageDirPath);
}
boolean privateImageHandler = false;
if (settings.getImageHandler() == null) {
settings.setImageHandler(
new HTMLConversionImageHandler(settings.getImageDirPath(),
settings.getImageTargetUri(),
settings.isImageIncludeUUID()));
privateImageHandler = true;
}
org.docx4j.XmlUtils.transform(doc, xslt, settings.getSettings(), result);
if (privateImageHandler) {
//remove a locally created imageHandler in case the SvgSettings get reused
settings.getSettings().remove(HtmlSettings.IMAGE_HANDLER);
}
}
public static boolean isDebugEnabled() {
return log.isDebugEnabled();
}
public static DocumentFragment notImplemented(NodeIterator nodes, String message) {
Node n = nodes.nextNode();
log.warn("NOT IMPLEMENTED: support for "+ n.getNodeName() + "\n" + message);
if (log.isDebugEnabled() ) {
if (message==null) message="";
log.debug( XmlUtils.w3CDomNodeToString(n) );
// Return something which will show up in the output
return message("NOT IMPLEMENTED: support for " + n.getNodeName() + " - " + message);
} else {
// Put it in a comment node instead?
return null;
}
}
public static DocumentFragment message(String message) {
if (!log.isDebugEnabled()) return null;
String html = ""
+ message
+ "";
javax.xml.parsers.DocumentBuilderFactory dbf = DocumentBuilderFactory
.newInstance();
dbf.setNamespaceAware(true);
StringReader reader = new StringReader(html);
InputSource inputSource = new InputSource(reader);
Document doc = null;
try {
doc = dbf.newDocumentBuilder().parse(inputSource);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
reader.close();
DocumentFragment docfrag = doc.createDocumentFragment();
docfrag.appendChild(doc.getDocumentElement());
return docfrag;
}
public static DocumentFragment createBlockForP(
PresentationMLPackage pmlPackage,
ResolvedLayout rl,
String lvl,
String cNvPrName,
String phType, NodeIterator childResults, NodeIterator lvlNpPr ) {
StyleTree styleTree = null;
try {
styleTree = pmlPackage.getStyleTree();
} catch (InvalidFormatException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
log.debug("lvl:" +lvl);
int level;
if (lvl.equals("NaN")) {
level = 1;
} else {
level = Integer.parseInt(lvl);
}
String pStyleVal;
System.out.println("cNvPrName: " + cNvPrName + "; " + "phType: " + phType );
if (cNvPrName.toLowerCase().indexOf("subtitle")>-1
|| phType.toLowerCase().indexOf("subtitle")>-1) {
// Subtitle on first page in default layout is styled as a Body.
pStyleVal = "Lvl" + level + "Master" + rl.getMasterNumber() + "Body";
} else if (cNvPrName.toLowerCase().indexOf("title")>-1
|| phType.toLowerCase().indexOf("title")>-1) {
pStyleVal = "Lvl" + level + "Master" + rl.getMasterNumber() + "Title";
} else {
// eg cNvPrName: TextBox 2; phType:
pStyleVal = "Lvl" + level + "Master" + rl.getMasterNumber() + "Other";
}
System.out.println("--> " + pStyleVal );
try {
// Create a DOM builder and parse the fragment
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
Document document = factory.newDocumentBuilder().newDocument();
//log.info("Document: " + document.getClass().getName() );
Node xhtmlP = document.createElement("p");
document.appendChild(xhtmlP);
// Set @class
log.debug(pStyleVal);
Tree pTree = styleTree.getParagraphStylesTree();
org.docx4j.model.styles.Node asn = pTree.get(pStyleVal);
((Element)xhtmlP).setAttribute("class",
StyleTree.getHtmlClassAttributeValue(pTree, asn)
);
StringBuffer inlineStyle = new StringBuffer();
// Do we have CTTextParagraphProperties
//
// Convert it to a WordML pPr
CTTextParagraphProperties lvlPPr = unmarshalFormatting(lvlNpPr);
if (lvlPPr!=null) {
log.debug("We have lvlPPr");
log.debug(
XmlUtils.marshaltoString(lvlPPr, true, true,
Context.jcPML, "FIXME", "lvl1pPr",
CTTextParagraphProperties.class)
);
PPr pPr = TextStyles.getWmlPPr(lvlPPr);
if (pPr!=null) {
AbstractHtmlExporter.createCss(pmlPackage, pPr, inlineStyle, false);
}
// TODO RPR
}
// Without this, top-margin is too large in Webkit (Midor).
// Not tested elsewhere...
inlineStyle.append("margin-left:3px; margin-top:3px;");
if (!inlineStyle.toString().equals("") ) {
((Element)xhtmlP).setAttribute("style", inlineStyle.toString() );
}
// Our fo:block wraps whatever result tree fragment
// our style sheet produced when it applied-templates
// to the child nodes
// init
Node n = childResults.nextNode();
do {
// getNumberXmlNode creates a span node, which is empty
// if there is no numbering.
// Let's get rid of any such .
// What we actually get is a document node
if (n.getNodeType()==Node.DOCUMENT_NODE) {
log.debug("handling DOCUMENT_NODE");
// Do just enough of the handling here
NodeList nodes = n.getChildNodes();
if (nodes != null) {
for (int i=0; i ");
} else {
XmlUtils.treeCopy( (Node)nodes.item(i), xhtmlP );
}
}
}
} else {
// log.info("Node we are importing: " + n.getClass().getName() );
// foBlockElement.appendChild(
// document.importNode(n, true) );
/*
* Node we'd like to import is of type org.apache.xml.dtm.ref.DTMNodeProxy
* which causes
* org.w3c.dom.DOMException: NOT_SUPPORTED_ERR: The implementation does not support the requested type of object or operation.
*
* See http://osdir.com/ml/text.xml.xerces-j.devel/2004-04/msg00066.html
*
* So instead of importNode, use
*/
XmlUtils.treeCopy( n, xhtmlP );
}
// next
n = childResults.nextNode();
} while ( n !=null );
DocumentFragment docfrag = document.createDocumentFragment();
docfrag.appendChild(document.getDocumentElement());
return docfrag;
} catch (Exception e) {
e.printStackTrace();
System.out.println(e.toString() );
log.error(e);
}
return null;
}
private static CTTextParagraphProperties unmarshalFormatting(NodeIterator lvlNpPr ) {
// Get the pPr node as a JAXB object,
// so we can read it using our standard
// methods. Its a bit sad that we
// can't just adorn our DOM tree with the
// original JAXB objects?
try {
//CTTextListStyle lstStyle = null;
CTTextParagraphProperties pPr = null;
if (lvlNpPr!=null) {
Node n = lvlNpPr.nextNode();
log.debug(n.getClass().getName());
String str = XmlUtils.w3CDomNodeToString(n);
//log.debug("'" + str + "'");
// Convert to String first ...
// unmarshalling the node directly doesn't work as expected
// (see comment in XmlUtils)
// if (n!=null) {
// return (CTTextParagraphProperties)XmlUtils.unmarshal(n, Context.jcPML,
// CTTextParagraphProperties.class);
// }
if (!str.equals("")) {
return (CTTextParagraphProperties)XmlUtils.unmarshalString(str, Context.jcPML,
CTTextParagraphProperties.class);
}
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
public static DocumentFragment createBlockForR(
PresentationMLPackage pmlPackage, NodeIterator rPrNodeIt,
NodeIterator childResults) {
DocumentFragment docfrag = null;
Document d = null;
Element span = null;
try {
// Create a DOM builder and parse the fragment
DocumentBuilderFactory factory = DocumentBuilderFactory
.newInstance();
d = factory.newDocumentBuilder().newDocument();
span = d.createElement("span");
d.appendChild(span);
CTTextCharacterProperties textCharProps = (CTTextCharacterProperties) nodeToObjectModel(
rPrNodeIt.nextNode(), CTTextCharacterProperties.class);
RPr rPr = TextStyles.getWmlRPr(textCharProps);
// Does our rPr contain anything else?
StringBuffer inlineStyle = new StringBuffer();
AbstractHtmlExporter.createCss(pmlPackage, rPr, inlineStyle);
if (!inlineStyle.toString().equals("")) {
span.setAttribute("style", inlineStyle.toString());
}
Node n = childResults.nextNode();
XmlUtils.treeCopy(n, span);
} catch (Exception e) {
e.printStackTrace();
log.error(e);
// If something went wrong with the formatting,
// still try to display the text!
Node n = childResults.nextNode();
XmlUtils.treeCopy(n, span);
}
// Machinery
docfrag = d.createDocumentFragment();
docfrag.appendChild(d.getDocumentElement());
return docfrag;
}
// public static CTTextListStyle unmarshalFormatting(NodeIterator lstStyleNodeIt ) {
//
// // Get the pPr node as a JAXB object,
// // so we can read it using our standard
// // methods. Its a bit sad that we
// // can't just adorn our DOM tree with the
// // original JAXB objects?
// try {
// CTTextListStyle lstStyle = null;
// if (lstStyleNodeIt!=null) {
// Node n = lstStyleNodeIt.nextNode();
// if (n!=null) {
// return (CTTextListStyle)XmlUtils.unmarshal(n, Context.jcPML, CTTextListStyle.class);
// }
// }
// } catch (Exception e) {
// // TODO Auto-generated catch block
// e.printStackTrace();
// }
// return null;
//
// }
public static String getCssForStyles(PresentationMLPackage pmlPackage) {
StringBuffer result = new StringBuffer();
StyleTree styleTree=null;
try {
styleTree = pmlPackage.getStyleTree();
} catch (InvalidFormatException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// Second iteration - paragraph level pPr *and rPr*
result.append("\n /* PARAGRAPH STYLES */ \n");
Tree pTree = styleTree.getParagraphStylesTree();
for (org.docx4j.model.styles.Node n : pTree.toList() ) {
Style s = n.getData().getStyle();
result.append( "."+ s.getStyleId() + " {display:block;" );
if (s.getPPr()==null) {
log.debug("null pPr for style " + s.getStyleId());
} else {
AbstractHtmlExporter.createCss(pmlPackage, s.getPPr(), result, false );
}
if (s.getRPr()==null) {
log.debug("null rPr for style " + s.getStyleId());
} else {
AbstractHtmlExporter.createCss(pmlPackage, s.getRPr(), result);
}
result.append( "}\n" );
}
if (log.isDebugEnabled()) {
return result.toString();
} else {
String debug = result.toString();
return debug;
}
}
public static DocumentFragment shapeToSVG(
PresentationMLPackage pmlPackage,
NodeIterator shapeIt ) {
DocumentFragment docfrag=null;
Document d=null;
try {
Object shape = null;
if (shapeIt!=null) {
Node n = shapeIt.nextNode();
if (n==null) {
d=makeErr( "[null node?!]" );
} else {
log.debug("Handling " + n.getNodeName());
if (n.getNodeName().equals("p:cxnSp") ) {
shape = nodeToObjectModel(n, CxnSp.class);
d = CxnSpToSVG( (CxnSp)shape);
} else {
log.info("** TODO " + n.getNodeName() );
d=makeErr( "[" + n.getNodeName() + "]" );
}
}
}
} catch (Exception e) {
log.error(e);
d=makeErr(e.getMessage() );
}
// Machinery
docfrag = d.createDocumentFragment();
docfrag.appendChild(d.getDocumentElement());
return docfrag;
}
/**
* Connection (line)
*/
public static Document CxnSpToSVG(CxnSp cxnSp) {
// Geometrical transforms
CTTransform2D xfrm = cxnSp.getSpPr().getXfrm();
Box b = new Box(xfrm.getOff().getX(), xfrm.getOff().getY(),
xfrm.getExt().getCx(), xfrm.getExt().getCy() );
if (xfrm.getRot()!=0) {
b.rotate(xfrm.getRot());
}
if (xfrm.isFlipH() ) {
b.flipH();
}
if (xfrm.isFlipV() ) {
b.flipV();
}
// Convert from EMU to pixels
b.toPixels();
// Wrap in a div positioning it on the page
Document document = createDocument();
Element xhtmlDiv = document.createElement("div");
// Firefox needs the following; Chrome doesn't
xhtmlDiv.setAttribute("style",
"position: absolute; width:100%; height:100%; left:0px; top:0px;");
Node n = document.appendChild(xhtmlDiv);
// Convert the object itself to SVG
Svg svg = oFactory.createSvg();
Line line = oFactory.createLine();
svg.getSVGDescriptionClassOrSVGAnimationClassOrSVGStructureClass().add(line);
line.setX1(b.getOffset().getXAsString() );
line.setY1(b.getOffset().getYAsString() );
Point otherEnd = b.getOtherCorner();
line.setX2( otherEnd.getXAsString() );
line.setY2( otherEnd.getYAsString() );
line.setStyle("stroke:rgb(99,99,99)");
// You can't see the line in Midori, unless you specify the color.
// width eg stroke-width:2 is optional
Document d2 = XmlUtils.marshaltoW3CDomDocument(svg, jcSVG);
XmlUtils.treeCopy(d2, n);
return document;
}
private static Document makeErr(String msg) {
Document d=null;
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
try {
d = factory.newDocumentBuilder().newDocument();
} catch (ParserConfigurationException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
Element span = d.createElement("span");
span.setAttribute("style", "color:red;");
d.appendChild(span);
Text err = d.createTextNode( msg );
span.appendChild(err);
return d;
}
public static Object nodeToObjectModel(Node n, Class declaredType) throws Docx4JException {
if (n==null) {
throw new Docx4JException("null input");
}
// if (log.isDebugEnabled() ) {
// System.out.println("IN: " + XmlUtils.w3CDomNodeToString(n));
// }
Object jaxb=null;
try {
jaxb = XmlUtils.unmarshal(n, Context.jcPML, declaredType);
} catch (JAXBException e1) {
throw new Docx4JException("Couldn't unmarshall " + XmlUtils.w3CDomNodeToString(n), e1);
}
try {
if (jaxb instanceof JAXBElement ) {
JAXBElement jb = (JAXBElement)jaxb;
if (jb.getDeclaredType().getName().equals(declaredType.getName() )) {
return jb.getValue();
} else {
log.error("UNEXPECTED " +
XmlUtils.JAXBElementDebug(jb)
);
throw new Docx4JException("Expected " + declaredType.getName() + " but got " +
XmlUtils.JAXBElementDebug(jb) );
}
} else if (jaxb.getClass().getName().equals(declaredType.getName() )) {
return jaxb;
} else {
log.error( jaxb.getClass().getName() );
throw new Docx4JException("Expected " + declaredType.getName() + " but got " +
jaxb.getClass().getName() );
}
} catch (ClassCastException e) {
throw new Docx4JException("Expected " + declaredType.getName() + " but got " +
jaxb.getClass().getName(), e );
}
}
public static Document createDocument() {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
Document document=null;
try {
document = factory.newDocumentBuilder().newDocument();
} catch (ParserConfigurationException e) {
e.printStackTrace();
}
return document;
}
}