package org.plutext.client.state;
import java.io.ByteArrayInputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPathFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.docx4j.openpackaging.exceptions.Docx4JException;
//import org.dom4j.Document;
//import org.dom4j.Element;
//import org.dom4j.Node;
import org.plutext.client.partWrapper.Part;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
public class PartVersionList {
// used to extend Part, and still could readily enough
/*
<pkg:part pkg:name="/customXml/item1.xml" pkg:contentType="application/xml" pkg:padding="32">
OR
*
<pkg:part pkg:name="/part-versions.xml" pkg:contentType="text/xml" xmlns:pkg="http://schemas.microsoft.com/office/2006/xmlPackage">
<pkg:xmlData>
<parts>
<part ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml" name="/docProps/app.xml" version="0" />
<part ContentType="application/vnd.openxmlformats-officedocument.custom-properties+xml" name="/docProps/custom.xml" version="0" />
<part ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.webSettings+xml" name="/word/webSettings.xml" version="0" />
<part ContentType="application/vnd.openxmlformats-officedocument.theme+xml" name="/word/theme/theme1.xml" version="0" />
<part ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.fontTable+xml" name="/word/fontTable.xml" version="0" />
<part ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml" name="/word/styles.xml" version="0" />
<part ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml" name="/word/settings.xml" version="0" />
<part ContentType="application/vnd.openxmlformats-package.core-properties+xml" name="/docProps/core.xml" version="0" />
<part ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml" name="/word/document.xml" version="0" />
<part ContentType="application/vnd.openxmlformats-package.relationships+xml" name="/word/_rels/document.xml.rels" version="0" />
</parts>
</pkg:xmlData>
*/
private static Logger log = LoggerFactory.getLogger(PartVersionList.class);
static List<String> sequenceableParts;
public static List<String> getSequenceableParts()
{
return sequenceableParts;
}
static
{
sequenceableParts = new ArrayList<String>();
sequenceableParts.add("/word/_rels/document.xml.rels");
sequenceableParts.add("/word/comments.xml");
sequenceableParts.add("/word/footnotes.xml");
sequenceableParts.add("/word/endnotes.xml");
}
private static DocumentBuilderFactory documentFactory;
private static DocumentBuilder documentBuilder;
static {
// Crimson doesn't support setTextContent; this.writeDocument also fails.
// We've already worked around the problem with setTextContent,
// but rather than do the same for writeDocument,
// let's just stop using it.
System.setProperty("javax.xml.parsers.DocumentBuilderFactory",
"com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl");
documentFactory = DocumentBuilderFactory.newInstance();
documentFactory.setNamespaceAware(true);
try {
documentBuilder = documentFactory.newDocumentBuilder();
} catch (ParserConfigurationException e) {
e.printStackTrace();
}
}
public PartVersionList()
{ }
public PartVersionList(Document xmlDoc)
{
// NB, this class uses dom4j document, since
// that is what is used in the underlying part.
this.xmlDoc = xmlDoc;
//this.xmlNode = xmlNode;
//log.Debug(xmlNode.OuterXml);
//init(xmlNode);
// We need to be able to remove this part from the
// document (and especially the reference to it
// in document.xml.rels), so that it isn't transmitted and
// persisted. That is done in StateDocx
//name = xmlNode.Attributes.GetNamedItem("name", Namespaces.PKG_NAMESPACE).Value;
}
public PartVersionList(String pvlString)
{
try {
xmlDoc = documentBuilder.parse( new ByteArrayInputStream(pvlString.getBytes("UTF-8")));
} catch (Exception e) {
//throw new Docx4JException("Problems parsing InputStream", e);
e.printStackTrace();
}
}
protected Document xmlDoc;
//protected XmlNode xmlNode;
//protected string name;
/*
* Best to store a record of part version numbers in a suitable
* Dictionary in StateDocx, rather than in the parts themselves,
* since the parts in the document can be newer than the parts
* in StateDocx, and we don't have persistent objects corresponding
* to the current parts in the document.
*
* 20090710, StateDocx stores a copy of this PartVersionList object;
* although it also contains Dictionary<string, Part> parts,
* it is in this PartVersionList object that we store the version
* numbers.
*/
protected HashMap<String, String> versions = new HashMap<String, String>();
/// <summary>
/// List of parts we need to fetch; order is important.
/// If present, document rels, then comments, footnotes,
/// and endnotes are listed first.
/// </summary>
/// <param name="currentLocalPVL"></param>
/// <returns></returns>
public List<String> partsNewerOnServer(PartVersionList currentLocalPVL)
{
List<String> relevantParts = new ArrayList<String>();
Iterator it = versions.keySet().iterator();
while (it.hasNext()) {
String name = (String) it.next();
String serverVersion = (String) versions.get(name);
String localVersion = currentLocalPVL.versions.get(name);
if (localVersion == null) {
// if the partName doesn't exist, then that's because its new,
// so we need it
log.debug(name + " - doesn't exist locally");
relevantParts.add(name);
} else if (Integer.parseInt(localVersion) < Integer
.parseInt(serverVersion)) {
// server version is newer, so we need it
log.debug(name + " - newer on server ");
relevantParts.add(name);
} else {
log.debug(name + " - local version is current ");
}
}
return orderParts(relevantParts);
}
private List<String> orderParts(List<String> relevantParts)
{
List<String> orderedParts = new ArrayList<String>();
// Enforce order: doc rels, comments, footnotes, endnotes
for(String pn : sequenceableParts) {
if (relevantParts.contains(pn)) {
orderedParts.add(pn);
}
}
// Now, the rest
for(String pn : relevantParts) {
if (!sequenceableParts.contains(pn)) {
orderedParts.add(pn);
}
}
return orderedParts;
}
public void setVersion(String partname, String version) {
versions.put(partname, version);
log.debug("setting " + partname + ", v" + version);
}
public String getVersion(String partname) {
return versions.get(partname);
}
// public boolean isPartPresent(String partname)
// {
// // We need this method in order to determine
// // whether we know about the theme part
// // (since getVersion can't tell us).
//
// String x = versions.get(partname);
// if (x==null) {
// log.warn(partname + " not found in PartVersionList");
// return false;
// } else {
// return true;
// }
// }
// initialisation
public void setVersions() {
log.info(xmlDoc.getDocumentElement().getLocalName() );
NodeList nodes;
if (xmlDoc.getDocumentElement().getLocalName().equals("parts"))
{
// xmlDoc from Microsoft.Office.Core.CustomXMLPart (StateDocx)
nodes = xmlDoc.getDocumentElement().getChildNodes();
}
else
{
// xmlDoc from pkg:part
nodes = xmlDoc.getDocumentElement().getFirstChild().getFirstChild().getChildNodes();
}
if (nodes != null) {
for (int i=0; i<nodes.getLength(); i++) {
Node n = (Node)nodes.item(i);
String name = n.getAttributes().getNamedItem("name").getNodeValue();
versions.put(name, n.getAttributes().getNamedItem("version").getNodeValue() );
log.debug("Set version on " + name);
}
}
// If it is a part we are interested in, but not there, it is
// version 0 of course ...
}
/**
* Determine whether this part is one that we update.
* These are our so-called "second" and "third class" citizens and lower castes.
* @param name
* @param contentType
* @return
*/
public static boolean relevant(String name, String contentType)
{
if (sequenceableParts.contains(name))
{
return true;
}
// Header/footer parts
if (contentType.equals("application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml")
|| contentType.equals("application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml"))
{
return true;
}
// Header/footer rels - in fact any rels except the root one,
// (document.xml.rels handled above)s
if (contentType.equals("application/vnd.openxmlformats-package.relationships+xml")
&& !name.equals("/_rels/.rels"))
{
return true;
}
// TODO: styles?, numbering?
// Handled by the below.
// 2009 08 31. A docx created in docx4all contains a minimal set
// of parts. Word will add others to the rels; we need to make
// sure these get transmitted as well.
// .. this code probably not relevant in docx4all, since docx4all
// never updates these parts?
if (name.startsWith("/word/")
&& !name.equals("/word/document.xml"))
{
return true;
}
return false;
}
}