/*
* Copyright 2008, Plutext Pty Ltd.
*
* This file is part of Docx4all.
Docx4all is free software: you can redistribute it and/or modify
it under the terms of version 3 of the GNU General Public License
as published by the Free Software Foundation.
Docx4all is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Docx4all. If not, see <http://www.gnu.org/licenses/>.
*/
package org.plutext.client.state;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.xml.bind.JAXBElement;
import org.docx4j.wml.Style;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.docx4j.wml.SdtBlock;
import org.docx4j.XmlUtils;
import org.plutext.client.wrappedTransforms.TransformAbstract;
/* This class keeps track of the styles we know to
* be in use, and their definitions. StateDocx
* contains an instance of this object.
*
* It is important that if a new style is brought
* into use in the document, it is
* defined in style.xml on the server (if not,
* the style will be stripped when Word loads
* the document!).
*
* On ContentControl Exit, we check the style of
* each paragraph/run (and ultimately table)
* in the content control, and if a new style has
* been brought into use, or the definition of a style
* altered, we report that to the server via the
* web service.
*
* This kind of approach is necessary because it is
* very common for the user to change the style on
* a paragraph without actually entering its content control!
* Indeed, other pPr can be changed as well without entering the CC, eg alignment.
*
* TODO: report all the styles this one is based on,
* as well.
*
* TODO: Updating other clients is a bit more of a challenge,
* since we can't just paste the XML into the document
* (or can we?). One option is to parse the XML, and
* programmatically define a suitable style. Another
* might be to use a context free chunk? Another is
* to serialise the Style object, transmit that
* to the server, and deserialise it on the other
* clients.
*/
public class StylesSnapshot
{
// TODO - consider how this relates to org.docx4all.swing.text.StyleSheet
// and to org.docx4j.openpackaging.parts.WordprocessingML.StyleDefinitionsPart
private static Logger log = LoggerFactory.getLogger(StylesSnapshot.class);
/*
*
<w:styles xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
<w:docDefaults>
:
<w:latentStyles w:defLockedState="0" w:defUIPriority="99" w:defSemiHidden="1" w:defUnhideWhenUsed="1" w:defQFormat="0" w:count="267">
:
<w:lsdException w:name="TOC Heading" w:uiPriority="39" w:qFormat="1" />
</w:latentStyles>
<w:style w:type="paragraph" w:default="1" w:styleId="Normal">
<w:name w:val="Normal" />
:
</w:style>
*/
// Copy of the styles at a point in time
HashMap<String, String> stylesSnapshot = new HashMap<String, String>();
// Live styles - regenerated as necessary
HashMap<String, Style> stylesLive = new HashMap<String, Style>();
// When marshalling, suppress XML declaration
boolean suppressDeclaration = true;
org.docx4j.wml.Styles docxStyles;
public StylesSnapshot(org.docx4j.wml.Styles docxStyles)
{
this.docxStyles = docxStyles;
//System.Diagnostics.Debug.WriteLine("Hello!!");
for (Style s : docxStyles.getStyle() )
{
//log.warn(s.OuterXml);
//log.warn(getStyleName(s));
stylesLive.put(getStyleId(s), s);
stylesSnapshot.put(getStyleId(s), XmlUtils.marshaltoString(s, suppressDeclaration));
}
}
/*public String getStyleName(XmlNode s)
{
//<w:style w:type="paragraph" w:default="1" w:styleId="Normal">
// <w:name w:val="Normal" />
return s.FirstChild.Attributes.GetNamedItem("val", WORDML_NAMESPACE).InnerText;
}*/
public String getStyleId(Style s)
{
//<w:style w:type="paragraph" w:default="1" w:styleId="Normal">
return s.getStyleId();
}
// public XmlNodeList getStyleNodes()
// {
// String oXML = Globals.ThisAddIn.Application.ActiveDocument.WordOpenXML;
// XmlDocument doc = new XmlDocument();
// doc.LoadXml(oXML);
// XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable);
// nsmgr.AddNamespace("w", Namespaces.WORDML_NAMESPACE);
//
// return doc.SelectNodes("//w:style", nsmgr);
// }
// public Style getStyleNode(String stylename)
// {
// return (Style)stylesLive.get(stylename);
//
// }
/* Iterate through the content control looking for any
* styles which have been added to the document, or updated.
* Return a string containing these. */
public String identifyAlteredStyles(SdtBlock cc)
{
// Get the styles in the style definitions part
stylesLive = new HashMap<String, Style>();
for (Style s : docxStyles.getStyle() ) {
stylesLive.put(getStyleId(s), s);
}
// Collect the styles in this SdtBlock
HashMap stylesInUse = new HashMap();
traverseRecursive(cc.getSdtContent().getContent(), stylesInUse);
StringBuilder result = new StringBuilder();
// See if any of them have changed
Iterator stylesInUseIterator = stylesInUse.entrySet().iterator();
while (stylesInUseIterator.hasNext()) {
Map.Entry pairs = (Map.Entry)stylesInUseIterator.next();
String styleName = (String)pairs.getValue();
result.append( identifyAlteredStylesWorker(styleName) );
}
return result.toString();
}
private String identifyAlteredStylesWorker(String styleName)
{
StringBuilder result = new StringBuilder();
try {
Style style = (Style)stylesLive.get(styleName);
String currentStyleXml = XmlUtils.marshaltoString(style, suppressDeclaration);
String cachedStyleXml = (String)stylesSnapshot.get(styleName);
if (cachedStyleXml==null) {
// This is a new style added to the document!
result.append("<!-- New -->");
result.append(currentStyleXml);
stylesSnapshot.put(styleName, currentStyleXml);
//log.warn("New style: " + styleName);
} else if ( !currentStyleXml.equals(cachedStyleXml)) {
// The style definition has changed, so add it to result
result.append("<!-- Updated -->");
result.append(currentStyleXml);
// .. and update the hashmap
stylesSnapshot.put(styleName, currentStyleXml);
// or styleMap.Add(getStyleName(currentStyleNode), currentStyleNode);
// log.warn("Old " + cachedStyleNode.OuterXml + " New " + currentStyleNode.OuterXml);
}
// else no change
}
catch (Exception e) {
e.printStackTrace();
}
return result.toString();
}
void traverseRecursive(List <Object> children, Map stylesInUse){
for (Object o : children ) {
//log.debug("object: " + o.getClass().getName() );
if (o instanceof org.docx4j.wml.P) {
org.docx4j.wml.P p = (org.docx4j.wml.P) o;
if (p.getPPr() != null && p.getPPr().getPStyle() != null) {
// Note this paragraph style
log.debug("put style "
+ p.getPPr().getPStyle().getVal());
stylesInUse.put(p.getPPr().getPStyle().getVal(),
p.getPPr().getPStyle().getVal());
}
if (p.getPPr() != null && p.getPPr().getRPr() != null) {
// Inspect RPr
inspectRPr(p.getPPr().getRPr(), stylesInUse);
}
traverseRecursive(p.getParagraphContent(), stylesInUse);
} else if (o instanceof org.docx4j.wml.SdtContentBlock) {
org.docx4j.wml.SdtBlock sdt = (org.docx4j.wml.SdtBlock) o;
// Don't bother looking in SdtPr
traverseRecursive(sdt.getSdtContent().getContent(),
stylesInUse);
} else if (o instanceof org.docx4j.wml.R) {
org.docx4j.wml.R run = (org.docx4j.wml.R) o;
if (run.getRPr() != null) {
inspectRPr(run.getRPr(), stylesInUse);
}
// don't need to traverse run.getRunContent()
} else if (o instanceof org.w3c.dom.Node) {
// If Xerces is on the path, this will be a org.apache.xerces.dom.NodeImpl;
// otherwise, it will be com.sun.org.apache.xerces.internal.dom.ElementNSImpl;
// Ignore these, eg w:bookmarkStart
log.debug("not traversing into unhandled Node: " + ((org.w3c.dom.Node)o).getNodeName() );
} else if ( o instanceof javax.xml.bind.JAXBElement) {
log.debug( "Encountered " + ((JAXBElement) o).getDeclaredType().getName() );
// if (((JAXBElement) o).getDeclaredType().getName().equals(
// "org.docx4j.wml.P")) {
// org.docx4j.wml.P p = (org.docx4j.wml.P) ((JAXBElement) o)
// .getValue();
//
// if (p.getPPr() != null && p.getPPr().getPStyle() != null) {
// // Note this paragraph style
// log.debug("put style "
// + p.getPPr().getPStyle().getVal());
// stylesInUse.put(p.getPPr().getPStyle().getVal(), p
// .getPPr().getPStyle().getVal());
// }
//
// if (p.getPPr() != null && p.getPPr().getRPr() != null) {
// // Inspect RPr
// inspectRPr(p.getPPr().getRPr(), fontsDiscovered,
// stylesInUse);
// }
//
// traverseMainDocumentRecursive(p.getParagraphContent(),
// fontsDiscovered, stylesInUse);
//
// }
} else {
log.error( "UNEXPECTED: " + o.getClass().getName() );
}
}
}
private void inspectRPr(Object rPrObj, Map stylesInUse) {
if ( rPrObj instanceof org.docx4j.wml.RPr) {
org.docx4j.wml.RPr rPr = (org.docx4j.wml.RPr)rPrObj;
if (rPr.getRStyle()!=null) {
// Note this run style
//log.debug("put style " + rPr.getRStyle().getVal() );
stylesInUse.put(rPr.getRStyle().getVal(), rPr.getRStyle().getVal());
}
} else if ( rPrObj instanceof org.docx4j.wml.ParaRPr) {
org.docx4j.wml.ParaRPr rPr = (org.docx4j.wml.ParaRPr)rPrObj;
if (rPr.getRStyle()!=null) {
// Note this run style
//log.debug("put style " + rPr.getRStyle().getVal() );
stylesInUse.put(rPr.getRStyle().getVal(), rPr.getRStyle().getVal());
}
} else {
log.error("Expected some kind of rPr, not " + rPrObj.getClass().getName() );
}
}
}