summaryrefslogtreecommitdiffstats
path: root/xmerge/source/xmerge/java/org/openoffice/xmerge/converter
diff options
context:
space:
mode:
Diffstat (limited to 'xmerge/source/xmerge/java/org/openoffice/xmerge/converter')
-rw-r--r--xmerge/source/xmerge/java/org/openoffice/xmerge/converter/dom/DOMDocument.java312
-rw-r--r--xmerge/source/xmerge/java/org/openoffice/xmerge/converter/dom/package-info.java40
-rw-r--r--xmerge/source/xmerge/java/org/openoffice/xmerge/converter/palm/PalmDB.java360
-rw-r--r--xmerge/source/xmerge/java/org/openoffice/xmerge/converter/palm/PalmDocument.java148
-rw-r--r--xmerge/source/xmerge/java/org/openoffice/xmerge/converter/palm/PdbDecoder.java128
-rw-r--r--xmerge/source/xmerge/java/org/openoffice/xmerge/converter/palm/PdbEncoder.java168
-rw-r--r--xmerge/source/xmerge/java/org/openoffice/xmerge/converter/palm/PdbHeader.java145
-rw-r--r--xmerge/source/xmerge/java/org/openoffice/xmerge/converter/palm/PdbUtil.java35
-rw-r--r--xmerge/source/xmerge/java/org/openoffice/xmerge/converter/palm/Record.java176
-rw-r--r--xmerge/source/xmerge/java/org/openoffice/xmerge/converter/palm/package-info.java122
-rw-r--r--xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/EmbeddedBinaryObject.java109
-rw-r--r--xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/EmbeddedObject.java101
-rw-r--r--xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/EmbeddedXMLObject.java280
-rw-r--r--xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/OfficeConstants.java333
-rw-r--r--xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/OfficeDocument.java1113
-rw-r--r--xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/OfficeDocumentException.java112
-rw-r--r--xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/OfficeZip.java424
-rw-r--r--xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/ParaStyle.java528
-rw-r--r--xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/Style.java191
-rw-r--r--xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/StyleCatalog.java284
-rw-r--r--xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/TextStyle.java575
-rw-r--r--xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/package-info.java23
-rw-r--r--xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxc/BookSettings.java208
-rw-r--r--xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxc/CellStyle.java471
-rw-r--r--xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxc/ColumnRowInfo.java180
-rw-r--r--xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxc/ColumnStyle.java244
-rw-r--r--xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxc/DocumentMergerImpl.java183
-rw-r--r--xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxc/Format.java426
-rw-r--r--xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxc/NameDefinition.java205
-rw-r--r--xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxc/RowStyle.java244
-rw-r--r--xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxc/SheetSettings.java346
-rw-r--r--xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxc/SpreadsheetDecoder.java152
-rw-r--r--xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxc/SpreadsheetEncoder.java81
-rw-r--r--xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxc/SxcConstants.java37
-rw-r--r--xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxc/SxcDocument.java80
-rw-r--r--xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxc/SxcDocumentDeserializer.java746
-rw-r--r--xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxc/SxcDocumentSerializer.java885
-rw-r--r--xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxc/SxcPluginFactory.java69
-rw-r--r--xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxc/package-info.java23
-rw-r--r--xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxw/SxwDocument.java80
-rw-r--r--xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxw/SxwPluginFactory.java67
-rw-r--r--xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxw/package-info.java23
-rw-r--r--xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/xslt/ConverterCapabilitiesImpl.java74
-rw-r--r--xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/xslt/DocumentDeserializerImpl.java189
-rw-r--r--xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/xslt/DocumentMergerImpl.java80
-rw-r--r--xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/xslt/DocumentSerializerImpl.java266
-rw-r--r--xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/xslt/GenericOfficeDocument.java81
-rw-r--r--xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/xslt/PluginFactoryImpl.java174
-rw-r--r--xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/xslt/XsltPlugin.properties27
-rw-r--r--xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/xslt/package-info.java58
50 files changed, 11406 insertions, 0 deletions
diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/dom/DOMDocument.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/dom/DOMDocument.java
new file mode 100644
index 000000000..fc2139838
--- /dev/null
+++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/dom/DOMDocument.java
@@ -0,0 +1,312 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+package org.openoffice.xmerge.converter.dom;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.StringWriter;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.stream.StreamResult;
+import javax.xml.transform.dom.DOMSource;
+
+import org.w3c.dom.Node;
+import org.w3c.dom.Document;
+import org.xml.sax.SAXException;
+import org.openoffice.xmerge.util.Debug;
+
+/**
+ * An implementation of {@code Document} for StarOffice documents.
+ */
+public class DOMDocument
+ implements org.openoffice.xmerge.Document {
+
+ /** Factory for {@code DocumentBuilder} objects. */
+ private static DocumentBuilderFactory factory =
+ DocumentBuilderFactory.newInstance();
+
+ /** DOM {@code Document} of content.xml. */
+ private Document contentDoc = null;
+
+ private String documentName = null;
+ private String fileName = null;
+ private String fileExt = null;
+
+ /**
+ * Default constructor.
+ *
+ * @param name {@code Document} name.
+ * @param ext {@code Document} extension.
+ */
+ public DOMDocument(String name,String ext) {
+ this(name,ext,true, false);
+ }
+
+ /**
+ * Returns the file extension of the {@code Document} represented.
+ *
+ * @return file extension of the {@code Document}.
+ */
+ private String getFileExtension() {
+ return fileExt;
+ }
+
+ /**
+ * Constructor with arguments to set {@code namespaceAware} and
+ * {@code validating} flags.
+ *
+ * @param name {@code Document} name (may or may not contain
+ * extension).
+ * @param ext {@code Document} extension.
+ * @param namespaceAware Value for {@code namespaceAware} flag.
+ * @param validating Value for {@code validating} flag.
+ */
+ private DOMDocument(String name, String ext,boolean namespaceAware,
+ boolean validating) {
+
+ factory.setValidating(validating);
+ factory.setNamespaceAware(namespaceAware);
+ this.fileExt = ext;
+ this.documentName = trimDocumentName(name);
+ this.fileName = documentName + getFileExtension();
+ }
+
+ /**
+ * Removes the file extension from the {@code Document} name.
+ *
+ * @param name Full {@code Document} name with extension.
+ *
+ * @return Name of {@code Document} without the extension.
+ */
+ private String trimDocumentName(String name) {
+ String temp = name.toLowerCase();
+ String ext = getFileExtension();
+
+ if (temp.endsWith(ext)) {
+ // strip the extension
+ int nlen = name.length();
+ int endIndex = nlen - ext.length();
+ name = name.substring(0,endIndex);
+ }
+
+ return name;
+ }
+
+ /**
+ * Return a DOM {@code Document} object of the document content file.
+ *
+ * <p>Note that a content DOM is not created when the constructor is called.
+ * So, either the {@code read} method or the {@code initContentDOM} method
+ * will need to be called ahead on this object before calling this method.
+ * </p>
+ *
+ * @return DOM {@code Document} object.
+ */
+ public Document getContentDOM() {
+
+ return contentDoc;
+ }
+
+ /**
+ * Sets the Content of the {@code Document} to the contents of the supplied
+ * {@code Node} list.
+ *
+ * @param newDom DOM {@code Document} object.
+ */
+ public void setContentDOM( Node newDom) {
+ contentDoc=(Document)newDom;
+ }
+
+ /**
+ * Return the name of the {@code Document}.
+ *
+ * @return The name of {@code Document}.
+ */
+ public String getName() {
+
+ return documentName;
+ }
+
+ /**
+ * Return the file name of the {@code Document}, possibly with the standard
+ * extension.
+ *
+ * @return The file name of {@code Document}.
+ */
+ public String getFileName() {
+
+ return fileName;
+ }
+
+ /**
+ * Read the Office {@code Document} from the specified {@code InputStream}.
+ *
+ * @param is Office document {@code InputStream}.
+ *
+ * @throws IOException If any I/O error occurs.
+ */
+ public void read(InputStream is) throws IOException {
+ Debug.log(Debug.INFO, "reading file");
+ try {
+ DocumentBuilder builder = factory.newDocumentBuilder();
+ contentDoc = builder.parse(is);
+ } catch (ParserConfigurationException ex) {
+ System.out.println("Error:"+ ex);
+ } catch (SAXException ex) {
+ System.out.println("Error:"+ ex);
+ }
+ }
+
+ /**
+ * Write out content to the supplied {@code OutputStream}.
+ *
+ * @param os XML {@code OutputStream}.
+ *
+ * @throws IOException If any I/O error occurs.
+ */
+ public void write(OutputStream os) throws IOException {
+
+ // set bytes for writing to output stream
+ byte contentBytes[] = docToBytes(contentDoc);
+
+ os.write(contentBytes);
+ }
+
+ /**
+ * Write out a {@code org.w3c.dom.Document} object into a {@code byte} array.
+ *
+ * <p>TODO: remove dependency on {@code com.sun.xml.tree.XmlDocument} package!
+ * </p>
+ *
+ * @param doc DOM {@code Document} object.
+ *
+ * @return {@code byte} array of DOM {@code Document} object.
+ *
+ * @throws IOException If any I/O error occurs.
+ */
+ private byte[] docToBytes(Document doc)
+ throws IOException {
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+ java.lang.reflect.Constructor<?> con;
+ java.lang.reflect.Method meth;
+
+ String domImpl = doc.getClass().getName();
+
+ System.err.println("type b " + domImpl);
+
+ /*
+ * We may have multiple XML parsers in the Classpath.
+ * Depending on which one is first, the actual type of
+ * doc may vary. Need a way to find out which API is being
+ * used and use an appropriate serialization method.
+ */
+ try {
+ // First of all try for JAXP 1.0
+ if (domImpl.equals("com.sun.xml.tree.XmlDocument")) {
+ System.out.println("Using JAXP");
+ Class<?> jaxpDoc = Class.forName("com.sun.xml.tree.XmlDocument");
+
+ // The method is in the XMLDocument class itself, not a helper
+ meth = jaxpDoc.getMethod("write",
+ new Class[]{Class.forName("java.io.OutputStream")});
+
+ meth.invoke(doc, new Object[]{baos});
+ } else if (domImpl.equals("org.apache.crimson.tree.XmlDocument")) {
+ System.out.println("Using Crimson");
+ Class<?> crimsonDoc = Class.forName("org.apache.crimson.tree.XmlDocument");
+ // The method is in the XMLDocument class itself, not a helper
+ meth = crimsonDoc.getMethod("write",
+ new Class[]{Class.forName("java.io.OutputStream")});
+
+ meth.invoke(doc, new Object[]{baos});
+ } else if (domImpl.equals("org.apache.xerces.dom.DocumentImpl")
+ || domImpl.equals("org.apache.xerces.dom.DeferredDocumentImpl")) {
+ System.out.println("Using Xerces");
+ // Try for Xerces
+ Class<?> xercesSer
+ = Class.forName("org.apache.xml.serialize.XMLSerializer");
+
+ // Get the OutputStream constructor
+ // May want to use the OutputFormat parameter at some stage too
+ con = xercesSer.getConstructor(new Class[]{Class.forName("java.io.OutputStream"),
+ Class.forName("org.apache.xml.serialize.OutputFormat")});
+
+ // Get the serialize method
+ meth = xercesSer.getMethod("serialize",
+ new Class[]{Class.forName("org.w3c.dom.Document")});
+
+ // Get an instance
+ Object serializer = con.newInstance(new Object[]{baos, null});
+
+ // Now call serialize to write the document
+ meth.invoke(serializer, new Object[]{doc});
+ } else if (domImpl.equals("gnu.xml.dom.DomDocument")) {
+ System.out.println("Using GNU");
+
+ Class<?> gnuSer = Class.forName("gnu.xml.dom.ls.DomLSSerializer");
+
+ // Get the serialize method
+ meth = gnuSer.getMethod("serialize",
+ new Class[]{Class.forName("org.w3c.dom.Node"),
+ Class.forName("java.io.OutputStream")});
+
+ // Get an instance
+ Object serializer = gnuSer.newInstance();
+
+ // Now call serialize to write the document
+ meth.invoke(serializer, new Object[]{doc, baos});
+ } else {
+ // We don't have another parser
+ try {
+ DOMSource domSource = new DOMSource(doc);
+ StringWriter writer = new StringWriter();
+ StreamResult result = new StreamResult(writer);
+ TransformerFactory tf = TransformerFactory.newInstance();
+ Transformer transformer = tf.newTransformer();
+ transformer.transform(domSource, result);
+ return writer.toString().getBytes();
+ } catch (Exception e) {
+ // We don't have another parser
+ IOException ex2 = new IOException("No appropriate API (JAXP/Xerces) to serialize XML document: " + domImpl);
+ ex2.initCause(e);
+ throw ex2;
+ }
+ }
+ }
+ catch (Exception e) {
+ // We may get some other errors, but the bottom line is that
+ // the steps being executed no longer work
+ IOException newEx = new IOException(e.getMessage());
+ newEx.initCause(e);
+ throw newEx;
+ }
+
+ byte bytes[] = baos.toByteArray();
+
+ return bytes;
+ }
+}
diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/dom/package-info.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/dom/package-info.java
new file mode 100644
index 000000000..79d5f797f
--- /dev/null
+++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/dom/package-info.java
@@ -0,0 +1,40 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+/**
+ * Provides classes for converting basic document types to/from a
+ * {@code DOMDocument} object, which can be used by the framework.
+ *
+ * <p>This package provides classes that handle the writing of data to an
+ * {@code OutputStream} object for the {@link
+ * org.openoffice.xmerge.DocumentSerializer DocumentSerializer} interface for;
+ * as well as the reading of data from an {@code InputStream} object for the
+ * framework's {@link org.openoffice.xmerge.DocumentDeserializer
+ * DocumentDeserializer} interface. Both these framework interfaces are simply
+ * converters from server-side documents to device specific documents and
+ * vice-versa.</p>
+ *
+ * <a name="streamformat"></a>
+ *
+ * <h2>Important Note</h2>
+ *
+ * <p>Methods in these classes are not thread safe for performance reasons.
+ * Users of these classes will have to make sure that the usage of these classes
+ * are done in a proper manner. Possibly more on this later.</p>
+ */
+package org.openoffice.xmerge.converter.dom;
diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/palm/PalmDB.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/palm/PalmDB.java
new file mode 100644
index 000000000..2f6ca0114
--- /dev/null
+++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/palm/PalmDB.java
@@ -0,0 +1,360 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+package org.openoffice.xmerge.converter.palm;
+
+import java.io.UnsupportedEncodingException;
+
+/**
+ * This class contains data for a single Palm database for use during a
+ * conversion process.
+ *
+ * <p>It contains zero or more {@code Record} objects stored in an array. The
+ * index of the {@code Record} object in the array is the {@code Record} id or
+ * number for that specific {@code Record} object. Note that this class does
+ * not check for maximum number of Records allowable in an actual PDB.</p>
+ *
+ * <p>This class also contains the PDB name associated with the Palm database
+ * it represents. A PDB name consists of 32 bytes of a certain encoding
+ * (extended ASCII in this case).</p>
+ *
+ * <p>The non default constructors take in a name parameter which may not be
+ * the exact PDB name to be used. The name parameter in {@code String} or
+ * {@code byte} array are converted to an exact {@code NAME_LENGTH} byte array.
+ * If the length of the name is less than {@code NAME_LENGTH}, it is padded
+ * with {@code '\0'} characters. If it is more, it gets truncated. The last
+ * character in the resulting byte array is always a {@code '\0'} character.
+ * The resulting byte array is stored in {@code bName}, and a corresponding
+ * {@code String} object {@code sName} that contains characters without the
+ * {@code '\0'} characters.</p>
+ *
+ * <p>The {@code write} method is called within the {@link
+ * org.openoffice.xmerge.converter.palm.PalmDocument#write
+ * PalmDocument.write} method for writing out its data to the
+ * {@code OutputStream} object.</p>
+ *
+ * <p>The {@code read} method is called within the {@link
+ * org.openoffice.xmerge.converter.palm.PalmDocument#read PalmDocument.read}
+ * method for reading in its data from the {@code InputStream} object.</p>
+ *
+ * @see PalmDocument
+ * @see Record
+ */
+
+public final class PalmDB {
+
+ /** Number of bytes for the name field in the PDB. */
+ public static final int NAME_LENGTH = 32;
+
+ /** List of {@code Record} objects. */
+ private Record[] records;
+
+ /** PDB name in bytes. */
+ private byte[] bName = null;
+
+ /** PDB name in String. */
+ private String sName = null;
+
+ /** Creator ID. */
+ private int creatorID = 0;
+
+ /** Type ID */
+ private int typeID = 0;
+
+ /**
+ * PDB version. Palm UInt16.
+ * It is treated as a number here, since there is no unsigned 16 bit in Java,
+ * {@code int} is used instead, but only 2 bytes are written out or read in.
+ */
+ private int version = 0;
+
+ /**
+ * PDB attribute - flags for the database.
+ * Palm UInt16. Unsignedness should be irrelevant.
+ */
+ private short attribute = 0;
+
+ /**
+ * Default constructor.
+ *
+ * @param creatorID The PDB Creator ID.
+ * @param typeID The PDB Type ID.
+ * @param version The PDB header version.
+ * @param attribute The PDB header attribute.
+ */
+ public PalmDB(int creatorID, int typeID, int version, short attribute) {
+ records = new Record[0];
+ setAttributes(creatorID, typeID, version, attribute);
+ }
+
+ /**
+ * Constructor to create {@code PalmDB} object with {@code Record} objects.
+ *
+ * <p>{@code recs.length} can be zero for an empty PDB.</p>
+ *
+ * @param name Suggested PDB name in a {@code String}.
+ * @param creatorID The PDB Creator ID.
+ * @param typeID The PDB Type ID.
+ * @param version The PDB header version.
+ * @param attribute The PDB header attribute.
+ * @param recs Array of {@code Record} objects.
+ *
+ * @throws UnsupportedEncodingException If {@code name} is not properly
+ * encoded.
+ * @throws NullPointerException If {@code recs} is {@code null}.
+ */
+ public PalmDB(String name, int creatorID, int typeID, int version,
+ short attribute, Record[] recs)
+ throws UnsupportedEncodingException {
+
+ this(name.getBytes(PdbUtil.ENCODING), creatorID, typeID, version,
+ attribute, recs);
+ }
+
+ /**
+ * Constructor to create object with {@code Record} objects.
+ *
+ * <p>{@code recs.length} can be zero for an empty PDB.</p>
+ *
+ * @param name Suggested PDB name in a {@code byte} array.
+ * @param creatorID The PDB Creator ID.
+ * @param typeID The PDB Type ID.
+ * @param version The PDB header version.
+ * @param attribute The PDB header attribute.
+ * @param recs Array of {@code Record} objects.
+ *
+ * @throws UnsupportedEncodingException If {@code name} is not properly
+ * encoded.
+ * @throws NullPointerException If {@code recs} is {@code null}.
+ */
+ public PalmDB(byte[] name, int creatorID, int typeID, int version,
+ short attribute, Record[] recs)
+ throws UnsupportedEncodingException {
+
+ store(name);
+
+ records = new Record[recs.length];
+ System.arraycopy(recs, 0, records, 0, recs.length);
+ setAttributes(creatorID, typeID, version, attribute);
+ }
+
+ /**
+ * Set the attributes for the {@code PalmDB} object.
+ *
+ * @param creatorID The PDB Creator ID.
+ * @param typeID The PDB Type ID.
+ * @param version The PDB header version.
+ * @param attribute The PDB header attribute.
+ */
+ private void setAttributes (int creatorID, int typeID, int version,
+ short attribute) {
+ this.creatorID = creatorID;
+ this.typeID = typeID;
+ this.version = version;
+ this.attribute = attribute;
+ }
+
+ /**
+ * This private method is mainly used by the constructors above.
+ *
+ * <p>to store bytes into name and also create a {@code String}
+ * representation, and also by the {@code read} method.</p>
+ *
+ * <p>TODO: Note that this method assumes that the {@code byte} array
+ * parameter contains one character per {@code byte}, else it would
+ * truncate improperly.</p>
+ *
+ * @param bytes PDB name in {@code byte<} array.
+ *
+ * @throws UnsupportedEncodingException If ENCODING is not supported.
+ */
+ private void store(byte[] bytes) throws UnsupportedEncodingException {
+
+ // note that this will initialize all bytes in name to 0.
+ bName = new byte[NAME_LENGTH];
+
+ // determine minimum length to copy over from bytes to bName.
+ // Note that the last byte in bName has to be '\0'.
+
+ int lastIndex = NAME_LENGTH - 1;
+ int len = (bytes.length < lastIndex)? bytes.length: lastIndex;
+
+ int i;
+ for (i = 0; i < len; i++) {
+
+ if (bytes[i] == 0) {
+ break;
+ }
+
+ bName[i] = bytes[i];
+ }
+
+ // set sName, no need to include the '\0' character.
+ sName = new String(bName, 0, i, PdbUtil.ENCODING);
+ }
+
+ /**
+ * Returns creator ID.
+ *
+ * @return The creator ID.
+ */
+ public int getCreatorID() {
+ return creatorID;
+ }
+
+ /**
+ * Returns type ID.
+ *
+ * @return The type ID.
+ */
+ public int getTypeID() {
+ return typeID;
+ }
+
+ /**
+ * Returns attribute flag.
+ *
+ * @return The attribute flag.
+ */
+ public short getAttribute() {
+ return attribute;
+ }
+
+ /**
+ * Returns version.
+ *
+ * @return The version.
+ */
+ public int getVersion() {
+ return version;
+ }
+
+ /**
+ * Return the number of Records contained in this PDB {@code PalmDB} object.
+ *
+ * @return Number of {@code Record} objects.
+ */
+ public int getRecordCount() {
+ return records.length;
+ }
+
+ /**
+ * Return the specific {@code Record} object associated with the
+ * {@code Record} number.
+ *
+ * @param index {@code Record} index number.
+ *
+ * @return The {@code Record} object in the specified index
+ *
+ * @throws ArrayIndexOutOfBoundsException If index is out of bounds.
+ */
+ public Record getRecord(int index) {
+ return records[index];
+ }
+
+ /**
+ * Return the list of {@code Record} objects.
+ *
+ * @return The array of {@code Record} objects.
+ */
+ public Record[] getRecords() {
+ return records;
+ }
+
+ /**
+ * Return the PDB name associated with this object.
+ *
+ * @return The PDB name.
+ */
+ public String getPDBNameString() {
+ return sName;
+ }
+
+ /**
+ * Return the PDB name associated with this object in {@code byte} array of
+ * exact length of 32 bytes.
+ *
+ * @return The PDB name in {@code byte} array of length 32.
+ */
+ public byte[] getPDBNameBytes() {
+ return bName;
+ }
+
+ /**
+ * Override equals method of {@code Object}.
+ *
+ * <p>Two {@code PalmDB} objects are equal if they contain the same
+ * information, i.e. PDB name and Records.</p>
+ *
+ * <p>This is used primarily for testing purposes only for now.</p>
+ *
+ * @param obj A {@code PalmDB} {@code Object} to compare.
+ *
+ * @return {@code true} if {@code obj} is equal to this, otherwise
+ * {@code false}.
+ */
+ @Override
+ public boolean equals(Object obj) {
+
+ boolean bool = false;
+
+ if (obj instanceof PalmDB) {
+
+ PalmDB pdb = (PalmDB) obj;
+
+ checkLabel: {
+
+ // compare sName
+ if (!sName.equals(pdb.sName)) {
+ break checkLabel;
+ }
+
+ // compare bName
+ if (bName.length != pdb.bName.length) {
+ break checkLabel;
+ }
+ for (int i = 0; i < bName.length; i++) {
+ if (bName[i] != pdb.bName[i]) {
+ break checkLabel;
+ }
+ }
+
+ // compare each Record
+ if (records.length != pdb.records.length) {
+ break checkLabel;
+ }
+ for (int i = 0; i < records.length; i++) {
+ if (!records[i].equals(pdb.records[i])) {
+ break checkLabel;
+ }
+ }
+
+ // all checks done
+ bool = true;
+ }
+ }
+
+ return bool;
+ }
+
+ @Override
+ public int hashCode() {
+ return 0;
+ }
+
+}
diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/palm/PalmDocument.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/palm/PalmDocument.java
new file mode 100644
index 000000000..b7fb67bcf
--- /dev/null
+++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/palm/PalmDocument.java
@@ -0,0 +1,148 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+package org.openoffice.xmerge.converter.palm;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ByteArrayOutputStream;
+
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+
+import org.openoffice.xmerge.Document;
+
+/**
+ * A {@code PalmDocument} is palm implementation of the {@code Document}
+ * interface.
+ *
+ * <p>This implementation allows the Palm device format to be read via an
+ * {@code InputStream} and written via an {@code OutputStream}.</p>
+ */
+public class PalmDocument
+ implements Document {
+
+ /** The internal representation of a pdb. */
+ private PalmDB pdb;
+
+ /** The file name. */
+ private String fileName;
+
+ /**
+ * Constructor to create a {@code PalmDocument} from an {@code InputStream}.
+ *
+ * @param is {@code InputStream} containing a PDB.
+ *
+ * @throws IOException If any I/O error occurs.
+ */
+ public PalmDocument(InputStream is) throws IOException {
+ read(is);
+ }
+
+ /**
+ * Constructor to create a {@code PalmDocument} with {@code Record} objects.
+ *
+ * <p>{@code recs.length} can be zero for an empty PDB.</p>
+ *
+ * @param name Suggested PDB name in {@code String}.
+ * @param creatorID The PDB Creator ID.
+ * @param typeID The PDB Type ID.
+ * @param version The PDB header version.
+ * @param attribute The PDB header attribute.
+ * @param recs Array of {@code Record} objects.
+ *
+ * @throws NullPointerException If {@code recs} is {@code null}.
+ */
+ public PalmDocument(String name, int creatorID, int typeID, int version,
+ short attribute, Record[] recs)
+ throws UnsupportedEncodingException {
+ pdb = new PalmDB(name, creatorID, typeID, version, attribute, recs);
+ fileName = pdb.getPDBNameString();
+ }
+
+ /**
+ * Reads in a file from the {@code InputStream}.
+ *
+ * @param is {@code InputStream} to read in its content.
+ *
+ * @throws IOException If any I/O error occurs.
+ */
+ public void read(InputStream is) throws IOException {
+ PdbDecoder decoder = new PdbDecoder();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ byte[] buf = new byte[4096];
+ int n;
+ while ((n = is.read(buf)) > 0) {
+ baos.write(buf, 0, n);
+ }
+ byte[] bytearr = baos.toByteArray();
+ pdb = decoder.parse(bytearr);
+ fileName = pdb.getPDBNameString();
+ }
+
+ /**
+ * Writes the {@code PalmDocument} to an {@code OutputStream}.
+ *
+ * @param os The {@code OutputStream} to write the content.
+ *
+ * @throws IOException If any I/O error occurs.
+ */
+ public void write(OutputStream os) throws IOException {
+ PdbEncoder encoder = new PdbEncoder(pdb);
+ encoder.write(os);
+ }
+
+ /**
+ * Returns the {@code PalmDB} contained in this object.
+ *
+ * @return The {@code PalmDB}.
+ */
+ public PalmDB getPdb() {
+ return pdb;
+ }
+
+ /**
+ * Sets the {@code PalmDocument} to a new {@code PalmDB} value.
+ *
+ * @param pdb The new {@code PalmDB} value.
+ */
+ public void setPdb(PalmDB pdb) {
+ this.pdb = pdb;
+
+ String name = pdb.getPDBNameString();
+ fileName = name;
+ }
+
+ /**
+ * Returns the name of the file.
+ *
+ * @return The name of the file represented in the {@code PalmDocument}.
+ */
+ public String getFileName() {
+ return fileName + ".pdb";
+ }
+
+ /**
+ * Returns the {@code Document} name.
+ *
+ * @return The {@code Document} name.
+ */
+ public String getName() {
+ return fileName;
+ }
+} \ No newline at end of file
diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/palm/PdbDecoder.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/palm/PdbDecoder.java
new file mode 100644
index 000000000..b3fbd4df0
--- /dev/null
+++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/palm/PdbDecoder.java
@@ -0,0 +1,128 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+package org.openoffice.xmerge.converter.palm;
+
+import java.io.IOException;
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+
+/**
+ * Provides functionality to decode a PDB formatted file into a {@code PalmDB}
+ * object given an {@code InputStream}.
+ *
+ * <p>This class is only used by the {@code PalmDB} object.</p>
+ *
+ * <p>Sample usage:</p>
+ * <blockquote><pre>{@code PdbDecoder decoder = new PdbDecoder("sample.pdb");
+ * PalmDB palmDB = decoder.parse();}</pre></blockquote>
+ *
+ * <p>This decoder has the following assumptions on the PDB file:</p>
+ * <ol>
+ * <li>There is only one RecordList section in the PDB.</li>
+ * <li>The {@code Record} indices in the RecordList are sorted in order, i.e.
+ * the first {@code Record} index refers to {@code Record} 0, and so
+ * forth.</li>
+ * <li>The raw {@code Record} in the {@code Record} section are sorted as
+ * well in order, i.e. first {@code Record} comes ahead of second
+ * {@code Record}, etc.</li>
+ * </ol>
+ *
+ * <p>Other decoders assume these as well.</p>
+ *
+ * @see PalmDB
+ * @see Record
+ */
+public final class PdbDecoder {
+
+ /**
+ * This method decodes a PDB file into a {@code PalmDB} object.
+ *
+ * <p>First, the header data is read using the {@code PdbHeader.read}
+ * method. Next, the RecordList section is read and the {@code Record}
+ * offsets are stored for use when parsing the Records. Based on these
+ * offsets, the bytes corresponding to each {@code Record} are read and
+ * each is stored in a {@code Record} object. Lastly, the data is used
+ * to create a {@code PalmDB} object.</p>
+ *
+ * @param b {@code byte[]} containing PDB.
+ *
+ * @throws IOException If I/O error occurs.
+ */
+ public PalmDB parse(byte[] b) throws IOException {
+
+ ByteArrayInputStream bais = new ByteArrayInputStream(b);
+ DataInputStream dis = new DataInputStream(bais);
+
+ // read the PDB header
+ PdbHeader header = new PdbHeader();
+ header.read(dis);
+
+ Record recArray[] = new Record[header.numRecords];
+ if (header.numRecords != 0) {
+
+ // read in the record indices + offsets
+ int recOffset[] = new int[header.numRecords];
+ byte recAttrs[] = new byte[header.numRecords];
+
+ for (int i = 0; i < header.numRecords; i++) {
+
+ recOffset[i] = dis.readInt();
+
+ // read in attributes (1 byte) + unique id (3 bytes)
+ // take away the unique id, store the attributes
+ int attr = dis.readInt();
+ recAttrs[i] = (byte) (attr >>> 24);
+ }
+
+ // read the records
+ int lastIndex = header.numRecords - 1;
+
+ for (int i = 0; i < lastIndex; i++) {
+
+ //dis.seek(recOffset[i]);
+ dis.reset();
+ int nBytesToSkip = recOffset[i];
+ while (nBytesToSkip > 0) {
+ nBytesToSkip -= dis.skip(nBytesToSkip);
+ }
+ int len = recOffset[i+1] - recOffset[i];
+ byte[] bytes = new byte[len];
+ dis.readFully(bytes);
+ recArray[i] = new Record(bytes, recAttrs[i]);
+ }
+
+ // last record
+ dis.reset();
+ int len = dis.available() - recOffset[lastIndex];
+ int nBytesToSkip = recOffset[lastIndex];
+ while (nBytesToSkip > 0) {
+ nBytesToSkip -= dis.skip(nBytesToSkip);
+ }
+ byte[] bytes = new byte[len];
+ dis.readFully(bytes);
+ recArray[lastIndex] = new Record(bytes, recAttrs[lastIndex]);
+ }
+
+ // create PalmDB and return it
+ PalmDB pdb = new PalmDB(header.pdbName, header.creatorID,
+ header.typeID, header.version, header.attribute, recArray);
+
+ return pdb;
+ }
+}
diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/palm/PdbEncoder.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/palm/PdbEncoder.java
new file mode 100644
index 000000000..ba32aed20
--- /dev/null
+++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/palm/PdbEncoder.java
@@ -0,0 +1,168 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+package org.openoffice.xmerge.converter.palm;
+
+import java.io.OutputStream;
+import java.io.BufferedOutputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.util.Date;
+
+/**
+ * Provides functionality to encode a {@code PalmDB} object into a PDB
+ * formatted file given a file {@code OutputStream}.
+ *
+ * <p>This class is only used by the {@code PalmDB} object.</p>
+ *
+ * <p>One needs to create one {@code PdbEncoder} object per {@code PalmDB}
+ * object to be encoded. This class keeps the PDB header data and functionality
+ * in the {@code PdbHeader} class.</p>
+ *
+ * <p>Sample usage:</p>
+ * <blockquote><pre>{@code PdbEncoder encoder = new PdbEncoder(palmDB, "STRW", "data");
+ * encoder.write(new FileOutputStream("sample.pdb"));}</pre></blockquote>
+ *
+ * @see PalmDB
+ * @see Record
+ */
+public final class PdbEncoder {
+
+ /** PDB header. */
+ private final PdbHeader header;
+
+ /** the PalmDB object. */
+ private final PalmDB db;
+
+ /** The pattern for unique_id=0x00BABE(start). */
+ private static final int START_UNIQUE_ID = 0x00BABE;
+
+
+ /**
+ * Constructor.
+ *
+ * @param db The {@code PalmDB} to be encoded.
+ */
+ public PdbEncoder(PalmDB db) {
+
+ header = new PdbHeader();
+ header.version = db.getVersion();
+
+ header.attribute = db.getAttribute();
+
+ this.db = db;
+
+ header.pdbName = db.getPDBNameBytes();
+ header.creatorID = db.getCreatorID();
+ header.typeID = db.getTypeID();
+
+ // set the following dates to current date
+ Date date = new Date();
+ header.creationDate = (date.getTime() / 1000) + PdbUtil.TIME_DIFF;
+ header.modificationDate = header.creationDate;
+
+ header.numRecords = db.getRecordCount();
+ }
+
+ /**
+ * Write out a PDB into the given {@code OutputStream}.
+ *
+ * <p>First, write out the header data by using the {@code PdbHeader.write}
+ * method. Next, calculate the RecordList section and write it out. Lastly,
+ * write out the bytes corresponding to each {@code Record}.</p>
+ *
+ * <p>The RecordList section contains a list of {@code Record} index info,
+ * where each {@code Record} index info contains:</p>
+ *
+ * <ul>
+ * <li>4 bytes local offset of the {@code Record} from the top of the
+ * PDB.</li>
+ * <li>1 byte of {@code Record} attribute.</li>
+ * <li>3 bytes unique {@code Record} ID.</li>
+ * </ul>
+ *
+ * <p>There should be a total of {@code header.numRecords} of {@code Record}
+ * index info.</p>
+ *
+ * @param os {@code OutputStream} to write out PDB.
+ *
+ * @throws IOException If I/O error occurs.
+ */
+ public void write(OutputStream os) throws IOException {
+
+ BufferedOutputStream bos = new BufferedOutputStream(os);
+ DataOutputStream dos = new DataOutputStream(bos);
+
+ // write out the PDB header
+ header.write(dos);
+
+ if (header.numRecords > 0) {
+
+ // compute for recOffset[]
+ int recOffset[] = new int[header.numRecords];
+ byte recAttr[] = new byte[header.numRecords];
+
+ // first recOffset will be at PdbUtil.HEADER_SIZE + all the
+ // record indices (@ 8 bytes each)
+ recOffset[0] = PdbUtil.HEADER_SIZE + (header.numRecords * 8);
+
+ int lastIndex = header.numRecords - 1;
+ for (int i = 0; i < lastIndex; i++) {
+
+ Record rec = db.getRecord(i);
+ int size = rec.getSize();
+ recAttr[i] = rec.getAttributes();
+
+ recOffset[i+1] = recOffset[i] + size;
+ }
+
+ // grab the last record's attribute.
+ Record lastRec = db.getRecord(lastIndex);
+ recAttr[lastIndex] = lastRec.getAttributes();
+
+ int uid = START_UNIQUE_ID;
+ for (int i = 0; i < header.numRecords; i++) {
+
+ // write out each record offset
+ dos.writeInt(recOffset[i]);
+
+ // write out record attribute (recAttr) and
+ // unique ID (uid) in 4 bytes (int) chunk.
+ // unique ID's have to be unique, thus
+ // increment each time.
+ int attr = (recAttr[i] << 24 );
+ attr |= uid;
+ dos.writeInt(attr);
+ uid++;
+ }
+
+ // write out the raw records
+ for (int i = 0; i < header.numRecords; i++) {
+
+ Record rec = db.getRecord(i);
+ byte bytes[] = rec.getBytes();
+ dos.write(bytes);
+ }
+ } else {
+ // placeholder bytes if there are no records in the list.
+ dos.writeShort(0);
+ }
+
+ dos.flush();
+ }
+} \ No newline at end of file
diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/palm/PdbHeader.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/palm/PdbHeader.java
new file mode 100644
index 000000000..280c6e3db
--- /dev/null
+++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/palm/PdbHeader.java
@@ -0,0 +1,145 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+package org.openoffice.xmerge.converter.palm;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+/**
+ * Class used only internally by {@code PdbEncoder} and {@code PdbDecoder} to
+ * store, read and write a PDB header.
+ *
+ * <p>Note that fields are intended to be accessible only at the package
+ * level.</p>
+ *
+ * <p>Some of the fields are internally represented using a larger type since
+ * Java does not have unsigned types. Some are not since they are not relevant
+ * for now. The {@code read} and {@code write} methods should handle them
+ * properly.</p>
+ *
+ * @see PalmDB
+ * @see Record
+ */
+final class PdbHeader {
+
+ /** Name of the database. 32 bytes. */
+ byte[] pdbName = null;
+
+ /**
+ * Flags for the database. Palm UInt16. Unsignedness should be irrelevant.
+ */
+ short attribute = 0;
+
+ /** Application-specific version for the database. Palm UInt16. */
+ int version = 0;
+
+ /** Date created. Palm UInt32. */
+ long creationDate = 0;
+
+ /** Date last modified. Palm UInt32. */
+ long modificationDate = 0;
+
+ /** Date last backup. Palm UInt32. */
+ private long lastBackupDate = 0;
+
+ /**
+ * Incremented every time a {@code Record} is added, deleted or modified.
+ * Palm UInt32.
+ */
+ private long modificationNumber = 0;
+
+ /** Optional field. Palm UInt32. Unsignedness should be irrelevant. */
+ private int appInfoID = 0;
+
+ /** Optional field. Palm UInt32. Unsignedness should be irrelevant. */
+ private int sortInfoID = 0;
+
+ /** Database type ID. Palm UInt32. Unsignedness should be irrelevant. */
+ int typeID = 0;
+
+ /** Database creator ID. Palm UInt32. Unsignedness should be irrelevant. */
+ int creatorID = 0;
+
+ /** ??? */
+ private int uniqueIDSeed = 0;
+
+ /** See numRecords. 4 bytes. */
+ private int nextRecordListID = 0;
+
+ /**
+ * Number of Records stored in the database header.
+ * If all the {@code Record} entries cannot fit in the header, then
+ * {@code nextRecordList} has the local ID of a RecordList that contains
+ * the next set of {@code Record}.
+ * Palm UInt16.
+ */
+ int numRecords = 0;
+
+ /**
+ * Read in the data for the PDB header.
+ *
+ * <p>Need to preserve the unsigned value for some of the fields.</p>
+ *
+ * @param in A {@code DataInput} object.
+ *
+ * @throws IOException If any I/O error occurs.
+ */
+ public void read(DataInput in) throws IOException {
+ pdbName = new byte[PalmDB.NAME_LENGTH];
+ in.readFully(pdbName);
+ attribute = in.readShort();
+ version = in.readUnsignedShort();
+ creationDate = in.readInt() & 0xffffffffL;
+ modificationDate = in.readInt() & 0xffffffffL;
+ lastBackupDate = in.readInt() & 0xffffffffL;
+ modificationNumber = in.readInt() & 0xffffffffL;
+ appInfoID = in.readInt();
+ sortInfoID = in.readInt();
+ creatorID = in.readInt();
+ typeID = in.readInt();
+ uniqueIDSeed = in.readInt();
+ nextRecordListID = in.readInt();
+ numRecords = in.readUnsignedShort();
+ }
+
+ /**
+ * Write out PDB header data.
+ *
+ * @param out A {@code DataOutput} object.
+ *
+ * @throws IOException If any I/O error occurs.
+ */
+ public void write(DataOutput out) throws IOException {
+ out.write(pdbName);
+ out.writeShort(attribute);
+ out.writeShort(version);
+ out.writeInt((int) creationDate);
+ out.writeInt((int) modificationDate);
+ out.writeInt((int) lastBackupDate);
+ out.writeInt((int) modificationNumber);
+ out.writeInt(appInfoID);
+ out.writeInt(sortInfoID);
+ out.writeInt(typeID);
+ out.writeInt(creatorID);
+ out.writeInt(uniqueIDSeed);
+ out.writeInt(nextRecordListID);
+ out.writeShort(numRecords);
+ }
+} \ No newline at end of file
diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/palm/PdbUtil.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/palm/PdbUtil.java
new file mode 100644
index 000000000..829c1c733
--- /dev/null
+++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/palm/PdbUtil.java
@@ -0,0 +1,35 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+package org.openoffice.xmerge.converter.palm;
+
+/**
+ * Contains common static methods and constants for use within the package.
+ */
+public final class PdbUtil {
+
+ /** Difference in seconds from Jan 01, 1904 to Jan 01, 1970. */
+ static final long TIME_DIFF = 2082844800;
+
+ /** Encoding scheme used. */
+ static final String ENCODING = "8859_1";
+
+ /** Size of a PDB header in bytes. */
+ static final int HEADER_SIZE = 78;
+
+} \ No newline at end of file
diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/palm/Record.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/palm/Record.java
new file mode 100644
index 000000000..12d7a0256
--- /dev/null
+++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/palm/Record.java
@@ -0,0 +1,176 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+package org.openoffice.xmerge.converter.palm;
+
+import java.io.OutputStream;
+import java.io.InputStream;
+import java.io.DataOutputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+
+/**
+ * Contains the raw bytes for a {@code Record} in a PDB.
+ *
+ * <p>Note that it is not associated with a {@code Record} number or ID.</p>
+ *
+ * @see PalmDocument
+ * @see PalmDB
+ */
+public final class Record {
+
+ /** {@code Record} {@code byte} array. */
+ private byte[] data;
+
+ /** {@code Record} attributes. */
+ private byte attributes = 0;
+
+ /**
+ * Default constructor.
+ */
+ public Record() {
+ data = new byte[0];
+ }
+
+ /**
+ * Constructor to create a {@code Record} filled with bytes.
+ *
+ * <p>Note that this does not check for 64k {@code Record} sizes. User of
+ * this class must check for that.</p>
+ *
+ * @param d {@code byte} array contents for this object.
+ */
+ public Record(byte[] d) {
+ this(d, (byte) 0);
+ }
+
+ /**
+ * Constructor to create a {@code Record} filled with bytes and assign
+ * {@code Record} attributes.
+ *
+ * <p>Note that this does not check for 64k {@code Record} sizes. User of
+ * this class must check for that.</p>
+ *
+ * @param d {@code byte} array contents for this object.
+ * @param attrs {@code Record} attributes.
+ */
+ public Record(byte[] d, byte attrs) {
+ data = new byte[d.length];
+ attributes = attrs;
+ System.arraycopy(d, 0, data, 0, d.length);
+ }
+
+ /**
+ * This method returns the number of bytes in this object.
+ *
+ * @return Number of bytes in this object.
+ */
+ public int getSize() {
+ return data.length;
+ }
+
+ /**
+ * This method returns the contents of this {@code Object}.
+ *
+ * @return Contents in {@code byte} array
+ */
+ public byte[] getBytes() {
+ return data;
+ }
+
+ /**
+ * <p>This method returns the {@code Record} attributes.</p>
+ *
+ * <blockquote><pre>{@code Record} attributes consists of (from high to low bit)
+ *
+ * delete (1) - dirty (1) - busy (1) - secret (1) - category (4)</pre></blockquote>
+ *
+ * @return {@code Record} attribute.
+ */
+ public byte getAttributes() {
+ return attributes;
+ }
+
+ /**
+ * Write out the {@code Record} attributes and {@code Record} length
+ * followed by the data in this {@code Record} object.
+ *
+ * @param outs The {@code OutputStream} to write the object.
+ *
+ * @throws IOException If any I/O error occurs.
+ */
+ public void write(OutputStream outs) throws IOException {
+ DataOutputStream out = new DataOutputStream(outs);
+ out.writeByte(attributes);
+ out.writeShort(data.length);
+ out.write(data);
+ }
+
+ /**
+ * Read the necessary data to create a PDB from the {@code InputStream}.
+ *
+ * @param ins The {@code InputStream} to read data from in order to
+ * restore the {@code object}.
+ *
+ * @throws IOException If any I/O error occurs.
+ */
+ public void read(InputStream ins) throws IOException {
+ DataInputStream in = new DataInputStream(ins);
+ attributes = in.readByte();
+ int len = in.readUnsignedShort();
+ data = new byte[len];
+ in.readFully(data);
+ }
+
+ /**
+ * Override equals method of {@code Object}.
+ *
+ * <p>Two {@code Record} objects are equal if they contain the same bytes
+ * in the array and the same attributes.</p>
+ *
+ * <p>This is used primarily for testing purposes only for now.</p>
+ *
+ * @param obj A {@code Record} object to compare with
+ *
+ * @return {@code true} if {@code obj} is equal, otherwise {@code false}.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof Record)) {
+ return false;
+ }
+ Record rec = (Record) obj;
+ if (rec.getAttributes() != attributes) {
+ return false;
+ }
+ if (rec.getSize() == data.length) {
+ for (int i = 0; i < data.length; i++) {
+ if (data[i] != rec.data[i]) {
+ return false;
+ }
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return 0;
+ }
+
+}
diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/palm/package-info.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/palm/package-info.java
new file mode 100644
index 000000000..4cc9fcd90
--- /dev/null
+++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/palm/package-info.java
@@ -0,0 +1,122 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+/**
+ * Provides classes for converting Palm database data to/from a
+ * {@code PalmDocument} object, which can be used by the framework.
+ *
+ * <p>This package provides classes that handle the writing of data to an
+ * {@code OutputStream} object for the {@link
+ * org.openoffice.xmerge.DocumentSerializer DocumentSerializer} interface for;
+ * as well as the reading of data from an {@code InputStream} object for the
+ * framework's {@link org.openoffice.xmerge.DocumentDeserializer
+ * DocumentDeserializer} interface. Both these framework interfaces are simply
+ * converters from server-side documents to device specific documents and
+ * vice-versa.
+ * Since all Palm databases have a general record oriented format, a Palm
+ * database converter specific I/O stream format is specified for the Palm sync
+ * client application to handle the byte stream in a generic way.
+ * This also means that Palm database converters should read and/or write using
+ * this I/O stream format as specified in the next section.</p>
+ *
+ * <a name="streamformat"></a>
+ *
+ * <h2>Palm database converter specific I/O stream format</h2>
+ *
+ * <p>Note that the format of the byte stream is not exactly that of a PDB file
+ * encoding. It does not need to contain the PDB header information nor record
+ * indices section. Instead, it contains the following ...</p>
+ * <pre>
+ * set header
+ * 4 bytes - creator id
+ * 4 bytes - type id
+ * 2 bytes - PDB header version
+ * 2 bytes - PDB header attribute
+ * unsigned 2 bytes - number of PDB data to follow
+ *
+ * for each PDB,
+ * 32 bytes - name of PDB i
+ * unsigned 2 bytes - number of records in PDB i
+ *
+ * for each record contained in PDB i,
+ * 1 byte - record attributes
+ * unsigned 2 bytes - size of record j in PDB i
+ * x bytes - data
+ * </pre>
+ *
+ * <p>Note that each PDB section is appended by another if there is more than
+ * one.</p>
+ *
+ * <p>Since the {@code PalmDocument} class takes care of the writing and reading
+ * of this format through its {@code write} and {@code read} methods,
+ * respectively, this format shall also be referred to as the <b>PalmDocument
+ * stream format</b>.</p>
+ *
+ * <h2>Usage of the classes for the specified I/O stream</h2>
+ *
+ * <p>When converting from a server document to device document(s), the
+ * framework requires writing the device document(s) to an {@code OutputStream}
+ * object via the {@code DocumentSerializer} interface. Note that a single
+ * server document may be converted into multiple PDB's on the Palm device.
+ * Each worksheet in the document is converted into a {@code PalmDocument}.
+ * Thus, if there is more than one worksheet in the document, more than one
+ * {@code PalmDocument} will be produced by the {@code DocumentSerializer}.</p>
+ *
+ * <p>A {@code DocumentSerializer} creates a {@code ConvertData} object, which
+ * contains all of the {@code PalmDocuments}. The {@link
+ * org.openoffice.xmerge.converter.palm.PalmDocument#write write} method to
+ * write to the given {@code OutputStream}.
+ * The {@code PalmDocument} object will take care of writing the data in the
+ * <a href=#streamformat>specified format</a>.</p>
+ *
+ * <p>A {@code DocumentDeserializer} can use the {@code PalmDocument} object's
+ * {@link org.openoffice.xmerge.converter.palm.PalmDocument#read read} method
+ * to fill in all the {@code PalmDocument} object's data.</p>
+ *
+ * <h2>PDB file encoding/decoding</h2>
+ *
+ * <p>The {@code PalmDocument} object's read and write functions are provided by
+ * the {@code PdbDecoder} and {@code PdbEncoder} objects.
+ * The {@code PdbEncoder} class provides the functionality of encoding a
+ * {@code PalmDB} object into an {@code InputStream}, while the
+ * {@code PdbDecoder} class provides the functionality of decoding a PDB file
+ * into an {@code OutputStream}.</p>
+ *
+ * <p>Refer to the class description of each for usage.</p>
+ *
+ * <h2>Important Note</h2>
+ *
+ * <p>Methods in these classes are not thread safe for performance reasons.
+ * Users of these classes will have to make sure that the usage of these classes
+ * are done in a proper manner. Possibly more on this later.</p>
+ *
+ * <h2>TODO list</h2>
+ *
+ * <ol>
+ * <li>Merge the PalmDB, PdbDecoder and PdbEncoder classes into the PalmDocument
+ * class.</li>
+ * <li>After reading more on the palm file format spec, I realized that there
+ * are certain optional fields that may need to be addressed still, like the
+ * appInfo block and sortInfo block.</li>
+ * <li>The current PdbDecoder only returns a PalmDB object. There are other
+ * information that we may want to expose from the PDB decoding process.</li>
+ * <li>Investigate on different language encoding on the Palm and how that
+ * affects the PDB name.</li>
+ * </ol>
+ */
+package org.openoffice.xmerge.converter.palm;
diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/EmbeddedBinaryObject.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/EmbeddedBinaryObject.java
new file mode 100644
index 000000000..6ac60f183
--- /dev/null
+++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/EmbeddedBinaryObject.java
@@ -0,0 +1,109 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+package org.openoffice.xmerge.converter.xml;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.DOMException;
+import org.w3c.dom.Element;
+
+/**
+ * This class represents embedded object's in an OpenOffice.org document that
+ * have a binary representation.
+ */
+public class EmbeddedBinaryObject extends EmbeddedObject {
+
+ /** The object's binary representation. */
+ private byte[] objData = null;
+
+ /**
+ * Constructor for an embedded object stored using an XML representation.
+ *
+ * @param name The name of the object.
+ * @param type The mime-type of the object. See the class summary.
+ */
+ public EmbeddedBinaryObject(String name, String type) {
+ super(name, type);
+ }
+
+ /**
+ * Package private constructor for use when reading an object from a
+ * compressed SX? file.
+ *
+ * @param name The name of the object.
+ * @param type The mime-type of the object. See the class summary.
+ * @param source The OfficeZip representation of the SX? file that stores
+ * the object.
+ */
+ EmbeddedBinaryObject(String name, String type, OfficeZip source) {
+ super(name, type, source);
+ }
+
+ /**
+ * This method returns the data for this object.
+ *
+ * @return A {@code byte} array containing the object's data.
+ */
+ public byte[] getBinaryData() {
+
+ // See if we came from a Zip file
+ if (objData == null && zipFile != null) {
+ objData = zipFile.getNamedBytes(objName);
+ }
+
+ return objData;
+ }
+
+ /**
+ * Sets the data for this object.
+ *
+ * @param data A {@code byte} array containing data for the object.
+ */
+ public void setBinaryData(byte[] data) {
+ objData = data;
+ hasChanged = true;
+ }
+
+ /**
+ * Package private method for writing the data of the EmbeddedObject to a
+ * SX? file.
+ *
+ * @param zip An {@code OfficeZip} instance representing the file the
+ * data is to be written to.
+ */
+ @Override
+ void write(OfficeZip zip) {
+ if (hasChanged) {
+ zip.setNamedBytes(objName, objData);
+ }
+ }
+
+ /**
+ * Package private method that constructs the manifest.xml entries for this
+ * embedded object.
+ */
+ @Override
+ void writeManifestData(Document manifestDoc) throws DOMException {
+ Element objNode = manifestDoc.createElement(OfficeConstants.TAG_MANIFEST_FILE);
+
+ objNode.setAttribute(OfficeConstants.ATTRIBUTE_MANIFEST_FILE_TYPE, objType);
+ objNode.setAttribute(OfficeConstants.ATTRIBUTE_MANIFEST_FILE_PATH, objName);
+
+ manifestDoc.getDocumentElement().appendChild(objNode);
+ }
+} \ No newline at end of file
diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/EmbeddedObject.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/EmbeddedObject.java
new file mode 100644
index 000000000..fc05447b2
--- /dev/null
+++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/EmbeddedObject.java
@@ -0,0 +1,101 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+package org.openoffice.xmerge.converter.xml;
+
+import java.io.IOException;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.DOMException;
+
+public abstract class EmbeddedObject {
+ protected String objName;
+ protected String objType;
+
+ /** Representation of the file from which this object was read. */
+ protected OfficeZip zipFile = null;
+
+ /** Flag indicating if this document has changed since reading or is new. */
+ protected boolean hasChanged = false;
+
+ /**
+ * Constructor for an embedded object stored using an XML representation.
+ *
+ * @param name The name of the object.
+ * @param type The mime-type of the object. See the class summary.
+ */
+ public EmbeddedObject(String name, String type) {
+ objName = name;
+ objType = type;
+
+ hasChanged = true;
+ }
+
+ /**
+ * Package private constructor for use when reading an object from a
+ * compressed SX? file.
+ *
+ * @param name The name of the object.
+ * @param type The mime-type of the object. See the class summary.
+ * @param source The OfficeZip representation of the SX? file that stores
+ * the object.
+ */
+ EmbeddedObject(String name, String type, OfficeZip source) {
+ this(name, type);
+ zipFile = source;
+ }
+
+ /**
+ * Retrieves the name of the embedded object represented by an instance of
+ * this class.
+ *
+ * <b>N.B.</b>The name refers to the name as found in the
+ * {@code META-INF/manifest.xml} file.
+ *
+ * @return The name of the object.
+ */
+ public final String getName() {
+ return objName;
+ }
+
+ /**
+ * Retrieves the type of the embedded object represented by an instance of
+ * this class.
+ *
+ * The {@code META-INF/manifest.xml} file currently represents the type of
+ * an object using MIME types.
+ */
+ public final String getType() {
+ return objType;
+ }
+
+ /**
+ * Package private method for writing the data of the EmbeddedObject to a
+ * SX? file.
+ *
+ * @param zip An {@code OfficeZip} instance representing the file the
+ * data is to be written to.
+ */
+ abstract void write(OfficeZip zip) throws IOException;
+
+ /**
+ * Package private method that constructs the manifest.xml entries for this
+ * embedded object.
+ */
+ abstract void writeManifestData(Document manifestDoc) throws DOMException;
+} \ No newline at end of file
diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/EmbeddedXMLObject.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/EmbeddedXMLObject.java
new file mode 100644
index 000000000..b081d6fa1
--- /dev/null
+++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/EmbeddedXMLObject.java
@@ -0,0 +1,280 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+package org.openoffice.xmerge.converter.xml;
+
+import java.io.IOException;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.DOMException;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import org.xml.sax.SAXException;
+
+/**
+ * This class represents those embedded objects in an OpenOffice.org document
+ * that have an XML representation.
+ *
+ * <p>Currently, according to the OpenOffice.org File Format 1.0 document,
+ * there are 6 such objects:</p>
+ * <blockquote><table summary="" border="1" cellpadding="3" cellspacing="0">
+ * <tr>
+ * <th>Description</th><th>Object</th>
+ * </tr><tr>
+ * <td>Formula created with Math</td><td>application/vnd.sun.xml.math</td>
+ * </tr><tr>
+ * <td>Charts created with Chart</td><td>application/vnd.sun.xml.chart</td>
+ * </tr><tr>
+ * <td>Spreadsheets created with Calc</td>
+ * <td>application/vnd.sun.xml.calc</td>
+ * </tr><tr>
+ * <td>Text created with Writer</td><td>application/vnd.sun.xml.writer</td>
+ * </tr><tr>
+ * <td>Drawings created with Draw</td><td>application/vnd.sun.xml.draw</td>
+ * </tr><tr>
+ * <td>Presentations created with Impress</td>
+ * <td>application/vnd.sun.xml.impress</td>
+ * </tr>
+ * </table></blockquote>
+ *
+ * <p>These object types are stored using a combination of content, settings and
+ * styles XML files.</p>
+ */
+public class EmbeddedXMLObject extends EmbeddedObject {
+
+ // Entries for the subdocuments that constitute this object;
+ private Document contentDOM = null;
+ private Document settingsDOM = null;
+ private Document stylesDOM = null;
+
+ private DocumentBuilder builder = null;
+
+ /**
+ * Constructor for an embedded object stored using an XML representation.
+ *
+ * @param name The name of the object.
+ * @param type The mime-type of the object. See the class summary.
+ */
+ public EmbeddedXMLObject(String name, String type) {
+ super(name, type);
+ }
+
+ /**
+ * Package private constructor for use when reading an object from a
+ * compressed SX? file.
+ *
+ * @param name The name of the object.
+ * @param type The mime-type of the object. See the class summary.
+ * @param source The OfficeZip representation of the SX? file that stores
+ * the object.
+ */
+ EmbeddedXMLObject(String name, String type, OfficeZip source) {
+ super(name, type, source);
+ }
+
+ /**
+ * Returns the content data for this embedded object.
+ *
+ * @return DOM representation of "content.xml".
+ *
+ * @throws SAXException If any parser error occurs.
+ * @throws IOException If any IO error occurs.
+ */
+ public Document getContentDOM() throws SAXException, IOException {
+
+ if (contentDOM == null) {
+ contentDOM = getNamedDOM("content.xml");
+ }
+
+ return contentDOM;
+ }
+
+ /**
+ * Sets the content data for the embedded object.
+ *
+ * @param content DOM representation of the object's content.
+ */
+ public void setContentDOM(Document content) {
+ contentDOM = content;
+ hasChanged = true;
+ }
+
+ /**
+ * Returns the settings data for this embedded object.
+ *
+ * @return DOM representation of "settings.xml"
+ *
+ * @throws SAXException If any parser error occurs.
+ * @throws IOException If any IO error occurs.
+ */
+ public Document getSettingsDOM() throws SAXException, IOException {
+
+ if (settingsDOM == null) {
+ settingsDOM = getNamedDOM("settings.xml");
+ }
+
+ return settingsDOM;
+ }
+
+ /**
+ * Sets the settings data for the embedded object.
+ *
+ * @param settings DOM representation of the object's styles.
+ */
+ public void setSettingsDOM(Document settings) {
+ settingsDOM = settings;
+ hasChanged = true;
+ }
+
+ /**
+ * Returns the style data for this embedded object.
+ *
+ * @return DOM representation of "styles.xml".
+ *
+ * @throws SAXException If any parser error occurs.
+ * @throws IOException If any IO error occurs.
+ */
+ public Document getStylesDOM() throws SAXException, IOException {
+
+ if (stylesDOM == null) {
+ stylesDOM = getNamedDOM("styles.xml");
+ }
+
+ return stylesDOM;
+ }
+
+ /**
+ * Sets the styles data for the embedded object.
+ *
+ * @param styles DOM representation of the object's styles.
+ */
+ public void setStylesDOM(Document styles) {
+ stylesDOM = styles;
+ hasChanged = true;
+ }
+
+ /**
+ * This method extracts the data for the given XML file from the SX? file
+ * and creates a DOM representation of it.
+ *
+ * @param name The name of the XML file to retrieve. It is paired with
+ * the object name to access the SX? file.
+ *
+ * @return DOM representation of the named XML file.
+ *
+ * @throws SAXException If any parser error occurs.
+ * @throws IOException If any IO error occurs.
+ */
+ private Document getNamedDOM(String name) throws SAXException, IOException {
+ if (zipFile == null) {
+ return null;
+ }
+
+ try {
+ if (builder == null) {
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+
+ factory.setValidating(false);
+ builder = factory.newDocumentBuilder();
+ }
+
+ byte[] data = zipFile.getNamedBytes((objName + "/" + name));
+ if (data != null) {
+ return OfficeDocument.parse(builder, data);
+ } else {
+ return null;
+ }
+
+ } catch (ParserConfigurationException pce) {
+ throw new SAXException(pce);
+ }
+ }
+
+ /**
+ * Package private method for writing the data of the EmbeddedObject to a
+ * SX? file.
+ *
+ * @param zip An {@code OfficeZip} instance representing the file the data
+ * is to be written to.
+ */
+ @Override
+ void write(OfficeZip zip) throws IOException {
+ if (hasChanged) {
+ if (contentDOM != null) {
+ zip.setNamedBytes((objName + "/content.xml"),
+ OfficeDocument.docToBytes(contentDOM));
+ }
+ if (settingsDOM != null) {
+ zip.setNamedBytes((objName + "/settings.xml"),
+ OfficeDocument.docToBytes(settingsDOM));
+ }
+ if (stylesDOM != null) {
+ zip.setNamedBytes((objName + "/styles.xml"),
+ OfficeDocument.docToBytes(stylesDOM));
+ }
+ }
+ }
+
+ /**
+ * Package private method that constructs the manifest.xml entries for this
+ * embedded object.
+ *
+ * @param manifestDoc {@code Document} containing the manifest entries.
+ */
+ @Override
+ void writeManifestData(Document manifestDoc) throws DOMException {
+ Node root = manifestDoc.getDocumentElement();
+
+ if (contentDOM != null) {
+ Element contentNode = manifestDoc.createElement(OfficeConstants.TAG_MANIFEST_FILE);
+
+ contentNode.setAttribute(OfficeConstants.ATTRIBUTE_MANIFEST_FILE_TYPE, "text/xml");
+ contentNode.setAttribute(OfficeConstants.ATTRIBUTE_MANIFEST_FILE_PATH, (objName + "/content.xml"));
+
+ root.appendChild(contentNode);
+ }
+
+ if (settingsDOM != null) {
+ Element settingsNode = manifestDoc.createElement(OfficeConstants.TAG_MANIFEST_FILE);
+
+ settingsNode.setAttribute(OfficeConstants.ATTRIBUTE_MANIFEST_FILE_TYPE, "text/xml");
+ settingsNode.setAttribute(OfficeConstants.ATTRIBUTE_MANIFEST_FILE_PATH, (objName + "/settings.xml"));
+
+ root.appendChild(settingsNode);
+ }
+
+ if (stylesDOM != null) {
+ Element stylesNode = manifestDoc.createElement(OfficeConstants.TAG_MANIFEST_FILE);
+
+ stylesNode.setAttribute(OfficeConstants.ATTRIBUTE_MANIFEST_FILE_TYPE, "text/xml");
+ stylesNode.setAttribute(OfficeConstants.ATTRIBUTE_MANIFEST_FILE_PATH, (objName + "/styles.xml"));
+ }
+
+ Element objectNode = manifestDoc.createElement(OfficeConstants.TAG_MANIFEST_FILE);
+
+ objectNode.setAttribute(OfficeConstants.ATTRIBUTE_MANIFEST_FILE_TYPE, objType);
+ objectNode.setAttribute(OfficeConstants.ATTRIBUTE_MANIFEST_FILE_PATH, (objName + "/"));
+
+ root.appendChild(objectNode);
+ }
+} \ No newline at end of file
diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/OfficeConstants.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/OfficeConstants.java
new file mode 100644
index 000000000..8605c6ad6
--- /dev/null
+++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/OfficeConstants.java
@@ -0,0 +1,333 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+package org.openoffice.xmerge.converter.xml;
+
+/**
+ * This interface contains constants for StarOffice XML tags, attributes
+ * (StarCalc cell types, etc.).
+ */
+public interface OfficeConstants {
+
+ /** Element tag for <i>office:document</i>, this is the root tag. */
+ String TAG_OFFICE_DOCUMENT = "office:document";
+
+ /**
+ * Element tag for <i>office:document-content</i>, this is the root tag in
+ * content.xml.
+ */
+ String TAG_OFFICE_DOCUMENT_CONTENT = "office:document-content";
+
+ /**
+ * Element tag for <i>office:document-settings</i>, this is the root tag in
+ * content.xml.
+ */
+ String TAG_OFFICE_DOCUMENT_SETTINGS= "office:document-settings";
+
+ /**
+ * Element tag for <i>office:document-meta</i>, this is the root tag in
+ * content.xml.
+ */
+ String TAG_OFFICE_DOCUMENT_META= "office:document-meta";
+
+ /**
+ * Element tag for <i>office:document-styles</i>, this is the root tag in
+ * styles.xml.
+ */
+ String TAG_OFFICE_DOCUMENT_STYLES = "office:document-styles";
+
+ /** Element tag for <i>office:styles</i>. */
+ String TAG_OFFICE_STYLES = "office:styles";
+
+ /** Element tag for <i>office:meta</i>. */
+ String TAG_OFFICE_META = "office:meta";
+
+ /** Element tag for <i>office:automatic-styles</i>. */
+ String TAG_OFFICE_AUTOMATIC_STYLES = "office:automatic-styles";
+
+ /** Element tag for <i>office:master-styles</i>. */
+ String TAG_OFFICE_MASTER_STYLES = "office:master-styles";
+
+ /** Element tag for <i>office:body</i>. */
+ String TAG_OFFICE_BODY = "office:body";
+
+ /** Element tag for <i>office:settings</i>. */
+ String TAG_OFFICE_SETTINGS = "office:settings";
+
+ /** Element tag for <i>office:font-decls</i>. */
+ String TAG_OFFICE_FONT_DECLS = "office:font-decls";
+
+ /** Element tag for <i>style:font-decl</i>. */
+ String TAG_STYLE_FONT_DECL = "style:font-decl";
+
+ /** Attribute tag for <i>style:name</i> of element <i>style:name</i>. */
+ String ATTRIBUTE_STYLE_NAME = "style:name";
+
+ /**
+ * Attribute tag for <i>style:font-pitch</i> of element
+ * <i>style:font-pitch</i>.
+ */
+ String ATTRIBUTE_STYLE_FONT_PITCH = "style:font-pitch";
+
+ /**
+ * Attribute tag for <i>fo:font-family</i> of element
+ * <i>fo:font-family</i>.
+ */
+ String ATTRIBUTE_FO_FONT_FAMILY = "fo:font-family";
+
+ /**
+ * Attribute tag for <i>fo:font-family</i> of element
+ * <i>fo:font-family</i>.
+ */
+ String ATTRIBUTE_FO_FONT_FAMILY_GENERIC = "fo:font-family-generic";
+
+ /** Element tag for <i>text:p</i>. */
+ String TAG_PARAGRAPH = "text:p";
+
+ /** Element tag for <i>text:h</i>. */
+ String TAG_HEADING = "text:h";
+
+ /** Element tag for <i>text:s</i>. */
+ String TAG_SPACE = "text:s";
+
+ /** Element tag for <i>text:tab-stop</i>. */
+ String TAG_TAB_STOP = "text:tab-stop";
+
+ /** Element tag for <i>text:line-break</i>. */
+ String TAG_LINE_BREAK = "text:line-break";
+
+ /** Element tag for <i>text:span</i>. */
+ String TAG_SPAN = "text:span";
+
+ /** Element tag for <i>text:a</i>. */
+ String TAG_HYPERLINK = "text:a";
+
+ /** Element tag for <i>text:unordered-list</i>. */
+ String TAG_UNORDERED_LIST = "text:unordered-list";
+
+ /** Element tag for <i>text:ordered-list</i>. */
+ String TAG_ORDERED_LIST = "text:ordered-list";
+
+ /** Element tag for <i>text:list-header</i>. */
+ String TAG_LIST_HEADER = "text:list-header";
+
+ /** Element tag for <i>text:list-item</i>. */
+ String TAG_LIST_ITEM = "text:list-item";
+
+ /** Attribute tag for <i>text:c</i> of element <i>text:s</i>. */
+ String ATTRIBUTE_SPACE_COUNT = "text:c";
+
+ /** Element tag for <i>table:table</i>. */
+ String TAG_TABLE = "table:table";
+
+ /** Element tag for <i>table:named-expression</i>. */
+ String TAG_NAMED_EXPRESSIONS = "table:named-expressions";
+
+ /** Element tag for <i>table:named-range</i>. */
+ String TAG_TABLE_NAMED_RANGE= "table:named-range";
+
+ /** Element tag for <i>table:named-expression</i>. */
+ String TAG_TABLE_NAMED_EXPRESSION= "table:named-expression";
+
+ /** Attribute tag for <i>table:name</i> of element <i>table:table</i>. */
+ String ATTRIBUTE_TABLE_NAME = "table:name";
+
+ /**
+ * Attribute tag for <i>table:expression</i> of element
+ * <i>table:named-range</i>.
+ */
+ String ATTRIBUTE_TABLE_EXPRESSION = "table:expression";
+
+ /**
+ * Attribute tag for <i>table:base-cell-address</i> of element
+ * <i>table:named-range</i>.
+ */
+ String ATTRIBUTE_TABLE_BASE_CELL_ADDRESS = "table:base-cell-address";
+
+ /**
+ * Attribute tag for <i>table:cell-range-address</i> of element
+ * <i>table:named-range</i>.
+ */
+ String ATTRIBUTE_TABLE_CELL_RANGE_ADDRESS = "table:cell-range-address";
+
+ /** Element tag for <i>table:table-row</i>. */
+ String TAG_TABLE_ROW = "table:table-row";
+
+ /** Element tag for <i>table:table-column</i>. */
+ String TAG_TABLE_COLUMN = "table:table-column";
+
+ /**
+ * Attribute tag for <i>table:default-cell-style-name</i> of element
+ * <i>table:table-column</i>.
+ */
+ String ATTRIBUTE_DEFAULT_CELL_STYLE = "table:default-cell-style-name";
+
+ /** Element tag for <i>table:scenario</i>. */
+ String TAG_TABLE_SCENARIO = "table:scenario";
+
+ /** Element tag for <i>table:table-cell</i>. */
+ String TAG_TABLE_CELL = "table:table-cell";
+
+ /**
+ * Attribute tag for <i>table:value-type</i> of element
+ * <i>table:table-cell</i>.
+ */
+ String ATTRIBUTE_TABLE_VALUE_TYPE = "table:value-type";
+
+ /**
+ * Attribute tag for <i>table:number-columns-repeated</i> of element
+ * <i>table:table-cell</i>.
+ */
+ String ATTRIBUTE_TABLE_NUM_COLUMNS_REPEATED =
+ "table:number-columns-repeated";
+
+ /**
+ * Attribute tag for <i>table:number-rows-repeated</i> of element
+ * <i>table:table-row</i>.
+ */
+ String ATTRIBUTE_TABLE_NUM_ROWS_REPEATED = "table:number-rows-repeated";
+
+ /**
+ * Attribute tag for <i>table:formula</i> of element
+ * <i>table:table-cell</i>.
+ */
+ String ATTRIBUTE_TABLE_FORMULA = "table:formula";
+
+ /**
+ * Attribute tag for <i>table:value</i> of element <i>table:table-cell</i>.
+ */
+ String ATTRIBUTE_TABLE_VALUE = "table:value";
+
+ /**
+ * Attribute tag for <i>table:date-value</i> of element
+ * <i>table:table-cell</i>.
+ */
+ String ATTRIBUTE_TABLE_DATE_VALUE = "table:date-value";
+
+ /**
+ * Attribute tag for <i>table:time-value</i> of element
+ * <i>table:table-cell</i>.
+ */
+ String ATTRIBUTE_TABLE_TIME_VALUE = "table:time-value";
+
+ /**
+ * Attribute tag for <i>table:string-value</i> of element
+ * <i>table:table-cell</i>.
+ */
+ String ATTRIBUTE_TABLE_STRING_VALUE = "table:string-value";
+
+ /**
+ * Attribute tag for <i>table:time-boolean-value</i> of element
+ * <i>table:table-cell</i>.
+ */
+ String ATTRIBUTE_TABLE_BOOLEAN_VALUE = "table:boolean-value";
+
+ /** Attribute tag for <i>table:style-name</i> of table elements. */
+ String ATTRIBUTE_TABLE_STYLE_NAME = "table:style-name";
+
+ /**
+ * Attribute tag for <i>table:currency</i> of element
+ * <i>table:table-cell</i>.
+ */
+ String ATTRIBUTE_TABLE_CURRENCY = "table:currency";
+
+ /** The cell contains data of type <i>string</i>. */
+ String CELLTYPE_STRING = "string";
+
+ /** The cell contains data of type <i>float</i>. */
+ String CELLTYPE_FLOAT = "float";
+
+ /** The cell contains data of type <i>time</i>. */
+ String CELLTYPE_TIME = "time";
+
+ /** The cell contains data of type <i>date</i>. */
+ String CELLTYPE_DATE = "date";
+
+ /** The cell contains data of type <i>currency</i>. */
+ String CELLTYPE_CURRENCY = "currency";
+
+ /** The cell contains data of type <i>boolean</i>. */
+ String CELLTYPE_BOOLEAN = "boolean";
+
+ /** The cell contains data of type <i>percent</i>. */
+ String CELLTYPE_PERCENT = "percentage";
+
+ /** StarWriter XML file extension. */
+ String SXW_FILE_EXTENSION = ".sxw";
+
+ /** StarWriter XML <i>office:class</i> value. */
+ String SXW_TYPE = "text";
+
+ /** StarCalc XML file extension. */
+ String SXC_FILE_EXTENSION = ".sxc";
+
+ /** StarCalc XML <i>office:class</i> value. */
+ String SXC_TYPE = "spreadsheet";
+
+ /** Element tag for <i>manifest:manifest</i>entry in Manifest XML */
+ String TAG_MANIFEST_ROOT = "manifest:manifest";
+
+ /** Element tag for <i>manifest:file-entry</i> entry in Manifest XML. */
+ String TAG_MANIFEST_FILE = "manifest:file-entry";
+
+ /**
+ * Attribute tag for <i>manifest:media-type</i> of element
+ * <i>manifest:file-entry</i>.
+ */
+ String ATTRIBUTE_MANIFEST_FILE_TYPE = "manifest:media-type";
+
+ /**
+ * Attribute tag for <i>manifest:full-path</i> of element
+ * <i>manifest:file-entry</i>.
+ */
+ String ATTRIBUTE_MANIFEST_FILE_PATH = "manifest:full-path";
+
+ // Tags and Elements for the settings.xml
+
+ /** Element tag for <i>config:config-item</i>. */
+ String TAG_CONFIG_ITEM = "config:config-item";
+
+ /** Element tag for <i>config:config-item-set</i>. */
+ String TAG_CONFIG_ITEM_SET = "config:config-item-set";
+
+ /** Element tag for <i>config:config-item-map-indexed</i>. */
+ String TAG_CONFIG_ITEM_MAP_INDEXED = "config:config-item-map-indexed";
+
+ /** Element tag for <i>config:config-item-map-named</i>. */
+ String TAG_CONFIG_ITEM_MAP_NAMED = "config:config-item-map-named";
+
+ /** Element tag for <i>config:config-item-map-entry</i>. */
+ String TAG_CONFIG_ITEM_MAP_ENTRY= "config:config-item-map-entry";
+
+ /**
+ * Attribute tag for <i>config:name</i> of element <i>config:config-item</i>.
+ */
+ String ATTRIBUTE_CONFIG_NAME = "config:name";
+
+ /**
+ * Attribute tag for <i>config:type</i> of element <i>config:config-item</i>.
+ */
+ String ATTRIBUTE_CONFIG_TYPE = "config:type";
+
+ /** StarWriter XML MIME type. */
+ String SXW_MIME_TYPE = "application/vnd.sun.xml.writer";
+
+ /** StarCalc XML MIME type. */
+ String SXC_MIME_TYPE = "application/vnd.sun.xml.calc";
+
+} \ No newline at end of file
diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/OfficeDocument.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/OfficeDocument.java
new file mode 100644
index 000000000..7fb3efe2b
--- /dev/null
+++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/OfficeDocument.java
@@ -0,0 +1,1113 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+package org.openoffice.xmerge.converter.xml;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.BufferedReader;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.io.InputStreamReader;
+import java.io.ByteArrayOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.HashMap;
+
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.w3c.dom.Node;
+import org.w3c.dom.Element;
+import org.w3c.dom.Document;
+import org.w3c.dom.DOMImplementation;
+import org.w3c.dom.DocumentType;
+import org.w3c.dom.NodeList;
+import org.xml.sax.InputSource;
+import org.w3c.dom.NamedNodeMap;
+import org.xml.sax.SAXException;
+
+import javax.xml.transform.*;
+import javax.xml.transform.dom.*;
+import javax.xml.transform.stream.*;
+
+import org.openoffice.xmerge.util.Debug;
+
+/**
+ * An implementation of {@code Document} for StarOffice documents.
+ */
+public abstract class OfficeDocument
+ implements org.openoffice.xmerge.Document, OfficeConstants {
+
+ /** Factory for {@code DocumentBuilder} objects. */
+ private static DocumentBuilderFactory factory =
+ DocumentBuilderFactory.newInstance();
+
+ /** DOM {@code Document} of content.xml. */
+ private Document contentDoc = null;
+
+ /** DOM {@code Document} of meta.xml. */
+ private Document metaDoc = null;
+
+ /** DOM {@code Document} of settings.xml. */
+ private Document settingsDoc = null;
+
+ /** DOM {@code Document} of content.xml. */
+ private Document styleDoc = null;
+
+ /** DOM {@code Document} of @code META-INF/manifest.xml. */
+ private Document manifestDoc = null;
+
+ private String documentName = null;
+ private String fileName = null;
+
+ /**
+ * {@code OfficeZip} object to store zip contents from read
+ * {@code InputStream}.
+ *
+ * <p>Note that this member will still be null if it was initialized using
+ * a template file instead of reading from a StarOffice zipped XML file.</p>
+ */
+ private OfficeZip zip = null;
+
+ /** Collection to keep track of the embedded objects in the document. */
+ private Map<String, EmbeddedObject> embeddedObjects = null;
+
+ /**
+ * Default constructor.
+ *
+ * @param name {@code Document} name.
+ */
+ public OfficeDocument(String name) {
+ this(name, true, false);
+ }
+
+ /**
+ * Constructor with arguments to set {@code namespaceAware} and
+ * {@code validating} flags.
+ *
+ * @param name {@code Document} name (may or may not contain
+ * extension).
+ * @param namespaceAware Value for {@code namespaceAware} flag.
+ * @param validating Value for {@code validating} flag.
+ */
+ public OfficeDocument(String name, boolean namespaceAware, boolean validating) {
+ factory.setValidating(validating);
+ factory.setNamespaceAware(namespaceAware);
+ this.documentName = trimDocumentName(name);
+ this.fileName = documentName + getFileExtension();
+ }
+
+ /**
+ * Removes the file extension from the {@code Document} name.
+ *
+ * @param name Full {@code Document} name with extension.
+ *
+ * @return Name of {@code Document} without the extension.
+ */
+ private String trimDocumentName(String name) {
+ String temp = name.toLowerCase();
+ String ext = getFileExtension();
+
+ if (temp.endsWith(ext)) {
+ // strip the extension
+ int nlen = name.length();
+ int endIndex = nlen - ext.length();
+ name = name.substring(0,endIndex);
+ }
+
+ return name;
+ }
+
+ /**
+ * Return a DOM {@code Document} object of the content.xml file.
+ *
+ * <p>Note that a content DOM is not created when the constructor is called.
+ * So, either the {@code read} method or the {@code initContentDOM} method
+ * will need to be called ahead on this object before calling this method.</p>
+ *
+ * @return DOM {@code Document} object.
+ */
+ public Document getContentDOM() {
+
+ return contentDoc;
+ }
+
+ /**
+ * Return a DOM {@code Document} object of the meta.xml file.
+ *
+ * <p>Note that a content DOM is not created when the constructor is called.
+ * So, either the {@code read} method or the {@code initContentDOM} method
+ * will need to be called ahead on this object before calling this method.</p>
+ *
+ * @return DOM <code>Document</code> object.
+ */
+ public Document getMetaDOM() {
+
+ return metaDoc;
+ }
+
+ /**
+ * Return a DOM {@code Document} object of the settings.xml file.
+ *
+ * <p>Note that a content DOM is not created when the constructor is called.
+ * So, either the {@code read} method or the {@code initContentDOM} method
+ * will need to be called ahead on this object before calling this method.</p>
+ *
+ * @return DOM {@code Document} object.
+ */
+ public Document getSettingsDOM() {
+
+ return settingsDoc;
+ }
+
+ /**
+ * Sets the content tree of the document.
+ *
+ * @param newDom {@code Node} containing the new content tree.
+ */
+ public void setContentDOM( Node newDom) {
+ contentDoc = (Document)newDom;
+ }
+
+ /**
+ * Sets the meta tree of the document.
+ *
+ * @param newDom {@code Node} containing the new meta tree.
+ */
+ public void setMetaDOM (Node newDom) {
+ metaDoc = (Document)newDom;
+ }
+
+ /**
+ * Sets the settings tree of the document.
+ *
+ * @param newDom {@code Node} containing the new settings tree.
+ */
+ public void setSettingsDOM (Node newDom) {
+ settingsDoc = (Document)newDom;
+ }
+
+ /**
+ * Sets the style tree of the document.
+ *
+ * @param newDom {@code Node} containing the new style tree.
+ */
+ public void setStyleDOM (Node newDom) {
+ styleDoc = (Document)newDom;
+ }
+
+ /**
+ * Return a DOM {@code Document} object of the style.xml file.
+ *
+ * <p>Note that this may return {@code null} if there is no style DOM.</p>
+ * <p>Note that a style DOM is not created when the constructor is called.
+ * Depending on the {@code InputStream}, a {@code read} method may or may
+ * not build a style DOM. When creating a new style DOM, call the
+ * {@code initStyleDOM} method first.</p>
+ *
+ * @return DOM {@code Document} object.
+ */
+ public Document getStyleDOM() {
+
+ return styleDoc;
+ }
+
+ /**
+ * Return the name of the {@code Document}.
+ *
+ * @return The name of {@code Document}.
+ */
+ public String getName() {
+
+ return documentName;
+ }
+
+ /**
+ * Return the file name of the {@code Document}, possibly with the standard
+ * extension.
+ *
+ * @return The file name of {@code Document}.
+ */
+ public String getFileName() {
+
+ return fileName;
+ }
+
+ /**
+ * Returns the file extension for this type of {@code Document}.
+ *
+ * @return The file extension of {@code Document}.
+ */
+ protected abstract String getFileExtension();
+
+ /**
+ * Returns all the embedded objects (graphics, formulae, etc.) present in
+ * this document.
+ *
+ * @return An {@code Iterator} of {@code EmbeddedObject} objects.
+ */
+ private Iterator<EmbeddedObject> getEmbeddedObjects() {
+
+ if (embeddedObjects == null && manifestDoc != null) {
+ embeddedObjects = new HashMap<String, EmbeddedObject>();
+
+ // Need to read the manifest file and construct a list of objects
+ NodeList nl = manifestDoc.getElementsByTagName(TAG_MANIFEST_FILE);
+
+ // Don't create the HashMap if there are no embedded objects
+ int len = nl.getLength();
+ for (int i = 0; i < len; i++) {
+ Node n = nl.item(i);
+
+ NamedNodeMap attrs = n.getAttributes();
+
+ String type = attrs.getNamedItem(ATTRIBUTE_MANIFEST_FILE_TYPE).getNodeValue();
+ String path = attrs.getNamedItem(ATTRIBUTE_MANIFEST_FILE_PATH).getNodeValue();
+
+ /*
+ * According to OpenOffice.org XML File Format document (ver. 1)
+ * there are only two types of embedded object:
+ *
+ * Objects with an XML representation.
+ * Objects without an XML representation.
+ *
+ * The former are represented by one or more XML files.
+ * The latter are in binary form.
+ */
+ if (type.startsWith("application/vnd.sun.xml"))
+ {
+ if (path.equals("/")) {
+ // Exclude the main document entries
+ continue;
+ }
+ // Take off the trailing '/'
+ String name = path.substring(0, path.length() - 1);
+ embeddedObjects.put(name, new EmbeddedXMLObject(name, type, zip));
+ }
+ else if (type.equals("text/xml")) {
+ // XML entries are either embedded StarOffice doc entries or main
+ // document entries
+ continue;
+ }
+ else { // FIX (HJ): allows empty MIME type
+ embeddedObjects.put(path, new EmbeddedBinaryObject(path, type, zip));
+ }
+ }
+ }
+
+ if (embeddedObjects == null) {
+ return null;
+ }
+
+ return embeddedObjects.values().iterator();
+ }
+
+ /**
+ * Read the Office {@code Document} from the given {@code InputStream}.
+ *
+ * @param is Office document {@code InputStream}.
+ *
+ * @throws IOException If any I/O error occurs.
+ */
+ public void read(InputStream is) throws IOException {
+
+ Debug.log(Debug.INFO, "reading Office file");
+ DocumentBuilder builder = null;
+
+ try {
+ builder = factory.newDocumentBuilder();
+ } catch (ParserConfigurationException ex) {
+ throw new OfficeDocumentException(ex);
+ }
+
+ // read in Office zip file format
+ zip = new OfficeZip();
+ zip.read(is);
+
+ // grab the content.xml and
+ // parse it into contentDoc.
+ byte contentBytes[] = zip.getContentXMLBytes();
+ if (contentBytes == null) {
+ throw new OfficeDocumentException("Entry content.xml not found in file");
+ }
+ try {
+ contentDoc = parse(builder, contentBytes);
+ } catch (SAXException ex) {
+ throw new OfficeDocumentException(ex);
+ }
+
+ // if style.xml exists, grab the style.xml
+ // parse it into styleDoc.
+ byte styleBytes[] = zip.getStyleXMLBytes();
+ if (styleBytes != null) {
+ try {
+ styleDoc = parse(builder, styleBytes);
+ } catch (SAXException ex) {
+ throw new OfficeDocumentException(ex);
+ }
+ }
+
+ byte metaBytes[] = zip.getMetaXMLBytes();
+ if (metaBytes != null) {
+ try {
+ metaDoc = parse(builder, metaBytes);
+ } catch (SAXException ex) {
+ throw new OfficeDocumentException(ex);
+ }
+ }
+
+ byte settingsBytes[] = zip.getSettingsXMLBytes();
+ if (settingsBytes != null) {
+ try {
+ settingsDoc = parse(builder, settingsBytes);
+
+ } catch (SAXException ex) {
+ throw new OfficeDocumentException(ex);
+ }
+ }
+
+ // Read in the META-INF/manifest.xml file
+ byte manifestBytes[] = zip.getManifestXMLBytes();
+ if (manifestBytes != null) {
+ try {
+ manifestDoc = parse(builder, manifestBytes);
+ } catch (SAXException ex) {
+ throw new OfficeDocumentException(ex);
+ }
+ }
+ }
+
+ /**
+ * Read the Office {@code Document} from the given {@code InputStream}.
+ *
+ * @param is Office document {@code InputStream}.
+ * @param isZip {@code boolean} Identifies whether a file is zipped or not.
+ *
+ * @throws IOException If any I/O error occurs.
+ */
+ public void read(InputStream is, boolean isZip) throws IOException {
+
+ Debug.log(Debug.INFO, "reading Office file");
+
+ DocumentBuilder builder = null;
+
+ try {
+ builder = factory.newDocumentBuilder();
+ } catch (ParserConfigurationException ex) {
+ throw new OfficeDocumentException(ex);
+ }
+
+ if (isZip) {
+ read(is);
+ } else {
+ try {
+ Reader r = secondHack(is);
+ InputSource ins = new InputSource(r);
+ org.w3c.dom.Document newDoc = builder.parse(ins);
+ Element rootElement = newDoc.getDocumentElement();
+
+ NodeList nodeList;
+ Node tmpNode;
+ Node rootNode = rootElement;
+
+ /* content */
+ contentDoc = createDOM(TAG_OFFICE_DOCUMENT_CONTENT);
+ rootElement = contentDoc.getDocumentElement();
+ rootNode = rootElement;
+
+ // FIX (HJ): Include office:font-decls in content DOM
+ nodeList = newDoc
+ .getElementsByTagName(TAG_OFFICE_FONT_DECLS);
+ if (nodeList.getLength() > 0) {
+ tmpNode = contentDoc.importNode(nodeList.item(0), true);
+ rootNode.appendChild(tmpNode);
+ }
+
+ nodeList = newDoc
+ .getElementsByTagName(TAG_OFFICE_AUTOMATIC_STYLES);
+ if (nodeList.getLength() > 0) {
+ tmpNode = contentDoc.importNode(nodeList.item(0), true);
+ rootNode.appendChild(tmpNode);
+ }
+
+ nodeList = newDoc.getElementsByTagName(TAG_OFFICE_BODY);
+ if (nodeList.getLength() > 0) {
+ tmpNode = contentDoc.importNode(nodeList.item(0), true);
+ rootNode.appendChild(tmpNode);
+ }
+
+ /* Styles */
+ styleDoc = createDOM(TAG_OFFICE_DOCUMENT_STYLES);
+ rootElement = styleDoc.getDocumentElement();
+ rootNode = rootElement;
+
+ // FIX (HJ): Include office:font-decls in styles DOM
+ nodeList = newDoc
+ .getElementsByTagName(TAG_OFFICE_FONT_DECLS);
+ if (nodeList.getLength() > 0) {
+ tmpNode = styleDoc.importNode(nodeList.item(0), true);
+ rootNode.appendChild(tmpNode);
+ }
+
+ nodeList = newDoc.getElementsByTagName(TAG_OFFICE_STYLES);
+ if (nodeList.getLength() > 0) {
+ tmpNode = styleDoc.importNode(nodeList.item(0), true);
+ rootNode.appendChild(tmpNode);
+ }
+
+ // FIX (HJ): Include office:automatic-styles in styles DOM
+ nodeList = newDoc
+ .getElementsByTagName(TAG_OFFICE_AUTOMATIC_STYLES);
+ if (nodeList.getLength() > 0) {
+ tmpNode = styleDoc.importNode(nodeList.item(0), true);
+ rootNode.appendChild(tmpNode);
+ }
+
+ // FIX (HJ): Include office:master-styles in styles DOM
+ nodeList = newDoc
+ .getElementsByTagName(TAG_OFFICE_MASTER_STYLES);
+ if (nodeList.getLength() > 0) {
+ tmpNode = styleDoc.importNode(nodeList.item(0), true);
+ rootNode.appendChild(tmpNode);
+ }
+
+ /* Settings */
+ settingsDoc = createDOM(TAG_OFFICE_DOCUMENT_SETTINGS);
+ rootElement = settingsDoc.getDocumentElement();
+ rootNode = rootElement;
+ nodeList = newDoc.getElementsByTagName(TAG_OFFICE_SETTINGS);
+ if (nodeList.getLength() > 0) {
+ tmpNode = settingsDoc
+ .importNode(nodeList.item(0), true);
+ rootNode.appendChild(tmpNode);
+ }
+ /* Meta */
+ metaDoc = createDOM(TAG_OFFICE_DOCUMENT_META);
+ rootElement = metaDoc.getDocumentElement();
+ rootNode = rootElement;
+ nodeList = newDoc.getElementsByTagName(TAG_OFFICE_META);
+ if (nodeList.getLength() > 0) {
+ tmpNode = metaDoc.importNode(nodeList.item(0), true);
+ rootNode.appendChild(tmpNode);
+ }
+ } catch (SAXException ex) {
+ throw new OfficeDocumentException(ex);
+ }
+ }
+
+ }
+
+ /**
+ * Parse given {@code byte} array into a DOM {@code Document} object using
+ * the {@code DocumentBuilder} object.
+ *
+ * @param builder {@code DocumentBuilder} object for parsing.
+ * @param bytes {@code byte} array for parsing.
+ *
+ * @return Resulting DOM {@code Document} object.
+ *
+ * @throws SAXException If any parsing error occurs.
+ */
+ static Document parse(DocumentBuilder builder, byte bytes[])
+ throws SAXException, IOException {
+
+ Document doc = null;
+
+ ByteArrayInputStream is = new ByteArrayInputStream(bytes);
+
+ // TODO: replace hack with a more appropriate fix.
+
+ Reader r = hack(is);
+ InputSource ins = new InputSource(r);
+ doc = builder.parse(ins);
+
+ return doc;
+ }
+
+ /**
+ * Method to return the MIME type of the document.
+ *
+ * @return String The document's MIME type.
+ */
+ protected abstract String getDocumentMimeType();
+
+ /**
+ * Write out Office ZIP file format.
+ *
+ * @param os XML {@code OutputStream}.
+ *
+ * @throws IOException If any I/O error occurs.
+ */
+ public void write(OutputStream os) throws IOException {
+ if (zip == null) {
+ zip = new OfficeZip();
+ }
+
+ initManifestDOM();
+
+ Element domEntry;
+ Element manifestRoot = manifestDoc.getDocumentElement();
+
+ // The EmbeddedObjects come first.
+ Iterator<EmbeddedObject> embObjs = getEmbeddedObjects();
+ if (embObjs != null) {
+ while (embObjs.hasNext()) {
+ EmbeddedObject obj = embObjs.next();
+ obj.writeManifestData(manifestDoc);
+
+ obj.write(zip);
+ }
+ }
+
+ // Add in the entry for the Pictures directory. Always present.
+ domEntry = manifestDoc.createElement(TAG_MANIFEST_FILE);
+ domEntry.setAttribute(ATTRIBUTE_MANIFEST_FILE_PATH, "Pictures/");
+ domEntry.setAttribute(ATTRIBUTE_MANIFEST_FILE_TYPE, "");
+ manifestRoot.appendChild(domEntry);
+
+ // Write content to the Zip file and then write any of the optional
+ // data, if it exists.
+ zip.setContentXMLBytes(docToBytes(contentDoc));
+
+ domEntry = manifestDoc.createElement(TAG_MANIFEST_FILE);
+ domEntry.setAttribute(ATTRIBUTE_MANIFEST_FILE_PATH, "content.xml");
+ domEntry.setAttribute(ATTRIBUTE_MANIFEST_FILE_TYPE, "text/xml");
+
+ manifestRoot.appendChild(domEntry);
+
+ if (styleDoc != null) {
+ zip.setStyleXMLBytes(docToBytes(styleDoc));
+
+ domEntry = manifestDoc.createElement(TAG_MANIFEST_FILE);
+ domEntry.setAttribute(ATTRIBUTE_MANIFEST_FILE_PATH, "styles.xml");
+ domEntry.setAttribute(ATTRIBUTE_MANIFEST_FILE_TYPE, "text/xml");
+ manifestRoot.appendChild(domEntry);
+ }
+
+ if (metaDoc != null) {
+ zip.setMetaXMLBytes(docToBytes(metaDoc));
+
+ domEntry = manifestDoc.createElement(TAG_MANIFEST_FILE);
+ domEntry.setAttribute(ATTRIBUTE_MANIFEST_FILE_PATH, "meta.xml");
+ domEntry.setAttribute(ATTRIBUTE_MANIFEST_FILE_TYPE, "text/xml");
+ manifestRoot.appendChild(domEntry);
+ }
+
+ if (settingsDoc != null) {
+ zip.setSettingsXMLBytes(docToBytes(settingsDoc));
+
+ domEntry = manifestDoc.createElement(TAG_MANIFEST_FILE);
+ domEntry.setAttribute(ATTRIBUTE_MANIFEST_FILE_PATH, "settings.xml");
+ domEntry.setAttribute(ATTRIBUTE_MANIFEST_FILE_TYPE, "text/xml");
+ manifestRoot.appendChild(domEntry);
+ }
+
+ zip.setManifestXMLBytes(docToBytes(manifestDoc));
+
+ zip.write(os);
+ }
+
+ /**
+ * Write out Office ZIP file format.
+ *
+ * @param os XML {@code OutputStream}.
+ * @param isZip {@code boolean}
+ *
+ * @throws IOException If any I/O error occurs.
+ */
+ public void write(OutputStream os, boolean isZip) throws IOException {
+
+ // Create an OfficeZip object if one does not exist.
+ if (isZip){
+ write(os);
+ } else {
+ try {
+ DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
+ DocumentBuilder builder= builderFactory.newDocumentBuilder();
+ DOMImplementation domImpl = builder.getDOMImplementation();
+ domImpl.createDocumentType("office:document","-//OpenOffice.org//DTD OfficeDocument 1.0//EN",null);
+ org.w3c.dom.Document newDoc = domImpl.createDocument("http://openoffice.org/2000/office","office:document",null);
+
+ Element rootElement=newDoc.getDocumentElement();
+ rootElement.setAttribute("xmlns:office","http://openoffice.org/2000/office");
+ rootElement.setAttribute("xmlns:style","http://openoffice.org/2000/style" );
+ rootElement.setAttribute("xmlns:text","http://openoffice.org/2000/text");
+ rootElement.setAttribute("xmlns:table","http://openoffice.org/2000/table");
+
+ rootElement.setAttribute("xmlns:draw","http://openoffice.org/2000/drawing");
+ rootElement.setAttribute("xmlns:fo","http://www.w3.org/1999/XSL/Format" );
+ rootElement.setAttribute("xmlns:xlink","http://www.w3.org/1999/xlink" );
+ rootElement.setAttribute("xmlns:dc","http://purl.org/dc/elements/1.1/" );
+ rootElement.setAttribute("xmlns:meta","http://openoffice.org/2000/meta" );
+ rootElement.setAttribute("xmlns:number","http://openoffice.org/2000/datastyle" );
+ rootElement.setAttribute("xmlns:svg","http://www.w3.org/2000/svg" );
+ rootElement.setAttribute("xmlns:chart","http://openoffice.org/2000/chart" );
+ rootElement.setAttribute("xmlns:dr3d","http://openoffice.org/2000/dr3d" );
+ rootElement.setAttribute("xmlns:math","http://www.w3.org/1998/Math/MathML" );
+ rootElement.setAttribute("xmlns:form","http://openoffice.org/2000/form" );
+ rootElement.setAttribute("xmlns:script","http://openoffice.org/2000/script" );
+ rootElement.setAttribute("xmlns:config","http://openoffice.org/2001/config" );
+ // #i41033# OASIS format needs the "office:class" set.
+ if(getDocumentMimeType().equals(SXC_MIME_TYPE))
+ rootElement.setAttribute("office:class","spreadsheet" );
+ else if(getDocumentMimeType().equals(SXW_MIME_TYPE))
+ rootElement.setAttribute("office:class","text" );
+ rootElement.setAttribute("office:version","1.0");
+
+ NodeList nodeList;
+ Node tmpNode;
+ Node rootNode = rootElement;
+ if (metaDoc !=null) {
+ nodeList= metaDoc.getElementsByTagName(TAG_OFFICE_META);
+ if (nodeList.getLength()>0) {
+ tmpNode = newDoc.importNode(nodeList.item(0),true);
+ rootNode.appendChild(tmpNode);
+ }
+ } if (styleDoc !=null) {
+ nodeList= styleDoc.getElementsByTagName(TAG_OFFICE_STYLES);
+ if (nodeList.getLength()>0){
+ tmpNode = newDoc.importNode(nodeList.item(0),true);
+ rootNode.appendChild(tmpNode);
+ }
+ } if (settingsDoc !=null) {
+ nodeList= settingsDoc.getElementsByTagName(TAG_OFFICE_SETTINGS);
+ if (nodeList.getLength()>0){
+ tmpNode = newDoc.importNode(nodeList.item(0),true);
+ rootNode.appendChild(tmpNode);
+ }
+ } if (contentDoc !=null) {
+ nodeList= contentDoc.getElementsByTagName(TAG_OFFICE_AUTOMATIC_STYLES);
+ if (nodeList.getLength()>0){
+ tmpNode = newDoc.importNode(nodeList.item(0),true);
+ rootNode.appendChild(tmpNode);
+ }
+ nodeList= contentDoc.getElementsByTagName(TAG_OFFICE_BODY);
+ if (nodeList.getLength()>0){
+ tmpNode = newDoc.importNode(nodeList.item(0),true);
+ rootNode.appendChild(tmpNode);
+ }
+ }
+
+ byte contentBytes[] = docToBytes(newDoc);
+ os.write(contentBytes);
+ } catch(Exception exc){
+ System.out.println("\nException in OfficeDocument.write():" +exc);
+ }
+ }
+ }
+
+
+ /**
+ * Write out a {@code org.w3c.dom.Document} object into a {@code byte}
+ * array.
+ *
+ * <p>TODO: remove dependency on {@code com.sun.xml.tree.XmlDocument}
+ * package!</p>
+ *
+ * @param doc DOM {@code Document} object.
+ *
+ * @return {@code byte} array of DOM {@code Document} object.
+ *
+ * @throws IOException If any I/O error occurs.
+ */
+ static byte[] docToBytes(Document doc)
+ throws IOException {
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+ java.lang.reflect.Constructor<?> con;
+ java.lang.reflect.Method meth;
+
+ String domImpl = doc.getClass().getName();
+
+ /*
+ * We may have multiple XML parsers in the Classpath.
+ * Depending on which one is first, the actual type of
+ * doc may vary. Need a way to find out which API is being
+ * used and use an appropriate serialization method.
+ */
+
+ try {
+ // First of all try for JAXP 1.0
+ if (domImpl.equals("com.sun.xml.tree.XmlDocument")) {
+
+ Debug.log(Debug.INFO, "Using JAXP");
+
+ Class<?> jaxpDoc = Class.forName("com.sun.xml.tree.XmlDocument");
+
+ // The method is in the XMLDocument class itself, not a helper
+ meth = jaxpDoc.getMethod("write",
+ new Class[] { Class.forName("java.io.OutputStream") } );
+
+ meth.invoke(doc, new Object [] { baos } );
+ } else if (domImpl.equals("org.apache.crimson.tree.XmlDocument")) {
+ Debug.log(Debug.INFO, "Using Crimson");
+
+ Class<?> crimsonDoc = Class.forName("org.apache.crimson.tree.XmlDocument");
+ // The method is in the XMLDocument class itself, not a helper
+ meth = crimsonDoc.getMethod("write",
+ new Class[] { Class.forName("java.io.OutputStream") } );
+
+ meth.invoke(doc, new Object [] { baos } );
+ } else if (domImpl.equals("org.apache.xerces.dom.DocumentImpl")
+ || domImpl.equals("org.apache.xerces.dom.DeferredDocumentImpl")) {
+
+ Debug.log(Debug.INFO, "Using Xerces");
+
+ // Try for Xerces
+ Class<?> xercesSer =
+ Class.forName("org.apache.xml.serialize.XMLSerializer");
+
+ // Get the OutputStream constructor
+ // May want to use the OutputFormat parameter at some stage too
+ con = xercesSer.getConstructor(new Class []
+ { Class.forName("java.io.OutputStream"),
+ Class.forName("org.apache.xml.serialize.OutputFormat") } );
+
+ // Get the serialize method
+ meth = xercesSer.getMethod("serialize",
+ new Class [] { Class.forName("org.w3c.dom.Document") } );
+
+ // Get an instance
+ Object serializer = con.newInstance(new Object [] { baos, null } );
+
+ // Now call serialize to write the document
+ meth.invoke(serializer, new Object [] { doc } );
+ } else if (domImpl.equals("gnu.xml.dom.DomDocument")) {
+ Debug.log(Debug.INFO, "Using GNU");
+
+ Class<?> gnuSer = Class.forName("gnu.xml.dom.ls.DomLSSerializer");
+
+ // Get the serialize method
+ meth = gnuSer.getMethod("serialize",
+ new Class [] { Class.forName("org.w3c.dom.Node"),
+ Class.forName("java.io.OutputStream") } );
+
+ // Get an instance
+ Object serializer = gnuSer.newInstance();
+
+ // Now call serialize to write the document
+ meth.invoke(serializer, new Object [] { doc, baos } );
+ } else {
+ try {
+ DOMSource domSource = new DOMSource(doc);
+ StringWriter writer = new StringWriter();
+ StreamResult result = new StreamResult(writer);
+ TransformerFactory tf = TransformerFactory.newInstance();
+ Transformer transformer = tf.newTransformer();
+ transformer.transform(domSource, result);
+ return writer.toString().getBytes();
+ } catch (Exception e) {
+ // We don't have another parser
+ IOException newEx = new IOException("No appropriate API (JAXP/Xerces) to serialize XML document: " + domImpl);
+ newEx.initCause(e);
+ throw newEx;
+ }
+ }
+ }
+ catch (Exception e) {
+ // We may get some other errors, but the bottom line is that
+ // the steps being executed no longer work
+ IOException newEx = new IOException(e.getMessage());
+ newEx.initCause(e);
+ throw newEx;
+ }
+
+ byte bytes[] = baos.toByteArray();
+
+ return bytes;
+ }
+
+ /**
+ * Initializes a new DOM {@code Document} with the content containing
+ * minimum OpenOffice XML tags.
+ *
+ * @throws IOException If any I/O error occurs.
+ */
+ public final void initContentDOM() throws IOException {
+
+ contentDoc = createDOM(TAG_OFFICE_DOCUMENT_CONTENT);
+
+ // this is a work-around for a bug in Office6.0 - not really
+ // needed but StarCalc 6.0 will crash without this tag.
+ Element root = contentDoc.getDocumentElement();
+
+ Element child = contentDoc.createElement(TAG_OFFICE_FONT_DECLS);
+ root.appendChild(child);
+
+ child = contentDoc.createElement(TAG_OFFICE_AUTOMATIC_STYLES);
+ root.appendChild(child);
+
+ child = contentDoc.createElement(TAG_OFFICE_BODY);
+ root.appendChild(child);
+ }
+
+ /**
+ * Initializes a new DOM {@code Document} with the content containing
+ * minimum OpenOffice XML tags.
+ *
+ * @throws IOException If any I/O error occurs.
+ */
+ public final void initSettingsDOM() throws IOException {
+
+ settingsDoc = createSettingsDOM(TAG_OFFICE_DOCUMENT_SETTINGS);
+
+ // this is a work-around for a bug in Office6.0 - not really
+ // needed but StarCalc 6.0 will crash without this tag.
+ Element root = settingsDoc.getDocumentElement();
+
+ Element child = settingsDoc.createElement(TAG_OFFICE_SETTINGS);
+ root.appendChild(child);
+ }
+
+ /**
+ * Initializes a new DOM Document with styles containing minimum OpenOffice
+ * XML tags.
+ *
+ * @throws IOException If any I/O error occurs.
+ */
+ public final void initStyleDOM() throws IOException {
+
+ styleDoc = createDOM(TAG_OFFICE_DOCUMENT_STYLES);
+ }
+
+ /**
+ * Creates a new DOM {@code Document} containing minimum OpenOffice XML tags.
+ *
+ * <p>This method uses the subclass {@code getOfficeClassAttribute} method
+ * to get the attribute for <i>office:class</i>.</p>
+ *
+ * @param rootName root name of {@code Document}.
+ *
+ * @throws IOException If any I/O error occurs.
+ */
+ private final Document createSettingsDOM(String rootName) throws IOException {
+
+ Document doc = null;
+
+ try {
+ DocumentBuilder builder = factory.newDocumentBuilder();
+ doc = builder.newDocument();
+ } catch (ParserConfigurationException ex) {
+ throw new OfficeDocumentException(ex);
+ }
+
+ Element root = doc.createElement(rootName);
+ doc.appendChild(root);
+
+ root.setAttribute("xmlns:office", "http://openoffice.org/2000/office");
+ root.setAttribute("xmlns:xlink", "http://openoffice.org/1999/xlink");
+ root.setAttribute("xmlns:config", "http://openoffice.org/2001/config");
+ root.setAttribute("office:version", "1.0");
+
+ return doc;
+ }
+
+ /**
+ * Creates a new DOM {@code Document} containing minimum OpenOffice XML tags.
+ *
+ * <p>This method uses the subclass {@code getOfficeClassAttribute} method
+ * to get the attribute for <i>office:class</i>.</p>
+ *
+ * @param rootName root name of <code>Document</code>.
+ *
+ * @throws IOException If any I/O error occurs.
+ */
+ private final Document createDOM(String rootName) throws IOException {
+
+ Document doc = null;
+
+ try {
+ DocumentBuilder builder = factory.newDocumentBuilder();
+ doc = builder.newDocument();
+ } catch (ParserConfigurationException ex) {
+ throw new OfficeDocumentException(ex);
+ }
+
+ Element root = doc.createElement(rootName);
+ doc.appendChild(root);
+
+ root.setAttribute("xmlns:office", "http://openoffice.org/2000/office");
+ root.setAttribute("xmlns:style", "http://openoffice.org/2000/style");
+ root.setAttribute("xmlns:text", "http://openoffice.org/2000/text");
+ root.setAttribute("xmlns:table", "http://openoffice.org/2000/table");
+ root.setAttribute("xmlns:draw", "http://openoffice.org/2000/drawing");
+ root.setAttribute("xmlns:fo", "http://www.w3.org/1999/XSL/Format");
+ root.setAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
+ root.setAttribute("xmlns:number", "http://openoffice.org/2000/datastyle");
+ root.setAttribute("xmlns:svg", "http://www.w3.org/2000/svg");
+ root.setAttribute("xmlns:chart", "http://openoffice.org/2000/chart");
+ root.setAttribute("xmlns:dr3d", "http://openoffice.org/2000/dr3d");
+ root.setAttribute("xmlns:math", "http://www.w3.org/1998/Math/MathML");
+ root.setAttribute("xmlns:form", "http://openoffice.org/2000/form");
+ root.setAttribute("xmlns:script", "http://openoffice.org/2000/script");
+ root.setAttribute("office:class", getOfficeClassAttribute());
+ root.setAttribute("office:version", "1.0");
+
+ return doc;
+ }
+
+ /**
+ * Return the <i>office:class</i> attribute value.
+ *
+ * @return The attribute value.
+ */
+ protected abstract String getOfficeClassAttribute();
+
+ /**
+ * Hacked code to filter {@literal <!DOCTYPE>} tag before sending stream to
+ * parser.
+ *
+ * <p>This hacked code needs to be changed later on.</p>
+ *
+ * <p>Issue: using current jaxp1.0 parser, there is no way to turn off
+ * processing of dtds. Current set of dtds have bugs, processing them will
+ * throw exceptions.</p>
+ *
+ * <p>This is a simple hack that assumes the whole {@literal <!DOCTYPE>} tag
+ * are all in the same line. This is sufficient for current StarOffice 6.0
+ * generated XML files. Since this hack really needs to go away, I don't
+ * want to spend too much time in making it a perfect hack.</p>
+ *
+ * FIX (HJ): Removed requirement for DOCTYPE to be in one line
+ * FIX (HJ): No longer removes newlines
+ *
+ * @param is {@code InputStream} to be filtered.
+ *
+ * @return Reader value without the {@literal <!DOCTYPE>} tag.
+ *
+ * @throws IOException If any I/O error occurs.
+ */
+ private static Reader hack(InputStream is) throws IOException {
+
+ BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
+ StringBuffer buffer = new StringBuffer();
+
+ String str;
+ while ((str = br.readLine()) != null) {
+
+ int sIndex = str.indexOf("<!DOCTYPE");
+ if (sIndex > -1) {
+
+ buffer.append(str.substring(0, sIndex));
+
+ int eIndex = str.indexOf('>', sIndex + 8 );
+ if (eIndex > -1) {
+
+ buffer.append(str.substring(eIndex + 1, str.length()));
+ // FIX (HJ): Preserve the newline
+ buffer.append("\n");
+
+ } else {
+
+ // FIX (HJ): More than one line. Search for '>' in following lines
+ boolean bOK = false;
+ while ((str = br.readLine())!=null) {
+ eIndex = str.indexOf('>');
+ if (eIndex>-1) {
+ buffer.append(str.substring(eIndex+1));
+ // FIX (HJ): Preserve the newline
+ buffer.append("\n");
+ bOK = true;
+ break;
+ }
+ }
+
+ if (!bOK) { throw new IOException("Invalid XML"); }
+ }
+
+ } else {
+
+ buffer.append(str);
+ // FIX (HJ): Preserve the newline
+ buffer.append("\n");
+ }
+ }
+
+ StringReader r = new StringReader(buffer.toString());
+ return r;
+ }
+
+ /**
+ * Transform the {@code InputStream} to a Reader Stream.
+ *
+ * <p>This hacked code needs to be changed later on.</p>
+ *
+ * <p>Issue: the new oasis input file stream means that the old input stream
+ * fails. see #i33702# </p>
+ *
+ * @param is {@code InputStream} to be filtered.
+ *
+ * @return Reader value of the {@code InputStream()}.
+ *
+ * @throws IOException If any I/O error occurs.
+ */
+ private static Reader secondHack(InputStream is) throws IOException {
+
+ BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
+ char[] charArray = new char[4096];
+ StringBuffer sBuf = new StringBuffer();
+ int n;
+ while ((n=br.read(charArray, 0, charArray.length)) > 0) {
+ sBuf.append(charArray, 0, n);
+ }
+
+ // ensure there is no trailing garbage after the end of the stream.
+ int sIndex = sBuf.lastIndexOf("</office:document>");
+ sBuf.delete(sIndex, sBuf.length());
+ sBuf.append("</office:document>");
+ StringReader r = new StringReader(sBuf.toString());
+ return r;
+ }
+
+ /**
+ * Method to create the initial entries in the manifest.xml file stored
+ * in an SX? file.
+ */
+ private void initManifestDOM() throws IOException {
+
+ try {
+ DocumentBuilder builder = factory.newDocumentBuilder();
+ DOMImplementation domImpl = builder.getDOMImplementation();
+
+ DocumentType docType = domImpl.createDocumentType(TAG_MANIFEST_ROOT,
+ "-//OpenOffice.org//DTD Manifest 1.0//EN",
+ "Manifest.dtd");
+ manifestDoc = domImpl.createDocument("manifest", TAG_MANIFEST_ROOT, docType);
+ } catch (ParserConfigurationException ex) {
+ throw new OfficeDocumentException(ex);
+ }
+
+ // Add the <manifest:manifest> entry
+ Element manifestRoot = manifestDoc.getDocumentElement();
+
+ manifestRoot.setAttribute("xmlns:manifest", "http://openoffice.org/2001/manifest");
+
+ Element docRoot = manifestDoc.createElement(TAG_MANIFEST_FILE);
+
+ docRoot.setAttribute(ATTRIBUTE_MANIFEST_FILE_PATH, "/");
+ docRoot.setAttribute(ATTRIBUTE_MANIFEST_FILE_TYPE, getDocumentMimeType());
+
+ manifestRoot.appendChild(docRoot);
+ }
+}
diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/OfficeDocumentException.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/OfficeDocumentException.java
new file mode 100644
index 000000000..12799065b
--- /dev/null
+++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/OfficeDocumentException.java
@@ -0,0 +1,112 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+package org.openoffice.xmerge.converter.xml;
+
+import java.io.IOException;
+
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+
+import org.openoffice.xmerge.util.Resources;
+
+/**
+ * Used by OfficeDocument to encapsulate exceptions.
+ *
+ * <p>It will add more details to the message string if it is of type
+ * {@code SAXParseException}.</p>
+ */
+public final class OfficeDocumentException extends IOException {
+
+ /**
+ * Constructor, capturing additional information from the {@code SAXException}.
+ *
+ * @param e The {@code SAXException}.
+ */
+ public OfficeDocumentException(SAXException e) {
+ super(constructMessage(e));
+ if (e.getException() != null) {
+ initCause(e.getException());
+ } else {
+ initCause(e);
+ }
+ }
+
+ private static String constructMessage(SAXException e) {
+ StringBuffer message = new StringBuffer();
+ if (e instanceof SAXParseException) {
+ String msgParseError =
+ Resources.getInstance().getString("PARSE_ERROR");
+ String msgLine =
+ Resources.getInstance().getString("LINE");
+ String msgColumn =
+ Resources.getInstance().getString("COLUMN");
+ String msgPublicId =
+ Resources.getInstance().getString("PUBLIC_ID");
+ String msgSystemId =
+ Resources.getInstance().getString("SYSTEM_ID");
+ SAXParseException spe = (SAXParseException) e;
+ message.append(msgParseError);
+ message.append(": ");
+ message.append(msgLine);
+ message.append(": ");
+ message.append(spe.getLineNumber());
+ message.append(", ");
+ message.append(msgColumn);
+ message.append(": ");
+ message.append(spe.getColumnNumber());
+ message.append(", ");
+ message.append(msgSystemId);
+ message.append(": ");
+ message.append(spe.getSystemId());
+ message.append(", ");
+ message.append(msgPublicId);
+ message.append(": ");
+ message.append(spe.getPublicId());
+ message.append("\n");
+ }
+
+ // if there exists an embedded exception
+ Exception ex = e.getException();
+ if (ex != null) {
+ message.append(ex.getMessage());
+ }
+ return message.toString();
+ }
+
+ /**
+ * Constructor, creates exception with provided message.
+ *
+ * @param s Message value for the exception.
+ */
+ public OfficeDocumentException(String s) {
+ super(s);
+ }
+
+ /**
+ * Constructor, creates exception with the message corresponding to the
+ * message value of the provided exception.
+ *
+ * @param e The {@code Exception}.
+ */
+ public OfficeDocumentException(Exception e) {
+ super(e.getMessage());
+ initCause(e);
+ }
+
+} \ No newline at end of file
diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/OfficeZip.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/OfficeZip.java
new file mode 100644
index 000000000..4965885b4
--- /dev/null
+++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/OfficeZip.java
@@ -0,0 +1,424 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+package org.openoffice.xmerge.converter.xml;
+
+import java.util.List;
+import java.util.ListIterator;
+import java.util.LinkedList;
+import java.util.zip.ZipInputStream;
+import java.util.zip.ZipOutputStream;
+import java.util.zip.ZipEntry;
+import java.util.zip.CRC32;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.IOException;
+import java.io.ByteArrayOutputStream;
+
+import org.openoffice.xmerge.util.Debug;
+
+/**
+ * Class used by {@link org.openoffice.xmerge.converter.xml.OfficeDocument
+ * OfficeDocument} to handle reading and writing from a ZIP file, as well as
+ * storing ZIP entries.
+ */
+class OfficeZip {
+
+ /** File name of the XML file in a zipped document. */
+ private static final String CONTENTXML = "content.xml";
+
+ private static final String STYLEXML = "styles.xml";
+ private static final String METAXML = "meta.xml";
+ private static final String SETTINGSXML = "settings.xml";
+ private static final String MANIFESTXML = "META-INF/manifest.xml";
+
+ private static final int BUFFERSIZE = 1024;
+
+ private final List<Entry> entryList;
+
+ private int contentIndex = -1;
+ private int styleIndex = -1;
+ private int metaIndex = -1;
+ private int settingsIndex = -1;
+ private int manifestIndex = -1;
+
+ /** Default constructor. */
+ OfficeZip() {
+
+ entryList = new LinkedList<Entry>();
+ }
+
+ /**
+ * Read each zip entry in the {@code InputStream} object and store in
+ * entryList both the {@code ZipEntry} object as well as the bits of each
+ * entry.
+ *
+ * <p>Call this method before calling the {@code getContentXMLBytes} method
+ * or the {@code getStyleXMLBytes} method.</p>
+ *
+ * <p>Keep track of the {@code CONTENTXML} and {@code STYLEXML} using
+ * {@code contentIndex} and {@code styleIndex}, respectively.</p>
+ *
+ * @param is {@code InputStream} object to read.
+ *
+ * @throws IOException If any I/O error occurs.
+ */
+ void read(InputStream is) throws IOException {
+
+ ZipInputStream zis = new ZipInputStream(is);
+ ZipEntry ze;
+ int i = -1;
+
+ while ((ze = zis.getNextEntry()) != null) {
+
+ String name = ze.getName();
+
+ Debug.log(Debug.TRACE, "reading entry: " + name);
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+ int len;
+ byte buffer[] = new byte[BUFFERSIZE];
+
+ while ((len = zis.read(buffer)) > 0) {
+ baos.write(buffer, 0, len);
+ }
+
+ byte bytes[] = baos.toByteArray();
+ Entry entry = new Entry(ze,bytes);
+
+ entryList.add(entry);
+
+ i++;
+
+ if (name.equalsIgnoreCase(CONTENTXML)) {
+ contentIndex = i;
+ }
+ else if (name.equalsIgnoreCase(STYLEXML)) {
+ styleIndex = i;
+ }
+ else if (name.equalsIgnoreCase(METAXML)) {
+ metaIndex = i;
+ }
+ else if (name.equalsIgnoreCase(SETTINGSXML)) {
+ settingsIndex = i;
+ }
+ else if (name.equalsIgnoreCase(MANIFESTXML)) {
+ manifestIndex = i;
+ }
+
+ }
+
+ zis.close();
+ }
+
+ /**
+ * This method returns the CONTENTXML file in a {@code byte} array.
+ *
+ * <p>It returns null if there is no {@code CONTENTXML} in this zip file.</p>
+ *
+ * @return CONTENTXML in a {@code byte} array.
+ */
+ byte[] getContentXMLBytes() {
+
+ return getEntryBytes(contentIndex);
+ }
+
+ /**
+ * This method returns the {@code STYLEXML} file in a {@code byte} array.
+ *
+ * <p>It returns {@code null} if there is no {@code STYLEXML} in this zip
+ * file.</p>
+ *
+ * @return STYLEXML in a {@code byte} array.
+ */
+ byte[] getStyleXMLBytes() {
+
+ return getEntryBytes(styleIndex);
+ }
+
+ /**
+ * This method returns the METAXML file in a {@code byte} array.
+ *
+ * <p>It returns {@code null} if there is no {@code METAXML} in this zip
+ * file.</p>
+ *
+ * @return METAXML in a {@code byte} array.
+ */
+ byte[] getMetaXMLBytes() {
+ return getEntryBytes(metaIndex);
+ }
+
+ /**
+ * This method returns the {@code SETTINGSXML} file in a {@code byte} array.
+ *
+ * <p>It returns {@code null} if there is no {@code SETTINGSXML} in this zip
+ * file.</p>
+ *
+ * @return SETTINGSXML in a <code>byte</code> array.
+ */
+ byte[] getSettingsXMLBytes() {
+ return getEntryBytes(settingsIndex);
+ }
+
+ /**
+ * This method returns the {@code MANIFESTXML} file in a {@code byte} array.
+ *
+ * <p>It returns {@code null} if there is no {@code MANIFESTXML} in this zip
+ * file.</p>
+ *
+ * @return MANIFESTXML in a {@code byte} array.
+ */
+ byte[] getManifestXMLBytes() {
+ return getEntryBytes(manifestIndex);
+ }
+
+ /**
+ * This method returns the bytes corresponding to the entry named in the
+ * parameter.
+ *
+ * @param name The name of the entry in the Zip file to retrieve.
+ *
+ * @return The data for the named entry in a {@code byte} array or
+ * {@code null} if no entry is found.
+ */
+ byte[] getNamedBytes(String name) {
+
+ // The list is not sorted, and sorting it for a binary search would
+ // invalidate the indices stored for the main files.
+
+ // Could improve performance by caching the name and index when
+ // iterating through the ZipFile in read().
+ for (int i = 0; i < entryList.size(); i++) {
+ Entry e = entryList.get(i);
+
+ if (e.zipEntry.getName().equals(name)) {
+ return getEntryBytes(i);
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * This method sets the bytes for the named entry.
+ *
+ * <p>It searches for a matching entry in the LinkedList. If no entry is
+ * found, a new one is created.</p>
+ *
+ * <p>Writing of data is deferred to {@code setEntryBytes()}.</p>
+ *
+ * @param name The name of the entry to search for.
+ * @param bytes The new data to write.
+ */
+ void setNamedBytes(String name, byte[] bytes) {
+ for (int i = 0; i < entryList.size(); i++) {
+ Entry e = entryList.get(i);
+
+ if (e.zipEntry.getName().equals(name)) {
+ setEntryBytes(i, bytes, name);
+ return;
+ }
+ }
+
+ // If we're here, no entry was found. Call setEntryBytes with an index
+ // of -1 to insert a new entry.
+ setEntryBytes(-1, bytes, name);
+ }
+
+ /**
+ * Used by the {@code getContentXMLBytes} method and the
+ * {@code getStyleXMLBytes} method to return the {@code byte} array from the
+ * corresponding {@code Entry} in the {@code entryList}.
+ *
+ * @param index Index of {@code Entry} object in {@code entryList}.
+ *
+ * @return {@code byte} array associated in that {@code Entry} object or
+ * {@code null}, if there is not such {@code Entry}.
+ */
+ private byte[] getEntryBytes(int index) {
+
+ byte[] bytes = null;
+
+ if (index > -1) {
+ Entry entry = entryList.get(index);
+ bytes = entry.bytes;
+ }
+ return bytes;
+ }
+
+ /**
+ * Set or replace the <code>byte</code> array for the {@code CONTENTXML} file.
+ *
+ * @param bytes {@code byte} array for the {@code CONTENTXML} file.
+ */
+ void setContentXMLBytes(byte bytes[]) {
+
+ contentIndex = setEntryBytes(contentIndex, bytes, CONTENTXML);
+ }
+
+ /**
+ * Set or replace the {@code byte} array for the {@code STYLEXML} file.
+ *
+ * @param bytes {@code byte} array for the {@code STYLEXML} file.
+ */
+ void setStyleXMLBytes(byte bytes[]) {
+
+ styleIndex = setEntryBytes(styleIndex, bytes, STYLEXML);
+ }
+
+ /**
+ * Set or replace the {@code byte} array for the {@code METAXML} file.
+ *
+ * @param bytes {@code byte} array for the {@code METAXML} file.
+ */
+ void setMetaXMLBytes(byte bytes[]) {
+
+ metaIndex = setEntryBytes(metaIndex, bytes, METAXML);
+ }
+
+ /**
+ * Set or replace the {@code byte} array for the {@code SETTINGSXML} file.
+ *
+ * @param bytes {@code byte} array for the {@code SETTINGSXML} file.
+ */
+ void setSettingsXMLBytes(byte bytes[]) {
+
+ settingsIndex = setEntryBytes(settingsIndex, bytes, SETTINGSXML);
+ }
+
+ /**
+ * Set or replace the {@code byte} array for the {@code MANIFESTXML} file.
+ *
+ * @param bytes {@code byte} array for the {@code MANIFESTXML} file.
+ */
+ void setManifestXMLBytes(byte bytes[]) {
+ manifestIndex = setEntryBytes(manifestIndex, bytes, MANIFESTXML);
+ }
+
+ /**
+ * Used by the {@code setContentXMLBytes} method and the
+ * {@code setStyleXMLBytes} to either replace an existing {@code Entry}, or
+ * create a new entry in {@code entryList}.
+ *
+ * <p>If there is an {@code Entry} object within {@code entryList} that
+ * corresponds to the index, replace the {@code ZipEntry} info.</p>
+ *
+ * @param index Index of <code>Entry</code> to modify.
+ * @param bytes <code>Entry</code> value.
+ * @param name Name of <code>Entry</code>.
+ *
+ * @return Index of value added or modified in entryList
+ */
+ private int setEntryBytes(int index, byte bytes[], String name) {
+
+ if (index > -1) {
+ // replace existing entry in entryList
+ Entry entry = entryList.get(index);
+ name = entry.zipEntry.getName();
+ int method = entry.zipEntry.getMethod();
+
+ ZipEntry ze = createZipEntry(name, bytes, method);
+
+ entry.zipEntry = ze;
+ entry.bytes= bytes;
+
+ } else {
+ // add a new entry into entryList
+ ZipEntry ze = createZipEntry(name, bytes, ZipEntry.DEFLATED);
+ Entry entry = new Entry(ze, bytes);
+ entryList.add(entry);
+ index = entryList.size() - 1;
+ }
+
+ return index;
+ }
+
+ /**
+ * Write out the ZIP entries into the {@code OutputStream} object.
+ *
+ * @param os <code>OutputStream</code> object to write ZIP.
+ *
+ * @throws IOException If any ZIP I/O error occurs.
+ */
+ void write(OutputStream os) throws IOException {
+
+ Debug.log(Debug.TRACE, "Writing out the following entries into zip.");
+
+ ZipOutputStream zos = new ZipOutputStream(os);
+
+ ListIterator<Entry> iterator = entryList.listIterator();
+ while (iterator.hasNext()) {
+
+ Entry entry = iterator.next();
+ ZipEntry ze = entry.zipEntry;
+
+ String name = ze.getName();
+
+ Debug.log(Debug.TRACE, "... " + name);
+
+ zos.putNextEntry(ze);
+ zos.write(entry.bytes);
+ }
+
+ zos.close();
+ }
+
+ /**
+ * Creates a {@code ZipEntry} object based on the given parameters.
+ *
+ * @param name Name for the {@code ZipEntry}.
+ * @param bytes {@code byte} array for {@code ZipEntry}.
+ * @param method ZIP method to be used for {@code ZipEntry}.
+ *
+ * @return A {@code ZipEntry} object.
+ */
+ private ZipEntry createZipEntry(String name, byte bytes[], int method) {
+
+ ZipEntry ze = new ZipEntry(name);
+
+ ze.setMethod(method);
+ ze.setSize(bytes.length);
+
+ CRC32 crc = new CRC32();
+ crc.reset();
+ crc.update(bytes);
+ ze.setCrc(crc.getValue());
+
+ ze.setTime(System.currentTimeMillis());
+
+ return ze;
+ }
+
+ /**
+ * This inner class is used as a data structure for holding a {@code ZipEntry}
+ * info and its corresponding bytes.
+ *
+ * <p>These are stored in {@code entryList}.</p>
+ */
+ private static class Entry {
+
+ ZipEntry zipEntry = null;
+ byte bytes[] = null;
+
+ Entry(ZipEntry zipEntry, byte bytes[]) {
+ this.zipEntry = zipEntry;
+ this.bytes = bytes;
+ }
+ }
+} \ No newline at end of file
diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/ParaStyle.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/ParaStyle.java
new file mode 100644
index 000000000..786e82b77
--- /dev/null
+++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/ParaStyle.java
@@ -0,0 +1,528 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+package org.openoffice.xmerge.converter.xml;
+
+import org.w3c.dom.NodeList;
+import org.w3c.dom.Node;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Element;
+
+import org.openoffice.xmerge.util.Debug;
+
+abstract class conversionAlgorithm {
+ abstract int I(String val);
+}
+
+/**
+ * This algorithm expects only values in millimeters, e.g. {@literal "20.3mm"}.
+ */
+class horizSize extends conversionAlgorithm {
+ @Override
+ int I(String value) {
+ if (value.endsWith("mm")) {
+ float size = (float)0.0;
+ String num = value.substring(0, value.length() - 2);
+ try {
+ size = Float.parseFloat(num);
+ } catch (Exception e) {
+ Debug.log(Debug.ERROR, "Error parsing " + value, e);
+ }
+ size *= 100;
+ return (int)size;
+ } else {
+ Debug.log(Debug.ERROR, "Unexpected value (" + value
+ + ") in horizSize.I()");
+ return 0;
+ }
+ }
+}
+
+/**
+ * This algorithm does line height {@literal -} can be either millimeters or a
+ * percentage.
+ */
+class lineHeight extends conversionAlgorithm {
+ @Override
+ int I(String value) {
+ if (value.endsWith("mm")) {
+ float size = (float)0.0;
+ String num = value.substring(0, value.length() - 2);
+ try {
+ size = Float.parseFloat(num);
+ } catch (Exception e) {
+ Debug.log(Debug.ERROR, "Error parsing " + value, e);
+ }
+ size *= 100;
+ return (int)size;
+ } else if (value.endsWith("%")) {
+ float size = (float)0.0;
+ String num = value.substring(0, value.length() - 1);
+ try {
+ size = Float.parseFloat(num);
+ } catch (Exception e) {
+ Debug.log(Debug.ERROR, "Error parsing " + value, e);
+ }
+ int retval = (int) size;
+ retval |= ParaStyle.LH_PCT;
+ return retval;
+ }
+ return 0;
+ }
+}
+
+/**
+ * This class converts alignment values.
+ */
+class alignment extends conversionAlgorithm {
+ @Override
+ int I(String value) {
+ if (value.equals("end"))
+ return ParaStyle.ALIGN_RIGHT;
+ if (value.equals("right"))
+ return ParaStyle.ALIGN_RIGHT;
+ if (value.equals("center"))
+ return ParaStyle.ALIGN_CENTER;
+ if (value.equals("justify"))
+ return ParaStyle.ALIGN_JUST;
+ if (value.equals("justified"))
+ return ParaStyle.ALIGN_JUST;
+ if (value.equals("start"))
+ return ParaStyle.ALIGN_LEFT;
+ if (value.equals("left"))
+ return ParaStyle.ALIGN_LEFT;
+ Debug.log(Debug.ERROR, "Unknown string ("
+ + value + ") in alignment.I()");
+ return ParaStyle.ALIGN_LEFT;
+ }
+}
+
+/**
+ * This class represents a paragraph {@code Style}.
+ *
+ * <blockquote><table summary="Paragraph style attributes and their values"
+ * border="1" cellpadding="3" cellspacing="0">
+ * <caption>Table with all paragraph style attributes and their values</caption>
+ * <tr><th>Attribute</th><th>Value</th></tr>
+ * <tr><td>MARGIN_LEFT</td><td>mm * 100</td></tr>
+ * <tr><td>MARGIN_RIGHT</td><td>mm * 100</td></tr>
+ * <tr><td>MARGIN_TOP</td><td>mm * 100 (space on top of paragraph)</td></tr>
+ * <tr><td>MARGIN_BOTTOM</td><td>mm * 100</td></tr>
+ * <tr><td>TEXT_INDENT</td><td>mm * 100 (first line indent)</td></tr>
+ * <tr>
+ * <td>LINE_HEIGHT</td>
+ * <td>mm * 100, unless or'ed with LH_PCT, in which case it is a percentage
+ * (e.g. 200% for double spacing) Can also be or'ed with LH_ATLEAST.
+ * Value is stored in bits indicated by LH_VALUEMASK.</td>
+ * </tr>
+ * <tr>
+ * <td>TEXT_ALIGN</td>
+ * <td>ALIGN_RIGHT, ALIGN_CENTER, ALIGN_JUST, ALIGN_LEFT</td>
+ * </tr>
+ * </table></blockquote>
+ */
+public class ParaStyle extends Style implements Cloneable {
+
+ /** Indent left property. */
+ private static final int TEXT_INDENT = 4;
+ /** Indent right property. */
+ private static final int LINE_HEIGHT = 5;
+ /** Align text property. */
+ private static final int TEXT_ALIGN = 6;
+ // This must always be one more than highest property
+ /** Total number of properties. */
+ private static final int NR_PROPERTIES = 7;
+
+ /**
+ * Array of flags indicating which attributes are set for this paragraph
+ * {@code Style}.
+ */
+ private boolean isSet[] = new boolean[NR_PROPERTIES];
+ /** Array of attribute values for this paragraph {@code Style}. */
+ private int value[] = new int[NR_PROPERTIES];
+ /** Array of attribute names for this paragraph {@code Style}. */
+ private final String attrName[] = {
+ "fo:margin-left",
+ "fo:margin-right",
+ "fo:margin-top",
+ "fo:margin-bottom",
+ "fo:text-indent",
+ "fo:line-height",
+ "fo:text-align"
+ };
+
+ /** Array of attribute structures for this paragraph {@code Style}. */
+ private final Class<?> algor[] = {
+ horizSize.class,
+ horizSize.class,
+ horizSize.class,
+ horizSize.class,
+ horizSize.class,
+ lineHeight.class,
+ alignment.class
+ };
+
+ /** Align right. */
+ public static final int ALIGN_RIGHT = 1;
+ /** Align center. */
+ public static final int ALIGN_CENTER = 2;
+ /** Align justified. */
+ public static final int ALIGN_JUST = 3;
+ /** Align left. */
+ public static final int ALIGN_LEFT = 4;
+
+ /** Line height percentage. */
+ public static final int LH_PCT = 0x40000000;
+
+ /** Line height mask. */
+ private static final int LH_VALUEMASK = 0x00FFFFFF;
+
+ /** Ignored tags. */
+ private static String[] ignored = {
+ "style:font-name", "fo:font-size", "fo:font-weight", "fo:color",
+ "fo:language", "fo:country", "style:font-name-asian",
+ "style:font-size-asian", "style:language-asian",
+ "style:country-asian", "style:font-name-complex",
+ "style:font-size-complex", "style:language-complex",
+ "style:country-complex", "style:text-autospace", "style:punctuation-wrap",
+ "style:line-break", "fo:keep-with-next", "fo:font-style",
+ "text:number-lines", "text:line-number"
+ };
+
+ /**
+ * Constructor for use when going from DOM to client device format.
+ *
+ * @param node A <i>style:style</i> {@code Node} which, which is assumed
+ * to have <i>family</i> attribute of <i>paragraph</i>.
+ * @param sc The {@code StyleCatalog}, which is used for looking up
+ * ancestor {@code Style} objects.
+ */
+ public ParaStyle(Node node, StyleCatalog sc) {
+
+ super(node, sc);
+
+ // Look for children. Only ones we care about are "style:properties"
+ // nodes. If any are found, recursively traverse them, passing
+ // along the style element to add properties to.
+
+ if (node.hasChildNodes()) {
+ NodeList children = node.getChildNodes();
+ int len = children.getLength();
+ for (int i = 0; i < len; i++) {
+ Node child = children.item(i);
+ String nodeName = child.getNodeName();
+ if (nodeName.equals("style:properties")) {
+ NamedNodeMap childAttrNodes = child.getAttributes();
+ if (childAttrNodes != null) {
+ int nChildAttrNodes = childAttrNodes.getLength();
+ for (int j = 0; j < nChildAttrNodes; j++) {
+ Node attr = childAttrNodes.item(j);
+ setAttribute(attr.getNodeName(), attr.getNodeValue());
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Constructor for use when going from client device format to DOM.
+ *
+ * @param name Name of the {@code Style}. Can be {@code null}.
+ * @param familyName Family of the {@code Style} {@literal -} usually
+ * <i>paragraph</i>, <i>text</i>, etc. Can be {@code null}.
+ * @param parentName Name of the parent {@code Style}, or {@code null}
+ * if none.
+ * @param attribs Array of attributes to set.
+ * @param values Array of values to set.
+ * @param sc The {@code StyleCatalog}, which is used for looking up
+ * ancestor {@code Style} objects.
+ */
+ public ParaStyle(String name, String familyName, String parentName,
+ String attribs[], String values[], StyleCatalog sc) {
+ super(name, familyName, parentName, sc);
+ if (attribs != null)
+ for (int i = 0; i < attribs.length; i++)
+ setAttribute(attribs[i], values[i]);
+ }
+
+ /**
+ * Alternate constructor for use when going from client device format to DOM.
+ *
+ * @param name Name of the {@code Style}. Can be {@code null}.
+ * @param familyName Family of the {@code Style} {@literal -} usually
+ * <i>paragraph</i>, <i>text</i>, etc. Can be {@code null}.
+ * @param parentName Name of the parent {@code Style}, or null if none.
+ * @param attribs Array of attributes indices to set.
+ * @param values Array of values to set.
+ * @param lookup The {@code StyleCatalog}, which is used for looking
+ * up ancestor {@code Style} objects.
+ */
+ public ParaStyle(String name, String familyName, String parentName,
+ int attribs[], String values[], StyleCatalog lookup) {
+ super(name, familyName, parentName, lookup);
+ if (attribs != null)
+ for (int i = 0; i < attribs.length; i++)
+ setAttribute(attribs[i], values[i]);
+ }
+
+ /**
+ * This code checks whether an attribute is one that we intentionally ignore.
+ *
+ * @param attribute The attribute to check.
+ *
+ * @return {@code true} if attribute can be ignored, {@code false} otherwise.
+ */
+ private boolean isIgnored(String attribute) {
+ for (int i = 0; i < ignored.length; i++) {
+ if (ignored[i].equals(attribute))
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Set an attribute for this paragraph {@code Style}.
+ *
+ * @param attr The attribute to set.
+ * @param value The attribute value to set.
+ */
+ private void setAttribute(String attr, String value) {
+ for (int i = 0; i < NR_PROPERTIES; i++) {
+ if (attr.equals(attrName[i])) {
+ setAttribute(i, value);
+ return;
+ }
+ }
+ if (!isIgnored(attr))
+ Debug.log(Debug.INFO, "ParaStyle Unhandled: " + attr + "=" + value);
+ }
+
+ /**
+ * Set an attribute for this paragraph {@code Style}.
+ *
+ * @param attr The attribute index to set.
+ * @param value The attribute value to set.
+ */
+ private void setAttribute(int attr, String value) {
+ isSet[attr] = true;
+ try {
+ this.value[attr] = ((conversionAlgorithm)algor[attr].newInstance()).I(value);
+ } catch (Exception e) {
+ Debug.log(Debug.ERROR, "Instantiation error", e);
+ }
+ }
+
+ /**
+ * Return the {@code Style} in use.
+ *
+ * @return The fully-resolved copy of the {@code Style} in use.
+ */
+ @Override
+ public Style getResolved() {
+ ParaStyle resolved = null;
+ try {
+ resolved = (ParaStyle)this.clone();
+ } catch (Exception e) {
+ Debug.log(Debug.ERROR, "Can't clone", e);
+ }
+
+ // Look up the parent style. (If there is no style catalog
+ // specified, we can't do any lookups).
+ ParaStyle parentStyle = null;
+ if (sc != null) {
+ if (parent != null) {
+ parentStyle = (ParaStyle)sc.lookup(parent, family, null,
+ this.getClass());
+ if (parentStyle == null)
+ Debug.log(Debug.ERROR, "parent style lookup of "
+ + parent + " failed!");
+ else
+ parentStyle = (ParaStyle)parentStyle.getResolved();
+ } else if (!name.equals("DEFAULT_STYLE")) {
+ parentStyle = (ParaStyle)sc.lookup("DEFAULT_STYLE", null, null,
+ this.getClass());
+ }
+ }
+
+ // If we found a parent, for any attributes which we don't have
+ // set, try to get the values from the parent.
+ if (parentStyle != null) {
+ parentStyle = (ParaStyle)parentStyle.getResolved();
+ for (int i = 0; i < NR_PROPERTIES; i++) {
+ if (!isSet[i] && parentStyle.isSet[i]) {
+ resolved.isSet[i] = true;
+ resolved.value[i] = parentStyle.value[i];
+ }
+ }
+ }
+ return resolved;
+ }
+
+ /**
+ * Private function to return the value as an element in a Comma Separated
+ * Value (CSV) format.
+ *
+ * @param value The value to format.
+ *
+ * @return The formatted value.
+ */
+ private static String toCSV(String value) {
+ if (value != null)
+ return "\"" + value + "\",";
+ else
+ return "\"\",";
+ }
+
+ /**
+ * Private function to return the value as a last element in a Comma
+ * Separated Value (CSV) format.
+ *
+ * @param value The value to format.
+ *
+ * @return The formatted value.
+ */
+ private static String toLastCSV(String value) {
+ if (value != null)
+ return "\"" + value + "\"";
+ else
+ return "\"\"";
+ }
+
+ /**
+ * Print a Comma Separated Value (CSV) header line for the spreadsheet dump.
+ */
+ public static void dumpHdr() {
+ System.out.println(toCSV("Name") + toCSV("Family") + toCSV("parent")
+ + toCSV("left mgn") + toCSV("right mgn")
+ + toCSV("top mgn") + toCSV("bottom mgn") + toCSV("txt indent")
+ + toCSV("line height") + toLastCSV("txt align"));
+ }
+
+ /**
+ * Dump this {@code Style} as a Comma Separated Value (CSV) line.
+ */
+ public void dumpCSV() {
+ String attributes = "";
+ for (int index = 0; index <= 6; index++) {
+ if (isSet[index]) {
+ attributes += toCSV("" + value[index]);
+ }
+ else
+ attributes += toCSV(null); // unspecified
+ }
+ System.out.println(toCSV(name) + toCSV(family) + toCSV(parent)
+ + attributes + toLastCSV(null));
+ }
+
+ /**
+ * Create the {@code Node} with the specified elements.
+ *
+ * @param parentDoc Parent {@code Document} of the {@code Node} to create.
+ * @param name Name of the {@code Node}.
+ *
+ * @return The created {@code Node}.
+ */
+ @Override
+ public Node createNode(org.w3c.dom.Document parentDoc, String name) {
+ Element node = parentDoc.createElement(name);
+ writeAttributes(node);
+ return node;
+ }
+
+ /**
+ * Return {@code true} if {@code style} is a subset of the {@code Style}.
+ *
+ * @param style {@code Style} to check.
+ *
+ * @return {@code true} if <code>style</code> is a subset, {@code false}
+ * otherwise.
+ */
+ @Override
+ public boolean isSubset(Style style) {
+
+ if (!super.isSubset(style))
+ return false;
+ if (!this.getClass().isAssignableFrom(style.getClass()))
+ return false;
+ ParaStyle ps = (ParaStyle)style;
+
+ for (int i = 0; i < NR_PROPERTIES; i++) {
+ if (ps.isSet[i]) {
+ if (i < NR_PROPERTIES - 1) {
+ // Compare the actual values. We allow a margin of error
+ // here because the conversion loses precision.
+ int diff;
+ if (value[i] > ps.value[i])
+ diff = value[i] - ps.value[i];
+ else
+ diff = ps.value[i] - value[i];
+ if (diff > 32)
+ return false;
+ } else {
+ if (i == TEXT_ALIGN)
+ if ((value[i] == 0) && (ps.value[i] == 4))
+ continue;
+ if (value[i] != ps.value[i])
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Add {@code Style} attributes to the given {@code Node}.
+ *
+ * <p>This may involve writing child {@code Node} objects as well.</p>
+ *
+ * @param node The {@code Node} to add {@code Style} attributes.
+ */
+ private void writeAttributes(Element node) {
+ for (int i = 0; i <= TEXT_INDENT; i++) {
+ if (isSet[i]) {
+ double temp = value[i] / 100.0;
+ String stringVal = Double.toString(temp) + "mm";
+ node.setAttribute(attrName[i], stringVal);
+ }
+ }
+
+ if (isSet[LINE_HEIGHT]) {
+ String stringVal;
+ if ((value[LINE_HEIGHT] & LH_PCT) != 0)
+ stringVal = Integer.toString(value[LINE_HEIGHT] & LH_VALUEMASK) + "%";
+ else {
+ double temp = (value[LINE_HEIGHT] & LH_VALUEMASK) / 100.0;
+ stringVal = Double.toString(temp) + "mm";
+ }
+ node.setAttribute(attrName[LINE_HEIGHT], stringVal);
+ }
+
+ if (isSet[TEXT_ALIGN]) {
+ String val;
+ switch (value[TEXT_ALIGN]) {
+ case ALIGN_RIGHT: val = "end"; break;
+ case ALIGN_CENTER: val = "center"; break;
+ case ALIGN_JUST: val = "justify"; break;
+ case ALIGN_LEFT: val = "left"; break;
+ default: val = "unknown"; break;
+ }
+ node.setAttribute(attrName[TEXT_ALIGN], val);
+ }
+ }
+} \ No newline at end of file
diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/Style.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/Style.java
new file mode 100644
index 000000000..d28d6a45f
--- /dev/null
+++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/Style.java
@@ -0,0 +1,191 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+package org.openoffice.xmerge.converter.xml;
+
+import org.w3c.dom.Node;
+import org.w3c.dom.NamedNodeMap;
+
+/**
+ * An object of class {@code Style} represents a <i>style</i> in an OpenOffice
+ * document.
+ *
+ * <p>In practice subclasses of this {@code Style}, such as {@code TextStyle},
+ * {@code ParaStyle} are used.</p>
+ *
+ * @see <a href="TextStyle.html">TextStyle</a>
+ * @see <a href="ParaStyle.html">ParaStyle</a>
+ */
+public class Style {
+
+ /** Name of the {@code Style}. */
+ protected String name = null;
+ /** Family of the {@code Style}. */
+ protected String family = null;
+ /** Parent of the {@code Style}. */
+ protected String parent = null;
+
+ /**
+ * A reference to the {@code StyleCatalog} to be used for looking up ancestor
+ * {@code Style} objects.
+ */
+ protected StyleCatalog sc;
+
+ /**
+ * Constructor for use when going from DOM to client device format.
+ *
+ * @param node A <i>style:style</i> or <i>style:default-style</i>
+ * {@code Node} from the document being parsed. No checking of
+ * {@code Node} is done, so if it is not of the proper type
+ * the results will be unpredictable.
+ * @param sc The {@code StyleCatalog}, which is used for looking up
+ * ancestor {@code Style} objects.
+ */
+ public Style(Node node, StyleCatalog sc) {
+
+ this.sc = sc;
+
+ // Run through the attributes of this node, saving
+ // the ones we're interested in.
+ if (node.getNodeName().equals("style:default-style"))
+ name = "DEFAULT_STYLE";
+ NamedNodeMap attrNodes = node.getAttributes();
+ if (attrNodes != null) {
+ int len = attrNodes.getLength();
+ for (int i = 0; i < len; i++) {
+ Node attr = attrNodes.item(i);
+ if (attr.getNodeName().equals("style:family"))
+ family = attr.getNodeValue();
+ else if (attr.getNodeName().equals("style:name")) {
+ name = attr.getNodeValue();
+ } else if (attr.getNodeName().equals("style:parent-style-name"))
+ parent = attr.getNodeValue();
+
+ }
+ }
+ }
+
+ /**
+ * Constructor for use when going from client device format to DOM.
+ *
+ * @param name Name of the {@code Style}. Can be {@code null}.
+ * @param family Family of the {@code Style} {@literal -} usually
+ * <i>paragraph</i>, <i>text</i>, etc. Can be {@code null}.
+ * @param parent Name of the parent {@code Style}, or {@code null} if none.
+ * @param sc The {@code StyleCatalog}, which is used for looking up
+ * ancestor {@code Style} objects.
+ */
+ public Style(String name, String family, String parent, StyleCatalog sc) {
+ this.sc = sc;
+ this.name = name;
+ this.family = family;
+ this.parent = parent;
+ }
+
+ /**
+ * Set the {@code StyleCatalog} to be used when looking up the {@code Style}
+ * parent.
+ *
+ * @param sc The {@code StyleCatalog}, which is used for looking up
+ * ancestor {@code Style} objects.
+ */
+ public void setCatalog(StyleCatalog sc) {
+ this.sc = sc;
+ }
+
+ /**
+ * Returns the name of this {@code Style}.
+ *
+ * @return The name of this {@code Style}.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Sets the name of this {@code Style}.
+ *
+ * @param newName The new name of this {@code Style}.
+ */
+ public void setName(String newName) {
+ name = newName;
+ }
+
+ /**
+ * Return the family of this {@code Style}.
+ *
+ * @return The family of this {@code Style}.
+ */
+ public String getFamily() {
+ return family;
+ }
+
+ /**
+ * Return the name of the parent of this {@code Style}.
+ *
+ * @return The parent of this {@code Style}.
+ */
+ public String getParent() {
+ return parent;
+ }
+
+ /**
+ * Return a {@code Style} object corresponding to this one, but with all of
+ * the inherited information from parent {@code Style} objects filled in.
+ *
+ * <p>The object returned will be a new object, not a reference to this
+ * object, even if it does not need any information added.</p>
+ *
+ * @return A resolved {@code Style} object in which to look up ancestors.
+ */
+ public Style getResolved() {
+ return new Style(name, family, parent, sc);
+ }
+
+ /**
+ * Write a {@code Node} in {@code parentDoc} representing this {@code Style}.
+ *
+ * <p>Note that the {@code Node} is returned unconnected.</p>
+ *
+ * @param parentDoc Document to which new {@code Node} will belong.
+ * @param name Name to use for new {@code Node}.
+ */
+ public Node createNode(org.w3c.dom.Document parentDoc, String name) {
+ // DJP: write this! Should call writeAttributes()
+ return null;
+ }
+
+ /**
+ * Return {@code true} if {@code Style} is a subset of this one.
+ *
+ * <p>Note that this will return true even if {@code Style} is less specific
+ * than this {@code Style}, so long as it does not contradict this
+ * {@code Style} in any way.</p>
+ *
+ * <p>This always returns true since only subclasses of {@code Style}
+ * contain any actual {@code Style} information.</p>
+ *
+ * @param style The {@code Style} to check.
+ *
+ * @return {@code true} if the {@code Style} is a subset, {@code false}
+ * otherwise.
+ */
+ public boolean isSubset(Style style) {
+ return true;
+ }
+} \ No newline at end of file
diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/StyleCatalog.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/StyleCatalog.java
new file mode 100644
index 000000000..f08d664d9
--- /dev/null
+++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/StyleCatalog.java
@@ -0,0 +1,284 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+package org.openoffice.xmerge.converter.xml;
+
+import java.lang.reflect.Constructor;
+import java.util.ArrayList;
+
+import org.openoffice.xmerge.util.Debug;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+/**
+ * A {@code StyleCatalog} holds a collection of {@code Style} objects.
+ *
+ * <p>It is intended for use when parsing or building a DOM document.</p>
+ *
+ * <p>Each entry in the {@code StyleCatalog} represents a {@code Style}, and is
+ * an object which is a subclass of {@code Style}.</p>
+ *
+ * @see <a href="Style.html">Style</a>
+ */
+public class StyleCatalog {
+
+ private final ArrayList<Style> styles; // The actual styles
+
+ /**
+ * Constructor.
+ *
+ * @param initialEntries Expected number of entries to set for efficiency
+ * purposes.
+ */
+ public StyleCatalog(int initialEntries) {
+ styles = new ArrayList<Style>(initialEntries);
+ }
+
+ /**
+ * Parse the {@code Document} starting from {@code node} and working downward,
+ * and add all styles found, so long as their family name is listed in
+ * {@code families}.
+ *
+ * <p>For each family name in {@code families} there must be a corresponding
+ * element in {@code classes}, which specifies the class type to use for that
+ * family. All of these classes must be subclasses of {@code Style}. There
+ * can be multiple classes specified for a particular family.</p>
+ *
+ * <p>If {@code defaultClass} is non-null, then all styles that are found
+ * will be added. Any {@code Style} whose family is not listed in
+ * {@code families} will be added using defaultClass, which, of course, must
+ * be a subclass of {@code Style}. If {@code alwaysCreateDefault} is
+ * {@code true}, then a class of type {@code defaultClass} will always be
+ * created, regardless of whether there was also a match in {@code families}.
+ * </p>
+ *
+ * <p>DJP ToDo: make it recursive so that {@code node} can be higher up in
+ * the {@code Document} tree.</p>
+ *
+ * @param node The node to be searched for {@code Style}
+ * objects.
+ * @param families An array of {@code Style} families to add.
+ * @param classes An array of class types corresponding to the
+ * families array.
+ * @param defaultClass All {@code Style} objects that are found are
+ * added to this class.
+ * @param alwaysCreateDefault A class of type {@code defaultClass} will
+ * always be created, regardless of whether
+ * there is a match in the families array.
+ */
+ public void add(Node node, String families[], Class<?> classes[],
+ Class<?> defaultClass, boolean alwaysCreateDefault) {
+
+ if (node == null)
+ return;
+
+ if (families == null)
+ families = new String[0];
+ if (classes == null)
+ classes = new Class[0];
+ if (!node.hasChildNodes()) {
+ return;
+ }
+ NodeList children = node.getChildNodes();
+ int len = children.getLength();
+
+ for (int i = 0; i < len; i++) {
+ boolean found = false;
+ Node child = children.item(i);
+ String name = child.getNodeName();
+ if (name.equals("style:default-style")
+ || name.equals("style:style")) {
+ String familyName = getFamilyName(child);
+ if (familyName == null) {
+ Debug.log(Debug.ERROR, "familyName is null!");
+ continue;
+ }
+
+ for (int j = 0; j < families.length; j++) {
+ if (families[j].equals(familyName)) {
+ callConstructor(classes[j], child);
+ found = true;
+ }
+ }
+ if ((!found || alwaysCreateDefault)
+ && (defaultClass != null))
+ callConstructor(defaultClass, child);
+ }
+ }
+ }
+
+ /**
+ * Call the constructor of class {@code cls} with parameters {@code node},
+ * and add the resulting {@code Style} to the catalog.
+ *
+ * @param cls The class whose constructor will be called.
+ * @param node The constructed class will be added to this node.
+ */
+ private void callConstructor(Class<?> cls, Node node) {
+ Class<?> params[] = new Class[2];
+ params[0] = Node.class;
+ params[1] = this.getClass();
+ try {
+ Constructor<?> c = cls.getConstructor(params);
+ Object p[] = new Object[2];
+ p[0] = node;
+ p[1] = this;
+ styles.add((Style) c.newInstance(p));
+ } catch (Exception e) {
+ Debug.log(Debug.ERROR, "Exception when calling constructor", e);
+ }
+ }
+
+ /**
+ * Add a {@code Style} to the catalog.
+ *
+ * @param s The {@code Style} to add.
+ */
+ public void add(Style s) {
+ styles.add(s);
+ }
+
+ /**
+ * Return the first {@code Style} matching the specified names.
+ *
+ * @param name Name to match, {@code null} is considered
+ * <i>always match</i>.
+ * @param family Family to match, {@code null} is considered
+ * <i>always match</i>.
+ * @param parent Parent to match, {@code null} is considered
+ * <i>always match</i>.
+ * @param styleClass styleClass to match, {@code null} is considered
+ * <i>always match</i>.
+ *
+ * @return {@code Style} value if all parameters match, {@code null}
+ * otherwise.
+ */
+ public Style lookup(String name, String family, String parent,
+ Class<?> styleClass) {
+ int nStyles = styles.size();
+ for (int i = 0; i < nStyles; i++) {
+ Style s = styles.get(i);
+ if ((name != null) && (s.getName() != null)
+ && (!s.getName().equals(name)))
+ continue;
+ if ((family != null) && (s.getFamily() != null)
+ && (!s.getFamily().equals(family)))
+ continue;
+ if ((parent != null) && (s.getParent() != null)
+ && (!s.getParent().equals(parent)))
+ continue;
+ if ((styleClass != null) && (s.getClass() != styleClass))
+ continue;
+ if (s.getName() == null) continue; // DJP: workaround for "null name" problem
+ return s;
+ }
+ return null; // none found
+ }
+
+ /**
+ * Given a {@code Style s} return all {@code Style} objects that match.
+ *
+ * @param s {@code Style} to match.
+ *
+ * @return An array of {@code Style} objects that match, an empty array if
+ * none match.
+ */
+ public Style[] getMatching(Style s) {
+
+ // Run through and count the matching styles so we know how big of
+ // an array to allocate.
+ int matchCount = 0;
+ int nStyles = styles.size();
+ for (int j = 0; j < nStyles; j++) {
+ Style p = styles.get(j).getResolved();
+ if (p.isSubset(s)) matchCount++;
+ }
+
+ // Now allocate the array, and run through again, populating it.
+ Style[] matchArray = new Style[matchCount];
+ matchCount = 0;
+ for (int j = 0; j < nStyles; j++) {
+ Style p = styles.get(j).getResolved();
+ if (p.isSubset(s)) matchArray[matchCount++] = p;
+ }
+ return matchArray;
+ }
+
+ /**
+ * Create a {@code Node} named {@code name} in {@code Document parentDoc},
+ * and write the entire {@code StyleCatalog} to it.
+ *
+ * <p>Note that the resulting node is returned, but is not connected into the
+ * document. Placing the output node in the document is left to the caller.
+ * </p>
+ *
+ * @param parentDoc The {@code Document} to add the {@code Node}.
+ * @param name The name of the {@code Node} to add.
+ *
+ * @return The {@code Element} that was created.
+ */
+ public Element writeNode(org.w3c.dom.Document parentDoc, String name) {
+ Element rootNode = parentDoc.createElement(name);
+
+ int len = styles.size();
+ for (int j = 0; j < len; j++) {
+ Style s = styles.get(j);
+
+ Element styleNode = parentDoc.createElement("style:style");
+
+ if (s.getName() != null)
+ styleNode.setAttribute("style:name", s.getName());
+ if (s.getParent() != null)
+ styleNode.setAttribute("style:parent-style-name", s.getParent());
+ if (s.getFamily() != null)
+ styleNode.setAttribute("style:family", s.getFamily());
+
+ Element propertiesNode = (Element) s.createNode(parentDoc, "style:properties");
+ // DJP: only add node if has children OR attributes
+ if (propertiesNode != null)
+ styleNode.appendChild(propertiesNode);
+
+ rootNode.appendChild(styleNode);
+ }
+
+ return rootNode;
+ }
+
+ /**
+ * Find the family attribute of a {@code Style Node}.
+ *
+ * @param node The {@code Node} to check.
+ *
+ * @return The family attribute, or {@code null} if one does not exist.
+ */
+ private String getFamilyName(Node node) {
+ NamedNodeMap attributes = node.getAttributes();
+ if (attributes != null) {
+ int len = attributes.getLength();
+ for (int i = 0; i < len; i++) {
+ Node attr = attributes.item(i);
+ if (attr.getNodeName().equals("style:family")) {
+ return attr.getNodeValue();
+ }
+ }
+ }
+ return null;
+ }
+} \ No newline at end of file
diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/TextStyle.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/TextStyle.java
new file mode 100644
index 000000000..29e7740c9
--- /dev/null
+++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/TextStyle.java
@@ -0,0 +1,575 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+// DJP ToDo: need way of specifying fg/bg colors on ws->DOM
+
+package org.openoffice.xmerge.converter.xml;
+
+import java.awt.Color;
+
+import org.w3c.dom.NodeList;
+import org.w3c.dom.Node;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Element;
+
+import org.openoffice.xmerge.util.Debug;
+
+/**
+ * Represents a text {@code Style} in an OpenOffice document.
+ */
+public class TextStyle extends Style implements Cloneable {
+
+ /** Indicates <i>bold</i> text. */
+ final private static int BOLD = 0x01;
+ /** Indicates <i>italic</i> text. */
+ final private static int ITALIC = 0x02;
+ /** Indicates <i>underlined</i> text. */
+ final private static int UNDERLINE = 0x04;
+ /** Indicates <i>strike-through</i> in the text. */
+ final private static int STRIKETHRU = 0x08;
+ /** Indicates <i>superscripted</i> text. */
+ final private static int SUPERSCRIPT = 0x10;
+ /** Indicates <i>subscripted</i> text. */
+ final private static int SUBSCRIPT = 0x20;
+
+ /** Values of text attributes. */
+ private int values = 0;
+ /** Bitwise mask of text attributes. */
+ private int mask = 0;
+
+ /** Font size in points. */
+ private int sizeInPoints = 0;
+ /** Font name. */
+ private String fontName = null;
+ /** Font {@code Color}. */
+ private Color fontColor = null;
+ /** Background {@code Color}. */
+ private Color bgColor = null;
+
+ /**
+ * Constructor for use when going from DOM to client device format.
+ *
+ * @param node The <i>style:style</i> {@code Node} containing the
+ * {@code Style}. (This {@code Node} is assumed have a
+ * <i>family</i> attribute of <i>text</i>).
+ * @param sc The {@code StyleCatalog}, which is used for looking up
+ * ancestor {@code Style} objects.
+ */
+ public TextStyle(Node node, StyleCatalog sc) {
+ super(node, sc);
+
+ // Run through the attributes of this node, saving
+ // the ones we're interested in.
+ NamedNodeMap attrNodes = node.getAttributes();
+ if (attrNodes != null) {
+ int len = attrNodes.getLength();
+ for (int i = 0; i < len; i++) {
+ Node attr = attrNodes.item(i);
+ handleAttribute(attr.getNodeName(), attr.getNodeValue());
+ }
+ }
+
+ // Look for children. Only ones we care about are "style:properties"
+ // nodes. If any are found, recursively traverse them, passing
+ // along the style element to add properties to.
+ if (!node.hasChildNodes()) {
+ return;
+ }
+ NodeList children = node.getChildNodes();
+ int len = children.getLength();
+ for (int i = 0; i < len; i++) {
+ Node child = children.item(i);
+ String nodeName = child.getNodeName();
+ if (nodeName.equals("style:properties")) {
+ NamedNodeMap childAttrNodes = child.getAttributes();
+ if (childAttrNodes != null) {
+ int nChildAttrNodes = childAttrNodes.getLength();
+ for (int j = 0; j < nChildAttrNodes; j++) {
+ Node attr = childAttrNodes.item(j);
+ handleAttribute(attr.getNodeName(),
+ attr.getNodeValue());
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Constructor for use when going from client device format to DOM
+ *
+ * @param name Name of text {@code Style}. Can be {@code null}.
+ * @param family Family of text {@code Style} (usually <i>text</i>).
+ * Can be {@code null}.
+ * @param parent Name of parent text {@code Style}, or {@code null} for
+ * none.
+ * @param mask Bitwise mask of text attributes that this text
+ * {@code Style} will specify. Can be any combination of
+ * the following, or'ed together: {@link #BOLD},
+ * {@link #ITALIC}, {@link #UNDERLINE}, {@link #STRIKETHRU},
+ * {@link #SUPERSCRIPT}, {@link #SUBSCRIPT}. This parameter
+ * determines what attributes this {@code Style} will
+ * specify. When an attribute is specified in a
+ * {@code Style}, its value can be either <i>on</i> or
+ * <i>off</i>. The on/off value for each attribute is
+ * controlled by the {@code values} parameter.
+ * @param values Values of text attributes that this text {@code Style}
+ * will be setting. Any of the attributes ({@link #BOLD},
+ * etc) listed for {@code mask} can be used for this.
+ * @param fontSize Font size in points.
+ * @param fontName Name of font.
+ * @param sc The {@code StyleCatalog}, which is used for looking up
+ * ancestor {@code Style} objects.
+ */
+ public TextStyle(String name, String family, String parent,
+ int mask, int values, int fontSize, String fontName, StyleCatalog sc) {
+ super(name, family, parent, sc);
+ this.mask = mask;
+ this.values = values;
+ this.sizeInPoints = fontSize;
+ this.fontName = fontName;
+ }
+
+ /**
+ * Parse a color specification of the form <i>#rrggbb</i>
+ *
+ * @param value {@code Color} specification to parse.
+ *
+ * @return The {@code Color} associated the value.
+ */
+ private Color parseColorString(String value) {
+ int red = 0;
+ int green = 0;
+ int blue = 0;
+ try {
+ // Assume color value is of form #rrggbb
+ red = Integer.parseInt(value.substring(1, 3), 16);
+ green = Integer.parseInt(value.substring(3, 5), 16);
+ blue = Integer.parseInt(value.substring(5, 7), 16);
+ } catch (NumberFormatException e) {
+ Debug.log(Debug.ERROR, "Problem parsing a color string", e);
+ } catch (IndexOutOfBoundsException e) {
+ Debug.log(Debug.ERROR, "Problem parsing a color string", e);
+ }
+ return new Color(red, green, blue);
+ }
+
+ /**
+ * Set an attribute.
+ *
+ * @param attr The attribute to set.
+ * @param value The attribute value to set.
+ */
+ private void handleAttribute(String attr, String value) {
+
+ if (attr.equals("fo:font-weight")) {
+ if (value.equals("bold")) turnAttributesOn(BOLD);
+ else if (value.equals("normal")) turnAttributesOff(BOLD);
+ }
+
+ else if (attr.equals("fo:font-style")) {
+ if (value.equals("italic")) turnAttributesOn(ITALIC);
+ else if (value.equals("oblique")) turnAttributesOn(ITALIC);
+ else if (value.equals("normal")) turnAttributesOff(ITALIC);
+ }
+
+ else if (attr.equals("style:text-underline")) {
+ if (value.equals("none"))
+ turnAttributesOff(UNDERLINE);
+ else
+ turnAttributesOn(UNDERLINE);
+ }
+
+ else if (attr.equals("style:text-crossing-out")) {
+ if (value.equals("none"))
+ turnAttributesOff(STRIKETHRU);
+ else
+ turnAttributesOn(STRIKETHRU);
+ }
+
+ else if (attr.equals("style:text-position")) {
+ if (value.startsWith("super "))
+ turnAttributesOn(SUPERSCRIPT);
+ else if (value.startsWith("sub "))
+ turnAttributesOn(SUBSCRIPT);
+ else if (value.startsWith("0% "))
+ turnAttributesOff(SUPERSCRIPT | SUBSCRIPT);
+ else {
+ String firstPart = value.substring(0, value.indexOf(' '));
+ if (firstPart.endsWith("%")) {
+ firstPart = firstPart.substring(0, value.indexOf('%'));
+ int amount;
+ try {
+ amount = Integer.parseInt(firstPart);
+ } catch (NumberFormatException e) {
+ amount = 0;
+ Debug.log(Debug.ERROR, "Problem with style:text-position tag", e);
+ }
+ if (amount < 0) turnAttributesOn(SUBSCRIPT);
+ else if (amount > 0) turnAttributesOn(SUPERSCRIPT);
+ }
+ }
+ }
+
+ else if (attr.equals("fo:font-size")) {
+ if (value.endsWith("pt")) {
+ String num = value.substring(0, value.length() - 2);
+ sizeInPoints = Integer.parseInt(num);
+ }
+ }
+
+ else if (attr.equals("style:font-name"))
+ fontName = value;
+
+ else if (attr.equals("fo:color"))
+ fontColor = parseColorString(value);
+
+ else if (attr.equals("style:text-background-color"))
+ bgColor = parseColorString(value);
+
+ else if (isIgnored(attr)) {}
+
+ else {
+ Debug.log(Debug.INFO, "TextStyle Unhandled: " + attr + "=" + value);
+ }
+ }
+
+ /**
+ * Return the font size for this {@code Style}.
+ *
+ * @return The font size in points
+ */
+ public int getFontSize() {
+ return sizeInPoints;
+ }
+
+ /**
+ * Return the name of the font for this {@code Style}.
+ *
+ * @return Name of font, or null if no font is specified by this
+ * {@code Style}.
+ */
+ public String getFontName() {
+ return fontName;
+ }
+
+ /**
+ * Return the font {@code Color} for this {@code Style}.
+ *
+ * <p>Can be {@code null} if none was specified.</p>
+ *
+ * @return {@code Color} value for this {@code Style}. Can be {@code null}.
+ */
+ public Color getFontColor() {
+ return fontColor;
+ }
+
+ /**
+ * Return the background {@code Color} for this {@code Style}.
+ *
+ * <p>Can be {@code null} if none was specified.</p>
+ *
+ * @return Background {@code Color} value for this {@code Style}. Can be
+ * {@code null}.
+ */
+ public Color getBackgroundColor() {
+ return bgColor;
+ }
+
+ /**
+ * Return a {@code Style} object corresponding to this one, but with all of
+ * the inherited information from parent {@code Style} objects filled in.
+ *
+ * <p>The object returned will be a new object, not a reference to this
+ * object, even if it does not need any information added.</p>
+ *
+ * @return The {@code StyleCatalog} in which to look up ancestors.
+ */
+ @Override
+ public Style getResolved() {
+ // Create a new object to return, which is a clone of this one.
+ TextStyle resolved = null;
+ try {
+ resolved = (TextStyle)this.clone();
+ } catch (Exception e) {
+ Debug.log(Debug.ERROR, "Can't clone", e);
+ }
+
+ // Look up the parentStyle. (If there is no style catalog
+ // specified, we can't do any lookups.)
+ TextStyle parentStyle = null;
+ if (sc != null) {
+ if (parent != null) {
+ parentStyle = (TextStyle)sc.lookup(parent, family, null,
+ this.getClass());
+ if (parentStyle == null)
+ Debug.log(Debug.ERROR, "parent style lookup of "
+ + parent + " failed!");
+ else
+ parentStyle = (TextStyle)parentStyle.getResolved();
+
+ } else if (!name.equals("DEFAULT_STYLE")) {
+ parentStyle = (TextStyle)sc.lookup("DEFAULT_STYLE", null,
+ null, this.getClass());
+ }
+ }
+
+ // If we found a parent, for any attributes which we don't have
+ // set, try to get the values from the parent.
+ if (parentStyle != null) {
+ parentStyle = (TextStyle)parentStyle.getResolved();
+
+ if ((sizeInPoints == 0) && (parentStyle.sizeInPoints != 0))
+ resolved.sizeInPoints = parentStyle.sizeInPoints;
+ if ((fontName == null) && (parentStyle.fontName != null))
+ resolved.fontName = parentStyle.fontName;
+ if ((fontColor == null) && (parentStyle.fontColor != null))
+ resolved.fontColor = parentStyle.fontColor;
+ if ((bgColor == null) && (parentStyle.bgColor != null))
+ resolved.bgColor = parentStyle.bgColor;
+ for (int m = BOLD; m <= SUBSCRIPT; m = m << 1) {
+ if (((mask & m) == 0) && ((parentStyle.mask & m) != 0)) {
+ resolved.mask |= m;
+ resolved.values |= (parentStyle.mask & m);
+ }
+ }
+
+ }
+ return resolved;
+ }
+
+ /**
+ * Set one or more text attributes to <i>on</i>.
+ *
+ * @param flags Flag values to set <i>on</i>.
+ */
+ private void turnAttributesOn(int flags) {
+ mask |= flags;
+ values |= flags;
+ }
+
+ /**
+ * Set one or more text attributes to <i>off</i>.
+ *
+ * @param flags The flag values to set <i>off</i>.
+ */
+ private void turnAttributesOff(int flags) {
+ mask |= flags;
+ values &= ~flags;
+ }
+
+ /**
+ * Private function to return the value as an element in a Comma Separated
+ * Value (CSV) format.
+ *
+ * @param value The value to format.
+ *
+ * @return The formatted value.
+ */
+ private static String toCSV(String value) {
+ if (value != null)
+ return "\"" + value + "\",";
+ else
+ return "\"\",";
+ }
+
+ /**
+ * Private function to return the value as a last element in a Comma
+ * Separated Value (CSV) format.
+ *
+ * @param value The value to format.
+ *
+ * @return The formatted value.
+ */
+ private static String toLastCSV(String value) {
+ if (value != null)
+ return "\"" + value + "\"";
+ else
+ return "\"\"";
+ }
+
+ /**
+ * Print a Comma Separated Value (CSV) header line for the spreadsheet dump.
+ */
+ public static void dumpHdr() {
+ System.out.println(toCSV("Name") + toCSV("Family") + toCSV("parent")
+ + toCSV("Font") + toCSV("Size")
+ + toCSV("Bold") + toCSV("Italic") + toCSV("Underline")
+ + toCSV("Strikethru") + toCSV("Superscript") + toLastCSV("Subscript"));
+ }
+
+ /**
+ * Dump this {@code Style} as a Comma Separated Value (CSV) line.
+ */
+ public void dumpCSV() {
+ StringBuilder attributes = new StringBuilder();
+ for (int bitVal = 0x01; bitVal <= 0x20; bitVal = bitVal << 1) {
+ if ((bitVal & mask) != 0) {
+ attributes.append(toCSV(((bitVal & values) != 0) ? "yes" : "no"));
+ }
+ else {
+ attributes.append(toCSV(null)); // unspecified
+ }
+ }
+ System.out.println(toCSV(name) + toCSV(family) + toCSV(parent)
+ + toCSV(fontName) + toCSV("" + sizeInPoints) + attributes.toString() + toLastCSV(null));
+ }
+
+ /**
+ * Create a new {@code Node} in the {@code Document}, and write this
+ * {@code Style} to it.
+ *
+ * @param parentDoc Parent {@code Document} of the {@code Node} to create.
+ * @param name Name to use for the new {@code Node} (e.g.
+ * <i>style:style</i>)
+ *
+ * @return Created {@code Node}.
+ */
+ @Override
+ public Node createNode(org.w3c.dom.Document parentDoc, String name) {
+ Element node = parentDoc.createElement(name);
+ writeAttributes(node);
+ return node;
+ }
+
+ /**
+ * Return {@code true} if {@code style} specifies as much or less than this
+ * {@code Style}, and nothing it specifies contradicts this {@code Style}.
+ *
+ * @param style The {@code Style} to check.
+ *
+ * @return {@code true} if {@code style} is a subset, {@code false}
+ * otherwise.
+ */
+ @Override
+ public boolean isSubset(Style style) {
+ if (style.getClass() != this.getClass())
+ return false;
+ TextStyle tStyle = (TextStyle)style;
+
+ if (tStyle.values != values)
+ return false;
+
+ if (tStyle.sizeInPoints != 0 && sizeInPoints != tStyle.sizeInPoints)
+ return false;
+
+ if (tStyle.fontName != null) {
+ if (fontName == null)
+ return false;
+ if (!fontName.equals(tStyle.fontName))
+ return false;
+ }
+
+ if (tStyle.fontColor != null) {
+ if (fontColor == null)
+ return false;
+ if (!fontColor.equals(tStyle.fontColor))
+ return false;
+ }
+
+ if (tStyle.bgColor != null) {
+ if (bgColor == null)
+ return false;
+ if (!bgColor.equals(tStyle.bgColor))
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Write this {@code Style} object's attributes to a {@code Node} in the
+ * {@code Document}.
+ *
+ * @param node The {@code Node} to add {@code Style} attributes.
+ */
+ private void writeAttributes(Element node) {
+
+ if ((mask & BOLD) != 0 && (values & BOLD) != 0)
+ node.setAttribute("fo:font-weight", "bold");
+
+ if ((mask & ITALIC) != 0 && (values & ITALIC) != 0)
+ node.setAttribute("fo:font-style", "italic");
+
+ if ((mask & UNDERLINE) != 0 && (values & UNDERLINE) != 0)
+ node.setAttribute("style:text-underline", "single");
+
+ if ((mask & STRIKETHRU) != 0 && (values & STRIKETHRU) != 0)
+ node.setAttribute("style:text-crossing-out", "single-line");
+
+ if ((mask & SUPERSCRIPT) != 0 && (values & SUPERSCRIPT) != 0)
+ node.setAttribute("style:text-position", "super 58%");
+
+ if ((mask & SUBSCRIPT) != 0 && (values & SUBSCRIPT) != 0)
+ node.setAttribute("style:text-position", "sub 58%");
+
+ if (sizeInPoints != 0) {
+ node.setAttribute("fo:font-size", Integer.toString(sizeInPoints) + "pt");
+ }
+
+ if (fontName != null)
+ node.setAttribute("style:font-name", fontName);
+
+ if (fontColor != null)
+ node.setAttribute("fo:color", buildColorString(fontColor));
+
+ if (bgColor != null)
+ node.setAttribute("style:text-background-color",
+ buildColorString(bgColor));
+ }
+
+ /**
+ * Given a {@code Color}, return a string of the form <i>{@literal #rrggbb}</i>.
+ *
+ * @param c The {@code Color} value.
+ *
+ * @return The {@code Color} value in the form <i>{@literal #rrggbb}</i>.
+ */
+ private String buildColorString(Color c) {
+ return String.format("#%06X", c.getRGB() & 0x00FFFFFF);
+ }
+
+ private static String[] ignored = {
+ "style:text-autospace", "style:text-underline-color",
+ "fo:margin-left", "fo:margin-right", "fo:text-indent",
+ "fo:margin-top", "fo:margin-bottom", "text:line-number",
+ "text:number-lines", "style:country-asian",
+ "style:font-size-asian", "style:font-name-complex",
+ "style:language-complex", "style:country-complex",
+ "style:font-size-complex", "style:punctuation-wrap",
+ "fo:language", "fo:country",
+ "style:font-name-asian", "style:language-asian",
+ "style:line-break", "fo:keep-with-next"
+ };
+
+ /**
+ * This code checks whether an attribute is one that we intentionally ignore.
+ *
+ * @param attribute The attribute to check.
+ *
+ * @return {@code true} if {@code attribute} can be ignored, otherwise
+ * {@code false}.
+ */
+ private boolean isIgnored(String attribute) {
+ for (String ignored1 : ignored) {
+ if (ignored1.equals(attribute)) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/package-info.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/package-info.java
new file mode 100644
index 000000000..3c32a4dba
--- /dev/null
+++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+/**
+ * {@code Document} and {@code PluginFactory} implementations for XML based
+ * formats.
+ */
+package org.openoffice.xmerge.converter.xml;
diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxc/BookSettings.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxc/BookSettings.java
new file mode 100644
index 000000000..e96951ada
--- /dev/null
+++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxc/BookSettings.java
@@ -0,0 +1,208 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+package org.openoffice.xmerge.converter.xml.sxc;
+
+import java.util.ArrayList;
+
+import org.openoffice.xmerge.converter.xml.OfficeConstants;
+import org.openoffice.xmerge.util.Debug;
+import org.openoffice.xmerge.util.XmlUtil;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+/**
+ * This is a class representing the different attributes for a worksheet
+ * contained in settings.xml.
+ */
+public class BookSettings implements OfficeConstants {
+
+ /** A w3c {@code Document}. */
+ private org.w3c.dom.Document settings = null;
+
+ private boolean hasColumnRowHeaders = true;
+ private String activeSheet = "";
+ private ArrayList<SheetSettings> worksheetSettings
+ = new ArrayList<SheetSettings>();
+
+ /**
+ * Constructor for a {@code BookSettings}.
+ *
+ * <p>Reads document settings from xml and inits SheetSettings variables.</p>
+ *
+ * @param root The root XML node to read from.
+ */
+ public BookSettings(Node root) {
+ readNode(root);
+ }
+
+ /**
+ * Constructor for a {@code BookSettings}.
+ *
+ * @param worksheetSettings If it's a row the height, a column the width
+ */
+ public BookSettings(ArrayList<SheetSettings> worksheetSettings) {
+ this.worksheetSettings = worksheetSettings;
+ }
+
+ /**
+ * Set the flag indicating whether we have row/column headers.
+ *
+ * @param hasColumnRowHeaders Flag to enable or disable headers.
+ */
+ public void setColumnRowHeaders(boolean hasColumnRowHeaders) {
+ this.hasColumnRowHeaders = hasColumnRowHeaders;
+ }
+
+ /**
+ * Gets the {@code Vector} of {@code SheetSettings}.
+ *
+ * @return {@code Vector} of {@code SheetSettings}
+ */
+ public ArrayList<SheetSettings> getSheetSettings() {
+ return worksheetSettings;
+ }
+
+ /**
+ * Gets the active sheet name.
+ *
+ * @return the active sheet name
+ */
+ public String getActiveSheet() {
+ return activeSheet;
+ }
+
+ /**
+ * Sets the active sheet name.
+ *
+ * @param activeSheet the active sheet name.
+ */
+ public void setActiveSheet(String activeSheet) {
+ this.activeSheet = activeSheet;
+ }
+
+ /**
+ * Adds an XML entry for a particular setting.
+ *
+ * @param root the root node at which to add the xml entry.
+ * @param attribute the name of the attribute to add.
+ * @param type the attribute type ({@code int}, {@code short} etc).
+ * @param value the value of the attribute.
+ */
+ private void addConfigItem(Node root, String attribute, String type,
+ String value) {
+
+ Element configItem = settings.createElement(TAG_CONFIG_ITEM);
+ configItem.setAttribute(ATTRIBUTE_CONFIG_NAME, attribute);
+ configItem.setAttribute(ATTRIBUTE_CONFIG_TYPE, type);
+
+ configItem.appendChild(settings.createTextNode(value));
+
+ root.appendChild(configItem);
+ }
+
+ /**
+ * Writes out a settings.xml entry for this {@code BookSettings} object.
+ *
+ * @param settings a {@code Document} object representing the settings.xml
+ * @param root the root xml node to add to.
+ */
+ public void writeNode(org.w3c.dom.Document settings, Node root) {
+
+ this.settings = settings;
+ Element configItemMapNamed = settings.createElement(TAG_CONFIG_ITEM_MAP_NAMED);
+ configItemMapNamed.setAttribute(ATTRIBUTE_CONFIG_NAME, "Tables");
+ for (SheetSettings s : worksheetSettings) {
+ s.writeNode(settings, configItemMapNamed);
+ }
+ addConfigItem(root, "ActiveTable", "string", activeSheet);
+ String booleanValue = Boolean.toString(hasColumnRowHeaders);
+ addConfigItem(root, "HasColumnRowHeaders", "boolean", booleanValue);
+ root.appendChild(configItemMapNamed);
+ }
+
+ /**
+ * Sets a variable based on a {@code String} value read from XML.
+ *
+ * @param name xml name of the attribute to set.
+ * @param value {@code String} value for the attribute.
+ */
+ private void addAttribute(String name, String value) {
+ if(name.equals("ActiveTable")) {
+ activeSheet = value;
+ } else if(name.equals("HasColumnRowHeaders")) {
+ hasColumnRowHeaders = Boolean.parseBoolean(value);
+ }
+ }
+
+ /**
+ * Reads document settings from xml and inits {@code SheetSettings} variables.
+ *
+ * @param root XML {@code Node} to read from.
+ */
+ private void readNode(Node root) {
+
+ if (root.hasChildNodes()) {
+
+ NodeList nodeList = root.getChildNodes();
+ int len = nodeList.getLength();
+ for (int i = 0; i < len; i++) {
+ Node child = nodeList.item(i);
+
+ if (child.getNodeType() == Node.ELEMENT_NODE) {
+ String nodeName = child.getNodeName();
+
+ if (nodeName.equals(TAG_CONFIG_ITEM)) {
+
+ NamedNodeMap cellAtt = child.getAttributes();
+
+ Node configNameNode =
+ cellAtt.getNamedItem(ATTRIBUTE_CONFIG_NAME);
+
+ String name = configNameNode.getNodeValue();
+ NodeList nodeList2 = child.getChildNodes();
+ int len2 = nodeList2.getLength();
+ String s = "";
+ for (int j = 0; j < len2; j++) {
+ Node child2 = nodeList2.item(j);
+ if (child2.getNodeType() == Node.TEXT_NODE) {
+ s = child2.getNodeValue();
+ }
+ }
+ addAttribute(name, s);
+
+ } else if (nodeName.equals(TAG_CONFIG_ITEM_MAP_NAMED)) {
+
+ readNode(child);
+
+ } else if (nodeName.equals(TAG_CONFIG_ITEM_MAP_ENTRY)) {
+
+ SheetSettings s = new SheetSettings(child);
+ worksheetSettings.add(s);
+
+ } else {
+
+ Debug.log(Debug.TRACE, "<OTHERS " + XmlUtil.getNodeInfo(child) + " />");
+ }
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxc/CellStyle.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxc/CellStyle.java
new file mode 100644
index 000000000..1628f199e
--- /dev/null
+++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxc/CellStyle.java
@@ -0,0 +1,471 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+package org.openoffice.xmerge.converter.xml.sxc;
+
+import java.awt.Color;
+
+import org.w3c.dom.NodeList;
+import org.w3c.dom.Node;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Element;
+
+import org.openoffice.xmerge.converter.xml.Style;
+import org.openoffice.xmerge.converter.xml.StyleCatalog;
+import org.openoffice.xmerge.util.Debug;
+
+/**
+ * Represents a text {@code Style} in an OpenOffice document.
+ */
+public class CellStyle extends Style implements Cloneable {
+
+ private Format fmt = new Format();
+
+ /**
+ * Constructor for use when going from DOM to client device format.
+ *
+ * @param node The <i>style:style</i> {@code Node} containing the
+ * {@code Style}. (This {@code Node} is assumed have a
+ * <i>family</i> attribute of <i>text</i>).
+ * @param sc The {@code StyleCatalog}, which is used for looking up
+ * ancestor {@code Style} objects.
+ */
+ public CellStyle(Node node, StyleCatalog sc) {
+ super(node, sc);
+
+ // Run through the attributes of this node, saving
+ // the ones we're interested in.
+ NamedNodeMap attrNodes = node.getAttributes();
+ if (attrNodes != null) {
+ int len = attrNodes.getLength();
+ for (int i = 0; i < len; i++) {
+ Node attr = attrNodes.item(i);
+ handleAttribute(attr.getNodeName(), attr.getNodeValue());
+ }
+ }
+
+ // Look for children. Only ones we care about are "style:properties"
+ // nodes. If any are found, recursively traverse them, passing
+ // along the style element to add properties to.
+ if (node.hasChildNodes()) {
+ NodeList children = node.getChildNodes();
+ int len = children.getLength();
+ for (int i = 0; i < len; i++) {
+ Node child = children.item(i);
+ String nodeName = child.getNodeName();
+ if (nodeName.equals("style:properties")) {
+ NamedNodeMap childAttrNodes = child.getAttributes();
+ if (childAttrNodes != null) {
+ int nChildAttrNodes = childAttrNodes.getLength();
+ for (int j = 0; j < nChildAttrNodes; j++) {
+ Node attr = childAttrNodes.item(j);
+ handleAttribute(attr.getNodeName(),
+ attr.getNodeValue());
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Constructor for use when going from client device format to DOM.
+ *
+ * @param name Name of cell {@code Style}. Can be {@code null}.
+ * @param family Family of text {@code Style} (usually <i>text</i>). Can
+ * be {@code null}.
+ * @param parent Name of parent text {@code Style}, or {@code null} for
+ * none.
+ * @param fmt size in points.
+ * @param sc The {@code StyleCatalog}, which is used for looking up
+ * ancestor {@code Style} objects.
+ */
+ public CellStyle(String name, String family, String parent,Format fmt,
+ StyleCatalog sc) {
+ super(name, family, parent, sc);
+ this.fmt = fmt;
+ }
+
+ /**
+ * Returns the {@code Format} object for this particular style.
+ *
+ * @return the {@code Format} object
+ */
+ public Format getFormat() {
+ return fmt;
+ }
+
+ /**
+ * Parse a color specification of the form <i>{@literal #rrggbb}</i>.
+ *
+ * @param value {@code Color} specification to parse.
+ *
+ * @return The {@code Color} associated the value.
+ */
+ private Color parseColorString(String value) {
+ int red = 0;
+ int green = 0;
+ int blue = 0;
+ try {
+ // Assume color value is of form #rrggbb
+ red = Integer.parseInt(value.substring(1, 3), 16);
+ green = Integer.parseInt(value.substring(3, 5), 16);
+ blue = Integer.parseInt(value.substring(5, 7), 16);
+ } catch (NumberFormatException e) {
+ Debug.log(Debug.ERROR, "Problem parsing a color string", e);
+ } catch (IndexOutOfBoundsException e) {
+ Debug.log(Debug.ERROR, "Problem parsing a color string", e);
+ }
+ return new Color(red, green, blue, 0);
+ }
+
+ /**
+ * Set an attribute.
+ *
+ * @param attr The attribute to set.
+ * @param value The attribute value to set.
+ */
+ private void handleAttribute(String attr, String value) {
+
+ if (attr.equals("fo:font-weight")) {
+ fmt.setAttribute(Format.BOLD, value.equals("bold"));
+ }
+
+ else if (attr.equals("fo:font-style")) {
+ if (value.equals("italic") || value.equals("oblique"))
+ fmt.setAttribute(Format.ITALIC, true);
+ else if (value.equals("normal"))
+ fmt.setAttribute(Format.ITALIC, false);
+ }
+
+ else if (attr.equals("style:text-underline")) {
+ fmt.setAttribute(Format.UNDERLINE, !value.equals("none"));
+ }
+
+ else if (attr.equals("style:text-crossing-out")) {
+ fmt.setAttribute(Format.STRIKETHRU, !value.equals("none"));
+ }
+
+ else if (attr.equals("style:text-position")) {
+ if (value.startsWith("super "))
+ fmt.setAttribute(Format.SUPERSCRIPT, true);
+ else if (value.startsWith("sub "))
+ fmt.setAttribute(Format.SUBSCRIPT, true);
+ else if (value.startsWith("0% "))
+ fmt.setAttribute(Format.SUPERSCRIPT | Format.SUBSCRIPT, false);
+ else {
+ String firstPart = value.substring(0, value.indexOf(' '));
+ if (firstPart.endsWith("%")) {
+ firstPart = firstPart.substring(0, value.indexOf('%'));
+ int amount;
+ try {
+ amount = Integer.parseInt(firstPart);
+ } catch (NumberFormatException e) {
+ amount = 0;
+ Debug.log(Debug.ERROR, "Problem with style:text-position tag", e);
+ }
+ if (amount < 0) fmt.setAttribute(Format.SUBSCRIPT, true);
+ else if (amount > 0) fmt.setAttribute(Format.SUPERSCRIPT, false);
+ }
+ }
+ }
+
+ else if (attr.equals("fo:font-size")) {
+ if (value.endsWith("pt")) {
+ String num = value.substring(0, value.length() - 2);
+ fmt.setFontSize(Integer.parseInt(num));
+ }
+ }
+
+ else if (attr.equals("style:font-name"))
+ fmt.setFontName(value);
+
+ else if (attr.equals("fo:color"))
+ fmt.setForeground(parseColorString(value));
+
+ else if (attr.equals("fo:background-color"))
+ fmt.setBackground(parseColorString(value));
+
+ else if (attr.equals("fo:text-align")) {
+ if(value.equals("center")) {
+ fmt.setAlign(Format.CENTER_ALIGN);
+ } else if(value.equals("end")) {
+ fmt.setAlign(Format.RIGHT_ALIGN);
+ } else if(value.equals("start")) {
+ fmt.setAlign(Format.LEFT_ALIGN);
+ }
+ }
+
+ else if (attr.equals("fo:vertical-align")) {
+ if(value.equals("top")) {
+ fmt.setVertAlign(Format.TOP_ALIGN);
+ } else if(value.equals("middle")) {
+ fmt.setVertAlign(Format.MIDDLE_ALIGN);
+ } else if(value.equals("bottom")) {
+ fmt.setVertAlign(Format.BOTTOM_ALIGN);
+ }
+ }
+
+ else if (attr.equals("fo:border")) {
+ fmt.setAttribute(Format.TOP_BORDER, !value.equals("none"));
+ fmt.setAttribute(Format.BOTTOM_BORDER, !value.equals("none"));
+ fmt.setAttribute(Format.LEFT_BORDER, !value.equals("none"));
+ fmt.setAttribute(Format.RIGHT_BORDER, !value.equals("none"));
+ }
+ else if (attr.equals("fo:border-top")) {
+ fmt.setAttribute(Format.TOP_BORDER, !value.equals("none"));
+ }
+ else if (attr.equals("fo:border-bottom")) {
+ fmt.setAttribute(Format.BOTTOM_BORDER, !value.equals("none"));
+ }
+ else if (attr.equals("fo:border-left")) {
+ fmt.setAttribute(Format.LEFT_BORDER, !value.equals("none"));
+ }
+ else if (attr.equals("fo:border-right")) {
+ fmt.setAttribute(Format.RIGHT_BORDER, !value.equals("none"));
+ }
+ else if (attr.equals("fo:wrap-option")) {
+ fmt.setAttribute(Format.WORD_WRAP, value.equals("wrap"));
+ }
+
+ else if (isIgnored(attr)) {}
+
+ else {
+ Debug.log(Debug.INFO, "CellStyle Unhandled: " + attr + "=" + value);
+ }
+ }
+
+ /**
+ * Return a {@code Style} object corresponding to this one, but with all of
+ * the inherited information from parent {@code Style} objects filled in.
+ *
+ * <p>The object returned will be a new object, not a reference to this
+ * object, even if it does not need any information added.</p>
+ *
+ * @return The {@code StyleCatalog} in which to look up ancestors.
+ */
+ @Override
+ public Style getResolved() {
+ // Create a new object to return, which is a clone of this one.
+ CellStyle resolved = null;
+ try {
+ resolved = (CellStyle)this.clone();
+ } catch (Exception e) {
+ Debug.log(Debug.ERROR, "Can't clone", e);
+ }
+
+ // Look up the parentStyle. (If there is no style catalog
+ // specified, we can't do any lookups.)
+ CellStyle parentStyle = null;
+ if (sc != null) {
+ if (parent != null) {
+ parentStyle = (CellStyle)sc.lookup(parent, family, null,
+ this.getClass());
+ if (parentStyle == null)
+ Debug.log(Debug.ERROR, "parent style lookup of "
+ + parent + " failed!");
+ else
+ parentStyle = (CellStyle)parentStyle.getResolved();
+
+ } else if (!name.equals("DEFAULT_STYLE")) {
+ parentStyle = (CellStyle)sc.lookup("DEFAULT_STYLE", null,
+ null, this.getClass());
+ }
+ }
+
+ // If we found a parent, for any attributes which we don't have
+ // set, try to get the values from the parent.
+ if (parentStyle != null) {
+ parentStyle = (CellStyle)parentStyle.getResolved();
+ Format parentFormat = parentStyle.getFormat();
+ Format resolvedFormat = resolved.getFormat();
+
+ if ((fmt.getAlign() == Format.LEFT_ALIGN) && (parentFormat.getAlign() != Format.LEFT_ALIGN))
+ resolvedFormat.setAlign(parentFormat.getAlign());
+ if ((fmt.getVertAlign() == Format.BOTTOM_ALIGN) && (parentFormat.getVertAlign() != Format.BOTTOM_ALIGN))
+ resolvedFormat.setVertAlign(parentFormat.getVertAlign());
+ if ((fmt.getFontSize() == 0) && (parentFormat.getFontSize() != 0))
+ resolvedFormat.setFontSize(parentFormat.getFontSize());
+ if ((fmt.getFontName() == null) && (parentFormat.getFontName() != null))
+ resolvedFormat.setFontName(parentFormat.getFontName());
+ if ((fmt.getForeground() == null) && (parentFormat.getForeground() != null))
+ resolvedFormat.setForeground(parentFormat.getForeground());
+ if ((fmt.getBackground() == null) && (parentFormat.getBackground() != null))
+ resolvedFormat.setBackground(parentFormat.getBackground());
+ for (int m = Format.BOLD; m <= Format.SUBSCRIPT; m = m << 1) {
+ if ((fmt.getAttribute(m)) && (parentFormat.getAttribute(m))) {
+ resolvedFormat.setAttribute(m, parentFormat.getAttribute(m));
+ }
+ }
+
+ }
+ return resolved;
+ }
+
+ /**
+ * Create a new {@code Node} in the {@code Document}, and write this
+ * {@code Style} to it.
+ *
+ * @param parentDoc Parent {@code Document} of the {@code Node} to create.
+ * @param name Name to use for the new {@code Node} (e.g.
+ * <i>style:style</i>)
+ *
+ * @return Created {@code Node}.
+ */
+ @Override
+ public Node createNode(org.w3c.dom.Document parentDoc, String name) {
+ Element node = parentDoc.createElement(name);
+ writeAttributes(node);
+ return node;
+ }
+
+ /**
+ * Return {@code true} if {@code style} specifies as much or less than this
+ * {@code Style}, and nothing it specifies contradicts this {@code Style}.
+ *
+ * @param style The {@code Style} to check.
+ *
+ * @return {@code true} if {@code style} is a subset, {@code false}
+ * otherwise.
+ */
+ @Override
+ public boolean isSubset(Style style) {
+ if (style.getClass() != this.getClass())
+ return false;
+ CellStyle tStyle = (CellStyle)style;
+
+ Format rhs = tStyle.getFormat();
+
+ return fmt.isSubset(rhs);
+ }
+
+ /**
+ * Write this {@code Style} object's attributes to a {@code Node} in the
+ * {@code Document}.
+ *
+ * @param node The {@code Node} to add {@code Style} attributes.
+ */
+ private void writeAttributes(Element node) {
+
+ if (fmt.getAlign()==Format.RIGHT_ALIGN)
+ node.setAttribute("fo:text-align", "end");
+
+ if (fmt.getAlign()==Format.LEFT_ALIGN)
+ node.setAttribute("fo:text-align", "start");
+
+ if (fmt.getAlign()==Format.CENTER_ALIGN)
+ node.setAttribute("fo:text-align", "center");
+
+ if (fmt.getVertAlign()==Format.TOP_ALIGN)
+ node.setAttribute("fo:vertical-align", "top");
+
+ if (fmt.getVertAlign()==Format.MIDDLE_ALIGN)
+ node.setAttribute("fo:vertical-align", "middle");
+
+ if (fmt.getVertAlign()==Format.BOTTOM_ALIGN)
+ node.setAttribute("fo:vertical-align", "bottom");
+
+ if (fmt.getAttribute(Format.BOLD))
+ node.setAttribute("fo:font-weight", "bold");
+
+ if (fmt.getAttribute(Format.ITALIC))
+ node.setAttribute("fo:font-style", "italic");
+
+ if (fmt.getAttribute(Format.UNDERLINE))
+ node.setAttribute("style:text-underline", "single");
+
+ if (fmt.getAttribute(Format.STRIKETHRU))
+ node.setAttribute("style:text-crossing-out", "single-line");
+
+ if (fmt.getAttribute(Format.SUPERSCRIPT))
+ node.setAttribute("style:text-position", "super 58%");
+
+ if (fmt.getAttribute(Format.SUBSCRIPT))
+ node.setAttribute("style:text-position", "sub 58%");
+
+ if (fmt.getFontSize() != 0) {
+ node.setAttribute("fo:font-size", Integer.toString(fmt.getFontSize()) + "pt");
+ }
+
+ if (fmt.getFontName() != null)
+ node.setAttribute("style:font-name", fmt.getFontName());
+
+ if (fmt.getForeground() != null)
+ node.setAttribute("fo:color", buildColorString(fmt.getForeground()));
+
+ if (fmt.getBackground() != null)
+ node.setAttribute("fo:background-color",
+ buildColorString(fmt.getBackground()));
+
+ if (fmt.getAttribute(Format.TOP_BORDER))
+ node.setAttribute("fo:border-top", "0.0008inch solid #000000");
+
+ if (fmt.getAttribute(Format.BOTTOM_BORDER))
+ node.setAttribute("fo:border-bottom", "0.0008inch solid #000000");
+
+ if (fmt.getAttribute(Format.RIGHT_BORDER))
+ node.setAttribute("fo:border-right", "0.0008inch solid #000000");
+
+ if (fmt.getAttribute(Format.LEFT_BORDER))
+ node.setAttribute("fo:border-left", "0.0008inch solid #000000");
+
+ if (fmt.getAttribute(Format.WORD_WRAP))
+ node.setAttribute("fo:wrap-option", "wrap");
+
+ }
+
+ /**
+ * Given a {@code Color}, return a string of the form <i>{@literal #rrggbb}</i>.
+ *
+ * @param c The {@code Color} value.
+ *
+ * @return The {@code Color} value in the form <i>{@literal #rrggbb}</i>.
+ */
+ private String buildColorString(Color c) {
+ return String.format("#%06X", c.getRGB() & 0x00FFFFFF);
+ }
+
+ private static String[] ignored = {
+ "style:text-autospace", "style:text-underline-color",
+ "fo:margin-left", "fo:margin-right", "fo:text-indent",
+ "fo:margin-top", "fo:margin-bottom", "text:line-number",
+ "text:number-lines", "style:country-asian",
+ "style:font-size-asian", "style:font-name-complex",
+ "style:language-complex", "style:country-complex",
+ "style:font-size-complex", "style:punctuation-wrap",
+ "fo:language", "fo:country",
+ "style:font-name-asian", "style:language-asian",
+ "style:line-break", "fo:keep-with-next"
+ };
+
+ /**
+ * This code checks whether an attribute is one that we intentionally ignore.
+ *
+ * @param attribute The attribute to check.
+ *
+ * @return {@code true} if {@code attribute} can be ignored, otherwise
+ * {@code false}.
+ */
+ private boolean isIgnored(String attribute) {
+ for (int i = 0; i < ignored.length; i++) {
+ if (ignored[i].equals(attribute))
+ return true;
+ }
+ return false;
+ }
+} \ No newline at end of file
diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxc/ColumnRowInfo.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxc/ColumnRowInfo.java
new file mode 100644
index 000000000..03649deff
--- /dev/null
+++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxc/ColumnRowInfo.java
@@ -0,0 +1,180 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+package org.openoffice.xmerge.converter.xml.sxc;
+
+/**
+ * This is a class to define a table-column structure.
+ *
+ * <p>This can then be used by plug-ins to write or read their own column types.
+ * </p>
+ */
+public class ColumnRowInfo {
+
+ final public static int COLUMN = 0x01;
+ final public static int ROW = 0x02;
+
+ final private static int DEFAULTROWSIZE_MIN = 250;
+ final private static int DEFAULTROWSIZE_MAX = 260;
+
+ private int type;
+ private int dimension = 0;
+ private int repeated = 1;
+ private boolean userDefined = true;
+ private Format fmt = new Format();
+
+ /**
+ * Constructor for a {@code ColumnRowInfo}.
+ *
+ * @param type whether {@code ROW} or {@code COLUMN} record.
+ */
+ public ColumnRowInfo(int type) {
+
+ this.type = type;
+ }
+
+ /**
+ * Constructor for a {@code ColumnRowInfo}.
+ *
+ * @param dimension if it's a row the height, a column the width.
+ * @param repeated how many times it is repeated.
+ * @param type whether {@code ROW} or {@code COLUMN} record.
+ */
+ private ColumnRowInfo(int dimension, int repeated, int type) {
+
+ this.dimension = dimension;
+ this.repeated = repeated;
+ this.type = type;
+ }
+
+ /**
+ * Constructor for a {@code ColumnRowInfo} that includes userDefined field.
+ *
+ * @param dimension if it's a row the height, a column the width.
+ * @param repeated how many times it is repeated.
+ * @param type whether {@code ROW} or {@code COLUMN} record.
+ * @param userDefined whether the record is manually set.
+ */
+ public ColumnRowInfo(int dimension, int repeated, int type, boolean userDefined) {
+
+ this(dimension, repeated, type);
+ this.userDefined = userDefined;
+ }
+
+ /**
+ * Sets the format.
+ *
+ * @param fmt The new format to use.
+ */
+ public void setFormat(Format fmt) {
+
+ this.fmt = fmt;
+ }
+
+ /**
+ * Get the current {@code Format}.
+ *
+ * @return The current {@code Format}.
+ */
+ public Format getFormat() {
+
+ return fmt;
+ }
+
+ /**
+ * Get the height (for rows) or width (for columns).
+ *
+ * @return The height or width.
+ */
+ public int getSize() {
+
+ return dimension;
+ }
+
+ /**
+ * Set the height (for rows) or width (for columns).
+ *
+ * @param dimension The height or width.
+ */
+ public void setSize(int dimension) {
+
+ this.dimension = dimension;
+ }
+
+ /**
+ * Get the repeat count for this item.
+ *
+ * @return The number of times this item is repeated.
+ */
+ public int getRepeated() {
+
+ return repeated;
+ }
+
+ /**
+ * Set the repeat count for this item.
+ *
+ * @param repeated The number of times this item is repeated.
+ */
+ public void setRepeated(int repeated) {
+
+ this.repeated = repeated;
+ }
+
+ /**
+ * Does this {@code ColumnRowInfo} represent a row?
+ *
+ * @return {@code true} if a row, {@code false} if not.
+ */
+ public boolean isRow() {
+
+ return type==ROW;
+ }
+
+ /**
+ * Does this {@code ColumnRowInfo} represent a column?
+ *
+ * @return {@code true} if a column, {@code false} if not.
+ */
+ public boolean isColumn() {
+
+ return type==COLUMN;
+ }
+
+ /**
+ * Test if the row height has been set manually.
+ *
+ * @return {@code true} if user defined otherwise {@code false}.
+ */
+ public boolean isUserDefined() {
+
+ return userDefined;
+ }
+
+ /**
+ * Test if the row height is default.
+ *
+ * @return {@code true} if default otherwise {@code false}.
+ */
+ public boolean isDefaultSize() {
+
+ return type==ROW &&
+ dimension>DEFAULTROWSIZE_MIN &&
+ dimension<DEFAULTROWSIZE_MAX;
+ }
+}
diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxc/ColumnStyle.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxc/ColumnStyle.java
new file mode 100644
index 000000000..c9ff20591
--- /dev/null
+++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxc/ColumnStyle.java
@@ -0,0 +1,244 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+package org.openoffice.xmerge.converter.xml.sxc;
+
+import org.w3c.dom.NodeList;
+import org.w3c.dom.Node;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Element;
+
+import org.openoffice.xmerge.converter.xml.Style;
+import org.openoffice.xmerge.converter.xml.StyleCatalog;
+import org.openoffice.xmerge.util.Debug;
+import org.openoffice.xmerge.util.TwipsConverter;
+
+/**
+ * Represents a text {@code Style} in an OpenOffice document.
+ */
+public class ColumnStyle extends Style implements Cloneable {
+
+ private int colWidth = 0;
+ /**
+ * Constructor for use when going from DOM to client device format.
+ *
+ * @param node The <i>style:style</i> {@code Node} containing the
+ * {@code Style}. (This {@code Node} is assumed have a
+ * <i>family</i> attribute of <i>text</i>).
+ * @param sc The {@code StyleCatalog}, which is used for looking up
+ * ancestor {@code Style} objects.
+ */
+ public ColumnStyle(Node node, StyleCatalog sc) {
+ super(node, sc);
+
+ // Run through the attributes of this node, saving
+ // the ones we're interested in.
+ NamedNodeMap attrNodes = node.getAttributes();
+ if (attrNodes != null) {
+ int len = attrNodes.getLength();
+ for (int i = 0; i < len; i++) {
+ Node attr = attrNodes.item(i);
+ handleAttribute(attr.getNodeName(), attr.getNodeValue());
+ }
+ }
+
+ // Look for children. Only ones we care about are "style:properties"
+ // nodes. If any are found, recursively traverse them, passing
+ // along the style element to add properties to.
+ if (node.hasChildNodes()) {
+ NodeList children = node.getChildNodes();
+ int len = children.getLength();
+ for (int i = 0; i < len; i++) {
+ Node child = children.item(i);
+ String nodeName = child.getNodeName();
+ if (nodeName.equals("style:properties")) {
+ NamedNodeMap childAttrNodes = child.getAttributes();
+ if (childAttrNodes != null) {
+ int nChildAttrNodes = childAttrNodes.getLength();
+ for (int j = 0; j < nChildAttrNodes; j++) {
+ Node attr = childAttrNodes.item(j);
+ handleAttribute(attr.getNodeName(),
+ attr.getNodeValue());
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Constructor for use when going from client device format to DOM.
+ *
+ * @param name Name of text {@code Style}. Can be {@code null}.
+ * @param family Family of text {@code Style} (usually <i>text</i>).
+ * Can be {@code null}.
+ * @param parent Name of parent text {@code Style}, or {@code null} for
+ * none.
+ * @param colWidth the width of this column.
+ * @param sc The {@code StyleCatalog}, which is used for looking up
+ * ancestor {@code Style} objects.
+ */
+ public ColumnStyle(String name, String family, String parent, int colWidth, StyleCatalog sc) {
+ super(name, family, parent, sc);
+ this.colWidth = colWidth;
+ }
+
+ /**
+ * Returns the width of this column.
+ *
+ * @return The width of this column.
+ */
+ public int getColWidth() {
+ return colWidth;
+ }
+
+ /**
+ * Sets the width of this column.
+ *
+ * @param colWidth The width of this column.
+ */
+ private void setColWidth(int colWidth) {
+
+ this.colWidth = colWidth;
+ }
+
+ /**
+ * Parse a colwidth in the form {@literal "1.234cm"} to twips.
+ *
+ * @param value {@code String} specification to parse.
+ *
+ * @return The twips equivalent.
+ */
+ private int parseColWidth(String value) {
+ return TwipsConverter.convert2twips(value, 255);
+ }
+
+ /**
+ * Set an attribute.
+ *
+ * @param attr The attribute to set.
+ * @param value The attribute value to set.
+ */
+ private void handleAttribute(String attr, String value) {
+
+ if (attr.equals("style:column-width")) {
+ colWidth = parseColWidth(value);
+ }
+ else {
+ Debug.log(Debug.INFO, "ColumnStyle Unhandled: " + attr + "=" + value);
+ }
+ }
+
+ /**
+ * Return a {@code Style} object corresponding to this one, but with all of
+ * the inherited information from parent {@code Style} objects filled in.
+ *
+ * <p>The object returned will be a new object, not a reference to this
+ * object, even if it does not need any information added.</p>
+ *
+ * @return The {@code Style} in which to look up ancestors.
+ */
+ @Override
+ public Style getResolved() {
+ // Create a new object to return, which is a clone of this one.
+ ColumnStyle resolved = null;
+ try {
+ resolved = (ColumnStyle)this.clone();
+ } catch (Exception e) {
+ Debug.log(Debug.ERROR, "Can't clone", e);
+ }
+
+ // Look up the parentStyle. (If there is no style catalog
+ // specified, we can't do any lookups.)
+ ColumnStyle parentStyle = null;
+ if (sc != null) {
+ if (parent != null) {
+ parentStyle = (ColumnStyle)sc.lookup(parent, family, null,
+ this.getClass());
+ if (parentStyle == null)
+ Debug.log(Debug.ERROR, "parent style lookup of "
+ + parent + " failed!");
+ else
+ parentStyle = (ColumnStyle)parentStyle.getResolved();
+
+ } else if (!name.equals("DEFAULT_STYLE")) {
+ parentStyle = (ColumnStyle)sc.lookup("DEFAULT_STYLE", null,
+ null, this.getClass());
+ }
+ }
+
+ // If we found a parent, for any attributes which we don't have
+ // set, try to get the values from the parent.
+ if (parentStyle != null) {
+ parentStyle = (ColumnStyle)parentStyle.getResolved();
+
+ if ((colWidth == 0) && (parentStyle.getColWidth() != 0))
+ resolved.setColWidth(parentStyle.getColWidth());
+ }
+ return resolved;
+ }
+
+ /**
+ * Create a new {@code Node} in the <code>Document</code>, and write this
+ * {@code Style} to it.
+ *
+ * @param parentDoc Parent {@code Document} of the {@code Node} to create.
+ * @param name Name to use for the new {@code Node} (e.g.
+ * <i>style:style</i>)
+ *
+ * @return Created {@code Node}.
+ */
+ @Override
+ public Node createNode(org.w3c.dom.Document parentDoc, String name) {
+ Element node = parentDoc.createElement(name);
+ writeAttributes(node);
+ return node;
+ }
+
+ /**
+ * Return {@code true} if {@code style} specifies as much or less than this
+ * {@code Style}, and nothing it specifies contradicts this {@code Style}.
+ *
+ * @param style The {@code Style} to check.
+ *
+ * @return {@code true} if <code>style</code> is a subset, {@code false}
+ * otherwise.
+ */
+ @Override
+ public boolean isSubset(Style style) {
+ if (style.getClass() != this.getClass())
+ return false;
+ ColumnStyle tStyle = (ColumnStyle)style;
+
+ return colWidth == tStyle.getColWidth();
+ }
+
+ /**
+ * Write this {@code Style} object's attributes to a {@code Node} in the
+ * {@code Document}.
+ *
+ * @param node The {@code Node} to add {@code Style} attributes.
+ */
+ private void writeAttributes(Element node) {
+
+ if(colWidth!=0) {
+ String width = TwipsConverter.twips2cm(colWidth) + "cm";
+ node.setAttribute("style:column-width", width);
+ }
+ }
+} \ No newline at end of file
diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxc/DocumentMergerImpl.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxc/DocumentMergerImpl.java
new file mode 100644
index 000000000..e768a3cac
--- /dev/null
+++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxc/DocumentMergerImpl.java
@@ -0,0 +1,183 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+package org.openoffice.xmerge.converter.xml.sxc;
+
+import org.w3c.dom.Node;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+
+import org.openoffice.xmerge.Document;
+import org.openoffice.xmerge.DocumentMerger;
+import org.openoffice.xmerge.MergeException;
+import org.openoffice.xmerge.ConverterCapabilities;
+import org.openoffice.xmerge.converter.xml.OfficeConstants;
+import org.openoffice.xmerge.merger.DiffAlgorithm;
+import org.openoffice.xmerge.merger.Difference;
+import org.openoffice.xmerge.merger.Iterator;
+import org.openoffice.xmerge.merger.NodeMergeAlgorithm;
+import org.openoffice.xmerge.merger.diff.IteratorRowCompare;
+import org.openoffice.xmerge.merger.diff.RowIterator;
+import org.openoffice.xmerge.merger.merge.SheetMerge;
+import org.openoffice.xmerge.merger.merge.PositionBaseRowMerge;
+import org.openoffice.xmerge.merger.MergeAlgorithm;
+import org.openoffice.xmerge.util.XmlUtil;
+import org.openoffice.xmerge.util.Debug;
+
+/**
+ * Generic small device implementation of {@code DocumentMerger} for
+ * the {@link org.openoffice.xmerge.converter.xml.sxc.SxcPluginFactory
+ * SxcPluginFactory}.
+ *
+ * <p>Used with SXC {@code Document} objects.</p>
+ */
+public class DocumentMergerImpl implements DocumentMerger {
+
+ private final ConverterCapabilities cc_;
+ private final org.openoffice.xmerge.Document orig;
+
+ /**
+ * Constructor.
+ *
+ * @param doc The original &quot;Office&quot; {@code Document} to merge.
+ * @param cc The {@code ConverterCapabilities}.
+ */
+ public DocumentMergerImpl(org.openoffice.xmerge.Document doc,
+ ConverterCapabilities cc) {
+ cc_ = cc;
+ this.orig = doc;
+ }
+
+ public void merge(Document modifiedDoc) throws MergeException {
+
+ SxcDocument sdoc1 = (SxcDocument)orig;
+ SxcDocument sdoc2 = (SxcDocument)modifiedDoc;
+
+ org.w3c.dom.Document doc1 = sdoc1.getContentDOM();
+ org.w3c.dom.Document doc2 = sdoc2.getContentDOM();
+
+ Element elem1 = doc1.getDocumentElement();
+ Element elem2 = doc2.getDocumentElement();
+
+ // get table name
+ NodeList workSheetList1 =
+ elem1.getElementsByTagName(OfficeConstants.TAG_TABLE);
+ NodeList workSheetList2 =
+ elem2.getElementsByTagName(OfficeConstants.TAG_TABLE);
+
+ int numOfWorkSheet = workSheetList1.getLength();
+
+ for (int i=0; i < numOfWorkSheet; i++) {
+ Node workSheet = workSheetList1.item(i);
+
+ // try to match the workSheet
+ Node matchingWorkSheet = matchWorkSheet(workSheet, workSheetList2);
+
+ if (matchingWorkSheet != null) {
+
+ // need to put it into a row Iterator
+ // use a straight comparison algorithm then do a merge on it
+ Iterator i1 = new RowIterator(cc_, workSheet);
+ Iterator i2 = new RowIterator(cc_, matchingWorkSheet);
+
+ // find out the diff
+ DiffAlgorithm diffAlgo = new IteratorRowCompare();
+
+ // find out the paragraph level diffs
+ Difference[] diffResult = diffAlgo.computeDiffs(i1, i2);
+
+ if (Debug.isFlagSet(Debug.INFO)) {
+ Debug.log(Debug.INFO, "Diff Result: ");
+
+ for (int j = 0; j < diffResult.length; j++) {
+ Debug.log(Debug.INFO, diffResult[j].debug());
+ }
+ }
+
+ // merge back the result
+ NodeMergeAlgorithm rowMerger = new PositionBaseRowMerge(cc_);
+ MergeAlgorithm merger = new SheetMerge(cc_, rowMerger);
+
+ merger.applyDifference(i1, i2, diffResult);
+ }
+ }
+
+ numOfWorkSheet = workSheetList2.getLength();
+
+ // for those workSheet from target don't have a matching one in the
+ // original workSheet list, we add it
+
+ // find out the office body node first
+ NodeList officeBodyList =
+ elem1.getElementsByTagName(OfficeConstants.TAG_OFFICE_BODY);
+
+ Node officeBody = officeBodyList.item(0);
+
+ // for each WorkSheets, try to see whether we have it or not
+ for (int j=0; j < numOfWorkSheet; j++) {
+ Node workSheet= workSheetList2.item(j);
+
+ // try to match the workSheet
+ Node matchingWorkSheet = matchWorkSheet(workSheet, workSheetList1);
+
+ // add the new WorkSheet to the original document iff match not found
+ if (matchingWorkSheet == null) {
+ Node cloneNode = XmlUtil.deepClone(officeBody, workSheet);
+ officeBody.appendChild(cloneNode);
+ }
+ }
+ }
+
+ /**
+ * Try to find a WorkSheet from the modified WorkSheetList that has a
+ * matching table name from the original WorkSheet.
+ *
+ * @param orgSheet The original WorkSheet.
+ * @param modSheetList The modified WorkSheet.
+ *
+ * @return The {@code Node} in modSheetList that matches the {@code orgSheet}.
+ */
+ private Node matchWorkSheet(Node orgSheet, NodeList modSheetList) {
+
+ Node matchSheet = null;
+
+ String orgTableName = ((Element)orgSheet).getAttribute(
+ OfficeConstants.ATTRIBUTE_TABLE_NAME);
+
+ if (orgTableName == null)
+ return null;
+
+ int numOfWorkSheet = modSheetList.getLength();
+
+ String modTableName;
+
+ for (int i=0; i < numOfWorkSheet; i++) {
+ modTableName = ((Element)modSheetList.item(i)).getAttribute(
+ OfficeConstants.ATTRIBUTE_TABLE_NAME);
+ if (modTableName == null)
+ continue;
+
+ if (orgTableName.equals(modTableName)) {
+ matchSheet = modSheetList.item(i);
+ break;
+ }
+ }
+
+ return matchSheet;
+ }
+}
diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxc/Format.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxc/Format.java
new file mode 100644
index 000000000..0a2404415
--- /dev/null
+++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxc/Format.java
@@ -0,0 +1,426 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+package org.openoffice.xmerge.converter.xml.sxc;
+
+import java.awt.Color;
+
+/**
+ * This class specifies the format for a given spreadsheet cell.
+ */
+public class Format {
+
+ /** Horizontal Alignment Constants. */
+ final public static int RIGHT_ALIGN = 0x01;
+ final public static int CENTER_ALIGN = 0x02;
+ final public static int LEFT_ALIGN = 0x03;
+
+ /** Vertical Alignment Constants. */
+ final public static int TOP_ALIGN = 0x01;
+ final public static int MIDDLE_ALIGN = 0x02;
+ final public static int BOTTOM_ALIGN = 0x03;
+
+ /** Indicates <i>bold</i> text. */
+ final public static int BOLD = 0x01;
+ /** Indicates <i>italic</i> text. */
+ final public static int ITALIC = 0x02;
+ /** Indicates <i>underlined</i> text. */
+ final public static int UNDERLINE = 0x04;
+ /** Indicates <i>strike-through</i> in the text. */
+ final public static int STRIKETHRU = 0x08;
+ /** Indicates <i>superscripted</i> text. */
+ final public static int SUPERSCRIPT = 0x10;
+ /** Indicates <i>subscripted</i> text. */
+ final public static int SUBSCRIPT = 0x20;
+
+ final public static int LEFT_BORDER = 0x40;
+ final public static int RIGHT_BORDER = 0x80;
+ final public static int TOP_BORDER = 0x100;
+ final public static int BOTTOM_BORDER = 0x200;
+
+ final public static int WORD_WRAP = 0x400;
+
+ private int align;
+ private int vertAlign;
+ private String category;
+ private String value;
+ private String formatSpecifier;
+ private int decimalPlaces;
+
+ /** Font name. */
+ private String fontName;
+ /** Font size in points. */
+ private int sizeInPoints;
+
+ private Color foreground, background;
+
+ /** Values of text attributes. */
+ private int attributes = 0;
+ /** Bitwise mask of text attributes. */
+ private int mask = 0;
+
+ /**
+ * Constructor for creating a new {@code Format}.
+ */
+ public Format() {
+ clearFormatting();
+ }
+
+ /**
+ * Constructor that creates a new {@code Format} object by setting all the
+ * format attributes.
+ *
+ * @param attributes Attributes flags (alignment, bold, etc.)
+ * @param fontSize Size of the font in points.
+ * @param fontName Name of the font to use.
+ */
+ public Format(int attributes, int fontSize, String fontName) {
+
+ this.attributes = attributes;
+ sizeInPoints = fontSize;
+ this.fontName = fontName;
+ }
+
+ /**
+ * Constructor for creating a new {@code Format} object based on an existing
+ * one.
+ *
+ * @param fmt {@code Format} to copy.
+ */
+ public Format(Format fmt) {
+ category = fmt.getCategory();
+ value = fmt.getValue();
+ formatSpecifier = fmt.getFormatSpecifier();
+ decimalPlaces = fmt.getDecimalPlaces();
+
+ attributes = fmt.attributes;
+ mask = fmt.mask;
+
+ fontName = fmt.getFontName();
+ align = fmt.getAlign();
+ vertAlign = fmt.getVertAlign();
+ foreground = fmt.getForeground();
+ background = fmt.getBackground();
+ sizeInPoints = fmt.sizeInPoints;
+ }
+
+ /**
+ * Reset this {@code Format} description.
+ */
+ public void clearFormatting() {
+ category = "";
+ value = "";
+ formatSpecifier = "";
+ decimalPlaces = 0;
+ attributes = 0;
+ mask = 0;
+ sizeInPoints = 10;
+ align = LEFT_ALIGN;
+ vertAlign = BOTTOM_ALIGN;
+ fontName = "";
+ foreground = null;
+ background = null;
+ }
+
+ /**
+ * Set one or more text attributes.
+ *
+ * @param flags Flag attributes to set.
+ * @param toggle {@code true} to set flags, {@code false} to clear them.
+ */
+ public void setAttribute(int flags, boolean toggle) {
+ mask |= flags;
+ if(toggle) {
+ attributes |= flags;
+ } else {
+ attributes &= ~flags;
+ }
+ }
+
+ /**
+ * Return {@code true} if the {@code attribute} is set to <i>on</i>.
+ *
+ * @param attribute Attribute to check ({@link #BOLD}, {@link #ITALIC},
+ * etc.).
+ *
+ * @return {@code true} if {@code attribute} is set to <i>on</i>, otherwise
+ * {@code false}.
+ */
+ public boolean getAttribute(int attribute) {
+ if ((mask & attribute) == 0)
+ return false;
+ return ((attributes & attribute) != 0);
+ }
+
+ /**
+ * Set the formatting category of this object, ie number, date, currency.
+ *
+ * <p>The <code>OfficeConstants</code> class contains string constants for
+ * the category types.</p>
+ *
+ * @see org.openoffice.xmerge.converter.xml.OfficeConstants
+ *
+ * @param newCategory The name of the category to be set.
+ */
+ public void setCategory(String newCategory) {
+ category = newCategory;
+ }
+
+ /**
+ * Return the formatting category of the object.
+ *
+ * @see org.openoffice.xmerge.converter.xml.OfficeConstants
+ *
+ * @return The formatting category of the object.
+ */
+ private String getCategory() {
+ return category;
+ }
+
+ /**
+ * In the case of Formula returns the value of the formula.
+ *
+ * @return The value of the formula.
+ */
+ private String getValue() {
+ return value;
+ }
+
+ /**
+ * In the case of formula the contents are set as the formula string and
+ * the value of the formula is a formatting attribute.
+ *
+ * @param newValue the formula value.
+ */
+ public void setValue(String newValue) {
+ value = newValue;
+ }
+
+ /**
+ * Set the {@code Format} specifier for this category.
+ *
+ * @param formatString The new {@code Format} specifier.
+ */
+ public void setFormatSpecifier(String formatString) {
+ formatSpecifier = formatString;
+ }
+
+ /**
+ * Get the {@code Format} specifier for this category.
+ *
+ * @return {@code Format} specifier for this category.
+ */
+ private String getFormatSpecifier() {
+ return formatSpecifier;
+ }
+
+ /**
+ * Set the precision of the number to be displayed.
+ *
+ * @param precision The number of decimal places to display.
+ */
+ public void setDecimalPlaces(int precision) {
+ decimalPlaces = precision;
+ }
+
+ /**
+ * Get the number of decimal places displayed.
+ *
+ * @return Number of decimal places.
+ */
+ private int getDecimalPlaces() {
+ return decimalPlaces;
+ }
+
+ /**
+ * Set the font used for this cell.
+ *
+ * @param fontName The name of the font.
+ */
+ public void setFontName(String fontName) {
+ this.fontName = fontName;
+ }
+
+ /**
+ * Get the font used for this cell.
+ *
+ * @return The font name.
+ */
+ public String getFontName() {
+ return fontName;
+ }
+
+ /**
+ * Set the font size (in points) used for this cell.
+ *
+ * @param fontSize The font size in points.
+ */
+ public void setFontSize(int fontSize) {
+ sizeInPoints = fontSize;
+ }
+
+ /**
+ * Get the font size (in points) used for this cell.
+ *
+ * @return The font size in points.
+ */
+ public int getFontSize() {
+ return sizeInPoints;
+ }
+
+ /**
+ * Set the vertical alignment used for this cell.
+ *
+ * @param vertAlign The vertical alignment.
+ */
+ public void setVertAlign(int vertAlign) {
+ this.vertAlign = vertAlign;
+ }
+
+ /**
+ * Get the vertical alignment used for this cell.
+ *
+ * @return The vertical alignment.
+ */
+ public int getVertAlign() {
+ return vertAlign;
+ }
+
+ /**
+ * Set the alignment used for this cell.
+ *
+ * @param align The alignment to use.
+ */
+ public void setAlign(int align) {
+ this.align = align;
+ }
+
+ /**
+ * Get the alignment used for this cell.
+ *
+ * @return The alignment.
+ */
+ public int getAlign() {
+ return align;
+ }
+
+ /**
+ * Set the Foreground {@code Color} for this cell.
+ *
+ * @param c A {@code Color} object representing the foreground color.
+ */
+ public void setForeground(Color c) {
+ if(c!=null)
+ foreground = new Color(c.getRGB());
+ }
+
+ /**
+ * Get the Foreground {@code Color} for this cell.
+ *
+ * @return Foreground {@code Color} value.
+ */
+ public Color getForeground() {
+ return foreground;
+ }
+
+ /**
+ * Set the Background {@code Color} for this cell.
+ *
+ * @param c A {@code Color} object representing the background color.
+ */
+ public void setBackground(Color c) {
+ if(c!=null)
+ background = new Color(c.getRGB());
+ }
+
+ /**
+ * Get the Background {@code Color} for this cell.
+ *
+ * @return Background {@code Color} value.
+ */
+ public Color getBackground() {
+ return background;
+ }
+
+ /**
+ * Get a {@code String} representation of this {@code Format}.
+ *
+ * @return A {@code String} indicating the value and category.
+ */
+ @Override
+ public String toString() {
+ return "Value : " + getValue() + " Category : " + getCategory();
+ }
+
+ /**
+ * Tests if the current {@code Format} object has default attribute values.
+ *
+ * @return {@code true} if it contains default value.
+ */
+ public boolean isDefault() {
+
+ Format rhs = new Format();
+
+ if (rhs.attributes!= attributes)
+ return false;
+
+ if (foreground!=rhs.foreground)
+ return false;
+
+ if (background!=rhs.background)
+ return false;
+
+ if (rhs.align!= align)
+ return false;
+
+ return rhs.vertAlign == vertAlign;
+ }
+
+ /**
+ * Return true if passed {@code Format} specifies as much or less than this
+ * {@code Format}, and nothing it specifies contradicts this {@code Format}.
+ *
+ * @param rhs The {@code Format} to check.
+ *
+ * @return {@code true} if {@code rhs} is a subset, {@code false} otherwise.
+ */
+ public boolean isSubset(Format rhs) {
+ if (rhs.getClass() != this.getClass())
+ return false;
+
+ if (rhs.attributes!= attributes)
+ return false;
+
+ if (rhs.sizeInPoints != 0 && sizeInPoints != rhs.sizeInPoints)
+ return false;
+
+ if (fontName == null ? rhs.fontName != null : !fontName.equals(rhs.fontName))
+ return false;
+
+ if (foreground!=rhs.foreground)
+ return false;
+
+ if (background!=rhs.background)
+ return false;
+
+ if (rhs.align!= align)
+ return false;
+
+ return rhs.vertAlign == vertAlign;
+ }
+}
diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxc/NameDefinition.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxc/NameDefinition.java
new file mode 100644
index 000000000..b243794b3
--- /dev/null
+++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxc/NameDefinition.java
@@ -0,0 +1,205 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+package org.openoffice.xmerge.converter.xml.sxc;
+
+import org.w3c.dom.Node;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+
+import org.openoffice.xmerge.converter.xml.OfficeConstants;
+import org.openoffice.xmerge.util.Debug;
+import org.openoffice.xmerge.util.XmlUtil;
+
+/**
+ * This is a class to define a Name Definition structure.
+ *
+ * <p>This can then be used by plug-ins to write or read their own definition
+ * types.</p>
+ */
+public class NameDefinition implements OfficeConstants {
+
+ private String name; // name which identifies the definition
+ private String definition; // the definition itself
+ private String baseCellAddress; // the basecelladdress
+ private boolean rangeType = false; // true if definition of type range
+ private boolean expressionType = false; // true if definition of type expression
+
+ /**
+ * Default Constructor for a {@code NameDefinition}.
+ */
+ public NameDefinition() {
+ }
+
+ /**
+ * Constructor that takes a {@code Node} to build a {@code NameDefinition}.
+ *
+ * @param root XML {@code Node} to read from.
+ */
+ public NameDefinition(Node root) {
+ readNode(root);
+ }
+
+ /**
+ * Constructor for a {@code NameDefinition}.
+ *
+ * @param name Name that identifies the definition.
+ * @param definition The definition itself.
+ * @param baseCellAddress The base cell address.
+ * @param rangeType {@code true} if definition of range type.
+ * @param expressionType {@code true} if definition of expression type.
+ */
+ public NameDefinition(String name, String definition, String
+ baseCellAddress, boolean rangeType, boolean expressionType ) {
+ this.name = name;
+ this.definition = definition;
+ this.baseCellAddress = baseCellAddress;
+ this.rangeType = rangeType;
+ this.expressionType = expressionType;
+ }
+
+ /**
+ * Returns Name of the definition.
+ *
+ * @return the name which identifies the definition.
+ */
+ private String getName() {
+ return name;
+ }
+
+ /**
+ * Sets the definition.
+ *
+ * @param newDefinition sets the definition.
+ */
+ public void setDefinition(String newDefinition) {
+ definition = newDefinition;
+ }
+
+ /**
+ * Returns the definition itself.
+ *
+ * @return the definition.
+ */
+ private String getDefinition() {
+ return definition;
+ }
+
+ /**
+ * Returns the base Cell address.
+ *
+ * @return the base cell address.
+ */
+ private String getBaseCellAddress() {
+ return baseCellAddress;
+ }
+
+ /**
+ * Tests if definition is of type expression.
+ *
+ * @return whether or not this name definition is of type expression.
+ */
+ private boolean isExpressionType() {
+ return expressionType;
+ }
+
+ /**
+ * Tests if definition is of type range.
+ *
+ * @return whether or not this name definition is of type range.
+ */
+ private boolean isRangeType() {
+ return rangeType;
+ }
+
+ /**
+ * Writes out a content.xml entry for this NameDefinition object.
+ *
+ * @param doc A {@code Document} object representing the settings.xml
+ * @param root The root xml {@code Node} to add to.
+ */
+ public void writeNode(org.w3c.dom.Document doc, Node root) {
+
+ if(isRangeType()) {
+
+ Debug.log(Debug.TRACE, "Found Range Name : " + getName());
+ Element namedRangeElement = doc.createElement(TAG_TABLE_NAMED_RANGE);
+ namedRangeElement.setAttribute(ATTRIBUTE_TABLE_NAME, getName());
+ namedRangeElement.setAttribute(ATTRIBUTE_TABLE_BASE_CELL_ADDRESS, getBaseCellAddress());
+ namedRangeElement.setAttribute(ATTRIBUTE_TABLE_CELL_RANGE_ADDRESS, getDefinition());
+ root.appendChild(namedRangeElement);
+ } else if (isExpressionType()) {
+
+ Debug.log(Debug.TRACE, "Found Expression Name : " + getName());
+ Element namedExpressionElement = doc.createElement(TAG_TABLE_NAMED_EXPRESSION);
+ namedExpressionElement.setAttribute(ATTRIBUTE_TABLE_NAME, getName());
+ namedExpressionElement.setAttribute(ATTRIBUTE_TABLE_BASE_CELL_ADDRESS,getBaseCellAddress());
+ namedExpressionElement.setAttribute(ATTRIBUTE_TABLE_EXPRESSION, getDefinition());
+ root.appendChild(namedExpressionElement);
+ } else {
+
+ Debug.log(Debug.TRACE, "Unknown Name Definition : " + getName());
+ }
+ }
+
+ /**
+ * Reads document settings from xml and inits Settings variables.
+ *
+ * @param root XML {@code Node} to read from.
+ */
+ private void readNode(Node root) {
+
+ String nodeName = root.getNodeName();
+ NamedNodeMap cellAtt = root.getAttributes();
+
+ if (nodeName.equals(TAG_TABLE_NAMED_RANGE)) {
+
+ Node tableNameNode =
+ cellAtt.getNamedItem(ATTRIBUTE_TABLE_NAME);
+ Node tableBaseCellAddress =
+ cellAtt.getNamedItem(ATTRIBUTE_TABLE_BASE_CELL_ADDRESS);
+ Node tableCellRangeAddress =
+ cellAtt.getNamedItem(ATTRIBUTE_TABLE_CELL_RANGE_ADDRESS);
+ Debug.log(Debug.TRACE,"Named-range : " + tableNameNode.getNodeValue());
+ // Create a named-range name definition
+ name = tableNameNode.getNodeValue();
+ definition = tableCellRangeAddress.getNodeValue();
+ baseCellAddress = tableBaseCellAddress.getNodeValue();
+ expressionType = true;
+ rangeType = false;
+
+ } else if (nodeName.equals(TAG_TABLE_NAMED_EXPRESSION)) {
+
+ Node tableNameNode =
+ cellAtt.getNamedItem(ATTRIBUTE_TABLE_NAME);
+ Node tableBaseCellAddress =
+ cellAtt.getNamedItem(ATTRIBUTE_TABLE_BASE_CELL_ADDRESS);
+ Node tableExpression=
+ cellAtt.getNamedItem(ATTRIBUTE_TABLE_EXPRESSION);
+ Debug.log(Debug.TRACE,"Named-expression: " + tableNameNode.getNodeValue());
+ // Create a named-range name definition
+ name = tableNameNode.getNodeValue();
+ definition = tableExpression.getNodeValue();
+ baseCellAddress = tableBaseCellAddress.getNodeValue();
+ expressionType = false;
+ rangeType = true;
+ } else {
+ Debug.log(Debug.TRACE, "<OTHERS " + XmlUtil.getNodeInfo(root) + " />");
+ }
+ }
+} \ No newline at end of file
diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxc/RowStyle.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxc/RowStyle.java
new file mode 100644
index 000000000..5b0446a61
--- /dev/null
+++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxc/RowStyle.java
@@ -0,0 +1,244 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+package org.openoffice.xmerge.converter.xml.sxc;
+
+import org.w3c.dom.NodeList;
+import org.w3c.dom.Node;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Element;
+
+import org.openoffice.xmerge.converter.xml.Style;
+import org.openoffice.xmerge.converter.xml.StyleCatalog;
+import org.openoffice.xmerge.util.Debug;
+import org.openoffice.xmerge.util.TwipsConverter;
+
+/**
+ * Represents a text {@code Style} in an OpenOffice document.
+ */
+public class RowStyle extends Style implements Cloneable {
+
+ private int rowHeight = 255;
+ /**
+ * Constructor for use when going from DOM to client device format.
+ *
+ * @param node The <i>style:style</i> {@code Node} containing the
+ * {@code Style}. (This {@code Node} is assumed have a
+ * <i>family</i> attribute of <i>text</i>).
+ * @param sc The {@code StyleCatalog}, which is used for looking up
+ * ancestor {@code Style} objects.
+ */
+ public RowStyle(Node node, StyleCatalog sc) {
+ super(node, sc);
+
+ // Run through the attributes of this node, saving
+ // the ones we're interested in.
+ NamedNodeMap attrNodes = node.getAttributes();
+ if (attrNodes != null) {
+ int len = attrNodes.getLength();
+ for (int i = 0; i < len; i++) {
+ Node attr = attrNodes.item(i);
+ handleAttribute(attr.getNodeName(), attr.getNodeValue());
+ }
+ }
+
+ // Look for children. Only ones we care about are "style:properties"
+ // nodes. If any are found, recursively traverse them, passing
+ // along the style element to add properties to.
+ if (!node.hasChildNodes()) {
+ return;
+ }
+ NodeList children = node.getChildNodes();
+ int len = children.getLength();
+ for (int i = 0; i < len; i++) {
+ Node child = children.item(i);
+ String nodeName = child.getNodeName();
+ if (nodeName.equals("style:properties")) {
+ NamedNodeMap childAttrNodes = child.getAttributes();
+ if (childAttrNodes != null) {
+ int nChildAttrNodes = childAttrNodes.getLength();
+ for (int j = 0; j < nChildAttrNodes; j++) {
+ Node attr = childAttrNodes.item(j);
+ handleAttribute(attr.getNodeName(),
+ attr.getNodeValue());
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Constructor for use when going from client device format to DOM.
+ *
+ * @param name Name of text {@code Style}. Can be {@code null}.
+ * @param family Family of text {@code Style} (usually <i>text</i>).
+ * Can be {@code null}.
+ * @param parent Name of parent text {@code Style}, or {@code null}
+ * for none.
+ * @param rowHeight The height of this row.
+ * @param sc The {@code StyleCatalog}, which is used for looking up
+ * ancestor {@code Style} objects.
+ */
+ public RowStyle(String name, String family, String parent,int rowHeight, StyleCatalog sc) {
+ super(name, family, parent, sc);
+ this.rowHeight=rowHeight;
+ }
+
+ /**
+ * Returns the height of this row.
+ *
+ * @return The height of this row.
+ */
+ public int getRowHeight() {
+ return rowHeight;
+ }
+
+ /**
+ * Sets the height of this row.
+ *
+ * @param RowHeight The height of this row.
+ */
+ private void setRowHeight(int RowHeight) {
+ this.rowHeight = RowHeight;
+ }
+
+ /**
+ * Parse a rowheight in the form {@literal "1.234cm"} to twips.
+ *
+ * @param value {@code String} specification to parse.
+ *
+ * @return The twips equivalent.
+ */
+ private int parseRowHeight(String value) {
+ return TwipsConverter.convert2twips(value, 255);
+ }
+
+ /**
+ * Set an attribute.
+ *
+ * @param attr The attribute to set.
+ * @param value The attribute value to set.
+ */
+ private void handleAttribute(String attr, String value) {
+
+ if (attr.equals("style:row-height")) {
+ rowHeight = parseRowHeight(value);
+ }
+ else {
+ Debug.log(Debug.INFO, "RowStyle Unhandled: " + attr + "=" + value);
+ }
+ }
+
+ /**
+ * Return a {@code Style} object corresponding to this one, but with all of
+ * the inherited information from parent {@code Style} objects filled in.
+ *
+ * <p>The object returned will be a new object, not a reference to this
+ * object, even if it does not need any information added.</p>
+ *
+ * @return The {@code StyleCatalog} in which to look up ancestors.
+ */
+ @Override
+ public Style getResolved() {
+ // Create a new object to return, which is a clone of this one.
+ RowStyle resolved = null;
+ try {
+ resolved = (RowStyle)this.clone();
+ } catch (Exception e) {
+ Debug.log(Debug.ERROR, "Can't clone", e);
+ }
+
+ // Look up the parentStyle. (If there is no style catalog
+ // specified, we can't do any lookups.)
+ RowStyle parentStyle = null;
+ if (sc != null) {
+ if (parent != null) {
+ parentStyle = (RowStyle)sc.lookup(parent, family, null,
+ this.getClass());
+ if (parentStyle == null)
+ Debug.log(Debug.ERROR, "parent style lookup of "
+ + parent + " failed!");
+ else
+ parentStyle = (RowStyle)parentStyle.getResolved();
+
+ } else if (!name.equals("DEFAULT_STYLE")) {
+ parentStyle = (RowStyle)sc.lookup("DEFAULT_STYLE", null,
+ null, this.getClass());
+ }
+ }
+
+ // If we found a parent, for any attributes which we don't have
+ // set, try to get the values from the parent.
+ if (parentStyle != null) {
+ parentStyle = (RowStyle)parentStyle.getResolved();
+
+ if ((rowHeight == 0) && (parentStyle.getRowHeight() != 0))
+ resolved.setRowHeight(parentStyle.getRowHeight());
+ }
+ return resolved;
+ }
+
+ /**
+ * Create a new {@code Node} in the {@code Document}, and write this
+ * {@code Style} to it.
+ *
+ * @param parentDoc Parent {@code Document} of the {@code Node} to create.
+ * @param name Name to use for the new {@code Node} (e.g.
+ * <i>style:style</i>)
+ *
+ * @return Created {@code Node}.
+ */
+ @Override
+ public Node createNode(org.w3c.dom.Document parentDoc, String name) {
+ Element node = parentDoc.createElement(name);
+ writeAttributes(node);
+ return node;
+ }
+
+ /**
+ * Return true if {@code style} specifies as much or less than this
+ * {@code Style}, and nothing it specifies contradicts this {@code Style}.
+ *
+ * @param style The {@code Style} to check.
+ *
+ * @return {@code true} if {@code style} is a subset, {@code false}
+ * otherwise.
+ */
+ @Override
+ public boolean isSubset(Style style) {
+ if (style.getClass() != this.getClass())
+ return false;
+ RowStyle tStyle = (RowStyle)style;
+
+ return rowHeight == tStyle.getRowHeight();
+ }
+
+ /**
+ * Write this {@code Style} object's attributes to a {@code Node} in the
+ * {@code Document}.
+ *
+ * @param node The {@code Node} to add {@code Style} attributes.
+ */
+ private void writeAttributes(Element node) {
+
+ if(rowHeight!=0) {
+ String height = TwipsConverter.twips2cm(rowHeight) + "cm";
+ node.setAttribute("style:row-height", height);
+ }
+ }
+} \ No newline at end of file
diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxc/SheetSettings.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxc/SheetSettings.java
new file mode 100644
index 000000000..28a63cb7d
--- /dev/null
+++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxc/SheetSettings.java
@@ -0,0 +1,346 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+package org.openoffice.xmerge.converter.xml.sxc;
+
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.NodeList;
+import org.w3c.dom.Node;
+import org.w3c.dom.Element;
+import java.awt.Point;
+
+import org.openoffice.xmerge.converter.xml.OfficeConstants;
+
+/**
+ * This is a class representing the different attributes for a worksheet
+ * contained in settings.xml.
+ */
+public class SheetSettings implements OfficeConstants {
+
+ /** A w3c {@code Document}. */
+ private org.w3c.dom.Document settings = null;
+
+ private String sheetName;
+ private int cursorX = 0;
+ private int cursorY = 0;
+ private int splitTypeX;
+ private int splitTypeY;
+ private int splitPointX = 0;
+ private int splitPointY = 0;
+ private int posLeft = 0;
+ private int posTop = 0;
+ private int paneNumber = 2;
+
+ final private static int SPLIT = 0x01;
+ final private static int FREEZE = 0x02;
+
+ /**
+ * Default Constructor for a {@code SheetSettings}.
+ */
+ public SheetSettings() {
+ }
+
+ /**
+ * Constructor that takes a {@code Node} to build a {@code SheetSettings}.
+ *
+ * @param root XML {@code Node} to read from.
+ */
+ public SheetSettings(Node root) {
+ readNode(root);
+ }
+
+ /**
+ * Constructor for a {@code SheetSettings}.
+ *
+ * @param name The name for the new sheet.
+ */
+ public SheetSettings(String name) {
+ sheetName = name;
+ }
+
+ /**
+ * Sets the position of the active cell.
+ *
+ * @param activeCell the current cursor position.
+ */
+ public void setCursor(Point activeCell) {
+ cursorX = (int) activeCell.getX();
+ cursorY = (int) activeCell.getY();
+ }
+
+ /**
+ * Gets the position of the active cell.
+ *
+ * @return The position as a {@code Point}.
+ */
+ public Point getCursor() {
+ return new Point(cursorX, cursorY);
+ }
+
+ /**
+ * Sets the position of the freeze.
+ *
+ * @param splitPoint the point at where the split occurs.
+ */
+ public void setFreeze(Point splitPoint) {
+ splitTypeX = FREEZE;
+ splitTypeY = FREEZE;
+ splitPointX = (int) splitPoint.getX();
+ splitPointY = (int) splitPoint.getY();
+ }
+
+ /**
+ * Sets the position of the split.
+ *
+ * @param splitPoint the point at where the split occurs.
+ */
+ public void setSplit(Point splitPoint) {
+
+ splitTypeX = SPLIT;
+ splitTypeY = SPLIT;
+ splitPointX = (int) splitPoint.getX();
+ splitPointY = (int) splitPoint.getY();
+ }
+
+ /**
+ * Gets the position of the split.
+ *
+ * @return The position as a {@code Point} where the split occurs.
+ */
+ public Point getSplit() {
+
+ return new Point(splitPointX, splitPointY);
+ }
+
+ /**
+ * Gets the type of the split.
+ *
+ * @return The split type as a {@code Point}.
+ */
+ public Point getSplitType() {
+
+ return new Point(splitTypeX, splitTypeY);
+ }
+
+ /**
+ * Gets the leftmost column visible in the right pane.
+ *
+ * @return the 0-based index to the column.
+ */
+ public int getLeft() {
+ return posLeft;
+ }
+
+ /**
+ * Gets the top row visible in the lower pane.
+ *
+ * @return The top row visible in the lower pane.
+ */
+ public int getTop() {
+ return posTop;
+ }
+
+ /**
+ * Gets the active Panel.
+ *
+ * <blockquote><table summary="Table with all values for an active panel"
+ * border="1" cellpadding="3" cellspacing="0">
+ * <tr><th>Value</th><th>Meaning</th></tr>
+ * <tr><td align="center">0</td><td>Bottom Right</td></tr>
+ * <tr><td align="center">1</td><td>Top Right</td></tr>
+ * <tr><td align="center">2</td><td>Bottom Left</td></tr>
+ * <tr><td align="center">3</td><td>Top Left</td></tr>
+ * </table></blockquote>
+ *
+ * @return {@code int} representing the active panel.
+ */
+ public int getPaneNumber() {
+ return paneNumber;
+ }
+
+ /**
+ * Sets the {@code sheetName} this settings object applies to.
+ *
+ * @param sheetName the name of the worksheet.
+ */
+ public void setSheetName(String sheetName) {
+ this.sheetName = sheetName;
+ }
+
+ /**
+ * Sets the active pane number.
+ *
+ * <blockquote><table summary="Table with all values for an active panel"
+ * border="1" cellpadding="3" cellspacing="0">
+ * <tr><th>Value</th><th>Meaning</th></tr>
+ * <tr><td align="center">0</td><td>Bottom Right</td></tr>
+ * <tr><td align="center">1</td><td>Top Right</td></tr>
+ * <tr><td align="center">2</td><td>Bottom Left</td></tr>
+ * <tr><td align="center">3</td><td>Top Left</td></tr>
+ * </table></blockquote>
+ *
+ * @param paneNumber the pane number.
+ */
+ public void setPaneNumber(int paneNumber) {
+ this.paneNumber = paneNumber;
+ }
+
+ /**
+ * Gets the name of the worksheet these {@code Settings} apply to.
+ *
+ * @return the name of the worksheet.
+ */
+ private String getSheetName() {
+ return sheetName;
+ }
+
+ /**
+ * Adds an XML entry for a particular setting.
+ *
+ * @param root the root {@code Node} at which to add the xml entry.
+ * @param attribute the name of the attribute to add.
+ * @param type the attribute type ({@code int}, {@code short} etc).
+ * @param value the value of the attribute.
+ */
+ private void addConfigItem(Node root, String attribute, String type, String value) {
+
+ Element configItem = settings.createElement(TAG_CONFIG_ITEM);
+ configItem.setAttribute(ATTRIBUTE_CONFIG_NAME, attribute);
+ configItem.setAttribute(ATTRIBUTE_CONFIG_TYPE, type);
+
+ configItem.appendChild(settings.createTextNode(value));
+
+ root.appendChild(configItem);
+ }
+
+ /**
+ * Writes out a settings.xml entry for this {@code SheetSettings} object.
+ *
+ * @param settings a {@code Document} object representing the settings.xml
+ * @param root the root xml node to add to.
+ */
+ public void writeNode(org.w3c.dom.Document settings, Node root) {
+
+ this.settings = settings;
+ Element configItemMapEntry = settings.createElement(TAG_CONFIG_ITEM_MAP_ENTRY);
+ configItemMapEntry.setAttribute(ATTRIBUTE_CONFIG_NAME, getSheetName());
+ addConfigItem(configItemMapEntry, "CursorPositionX", "int", Integer.toString(cursorX));
+ addConfigItem(configItemMapEntry, "CursorPositionY", "int", Integer.toString(cursorY));
+
+ String splitMode = Integer.toString(splitTypeX);
+ if(splitPointX==0) {
+ splitMode = "0";
+ }
+ addConfigItem(configItemMapEntry, "HorizontalSplitMode", "short", splitMode);
+
+ splitMode = Integer.toString(splitTypeY);
+ if(splitPointY==0) {
+ splitMode = "0";
+ }
+ addConfigItem(configItemMapEntry, "VerticalSplitMode", "short", splitMode);
+
+ addConfigItem(configItemMapEntry, "HorizontalSplitPosition", "int", Integer.toString(splitPointX));
+ addConfigItem(configItemMapEntry, "VerticalSplitPosition", "int", Integer.toString(splitPointY));
+ addConfigItem(configItemMapEntry, "ActiveSplitRange", "short", Integer.toString(paneNumber));
+
+ addConfigItem(configItemMapEntry, "PositionLeft", "int", "0");
+ addConfigItem(configItemMapEntry, "PositionRight", "int", Integer.toString(posLeft));
+ addConfigItem(configItemMapEntry, "PositionTop", "int", "0");
+ addConfigItem(configItemMapEntry, "PositionBottom", "int", Integer.toString(posTop));
+ root.appendChild(configItemMapEntry);
+ }
+
+ /**
+ * Sets a variable based on a {@code String} value read from XML.
+ *
+ * @param name xml name of the attribute to set.
+ * @param value {@code String} value for the attribute.
+ */
+ private void addAttribute(String name, String value) {
+
+ if(name.equals("CursorPositionX")) {
+ cursorX = Integer.parseInt(value);
+ } else if(name.equals("CursorPositionY")) {
+ cursorY = Integer.parseInt(value);
+
+ } else if(name.equals("HorizontalSplitPosition")) {
+ splitPointX = Integer.parseInt(value);
+ } else if(name.equals("VerticalSplitPosition")) {
+ splitPointY = Integer.parseInt(value);
+ } else if(name.equals("ActiveSplitRange")) {
+ paneNumber = Integer.parseInt(value);
+
+ } else if(name.equals("PositionRight")) {
+ posLeft = Integer.parseInt(value);
+ } else if(name.equals("PositionBottom")) {
+ posTop = Integer.parseInt(value);
+
+ } else if(name.equals("HorizontalSplitMode")) {
+ splitTypeX = Integer.parseInt(value);
+ } else if(name.equals("VerticalSplitMode")) {
+ splitTypeY = Integer.parseInt(value);
+ }
+ }
+
+ /**
+ * Reads document settings from xml and inits {@code SheetSettings} variables.
+ *
+ * @param root XML {@code Node} to read from.
+ */
+ private void readNode(Node root) {
+
+ NamedNodeMap sheetAtt = root.getAttributes();
+
+ Node sheetNameNode = sheetAtt.getNamedItem(ATTRIBUTE_CONFIG_NAME);
+
+ sheetName = sheetNameNode.getNodeValue();
+
+ if (root.hasChildNodes()) {
+
+ NodeList nodeList = root.getChildNodes();
+ int len = nodeList.getLength();
+ for (int i = 0; i < len; i++) {
+ Node child = nodeList.item(i);
+
+ if (child.getNodeType() == Node.ELEMENT_NODE) {
+ String nodeName = child.getNodeName();
+
+ if (nodeName.equals(TAG_CONFIG_ITEM)) {
+
+ NamedNodeMap cellAtt = child.getAttributes();
+
+ Node configNameNode =
+ cellAtt.getNamedItem(ATTRIBUTE_CONFIG_NAME);
+
+ String name = configNameNode.getNodeValue();
+ NodeList nodeList2 = child.getChildNodes();
+ int len2 = nodeList2.getLength();
+ String s = "";
+ for (int j = 0; j < len2; j++) {
+ Node child2 = nodeList2.item(j);
+ if (child2.getNodeType() == Node.TEXT_NODE) {
+ s = child2.getNodeValue();
+ }
+ }
+ addAttribute(name, s);
+ }
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxc/SpreadsheetDecoder.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxc/SpreadsheetDecoder.java
new file mode 100644
index 000000000..4ad1080da
--- /dev/null
+++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxc/SpreadsheetDecoder.java
@@ -0,0 +1,152 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+package org.openoffice.xmerge.converter.xml.sxc;
+
+import java.io.IOException;
+import java.util.Iterator;
+
+import org.openoffice.xmerge.ConvertData;
+
+/**
+ * This {@code class} is a {@code abstract class} for encoding a &quot;Device&quot;
+ * {@code Document} format into an alternative spreadsheet format.
+ */
+public abstract class SpreadsheetDecoder {
+
+ /**
+ * Returns the total number of sheets in the WorkBook.
+ *
+ * @return The number of sheets in the WorkBook.
+ */
+ public abstract int getNumberOfSheets();
+
+ /**
+ * Returns an Enumeration to a Vector of {@code NameDefinition}.
+ *
+ * @return The Enumeration.
+ */
+ public abstract Iterator<NameDefinition> getNameDefinitions();
+
+ /**
+ * Returns the {@code BookSettings}.
+ *
+ * @return The Enumeration.
+ */
+ public abstract BookSettings getSettings();
+
+ /**
+ * Returns an Enumeration to a Vector of {@code ColumnRowInfo}.
+ *
+ * @return The Enumeration.
+ */
+ public abstract Iterator<ColumnRowInfo> getColumnRowInfos();
+
+ /**
+ * Returns the number of populated rows in the current WorkSheet.
+ *
+ * @return the number of populated rows in the current WorkSheet.
+ */
+ public abstract int getNumberOfRows();
+
+ /**
+ * Returns the number of populated columns in the current WorkSheet.
+ *
+ * @return The number of populated columns in the current WorkSheet.
+ */
+ public abstract int getNumberOfColumns();
+
+ /**
+ * Returns the name of the current WorkSheet.
+ *
+ * @return Name of the current WorkSheet.
+ */
+ public abstract String getSheetName();
+
+ /**
+ * Returns the number of the active column.
+ *
+ * @return The number of the active column.
+ */
+ public abstract int getColNumber();
+
+ /**
+ * Returns the number of the active row.
+ *
+ * @return The number of the active row.
+ */
+ public abstract int getRowNumber();
+
+ /**
+ * Sets the active WorkSheet.
+ *
+ * @param sheetIndex The index of the sheet to be made active.
+ *
+ * @throws IOException If any I/O error occurs.
+ */
+ public abstract void setWorksheet(int sheetIndex) throws IOException;
+
+ /**
+ * Move on the next populated cell in the current WorkSheet.
+ *
+ * @return {@code true} if successful, {@code false} otherwise.
+ *
+ * @throws IOException If any I/O error occurs.
+ */
+ public abstract boolean goToNextCell() throws IOException;
+
+ /**
+ * Return the contents of the active cell.
+ *
+ * @return The cell contents.
+ */
+ public abstract String getCellContents();
+
+ /**
+ * Return the value of the active cell.
+ *
+ * <p>Used in the case of Formula where the cell contents and the cell
+ * value are not the same thing.</p>
+ *
+ * @return The cell value.
+ */
+ public abstract String getCellValue();
+
+ /**
+ * Return the data type of the active cell.
+ *
+ * @return The cell data type.
+ */
+ public abstract String getCellDataType();
+
+ /**
+ * Return a {@code Format} object describing the active cells formatting.
+ *
+ * @return {@code Format} object for the cell.
+ */
+ public abstract Format getCellFormat();
+
+ /**
+ * Add the contents of a {@code ConvertData} to the workbook.
+ *
+ * @param cd The {@code ConvertData} containing the content.
+ *
+ * @throws IOException If any I/O error occurs.
+ */
+ public abstract void addDeviceContent(ConvertData cd) throws IOException;
+} \ No newline at end of file
diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxc/SpreadsheetEncoder.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxc/SpreadsheetEncoder.java
new file mode 100644
index 000000000..e87748773
--- /dev/null
+++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxc/SpreadsheetEncoder.java
@@ -0,0 +1,81 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+package org.openoffice.xmerge.converter.xml.sxc;
+
+import java.io.IOException;
+import java.util.ArrayList;
+
+/**
+ * This {@code class} is an {@code abstract class} for encoding an SXC into an
+ * alternative spreadsheet format.
+ *
+ * <p>ToDo - Add appropriate exceptions to each of the methods.</p>
+ *
+ */
+public abstract class SpreadsheetEncoder {
+
+ /**
+ * Create a new WorkSheet within the WorkBook.
+ *
+ * @param sheetName The name of the WorkSheet.
+ *
+ * @throws IOException If any I/O error occurs.
+ */
+ public abstract void createWorksheet(String sheetName) throws IOException;
+
+ /**
+ * Add a cell to the current WorkSheet.
+ *
+ * @param row The row number of the cell
+ * @param column The column number of the cell
+ * @param fmt The {@code Format} object describing the appearance
+ * of this cell.
+ * @param cellContents The text or formula of the cell's contents.
+ */
+ public abstract void addCell(int row, int column, Format fmt,
+ String cellContents) throws IOException;
+
+ /**
+ * Get the number of sheets in the WorkBook.
+ *
+ * @return The number of sheets in the WorkBook.
+ */
+ public abstract int getNumberOfSheets();
+
+ /**
+ * Set the width of the columns in the WorkBook.
+ *
+ * @param columnRows An {@code IntArrayList} of column widths.
+ */
+ public abstract void setColumnRows(ArrayList<ColumnRowInfo> columnRows) throws IOException;
+
+ /**
+ * Set the name definition of this spreadsheet.
+ *
+ * @param nd The {@code NameDefinition} to use.
+ */
+ public abstract void setNameDefinition(NameDefinition nd) throws IOException;
+
+ /**
+ * Adds settings to the WorkBook.
+ *
+ * @param s The {@code BookSettings} to add.
+ */
+ public abstract void addSettings(BookSettings s) throws IOException;
+} \ No newline at end of file
diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxc/SxcConstants.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxc/SxcConstants.java
new file mode 100644
index 000000000..00a59d5d2
--- /dev/null
+++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxc/SxcConstants.java
@@ -0,0 +1,37 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+package org.openoffice.xmerge.converter.xml.sxc;
+
+/**
+ * Interface defining constants for Sxc attributes.
+ */
+public interface SxcConstants {
+
+ /** Family name for column styles. */
+ String COLUMN_STYLE_FAMILY = "table-column";
+
+ /** Family name for row styles. */
+ String ROW_STYLE_FAMILY = "table-row";
+
+ /** Family name for table-cell styles. */
+ String TABLE_CELL_STYLE_FAMILY = "table-cell";
+
+ /** Name of the default style. */
+ String DEFAULT_STYLE = "Default";
+} \ No newline at end of file
diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxc/SxcDocument.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxc/SxcDocument.java
new file mode 100644
index 000000000..91a6ec349
--- /dev/null
+++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxc/SxcDocument.java
@@ -0,0 +1,80 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+package org.openoffice.xmerge.converter.xml.sxc;
+
+import org.openoffice.xmerge.converter.xml.OfficeDocument;
+import org.openoffice.xmerge.converter.xml.OfficeConstants;
+
+/**
+ * This class is an implementation of {@code OfficeDocument} for the SXC format.
+ */
+public class SxcDocument extends OfficeDocument {
+
+ /**
+ * Constructor with arguments to set {@code name}.
+ *
+ * @param name The name of the {@code Document}.
+ */
+ public SxcDocument(String name) {
+ super(name);
+ }
+
+ /**
+ * Constructor with arguments to set {@code name}, the {@code namespaceAware}
+ * flag, and the {@code validating} flag.
+ *
+ * @param name The name of the {@code Document}.
+ * @param namespaceAware The value of the {@code namespaceAware} flag.
+ * @param validating The value of the {@code validating} flag.
+ */
+ public SxcDocument(String name, boolean namespaceAware, boolean validating) {
+
+ super(name, namespaceAware, validating);
+ }
+
+ /**
+ * Returns the Office file extension for the SXC format.
+ *
+ * @return The Office file extension for the SXC format.
+ */
+ @Override
+ protected String getFileExtension() {
+ return OfficeConstants.SXC_FILE_EXTENSION;
+ }
+
+ /**
+ * Returns the Office attribute for the SXC format.
+ *
+ * @return The Office attribute for the SXC format.
+ */
+ @Override
+ protected String getOfficeClassAttribute() {
+ return OfficeConstants.SXC_TYPE;
+ }
+
+ /**
+ * Method to return the MIME type of the document.
+ *
+ * @return String The document's MIME type.
+ */
+ @Override
+ protected final String getDocumentMimeType() {
+ return OfficeConstants.SXC_MIME_TYPE;
+ }
+} \ No newline at end of file
diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxc/SxcDocumentDeserializer.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxc/SxcDocumentDeserializer.java
new file mode 100644
index 000000000..1523b9089
--- /dev/null
+++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxc/SxcDocumentDeserializer.java
@@ -0,0 +1,746 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+package org.openoffice.xmerge.converter.xml.sxc;
+
+import java.io.IOException;
+import java.util.Iterator;
+
+import org.openoffice.xmerge.ConvertData;
+import org.openoffice.xmerge.ConvertException;
+import org.openoffice.xmerge.Document;
+import org.openoffice.xmerge.DocumentDeserializer;
+import org.openoffice.xmerge.converter.xml.OfficeConstants;
+import org.openoffice.xmerge.converter.xml.Style;
+import org.openoffice.xmerge.converter.xml.StyleCatalog;
+import org.openoffice.xmerge.util.Debug;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+/**
+ * General spreadsheet implementation of {@code DocumentDeserializer} for the
+ * {@link org.openoffice.xmerge.converter.xml.sxc.SxcPluginFactory
+ * SxcPluginFactory}.
+ *
+ * <p>Used with SXC {@code Document} objects.</p>
+ *
+ * <p>The {@code deserialize} method uses a {@code DocDecoder} to read the device
+ * spreadsheet format into a {@code String} object, then it calls
+ * {@code buildDocument} to create a {@code SxcDocument} object from it.</p>
+ */
+public abstract class SxcDocumentDeserializer implements OfficeConstants,
+ DocumentDeserializer {
+
+ /**
+ * A {@code SpreadsheetDecoder} object for decoding from device formats.
+ */
+ private SpreadsheetDecoder decoder = null;
+
+ /** A w3c {@code Document}. */
+ private org.w3c.dom.Document settings = null;
+
+ /** A w3c {@code Document}. */
+ private org.w3c.dom.Document doc = null;
+
+ /** A {@code ConvertData} object assigned to this object. */
+ private final ConvertData cd ;
+
+ /** A {@code StyleCatalog} for the workbook. */
+ private StyleCatalog styleCat = null;
+
+ private int textStyles = 1;
+ private int colStyles = 1;
+ private int rowStyles = 1;
+
+ /**
+ * Constructor.
+ *
+ * @param cd {@code ConvertData} consisting of a device content object.
+ */
+ public SxcDocumentDeserializer(ConvertData cd) {
+ this.cd = cd;
+ }
+
+ /**
+ * This {@code abstract} method will be implemented by concrete subclasses
+ * and will return an application-specific Decoder.
+ *
+ * @param workbook The WorkBook to read.
+ * @param password The WorkBook password.
+ *
+ * @return The appropriate {@code SpreadSheetDecoder}.
+ *
+ * @throws IOException If any I/O error occurs.
+ */
+ public abstract SpreadsheetDecoder createDecoder(String workbook, String[] worksheetNames, String password)
+ throws IOException;
+
+ /**
+ * This method will return the name of the WorkBook from the
+ * {@code ConvertData}.
+ *
+ * <p>Allows for situations where the WorkBook name differs from the Device
+ * Content name.</p>
+ *
+ * <p>Implemented in the Deserializer as the Decoder's constructor requires
+ * a name.</p>
+ *
+ * @param cd The {@code ConvertData} containing the Device content.
+ *
+ * @return The WorkBook name.
+ */
+ protected abstract String getWorkbookName(ConvertData cd) throws IOException;
+
+ /**
+ * This method will return the name of the WorkSheet from the
+ * {@code ConvertData}.
+ *
+ * @param cd The {@code ConvertData} containing the Device content.
+ *
+ * @return The WorkSheet names.
+ */
+ protected abstract String[] getWorksheetNames(ConvertData cd) throws IOException;
+
+ /**
+ * Method to convert a set of &quot;Device&quot; {@code Document} objects
+ * into a {@code SxcDocument} object and returns it as a {@code Document}.
+ *
+ * <p>This method is not thread safe for performance reasons. This method
+ * should not be called from within two threads. It would be best to call
+ * this method only once per object instance.</p>
+ *
+ * @return document A {@code SxcDocument} consisting of the data converted
+ * from the input stream.
+ *
+ * @throws ConvertException If any conversion error occurs.
+ * @throws IOException If any I/O error occurs.
+ */
+ public Document deserialize() throws ConvertException,
+ IOException {
+
+ // Get the name of the WorkBook from the ConvertData.
+ String[] worksheetNames = getWorksheetNames(cd);
+ String workbookName = getWorkbookName(cd);
+
+ // Create a document
+ SxcDocument sxcDoc = new SxcDocument(workbookName);
+ sxcDoc.initContentDOM();
+ sxcDoc.initSettingsDOM();
+
+ // Default to an initial 5 entries in the catalog.
+ styleCat = new StyleCatalog(5);
+
+ doc = sxcDoc.getContentDOM();
+ settings = sxcDoc.getSettingsDOM();
+ initFontTable();
+ // Little fact for the curious reader: workbookName should
+ // be the name of the StarCalc file minus the file extension suffix.
+
+ // Create a Decoder to decode the DeviceContent to a spreadsheet document
+ // ToDo - we aren't using a password in StarCalc, so we can
+ // use any value for password here. If StarCalc XML supports
+ // passwords in the future, we should try to get the correct
+ // password value here.
+
+ decoder = createDecoder(workbookName, worksheetNames, "password");
+
+ Debug.log(Debug.TRACE, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
+ Debug.log(Debug.TRACE, "<DEBUGLOG>");
+
+ decoder.addDeviceContent(cd);
+ decode();
+
+ Debug.log(Debug.TRACE, "</DEBUGLOG>");
+
+ return sxcDoc;
+ }
+
+ /**
+ * This initializes a font table so we can include some basic font support
+ * for spreadsheets.
+ */
+ private void initFontTable() {
+
+ String fontTable[]= new String[] { "Tahoma", "Tahoma", "swiss", "variable",
+ "Courier New", "&apos;Courier New&apos;",
+ "modern", "fixed" };
+ // Traverse to the office:body element.
+ // There should only be one.
+ NodeList list = doc.getElementsByTagName(TAG_OFFICE_FONT_DECLS);
+ Node root = list.item(0);
+
+ for(int i=0;i<fontTable.length;) {
+
+ // Create an element node for the table
+ Element tableElement = doc.createElement(TAG_STYLE_FONT_DECL);
+
+ tableElement.setAttribute(ATTRIBUTE_STYLE_NAME, fontTable[i++]);
+ tableElement.setAttribute(ATTRIBUTE_FO_FONT_FAMILY, fontTable[i++]);
+ tableElement.setAttribute(ATTRIBUTE_FO_FONT_FAMILY_GENERIC, fontTable[i++]);
+ tableElement.setAttribute(ATTRIBUTE_STYLE_FONT_PITCH, fontTable[i++]);
+
+ root.appendChild(tableElement);
+ }
+
+ }
+
+ /**
+ * Outer level method used to decode a WorkBook into a {@code Document}.
+ *
+ * @throws IOException If any I/O error occurs.
+ */
+ protected void decode() throws IOException {
+
+ // Get number of worksheets
+ int numSheets = decoder.getNumberOfSheets();
+ // #i33702# - check for an Empty InputStream.
+ if(numSheets == 0)
+ {
+ System.err.println("Error decoding invalid Input stream");
+ return;
+ }
+
+ // Traverse to the office:body element.
+ // There should only be one.
+ NodeList list = doc.getElementsByTagName(TAG_OFFICE_BODY);
+ Node node = list.item(0);
+
+ for (int i = 0; i < numSheets; i++) {
+
+ // Set the decoder to the correct worksheet
+ decoder.setWorksheet(i);
+
+ int len = list.getLength();
+
+ if (len > 0) {
+
+ // Process the spreadsheet
+ processTable(node);
+ }
+ }
+
+ // Add the Defined Name table if there is one
+ Iterator<NameDefinition> nameDefinitionTable = decoder.getNameDefinitions();
+ if(nameDefinitionTable.hasNext()) {
+ processNameDefinition(node, nameDefinitionTable);
+ }
+
+ // add settings
+ NodeList settingsList = settings.getElementsByTagName(TAG_OFFICE_SETTINGS);
+ Node settingsNode = settingsList.item(0);
+ processSettings(settingsNode);
+
+ }
+
+ /**
+ * This method process the settings portion of the {@code Document}.
+ *
+ * @param root The root {@code Node} of the {@code Document} we are
+ * building. This {@code Node} should be a
+ * {@code TAG_OFFICE_SETTINGS} tag.
+ */
+ protected void processSettings(Node root) {
+
+ Element configItemSetEntry = settings.createElement(TAG_CONFIG_ITEM_SET);
+ configItemSetEntry.setAttribute(ATTRIBUTE_CONFIG_NAME, "view-settings");
+ Element configItemMapIndexed = settings.createElement(TAG_CONFIG_ITEM_MAP_INDEXED);
+ configItemMapIndexed.setAttribute(ATTRIBUTE_CONFIG_NAME, "Views");
+ Element configItemMapEntry = settings.createElement(TAG_CONFIG_ITEM_MAP_ENTRY);
+ BookSettings bs = decoder.getSettings();
+ bs.writeNode(settings, configItemMapEntry);
+
+ configItemMapIndexed.appendChild(configItemMapEntry);
+ configItemSetEntry.appendChild(configItemMapIndexed);
+ root.appendChild(configItemSetEntry);
+ }
+
+ /**
+ * This method process a Name Definition Table and generates a portion of
+ * the {@code Document}.
+ *
+ * @param root The root {@code Node} of the {@code Document} we are
+ * building. This {@code Node} should be a
+ * {@code TAG_OFFICE_BODY} tag.
+ *
+ * @throws IOException If any I/O error occurs.
+ */
+ protected void processNameDefinition(Node root, Iterator<NameDefinition> eNameDefinitions) throws IOException {
+
+ Debug.log(Debug.TRACE, "<NAMED-EXPRESSIONS>");
+
+ Element namedExpressionsElement = doc.createElement(TAG_NAMED_EXPRESSIONS);
+
+ while(eNameDefinitions.hasNext()) {
+
+ NameDefinition tableEntry = eNameDefinitions.next();
+ tableEntry.writeNode(doc, namedExpressionsElement);
+ }
+
+ root.appendChild(namedExpressionsElement);
+
+ Debug.log(Debug.TRACE, "</NAMED-EXPRESSIONS>");
+ }
+
+ /**
+ * This method process a WorkSheet and generates a portion of the
+ * {@code Document}.
+ *
+ * <p>A spreadsheet is represented as a table Node in StarOffice XML format.</p>
+ *
+ * @param root The root {@code Node} of the {@code Document} we are
+ * building. This {@code Node} should be a
+ * {@code TAG_OFFICE_BODY} tag.
+ *
+ * @throws IOException If any I/O error occurs.
+ */
+ protected void processTable(Node root) throws IOException {
+
+ Debug.log(Debug.TRACE, "<TABLE>");
+
+ // Create an element node for the table
+ Element tableElement = doc.createElement(TAG_TABLE);
+
+ // Get the sheet name
+ String sheetName = decoder.getSheetName();
+
+ // Set the table name attribute
+ tableElement.setAttribute(ATTRIBUTE_TABLE_NAME, sheetName);
+
+ // ToDo - style currently hardcoded - get real value
+ // Set table style-name attribute
+ tableElement.setAttribute(ATTRIBUTE_TABLE_STYLE_NAME, "Default");
+
+ // Append the table element to the root node
+ root.appendChild(tableElement);
+
+ Debug.log(Debug.TRACE, "<SheetName>" + sheetName + "</SheetName>");
+
+ // Add the various different table-columns
+ processColumns(tableElement);
+
+ // Get each cell and add to doc
+ processCells(tableElement);
+
+ Debug.log(Debug.TRACE, "</TABLE>");
+ }
+
+ /**
+ * This method process the cells in a {@code Document} and generates a
+ * portion of the {@code Document}.
+ *
+ * <p>This method assumes that records are sorted by row and then column.</p>
+ *
+ * @param root The {@code Node} of the {@code Document} we are building
+ * that we will append our cell {@code Node} objects. This
+ * {@code Node} should be a {@code TAG_TABLE} tag.
+ *
+ * @throws IOException If any I/O error occurs.
+ */
+ protected void processColumns(Node root) throws IOException {
+
+ for(Iterator<ColumnRowInfo> e = decoder.getColumnRowInfos();e.hasNext();) {
+
+ ColumnRowInfo ci = e.next();
+ if(ci.isColumn()) {
+ ColumnStyle cStyle = new ColumnStyle("Default",SxcConstants.COLUMN_STYLE_FAMILY,
+ SxcConstants.DEFAULT_STYLE, ci.getSize(), null);
+
+ Style result[] = styleCat.getMatching(cStyle);
+ String styleName;
+ if(result.length==0) {
+
+ cStyle.setName("co" + colStyles++);
+ styleName = cStyle.getName();
+ Debug.log(Debug.TRACE,"No existing style found, adding " + styleName);
+ styleCat.add(cStyle);
+ } else {
+ ColumnStyle existingStyle = (ColumnStyle) result[0];
+ styleName = existingStyle.getName();
+ Debug.log(Debug.TRACE,"Existing style found : " + styleName);
+ }
+
+ // Create an element node for the new row
+ Element colElement = doc.createElement(TAG_TABLE_COLUMN);
+ colElement.setAttribute(ATTRIBUTE_TABLE_STYLE_NAME, styleName);
+ if(ci.getRepeated()!=1) {
+ String repeatStr = String.valueOf(ci.getRepeated());
+ colElement.setAttribute(ATTRIBUTE_TABLE_NUM_COLUMNS_REPEATED, repeatStr);
+ }
+ root.appendChild(colElement);
+ }
+ }
+ }
+
+ /**
+ * This method process the cells in a {@code Document} and generates a
+ * portion of the {@code Document}.
+ *
+ * <p>This method assumes that records are sorted by row and then column.</p>
+ *
+ * @param root The {@code Node} of the {@code Document} we are building
+ * that we will append our cell {@code Node} objects. This
+ * {@code Node} should be a {@code TAG_TABLE} tag.
+ *
+ * @throws IOException If any I/O error occurs.
+ */
+ protected void processCells(Node root) throws IOException {
+
+ // The current row element
+ Element rowElement = null;
+
+ // The current cell element
+ Element cellElement = null;
+
+ // The row number - we may not have any rows (empty sheet)
+ // so set to zero.
+ int row = 0;
+
+ // The column number - This is the expected column number of
+ // the next cell we are reading.
+ int col = 1;
+
+ // The number of columns in the spreadsheet
+ int lastColumn = decoder.getNumberOfColumns();
+
+ Node autoStylesNode = null;
+
+ // Loop over all cells in the spreadsheet
+ while (decoder.goToNextCell()) {
+
+ // Get the row number
+ int newRow = decoder.getRowNumber();
+
+ // Is the cell in a new row, or part of the current row?
+ if (newRow != row) {
+
+ // Make sure that all the cells in the previous row
+ // have been entered.
+ if (col <= lastColumn && rowElement != null) {
+ int numSkippedCells = lastColumn - col + 1;
+ addEmptyCells(numSkippedCells, rowElement);
+ }
+
+ // log an end row - if we already have a row
+ if (row != 0) {
+ Debug.log(Debug.TRACE, "</tr>");
+ }
+
+ // How far is the new row from the last row?
+ int deltaRows = newRow - row;
+
+ // Check if we have skipped any rows
+ if (deltaRows > 1) {
+ // Add in empty rows
+ addEmptyRows(deltaRows-1, root, lastColumn);
+ }
+
+ // Re-initialize column (since we are in a new row)
+ col = 1;
+
+ // Create an element node for the new row
+ rowElement = doc.createElement(TAG_TABLE_ROW);
+
+ for(Iterator<ColumnRowInfo> e = decoder.getColumnRowInfos();e.hasNext();) {
+ ColumnRowInfo cri = e.next();
+ if(cri.isRow() && cri.getRepeated()==newRow-1) {
+ // We have the correct Row BIFFRecord for this row
+ RowStyle rStyle = new RowStyle("Default",SxcConstants.ROW_STYLE_FAMILY,
+ SxcConstants.DEFAULT_STYLE, cri.getSize(), null);
+
+ Style result[] = styleCat.getMatching(rStyle);
+ String styleName;
+ if(result.length==0) {
+
+ rStyle.setName("ro" + rowStyles++);
+ styleName = rStyle.getName();
+ Debug.log(Debug.TRACE,"No existing style found, adding " + styleName);
+ styleCat.add(rStyle);
+ } else {
+ RowStyle existingStyle = (RowStyle) result[0];
+ styleName = existingStyle.getName();
+ Debug.log(Debug.TRACE,"Existing style found : " + styleName);
+ }
+ rowElement.setAttribute(ATTRIBUTE_TABLE_STYLE_NAME, styleName);
+ // For now we will not use the repeat column attribute
+ }
+ }
+
+ // Append the row element to the root node
+ root.appendChild(rowElement);
+
+ // Update row number
+ row = newRow;
+
+ Debug.log(Debug.TRACE, "<tr>");
+ }
+
+ if (rowElement == null) {
+ //utterly busted
+ break;
+ }
+
+ // Get the column number of the current cell
+ int newCol = decoder.getColNumber();
+
+ // Check to see if some columns were skipped
+ if (newCol != col) {
+
+ // How many columns have we skipped?
+ int numColsSkipped = newCol - col;
+
+ addEmptyCells(numColsSkipped, rowElement);
+
+ // Update the column number to account for the
+ // skipped cells
+ col = newCol;
+ }
+
+ // Lets start dealing with the cell data
+ Debug.log(Debug.TRACE, "<td>");
+
+ // Get the cell's contents
+ String cellContents = decoder.getCellContents();
+
+ // Get the type of the data in the cell
+ String cellType = decoder.getCellDataType();
+
+ // Get the cell format
+ Format fmt = decoder.getCellFormat();
+
+ // Create an element node for the cell
+ cellElement = doc.createElement(TAG_TABLE_CELL);
+
+ Node bodyNode = doc.getElementsByTagName(TAG_OFFICE_BODY).item(0);
+
+ // Not every document has an automatic style tag
+ autoStylesNode = doc.getElementsByTagName(
+ TAG_OFFICE_AUTOMATIC_STYLES).item(0);
+
+ if (autoStylesNode == null) {
+ autoStylesNode = doc.createElement(TAG_OFFICE_AUTOMATIC_STYLES);
+ doc.insertBefore(autoStylesNode, bodyNode);
+ }
+
+ CellStyle tStyle = new
+ CellStyle( "Default",SxcConstants.TABLE_CELL_STYLE_FAMILY,
+ SxcConstants.DEFAULT_STYLE, fmt, null);
+ String styleName;
+ Style result[] = styleCat.getMatching(tStyle);
+ if(result.length==0) {
+
+ tStyle.setName("ce" + textStyles++);
+ styleName = tStyle.getName();
+ Debug.log(Debug.TRACE,"No existing style found, adding " + styleName);
+ styleCat.add(tStyle);
+ } else {
+ CellStyle existingStyle = (CellStyle) result[0];
+ styleName = existingStyle.getName();
+ Debug.log(Debug.TRACE,"Existing style found : " + styleName);
+ }
+
+ cellElement.setAttribute(ATTRIBUTE_TABLE_STYLE_NAME, styleName);
+
+ // Store the cell data into the appropriate attributes
+ processCellData(cellElement, cellType, cellContents);
+
+ // Append the cell element to the row node
+ rowElement.appendChild(cellElement);
+
+ // Append the cellContents as a text node
+ Element textElement = doc.createElement(TAG_PARAGRAPH);
+ cellElement.appendChild(textElement);
+ textElement.appendChild(doc.createTextNode(cellContents));
+
+ Debug.log(Debug.TRACE, cellContents);
+ Debug.log(Debug.TRACE, "</td>");
+
+ // Increment to the column number of the next expected cell
+ col++;
+ }
+
+ // Make sure that the last row is padded correctly
+ if (col <= lastColumn && rowElement != null) {
+ int numSkippedCells = lastColumn - col + 1;
+ addEmptyCells(numSkippedCells, rowElement);
+ }
+
+ // Now write the style catalog to the document
+ if(autoStylesNode!=null) {
+ Debug.log(Debug.TRACE, "Well the autostyle node was found!!!");
+ NodeList nl = styleCat.writeNode(doc, "dummy").getChildNodes();
+ int nlLen = nl.getLength(); // nl.item reduces the length
+ for (int i = 0; i < nlLen; i++) {
+ autoStylesNode.appendChild(nl.item(0));
+ }
+ }
+
+ if (row != 0) {
+ // The sheet does have rows, so write out a /tr
+ Debug.log(Debug.TRACE, "</tr>");
+ }
+ }
+
+ /**
+ * This method will add empty rows to the {@code Document}.
+ *
+ * <p>It is called when the conversion process encounters a row (or rows)
+ * that do not contain any data in its cells.</p>
+ *
+ * @param numEmptyRows The number of empty rows that we need to add to
+ * the {@code Document}.
+ * @param root The {@code Node} of the {@code Document} we are
+ * building that we will append our empty row
+ * {@code Node} objects. This {@code Node} should
+ * be a {@code TAG_TABLE} tag.
+ * @param numEmptyCells The number of empty cells in the empty row.
+ */
+ protected void addEmptyRows(int numEmptyRows, Node root, int numEmptyCells) {
+
+ // Create an element node for the row
+ Element rowElement = doc.createElement(TAG_TABLE_ROW);
+
+ // ToDo - style currently hardcoded - get real value
+ // Set row style-name attribute
+ rowElement.setAttribute(ATTRIBUTE_TABLE_STYLE_NAME, "Default");
+
+ // Set cell number-rows-repeated attribute
+ rowElement.setAttribute(ATTRIBUTE_TABLE_NUM_ROWS_REPEATED,
+ Integer.toString(numEmptyRows));
+
+ // Append the row element to the root node
+ root.appendChild(rowElement);
+
+ // Open Office requires the empty row to have an empty cell (or cells)
+ addEmptyCells(numEmptyCells, rowElement);
+
+ // Write empty rows to the log
+ for (int i = 0; i < numEmptyRows; i++) {
+ Debug.log(Debug.TRACE, "<tr />");
+ }
+
+ }
+
+ /**
+ * This method will add empty cells to the {@code Document}.
+ *
+ * <p>It is called when the conversion process encounters a row that
+ * contains some cells without data.</p>
+ *
+ * @param numColsSkipped The number of empty cells that we need to add to
+ * the current row.
+ * @param row The {@code Node} of the {@code Document} we are
+ * building that we will append our empty cell
+ * {@code Node} objects. This {@code Node} should
+ * be a {@code TAG_TABLE_ROW} tag.
+ */
+ protected void addEmptyCells(int numColsSkipped, Node row) {
+
+ // Create an empty cellElement
+ Element cellElement = doc.createElement(TAG_TABLE_CELL);
+
+ // ToDo - style currently hardcoded - get real value
+ // Set cell style-name attribute
+ cellElement.setAttribute(ATTRIBUTE_TABLE_STYLE_NAME, "Default");
+
+ // If we skipped more than 1 cell, we must set the
+ // appropriate attribute
+ if (numColsSkipped > 1) {
+
+ // Set cell number-columns-repeated attribute
+ cellElement.setAttribute(ATTRIBUTE_TABLE_NUM_COLUMNS_REPEATED,
+ Integer.toString(numColsSkipped));
+ }
+
+ // Append the empty cell element to the row node
+ row.appendChild(cellElement);
+
+ // Write empty cells to the log
+ for (int i = 0; i < numColsSkipped; i++) {
+ Debug.log(Debug.TRACE, "<td />");
+ }
+ }
+
+ /**
+ * This method process the data in a cell and sets the appropriate attributes
+ * on the cell {@code Element}.
+ *
+ * @param cellElement A {@code TAG_TABLE_CELL} {@code Element} that we
+ * will be adding attributes to based on the type of
+ * data in the cell.
+ * @param type The type of data contained in the cell.
+ * @param contents The contents of the data contained in the cell.
+ */
+ protected void processCellData(Element cellElement, String type,
+ String contents) {
+
+ // Set cell value-type attribute
+ cellElement.setAttribute(ATTRIBUTE_TABLE_VALUE_TYPE, type);
+
+ // Does the cell contain a formula?
+ if (contents.startsWith("=")) {
+
+ cellElement.setAttribute(ATTRIBUTE_TABLE_FORMULA, contents);
+
+ cellElement.setAttribute(ATTRIBUTE_TABLE_VALUE, decoder.getCellValue());
+ // String data does not require any additional attributes
+ } else if (!type.equals(CELLTYPE_STRING)) {
+
+ if (type.equals(CELLTYPE_TIME)) {
+
+ // Data returned in StarOffice XML format, so store in
+ // attribute
+ cellElement.setAttribute(ATTRIBUTE_TABLE_TIME_VALUE,
+ contents);
+
+ } else if (type.equals(CELLTYPE_DATE)) {
+
+ // Data returned in StarOffice XML format, so store in
+ // attribute
+ cellElement.setAttribute(ATTRIBUTE_TABLE_DATE_VALUE,
+ contents);
+
+ } else if (type.equals(CELLTYPE_BOOLEAN)) {
+
+ // StarOffice XML format requires stored boolean value
+ // to be in lower case
+ cellElement.setAttribute(ATTRIBUTE_TABLE_BOOLEAN_VALUE,
+ contents.toLowerCase());
+
+ } else if (type.equals(CELLTYPE_CURRENCY)) {
+ // TODO - StarOffice XML format requires a correct style to
+ // display currencies correctly. Need to implement styles.
+ // TODO - USD is for US currencies. Need to pick up
+ // the correct currency location from the source file.
+ cellElement.setAttribute(ATTRIBUTE_TABLE_CURRENCY, "USD");
+
+ // Data comes stripped of currency symbols
+ cellElement.setAttribute(ATTRIBUTE_TABLE_VALUE, contents);
+
+ } else if (type.equals(CELLTYPE_PERCENT)) {
+ // Data comes stripped of percent signs
+ cellElement.setAttribute(ATTRIBUTE_TABLE_VALUE, contents);
+
+ } else {
+ // Remaining data types use table-value attribute
+
+ cellElement.setAttribute(ATTRIBUTE_TABLE_VALUE, contents);
+ }
+ }
+ }
+}
diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxc/SxcDocumentSerializer.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxc/SxcDocumentSerializer.java
new file mode 100644
index 000000000..531395624
--- /dev/null
+++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxc/SxcDocumentSerializer.java
@@ -0,0 +1,885 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+package org.openoffice.xmerge.converter.xml.sxc;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+
+import org.openoffice.xmerge.ConvertData;
+import org.openoffice.xmerge.ConvertException;
+import org.openoffice.xmerge.DocumentSerializer;
+import org.openoffice.xmerge.converter.xml.OfficeConstants;
+import org.openoffice.xmerge.converter.xml.StyleCatalog;
+import org.openoffice.xmerge.util.Debug;
+import org.openoffice.xmerge.util.XmlUtil;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+/**
+ * General spreadsheet implementation of {@code DocumentSerializer} for the
+ * {@link org.openoffice.xmerge.converter.xml.sxc.SxcPluginFactory
+ * SxcPluginFactory}.
+ *
+ * <p>Used with SXC {@code Document} objects.</p>
+ *
+ * <p>The {@code serialize} method traverses the DOM {@code Document} from the
+ * given {@code Document} object. It uses a {@code DocEncoder} object for the
+ * actual conversion of contents to the device spreadsheet format.</p>
+ */
+public abstract class SxcDocumentSerializer implements OfficeConstants,
+ DocumentSerializer {
+
+ /** {@code Format} object describing the cell. */
+ private Format fmt = null;
+
+ /** The row number. */
+ private int rowID = 1;
+
+ /** The column number. */
+ private int colID = 1;
+
+ /** The number of times the current row is repeated. */
+ private int rowsRepeated = 1;
+
+ /** The number of times the current column is repeated. */
+ private int colsRepeated = 1;
+
+ /** The number of times the current column is repeated. */
+ private StyleCatalog styleCat = null;
+
+ /**
+ * An array of column widths of the current worksheet.
+ * Width is measured in number of characters.
+ */
+ private ArrayList<ColumnRowInfo> ColumnRowList;
+
+ /**
+ * A {@code SpreadsheetEncoder} object for encoding to appropriate format.
+ */
+ protected SpreadsheetEncoder encoder = null;
+
+ /**
+ * Constructor.
+ *
+ * @param document Input {@code SxcDocument} {@code Document}.
+ */
+ public SxcDocumentSerializer() {
+ fmt = new Format();
+ }
+
+ /**
+ * Method to convert a DOM {@code Document} into &quot;Device&quot;
+ * {@code Document} objects.
+ *
+ * <p>This method is not thread safe for performance reasons. This method
+ * should not be called from within two threads. It would be best to call
+ * this method only once per object instance.</p>
+ *
+ * @return {@code ConvertData} containing &quot;Device&quot;
+ * {@code Document}objects.
+ *
+ * @throws ConvertException If any conversion error occurs.
+ * @throws IOException If any I/O error occurs.
+ */
+ public abstract ConvertData serialize() throws ConvertException,
+ IOException;
+
+ /**
+ * This method traverses <i>office:settings</i> {@code Element}.
+ *
+ * @param node <i>office:settings</i> {@code Node}.
+ *
+ * @throws IOException If any I/O error occurs.
+ */
+ public void traverseSettings(Node node) throws IOException {
+ if (node.hasChildNodes()) {
+
+ NodeList nodeList = node.getChildNodes();
+ int len = nodeList.getLength();
+ for (int i = 0; i < len; i++) {
+ Node child = nodeList.item(i);
+
+ if (child.getNodeType() == Node.ELEMENT_NODE) {
+ String nodeName = child.getNodeName();
+
+ if (nodeName.equals(TAG_CONFIG_ITEM_SET)) {
+
+ traverseSettings(child);
+
+ } else if (nodeName.equals(TAG_CONFIG_ITEM_MAP_INDEXED)) {
+
+ traverseSettings(child);
+
+ } else if (nodeName.equals(TAG_CONFIG_ITEM_MAP_ENTRY)) {
+
+ BookSettings bs = new BookSettings(child);
+ encoder.addSettings(bs);
+
+ } else {
+
+ Debug.log(Debug.TRACE, "<OTHERS " + XmlUtil.getNodeInfo(child) + " />");
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Handles the loading of defined styles from the style.xml file as well as
+ * automatic styles from the content.xml file.
+ *
+ * <p>Any change to a defined style, such as a short bold section, falls
+ * into the latter category.
+ */
+ protected void loadStyles(SxcDocument sxcDoc) {
+
+ styleCat = new StyleCatalog(25);
+ NodeList nl = null;
+ String families[] = new String[] { SxcConstants.COLUMN_STYLE_FAMILY,
+ SxcConstants.ROW_STYLE_FAMILY,
+ SxcConstants.TABLE_CELL_STYLE_FAMILY };
+ Class<?> classes[] = new Class[] { ColumnStyle.class,
+ RowStyle.class,
+ CellStyle.class};
+ /*
+ * Process the content XML for any other style info.
+ */
+ org.w3c.dom.Document contentDom = sxcDoc.getContentDOM();
+ nl = contentDom.getElementsByTagName(TAG_OFFICE_AUTOMATIC_STYLES);
+ if (nl.getLength() != 0) {
+ styleCat.add(nl.item(0), families, classes, null, false);
+ }
+
+ org.w3c.dom.Document stylesDom = sxcDoc.getStyleDOM();
+ nl = stylesDom.getElementsByTagName(TAG_OFFICE_STYLES);
+ if (nl.getLength() != 0) {
+ styleCat.add(nl.item(0), families, classes, null, false);
+ }
+ }
+
+ /**
+ * This method traverses <i>office:body</i> {@code Element}.
+ *
+ * @param node <i>office:body</i> {@code Node}.
+ *
+ * @throws IOException If any I/O error occurs.
+ */
+ protected void traverseBody(Node node) throws IOException {
+
+ Debug.log(Debug.TRACE, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
+ Debug.log(Debug.TRACE, "<DEBUGLOG>");
+
+ if (node.hasChildNodes()) {
+
+ NodeList nodeList = node.getChildNodes();
+ int len = nodeList.getLength();
+
+ for (int i = 0; i < len; i++) {
+ Node searchNode = nodeList.item(i);
+ if (searchNode.getNodeType() == Node.ELEMENT_NODE) {
+
+ String nodeName = searchNode.getNodeName();
+
+ if (nodeName.equals(TAG_NAMED_EXPRESSIONS)) {
+
+ traverseNamedExpressions(searchNode);
+
+ } else {
+
+ Debug.log(Debug.TRACE, "Skipping " + XmlUtil.getNodeInfo(searchNode) + " />");
+ }
+ }
+ }
+
+ for (int i = 0; i < len; i++) {
+ Node child = nodeList.item(i);
+
+ if (child.getNodeType() == Node.ELEMENT_NODE) {
+ String nodeName = child.getNodeName();
+
+ if (nodeName.equals(TAG_TABLE)) {
+
+ traverseTable(child);
+
+ } else {
+
+ Debug.log(Debug.TRACE, "<OTHERS " + XmlUtil.getNodeInfo(child) + " />");
+ }
+ }
+ }
+ }
+
+ Debug.log(Debug.TRACE, "</DEBUGLOG>");
+ }
+
+ /**
+ * This method traverses the <i>table:table</i> element {@code Node}.
+ *
+ * @param node A <i>table:table</i> {@code Node}.
+ *
+ * @throws IOException If any I/O error occurs.
+ */
+ protected void traverseNamedExpressions(Node node) throws IOException {
+
+ Debug.log(Debug.TRACE, "<NAMED:EXPRESSIONS>");
+
+ if (node.hasChildNodes()) {
+
+ NodeList nodeList = node.getChildNodes();
+ int len = nodeList.getLength();
+
+ for (int i = 0; i < len; i++) {
+ Node child = nodeList.item(i);
+
+ if (child.getNodeType() == Node.ELEMENT_NODE) {
+ NameDefinition nd = new NameDefinition(child);
+ encoder.setNameDefinition(nd);
+ }
+ }
+ }
+
+ Debug.log(Debug.TRACE, "</NAMED:EXPRESSIONS>");
+ }
+
+ /**
+ * This method traverses the <i>table:table</i> element {@code Node}.
+ *
+ * @param node A <i>table:table</i> {@code Node}.
+ *
+ * @throws IOException If any I/O error occurs.
+ */
+ protected void traverseTable(Node node) throws IOException {
+
+ Debug.log(Debug.TRACE, "<TABLE>");
+
+ ColumnRowList = new ArrayList<ColumnRowInfo>();
+
+ // Get table attributes
+ // ToDo - extract style from attribute
+
+ NamedNodeMap att = node.getAttributes();
+
+ String tableName =
+ att.getNamedItem(ATTRIBUTE_TABLE_NAME).getNodeValue();
+
+ rowID = 1;
+
+ encoder.createWorksheet(tableName);
+
+ if (node.hasChildNodes()) {
+
+ NodeList nodeList = node.getChildNodes();
+ int len = nodeList.getLength();
+
+ for (int i = 0; i < len; i++) {
+ Node child = nodeList.item(i);
+
+ if (child.getNodeType() == Node.ELEMENT_NODE) {
+ String nodeName = child.getNodeName();
+
+ if (nodeName.equals(TAG_TABLE_ROW)) {
+ // TODO - handle all the possible rows
+ // spelled out in the entities
+
+ traverseTableRow(child);
+
+ } else if (nodeName.equals(TAG_TABLE_COLUMN)) {
+
+ traverseTableColumn(child);
+
+ } else if (nodeName.equals(TAG_TABLE_SCENARIO)) {
+
+ // TODO
+
+ } else {
+
+ Debug.log(Debug.TRACE, "<OTHERS " + XmlUtil.getNodeInfo(child) + " />");
+ }
+ }
+ }
+ }
+
+ // Add column width info to the current sheet
+ encoder.setColumnRows(ColumnRowList);
+
+ Debug.log(Debug.TRACE, "</TABLE>");
+ }
+
+ /**
+ * This method traverses the <i>table:table-row</i> element {@code Node}.
+ *
+ * @param node A <i>table:table-row</i> {@code Node}.
+ *
+ * @throws IOException If any I/O error occurs.
+ */
+ protected void traverseTableRow(Node node) throws IOException {
+
+ // Get the attributes of the row
+ NamedNodeMap cellAtt = node.getAttributes();
+
+ if (cellAtt != null) {
+
+ Node rowStyle =
+ cellAtt.getNamedItem(ATTRIBUTE_TABLE_STYLE_NAME);
+
+ Node tableNumRowRepeatingNode = cellAtt.getNamedItem(ATTRIBUTE_TABLE_NUM_ROWS_REPEATED);
+ int repeatedRows = 1;
+
+ if(tableNumRowRepeatingNode!=null) {
+ String repeatStr = tableNumRowRepeatingNode.getNodeValue();
+ Debug.log(Debug.TRACE, "traverseTableRow() repeated-rows : " + repeatStr);
+ repeatedRows = Integer.parseInt(repeatStr);
+ }
+
+ String styleName = "";
+
+ if ( rowStyle != null) {
+ styleName = rowStyle.getNodeValue();
+ }
+ if(styleName.equalsIgnoreCase("Default") || styleName.length()==0) {
+
+ Debug.log(Debug.TRACE, "No defined Row Style Attribute was found");
+
+ } else {
+
+ RowStyle rStyle = ( RowStyle)styleCat.lookup(styleName,
+ SxcConstants.ROW_STYLE_FAMILY, null,
+ RowStyle.class);
+
+ int rowHeight = rStyle != null ? rStyle.getRowHeight() : 0;
+
+ Debug.log(Debug.TRACE, "traverseTableRow() Row Height : " + rowHeight);
+ ColumnRowInfo ri = new ColumnRowInfo( rowHeight,
+ repeatedRows,
+ ColumnRowInfo.ROW,
+ rowHeight!=0);
+ ColumnRowList.add(ri);
+ }
+
+ // Get the attribute representing the number of rows repeated
+ Node rowsRepeatedNode =
+ cellAtt.getNamedItem(ATTRIBUTE_TABLE_NUM_ROWS_REPEATED);
+
+ // There is a number of rows repeated attribute:
+ if (rowsRepeatedNode != null) {
+ // Get the number of times the row is repeated
+ String rowsRepeatedString = rowsRepeatedNode.getNodeValue();
+ rowsRepeated = Integer.parseInt(rowsRepeatedString);
+ } else {
+ // The row is not repeated
+ rowsRepeated = 1;
+ }
+ }
+
+ Debug.log(Debug.TRACE, "<TR>");
+
+ if (node.hasChildNodes()) {
+
+ NodeList nodeList = node.getChildNodes();
+ int len = nodeList.getLength();
+
+ for (int i = 0; i < len; i++) {
+ Node child = nodeList.item(i);
+
+ if (child.getNodeType() == Node.ELEMENT_NODE) {
+ String nodeName = child.getNodeName();
+
+ if (nodeName.equals(TAG_TABLE_CELL)) {
+
+ traverseCell(child);
+
+ } else {
+
+ Debug.log(Debug.TRACE, "<OTHERS " + XmlUtil.getNodeInfo(child) + " />");
+ }
+ }
+ }
+ }
+
+ // Increase the row counter by the number of rows which are repeated
+ rowID += rowsRepeated;
+
+ // Re-initialize number of rows repeated before processing the next
+ // row data.
+ rowsRepeated = 1;
+
+ // When starting a new row, set the column counter back to the
+ // first column.
+ colID = 1;
+
+ // Re-initialize number of columns repeated before processing
+ // the next row data.
+ colsRepeated = 1;
+
+ Debug.log(Debug.TRACE, "</TR>");
+ }
+
+ /**
+ * This method traverses the <i>table:table-column</i> {@code Node}.
+ *
+ * <p>Not yet implemented.</p>
+ *
+ * @param node A <i>table:table-column</i> {@code Node}.
+ *
+ * @throws IOException If any I/O error occurs.
+ */
+ protected void traverseTableColumn(Node node) throws IOException {
+
+ Debug.log(Debug.TRACE, "traverseColumn() : ");
+ NamedNodeMap cellAtt = node.getAttributes();
+ Node tableStyleNode = cellAtt.getNamedItem(ATTRIBUTE_TABLE_STYLE_NAME);
+ Node tableNumColRepeatingNode = cellAtt.getNamedItem(ATTRIBUTE_TABLE_NUM_COLUMNS_REPEATED);
+ Node tableDefaultCellStyle = cellAtt.getNamedItem(ATTRIBUTE_DEFAULT_CELL_STYLE);
+
+ int repeatedColumns = 1;
+ int columnWidth = 0;
+ ColumnRowInfo col = new ColumnRowInfo(ColumnRowInfo.COLUMN);
+
+ if(tableNumColRepeatingNode!=null) {
+ Debug.log(Debug.TRACE, "traverseColumn() repeated-cols : " + tableNumColRepeatingNode.getNodeValue());
+ repeatedColumns = Integer.parseInt(tableNumColRepeatingNode.getNodeValue());
+ col.setRepeated(repeatedColumns);
+ }
+
+ String cellStyleName = "";
+
+ if(tableDefaultCellStyle!=null) {
+ cellStyleName = tableDefaultCellStyle.getNodeValue();
+
+ Debug.log(Debug.TRACE, "traverseColumn() default-cell-style : " + cellStyleName);
+ }
+
+ CellStyle cellStyle = null;
+
+ if(cellStyleName.equalsIgnoreCase("Default") || cellStyleName.length()==0) {
+
+ Debug.log(Debug.TRACE, "No default cell Style Attribute was found");
+
+ } else {
+
+ cellStyle = (CellStyle)styleCat.lookup(cellStyleName,
+ SxcConstants.TABLE_CELL_STYLE_FAMILY, null,
+ CellStyle.class);
+ }
+
+ if (cellStyle != null) {
+ Format defaultFmt = new Format(cellStyle.getFormat());
+ col.setFormat(defaultFmt);
+ }
+
+ String styleName = "";
+
+ if(tableStyleNode!=null) {
+ styleName = tableStyleNode.getNodeValue();
+ }
+
+ if(styleName.equalsIgnoreCase("Default") || styleName.length()==0) {
+
+ Debug.log(Debug.TRACE, "No defined Style Attribute was found");
+
+ } else {
+
+ ColumnStyle cStyle = (ColumnStyle)styleCat.lookup(styleName,
+ SxcConstants.COLUMN_STYLE_FAMILY, null,
+ ColumnStyle.class);
+
+ columnWidth = cStyle != null ? cStyle.getColWidth() : 0;
+ col.setSize(columnWidth);
+ Debug.log(Debug.TRACE, "traverseColumn() Column Width : " + columnWidth);
+
+ }
+ ColumnRowList.add(col);
+ }
+
+ /**
+ * This method traverses a <i>table:table-cell</i> element {@code Node}.
+ *
+ * @param node a <i>table:table-cell</i> {@code Node}.
+ *
+ * @throws IOException if any I/O error occurs.
+ */
+ protected void traverseCell(Node node) throws IOException {
+
+ NamedNodeMap cellAtt = node.getAttributes();
+
+ fmt.clearFormatting();
+
+ // Get the type of data in the cell
+ Node tableValueTypeNode =
+ cellAtt.getNamedItem(ATTRIBUTE_TABLE_VALUE_TYPE);
+
+ // Get the number of columns this cell is repeated
+ Node colsRepeatedNode =
+ cellAtt.getNamedItem(ATTRIBUTE_TABLE_NUM_COLUMNS_REPEATED);
+
+ // Get the style type
+ Node tableStyleNode =
+ cellAtt.getNamedItem(ATTRIBUTE_TABLE_STYLE_NAME);
+
+ String styleName = "";
+
+ if(tableStyleNode!=null) {
+ styleName = tableStyleNode.getNodeValue();
+ }
+
+ CellStyle cStyle = null;
+
+ if(styleName.equalsIgnoreCase("Default")) {
+
+ Debug.log(Debug.TRACE, "No defined Style Attribute was found");
+
+ } else if(styleName.length()!=0) {
+
+ cStyle = (CellStyle)styleCat.lookup(styleName,
+ SxcConstants.TABLE_CELL_STYLE_FAMILY, null,
+ CellStyle.class);
+ }
+
+ if (cStyle != null) {
+ Format definedFormat = cStyle.getFormat();
+ fmt = new Format(definedFormat);
+ }
+
+ // There is a number of cols repeated attribute
+ if (colsRepeatedNode != null) {
+ // Get the number of times the cell is repeated
+ String colsRepeatedString = colsRepeatedNode.getNodeValue();
+ colsRepeated = Integer.parseInt(colsRepeatedString);
+ } else {
+ // The cell is not repeated
+ colsRepeated = 1;
+ }
+
+
+ // if there is no style we need to check to see if there is a default
+ // cell style defined in the table-column's
+
+ if (fmt.isDefault() && styleName.length()==0) {
+ int index = 1;
+ for(Iterator<ColumnRowInfo> e = ColumnRowList.iterator();e.hasNext();) {
+ ColumnRowInfo cri = e.next();
+ if(cri.isColumn()) {
+ if(colID>=index && colID<(index+cri.getRepeated())) {
+ fmt = new Format(cri.getFormat());
+ }
+ index += cri.getRepeated();
+ }
+ }
+ }
+
+ if (tableValueTypeNode != null) {
+
+ String cellType =
+ tableValueTypeNode.getNodeValue();
+
+ if (cellType.equalsIgnoreCase(CELLTYPE_STRING)) {
+
+ // has text:p tag
+ fmt.setCategory(CELLTYPE_STRING);
+ Node tableStringValueNode = cellAtt.getNamedItem(ATTRIBUTE_TABLE_STRING_VALUE);
+ Debug.log(Debug.TRACE,"Cell Type String : " + tableStringValueNode);
+ if(tableStringValueNode != null) {
+ fmt.setValue(tableStringValueNode.getNodeValue());
+ }
+
+ } else if (cellType.equalsIgnoreCase(CELLTYPE_FLOAT)) {
+
+ // has table:value attribute
+ // has text:p tag
+
+ // Determine the number of decimal places StarCalc
+ // is displaying for this floating point output.
+ fmt.setCategory(CELLTYPE_FLOAT);
+ fmt.setDecimalPlaces(getDecimalPlaces(node));
+ Node tableValueNode = cellAtt.getNamedItem(ATTRIBUTE_TABLE_VALUE);
+ fmt.setValue(tableValueNode.getNodeValue());
+
+
+ } else if (cellType.equalsIgnoreCase(CELLTYPE_TIME)) {
+
+ // has table:time-value attribute
+ // has text:p tag - which is the value we convert
+
+ fmt.setCategory(CELLTYPE_TIME);
+ Node tableTimeNode = cellAtt.getNamedItem(ATTRIBUTE_TABLE_TIME_VALUE);
+ fmt.setValue(tableTimeNode.getNodeValue());
+
+ } else if (cellType.equalsIgnoreCase(CELLTYPE_DATE)) {
+
+ // has table:date-value attribute
+ // has text:p tag - which is the value we convert
+
+ fmt.setCategory(CELLTYPE_DATE);
+ Node tableDateNode = cellAtt.getNamedItem(ATTRIBUTE_TABLE_DATE_VALUE);
+ fmt.setValue(tableDateNode.getNodeValue());
+
+ } else if (cellType.equalsIgnoreCase(CELLTYPE_CURRENCY)) {
+
+ // has table:currency
+ // has table:value attribute
+ // has text:p tag
+
+ fmt.setCategory(CELLTYPE_CURRENCY);
+ fmt.setDecimalPlaces(getDecimalPlaces(node));
+ Node tableValueNode = cellAtt.getNamedItem(ATTRIBUTE_TABLE_VALUE);
+ fmt.setValue(tableValueNode.getNodeValue());
+
+ } else if (cellType.equalsIgnoreCase(CELLTYPE_BOOLEAN)) {
+
+ // has table:boolean-value attribute
+ // has text:p tag - which is the value we convert
+
+ fmt.setCategory(CELLTYPE_BOOLEAN);
+ Node tableBooleanNode = cellAtt.getNamedItem(ATTRIBUTE_TABLE_BOOLEAN_VALUE);
+ fmt.setValue(tableBooleanNode.getNodeValue());
+
+ } else if (cellType.equalsIgnoreCase(CELLTYPE_PERCENT)) {
+
+ // has table:value attribute
+ // has text:p tag
+
+ fmt.setCategory(CELLTYPE_PERCENT);
+ fmt.setDecimalPlaces(getDecimalPlaces(node));
+ Node tableValueNode = cellAtt.getNamedItem(ATTRIBUTE_TABLE_VALUE);
+ fmt.setValue(tableValueNode.getNodeValue());
+
+ } else {
+
+ Debug.log(Debug.TRACE,"No defined value type" + cellType);
+ // Should never get here
+
+ }
+ }
+
+ Node tableFormulaNode = cellAtt.getNamedItem(ATTRIBUTE_TABLE_FORMULA);
+
+ if(tableFormulaNode != null)
+ {
+ if(tableValueTypeNode == null) { // If there is no value-type Node we must assume string-value
+ fmt.setCategory(CELLTYPE_STRING);
+ Node tableStringValueNode = cellAtt.getNamedItem(ATTRIBUTE_TABLE_STRING_VALUE);
+ fmt.setValue(tableStringValueNode.getNodeValue());
+ }
+ String cellFormula = tableFormulaNode.getNodeValue();
+ addCell(cellFormula);
+ } else {
+
+ // Text node, Date node, or Time node
+
+ Debug.log(Debug.INFO,
+ "TextNode, DateNode, TimeNode or BooleanNode\n");
+ // This handles the case where we have style information but no content
+ if (node.hasChildNodes()) {
+ NodeList childList = node.getChildNodes();
+ int len = childList.getLength();
+
+ for (int i = 0; i < len; i++) {
+ Node child = childList.item(i);
+ if (child.getNodeType() == Node.ELEMENT_NODE) {
+ String childName = child.getNodeName();
+ if (childName.equals(TAG_PARAGRAPH)) {
+ traverseParagraph(child);
+ }
+ }
+ }
+ } else if(!fmt.isDefault()) {
+ addCell("");
+ }
+ }
+
+ // Increase the column counter by the number of times the
+ // last cell was repeated.
+ colID += colsRepeated;
+
+ // Re-initialize the number of columns repeated before processing
+ // the next cell data.
+ colsRepeated = 1;
+
+ }
+
+ /**
+ * This method traverses the <i>text:p</i> element {@code Node}.
+ *
+ * @param node A <i>text:p</i> {@code Node}.
+ *
+ * @throws IOException If any I/O error occurs.
+ */
+ protected void traverseParagraph(Node node) throws IOException {
+
+ NamedNodeMap cellAtt = node.getAttributes();
+
+ int debug_i=0;
+ Node debug_attrib = null;
+ if (cellAtt == null || cellAtt.item(0) == null) {
+ Debug.log(Debug.INFO, "No Paragraph Attributes\n");
+ } else {
+ while ((debug_attrib = cellAtt.item(debug_i++)) != null) {
+ Debug.log(Debug.INFO, "Paragraph Attribute " + debug_i +
+ ": " + debug_attrib.getNodeName() + " : " +
+ debug_attrib.getNodeValue() + "\n");
+ }
+ }
+
+ if (node.hasChildNodes()) {
+
+ NodeList nodeList = node.getChildNodes();
+
+ int len = nodeList.getLength();
+
+ StringBuffer buffer = new StringBuffer();
+
+ for (int i = 0; i < len; i++) {
+
+ Node child = nodeList.item(i);
+
+ // TODO: need to handle space/tabs/newline nodes later
+ short nodeType = child.getNodeType();
+
+ switch (nodeType) {
+
+ case Node.TEXT_NODE:
+ buffer.append(child.getNodeValue());
+ break;
+
+ case Node.ENTITY_REFERENCE_NODE:
+
+ NodeList nodeList2 = child.getChildNodes();
+ int len2 = nodeList2.getLength();
+
+ for (int j = 0; j < len2; j++) {
+ Node child2 = nodeList2.item(j);
+
+ if (child2.getNodeType() == Node.TEXT_NODE) {
+ buffer.append(child2.getNodeValue());
+ }
+ }
+
+ break;
+ }
+ }
+
+ String s = buffer.toString();
+ addCell(s);
+
+ }
+ }
+
+ /**
+ * This method will take the input cell value and add it to the spreadsheet
+ * {@code Document} we are currently encoding.
+ *
+ * <p>This method correctly handles cells that are repeated in either the
+ * row, cell, or both directions.</p>
+ *
+ * @param cellValue The contents of the cell we want to add to the
+ * spreadsheet {@code Document}.
+ *
+ * @throws IOException If any I/O error occurs.
+ */
+ protected void addCell(String cellValue) throws IOException {
+
+ int col = colID;
+ int row = rowID;
+
+ for (int i = 0; i < rowsRepeated; i++) {
+
+ // Log the columns when there are rowsRepeated.
+ if (i > 0) {
+ Debug.log(Debug.TRACE, "</TR>");
+ Debug.log(Debug.TRACE, "<TR>");
+ }
+
+ col = colID;
+
+ for (int j = 0; j < colsRepeated; j++) {
+
+ Debug.log(Debug.TRACE, "<TD>");
+
+ // Add the cell data to the encoded spreadsheet document
+ encoder.addCell(row, col, fmt, cellValue);
+
+ Debug.log(Debug.TRACE, cellValue);
+ Debug.log(Debug.TRACE, "</TD>");
+
+ col++;
+ }
+
+ row++;
+
+ }
+
+ }
+
+ /**
+ * This method takes a <i>table:table-cell</i> {@code Node} and traverses
+ * down to the <i>text:p</i> tag.
+ *
+ * <p>The value is extracted from the <i>text:p</i> tag and the number of
+ * decimal places is calculated.</p>
+ *
+ * @param node A <i>table:table-cell</i> {@code Node}.
+ *
+ * @return The number of decimal places in the display string of the data
+ * in the input {@code Node}.
+ */
+ protected int getDecimalPlaces(Node node) {
+
+ int decimals = 0;
+ Element element = null;
+
+ // cast org.w3c.dom.Node to org.w3c.dom.Element
+ if (node instanceof Element) {
+ element = (Element) node;
+ } else {
+ return decimals;
+ }
+
+ // Traverse to the text:p element, there should only be one.
+ NodeList list = element.getElementsByTagName(TAG_PARAGRAPH);
+
+ if (list.getLength() != 1) {
+ return decimals;
+ }
+
+ Node paragraph = list.item(0);
+ if (paragraph.hasChildNodes()) {
+
+ NodeList nodeList = paragraph.getChildNodes();
+ int len = nodeList.getLength();
+ for (int j = 0; j < len; j++) {
+
+ Node child = nodeList.item(j);
+ if (child.getNodeType() == Node.TEXT_NODE) {
+
+ String s = child.getNodeValue();
+ int k = s.lastIndexOf('.');
+ if (k > 0) {
+ s = s.substring(k+1);
+ decimals = s.length();
+ }
+ }
+ }
+ }
+
+ return decimals;
+ }
+}
diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxc/SxcPluginFactory.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxc/SxcPluginFactory.java
new file mode 100644
index 000000000..1b3008c63
--- /dev/null
+++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxc/SxcPluginFactory.java
@@ -0,0 +1,69 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+package org.openoffice.xmerge.converter.xml.sxc;
+
+import java.io.InputStream;
+import java.io.IOException;
+
+import org.openoffice.xmerge.util.registry.ConverterInfo;
+import org.openoffice.xmerge.PluginFactory;
+import org.openoffice.xmerge.Document;
+import org.openoffice.xmerge.DocumentMergerFactory;
+
+/**
+ * General implementation of the {@code PluginFactory} interface for SXC
+ * {@code Document} objects.
+ *
+ * @see org.openoffice.xmerge.DocumentDeserializer
+ * @see org.openoffice.xmerge.DocumentMerger
+ * @see org.openoffice.xmerge.DocumentSerializer
+ */
+public abstract class SxcPluginFactory
+ extends PluginFactory implements DocumentMergerFactory {
+
+ /**
+ * Constructor that caches the {@code ConvertInfo} that corresponds to the
+ * registry information for this plug-in.
+ *
+ * @param ci {@code ConvertInfo} object.
+ */
+ public SxcPluginFactory(ConverterInfo ci) {
+ super(ci);
+ }
+
+ @Override
+ public Document createOfficeDocument(String name, InputStream is)
+ throws IOException {
+
+ // Read zipped XML stream
+ SxcDocument doc = new SxcDocument(name);
+ doc.read(is);
+ return doc;
+ }
+
+ @Override
+ public Document createOfficeDocument(String name, InputStream is,boolean isZip)
+ throws IOException {
+
+ // Read zipped XML stream
+ SxcDocument doc = new SxcDocument(name);
+ doc.read(is,isZip);
+ return doc;
+ }
+} \ No newline at end of file
diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxc/package-info.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxc/package-info.java
new file mode 100644
index 000000000..62886c7de
--- /dev/null
+++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxc/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+/**
+ * Provides base implementation of StarCalc XML conversion to and from different
+ * {@literal "Device"} {@code Document} formats.
+ */
+package org.openoffice.xmerge.converter.xml.sxc;
diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxw/SxwDocument.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxw/SxwDocument.java
new file mode 100644
index 000000000..37cf99699
--- /dev/null
+++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxw/SxwDocument.java
@@ -0,0 +1,80 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+package org.openoffice.xmerge.converter.xml.sxw;
+
+import org.openoffice.xmerge.converter.xml.OfficeDocument;
+import org.openoffice.xmerge.converter.xml.OfficeConstants;
+
+/**
+ * This class is an implementation of {@code OfficeDocument} for the SXW format.
+ */
+public class SxwDocument extends OfficeDocument {
+
+ /**
+ * Constructor with arguments to set {@code name}.
+ *
+ * @param name The name of the {@code Document}.
+ */
+ public SxwDocument(String name) {
+ super(name);
+ }
+
+ /**
+ * Constructor with arguments to set {@code name}, the {@code namespaceAware}
+ * flag, and the {@code validating} flag.
+ *
+ * @param name The name of the {@code Document}.
+ * @param namespaceAware The value of the {@code namespaceAware} flag.
+ * @param validating The value of the {@code validating} flag.
+ */
+ public SxwDocument(String name, boolean namespaceAware, boolean validating) {
+
+ super(name, namespaceAware, validating);
+ }
+
+ /**
+ * Returns the Office file extension for the SXW format.
+ *
+ * @return The Office file extension for the SXW format.
+ */
+ @Override
+ protected String getFileExtension() {
+ return OfficeConstants.SXW_FILE_EXTENSION;
+ }
+
+ /**
+ * Returns the Office attribute for the SXW format.
+ *
+ * @return The Office attribute for the SXW format.
+ */
+ @Override
+ protected String getOfficeClassAttribute() {
+ return OfficeConstants.SXW_TYPE;
+ }
+
+ /**
+ * Method to return the MIME type of the document.
+ *
+ * @return The document's MIME type.
+ */
+ @Override
+ protected final String getDocumentMimeType() {
+ return OfficeConstants.SXW_MIME_TYPE;
+ }
+} \ No newline at end of file
diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxw/SxwPluginFactory.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxw/SxwPluginFactory.java
new file mode 100644
index 000000000..c529a3126
--- /dev/null
+++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxw/SxwPluginFactory.java
@@ -0,0 +1,67 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+package org.openoffice.xmerge.converter.xml.sxw;
+
+import java.io.InputStream;
+import java.io.IOException;
+
+import org.openoffice.xmerge.PluginFactory;
+import org.openoffice.xmerge.Document;
+import org.openoffice.xmerge.util.registry.ConverterInfo;
+
+/**
+ * General implementation of the {@code PluginFactory} interface for SXW
+ * documents.
+ *
+ * @see org.openoffice.xmerge.DocumentDeserializer
+ * @see org.openoffice.xmerge.DocumentMerger
+ * @see org.openoffice.xmerge.DocumentSerializer
+ */
+public abstract class SxwPluginFactory extends PluginFactory {
+
+ /**
+ * Constructor that caches the {@code ConvertInfo} that corresponds to the
+ * registry information for this plug-in.
+ *
+ * @param ci {@code ConvertInfo} object.
+ */
+ public SxwPluginFactory (ConverterInfo ci) {
+ super(ci);
+ }
+
+ @Override
+ public Document createOfficeDocument(String name, InputStream is)
+ throws IOException {
+
+ // Read zipped XML stream
+ SxwDocument doc = new SxwDocument(name);
+ doc.read(is);
+ return doc;
+ }
+
+ @Override
+ public Document createOfficeDocument(String name, InputStream is,boolean isZip)
+ throws IOException {
+
+ // Read XML stream
+ SxwDocument doc = new SxwDocument(name);
+ doc.read(is,isZip);
+ return doc;
+ }
+} \ No newline at end of file
diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxw/package-info.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxw/package-info.java
new file mode 100644
index 000000000..98dfafd4d
--- /dev/null
+++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/sxw/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+/**
+ * Provides base implementation of StarWriter XML conversion to and from
+ * different {@literal "Device"} {@code Document} formats.
+ */
+package org.openoffice.xmerge.converter.xml.sxw;
diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/xslt/ConverterCapabilitiesImpl.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/xslt/ConverterCapabilitiesImpl.java
new file mode 100644
index 000000000..64a94d350
--- /dev/null
+++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/xslt/ConverterCapabilitiesImpl.java
@@ -0,0 +1,74 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+package org.openoffice.xmerge.converter.xml.xslt;
+
+import org.openoffice.xmerge.ConverterCapabilities;
+import org.openoffice.xmerge.converter.xml.OfficeConstants;
+
+/**
+ * Xslt implementation of {@code ConverterCapabilities} for the {@link
+ * org.openoffice.xmerge.converter.xml.xslt.PluginFactoryImpl PluginFactoryImpl}.
+ *
+ * <p>Used with StarWriter XML to/from XSLT supported formats conversions. The
+ * {@code ConverterCapibilies} specify which &quot;Office&quot; {@code Document}
+ * tags and attributes are supported on the &quot;Device&quot; {@code Document}
+ * format.</p>
+ */
+public final class ConverterCapabilitiesImpl
+ implements ConverterCapabilities {
+
+ public boolean canConvertTag(String tag) {
+
+ if (OfficeConstants.TAG_OFFICE_DOCUMENT.equals(tag))
+ return true;
+ else if (OfficeConstants.TAG_OFFICE_DOCUMENT_CONTENT.equals(tag))
+ return true;
+ else if (OfficeConstants.TAG_OFFICE_BODY.equals(tag))
+ return true;
+ else if (OfficeConstants.TAG_PARAGRAPH.equals(tag))
+ return true;
+ else if (OfficeConstants.TAG_HEADING.equals(tag))
+ return true;
+ else if (OfficeConstants.TAG_ORDERED_LIST.equals(tag))
+ return true;
+ else if (OfficeConstants.TAG_UNORDERED_LIST.equals(tag))
+ return true;
+ else if (OfficeConstants.TAG_LIST_ITEM.equals(tag))
+ return true;
+ else if (OfficeConstants.TAG_LIST_HEADER.equals(tag))
+ return true;
+ else if (OfficeConstants.TAG_SPAN.equals(tag))
+ return true;
+ else if (OfficeConstants.TAG_HYPERLINK.equals(tag))
+ return true;
+ else if (OfficeConstants.TAG_LINE_BREAK.equals(tag))
+ return true;
+ else if (OfficeConstants.TAG_SPACE.equals(tag))
+ return true;
+ else if (OfficeConstants.TAG_TAB_STOP.equals(tag))
+ return true;
+
+ return false;
+ }
+
+ public boolean canConvertAttribute(String tag, String attribute) {
+ return OfficeConstants.TAG_SPACE.equals(tag)
+ && OfficeConstants.ATTRIBUTE_SPACE_COUNT.equals(attribute);
+ }
+} \ No newline at end of file
diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/xslt/DocumentDeserializerImpl.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/xslt/DocumentDeserializerImpl.java
new file mode 100644
index 000000000..c8eb43fdf
--- /dev/null
+++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/xslt/DocumentDeserializerImpl.java
@@ -0,0 +1,189 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+package org.openoffice.xmerge.converter.xml.xslt;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Iterator;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.transform.Source;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.URIResolver;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+import javax.xml.transform.stream.StreamSource;
+
+import org.openoffice.xmerge.ConvertData;
+import org.openoffice.xmerge.ConvertException;
+import org.openoffice.xmerge.Document;
+import org.openoffice.xmerge.DocumentDeserializer;
+import org.openoffice.xmerge.converter.dom.DOMDocument;
+import org.openoffice.xmerge.util.Debug;
+import org.openoffice.xmerge.util.registry.ConverterInfo;
+
+/**
+ * Xslt implementation of {@code org.openoffice.xmerge.DocumentSerializer}
+ * for the {@link org.openoffice.xmerge.converter.xml.xslt.PluginFactoryImpl
+ * PluginFactoryImpl}.
+ *
+ * <p>The {@code serialize} method transforms the DOM document from the given
+ * {@code Document} object by means of a supplied Xsl Stylesheet.</p>
+ */
+public final class DocumentDeserializerImpl
+ implements DocumentDeserializer,URIResolver {
+
+ /** A {@code ConvertData} object assigned to this object. */
+ private final ConvertData cd;
+ private final PluginFactoryImpl pluginFactory;
+
+ /**
+ * Constructor that assigns the given {@code ConvertData} to this object.
+ *
+ * @param pf A {@code PluginFactoryImpl} object.
+ * @param cd A {@code ConvertData} object to read data for the conversion
+ * process by the {@code deserialize} method.
+ */
+ public DocumentDeserializerImpl(PluginFactoryImpl pf,ConvertData cd) {
+ this.cd = cd;
+ pluginFactory = pf;
+ }
+
+ /**
+ * This method performs the xslt transformation on the supplied
+ * {@code Document} and returns a {@code ByteArrayOutputStream} object.
+ *
+ * <p>Xslt transformation code.</p>
+ *
+ * @return A {@code ByteArrayOutputStream} object containing the result
+ * of the Xslt transformation.
+ */
+ public Document deserialize() throws ConvertException, IOException {
+ log("\nFound the XSLT deserializer");
+ Iterator<Object> enumerate = cd.getDocumentEnumeration();
+ org.w3c.dom.Document domDoc = null;
+ DOMDocument docOut = null;
+ ByteArrayOutputStream baos = null;
+ GenericOfficeDocument sxwDoc = new GenericOfficeDocument("output");
+ while (enumerate.hasNext()) {
+ docOut = (DOMDocument) enumerate.next();
+ }
+ if (docOut != null) {
+ try {
+ domDoc = docOut.getContentDOM();
+ baos = transform(domDoc);
+ sxwDoc.initContentDOM();
+ DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
+ dFactory.setNamespaceAware(true);
+ DocumentBuilder dBuilder = dFactory.newDocumentBuilder();
+ sxwDoc.setContentDOM(dBuilder.parse(new ByteArrayInputStream(baos.toByteArray())));
+
+ } catch (Exception e) {
+ System.out.println("The following error occurred:" + e);
+ }
+ }
+ return sxwDoc;
+ }
+
+ public Source resolve(String href, String base) throws TransformerException {
+ if (href != null) {
+ if (href.equals("javax.xml.transform.dom.DOMSource") || href.length() == 0) {
+ return null;
+ }
+ try {
+ ConverterInfo ci = pluginFactory.getConverterInfo();
+ String newhRef = "jar:" + ci.getJarName() + "!/" + href;
+ StreamSource sheetFile = new StreamSource(newhRef);
+ return sheetFile;
+ } catch (Exception e) {
+ System.out.println("\nException in Xslt Resolver " + e);
+ return null;
+ }
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * This method performs the xslt transformation on the supplied Dom Tree.
+ *
+ * <p>Xslt transformation code.</p>
+ */
+ private ByteArrayOutputStream transform(org.w3c.dom.Document xmlDoc){
+
+ log("\nTransforming...");
+ ConverterInfo ci = pluginFactory.getConverterInfo();
+ ByteArrayOutputStream baos= new ByteArrayOutputStream();
+ try{
+ DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
+ dFactory.setNamespaceAware(true);
+ DocumentBuilder dBuilder = dFactory.newDocumentBuilder();
+
+ String teststr = ci.getXsltDeserial();
+ teststr= teststr.substring(0,6);
+ org.w3c.dom.Document xslDoc=null;
+ if ((teststr.equals("http:/"))||(teststr.equals("file:/"))
+ ||(teststr.equals("jar://"))){
+ log(ci.getXsltDeserial());
+ xslDoc= dBuilder.parse(ci.getXsltDeserial());
+
+ }
+ else{
+ log(ci.getJarName()+"!/"+ci.getXsltDeserial());
+ xslDoc = dBuilder.parse(
+ "jar:"+ci.getJarName()+"!/"+ci.getXsltDeserial());
+ }
+
+
+ DOMSource xslDomSource = new DOMSource(xslDoc);
+ DOMSource xmlDomSource = new DOMSource(xmlDoc);
+
+ //call the transformer using the XSL, Source and Result dom.
+ TransformerFactory tFactory = TransformerFactory.newInstance();
+ tFactory.setURIResolver(this);
+ Transformer transformer = tFactory.newTransformer(xslDomSource);
+ transformer.transform(xmlDomSource,new StreamResult(baos));
+
+ log("\n** Transform Complete ***");
+
+ }
+ catch (StackOverflowError sOE){
+ System.out.println("\nERROR : Stack Overflow Error During Transformation\n Try increasing the stack size by passing the -Xss1m option to the JRE.");
+ throw sOE;
+ }
+ catch(Exception e){
+ System.out.println("An error occurred in the transformation : "+e);
+ }
+ return baos;
+ }
+
+ /**
+ * Sends message to the log object.
+ *
+ * @param str Debug message.
+ */
+ private void log(String str) {
+
+ Debug.log(Debug.TRACE, str);
+ }
+}
diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/xslt/DocumentMergerImpl.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/xslt/DocumentMergerImpl.java
new file mode 100644
index 000000000..b8d3ddfb9
--- /dev/null
+++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/xslt/DocumentMergerImpl.java
@@ -0,0 +1,80 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+package org.openoffice.xmerge.converter.xml.xslt;
+
+import org.w3c.dom.Document;
+
+import org.openoffice.xmerge.DocumentMerger;
+import org.openoffice.xmerge.MergeException;
+import org.openoffice.xmerge.ConverterCapabilities;
+import org.openoffice.xmerge.merger.DiffAlgorithm;
+import org.openoffice.xmerge.merger.Difference;
+import org.openoffice.xmerge.merger.NodeMergeAlgorithm;
+import org.openoffice.xmerge.merger.Iterator;
+import org.openoffice.xmerge.merger.diff.ParaNodeIterator;
+import org.openoffice.xmerge.merger.diff.IteratorLCSAlgorithm;
+import org.openoffice.xmerge.merger.merge.DocumentMerge;
+import org.openoffice.xmerge.merger.merge.CharacterBaseParagraphMerge;
+import org.openoffice.xmerge.util.Debug;
+
+/**
+ * Xslt implementation of {@code DocumentMerger} for the {@link
+ * org.openoffice.xmerge.converter.xml.xslt.PluginFactoryImpl PluginFactoryImpl}.
+ */
+public class DocumentMergerImpl implements DocumentMerger {
+
+ private final ConverterCapabilities cc_;
+ private final org.openoffice.xmerge.Document orig;
+
+ public DocumentMergerImpl(org.openoffice.xmerge.Document doc, ConverterCapabilities cc) {
+ cc_ = cc;
+ this.orig = doc;
+ }
+
+ public void merge(org.openoffice.xmerge.Document modifiedDoc) throws MergeException {
+
+ GenericOfficeDocument wdoc1 = (GenericOfficeDocument) orig;
+ GenericOfficeDocument wdoc2 = (GenericOfficeDocument) modifiedDoc;
+
+ Document doc1 = wdoc1.getContentDOM();
+ Document doc2 = wdoc2.getContentDOM();
+
+ Iterator i1 = new ParaNodeIterator(cc_, doc1.getDocumentElement());
+ Iterator i2 = new ParaNodeIterator(cc_, doc2.getDocumentElement());
+
+ DiffAlgorithm diffAlgo = new IteratorLCSAlgorithm();
+
+ // find out the paragraph level diffs
+ Difference[] diffTable = diffAlgo.computeDiffs(i1, i2);
+
+ if (Debug.isFlagSet(Debug.INFO)) {
+ Debug.log(Debug.INFO, "Diff Result: ");
+
+ for (int i = 0; i < diffTable.length; i++) {
+ Debug.log(Debug.INFO, diffTable[i].debug());
+ }
+ }
+
+ // merge the paragraphs
+ NodeMergeAlgorithm charMerge = new CharacterBaseParagraphMerge();
+ DocumentMerge docMerge = new DocumentMerge(cc_, charMerge);
+
+ docMerge.applyDifference(i1, i2, diffTable);
+ }
+}
diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/xslt/DocumentSerializerImpl.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/xslt/DocumentSerializerImpl.java
new file mode 100644
index 000000000..0d1e8d7ba
--- /dev/null
+++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/xslt/DocumentSerializerImpl.java
@@ -0,0 +1,266 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+package org.openoffice.xmerge.converter.xml.xslt;
+
+import org.w3c.dom.NodeList;
+import org.w3c.dom.Node;
+import org.w3c.dom.Element;
+import org.w3c.dom.*;
+
+import java.io.IOException;
+import java.io.ByteArrayOutputStream;
+import java.io.ByteArrayInputStream;
+
+import org.openoffice.xmerge.Document;
+import org.openoffice.xmerge.ConvertData;
+import org.openoffice.xmerge.ConvertException;
+import org.openoffice.xmerge.DocumentSerializer;
+import org.openoffice.xmerge.converter.dom.DOMDocument;
+import org.openoffice.xmerge.util.registry.ConverterInfo;
+import org.openoffice.xmerge.converter.xml.OfficeConstants;
+
+// Imported TraX classes
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.stream.StreamSource;
+import javax.xml.transform.stream.StreamResult;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.URIResolver;
+import javax.xml.transform.Source;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+
+/**
+ * Xslt implementation of {@code org.openoffice.xmerge.DocumentSerializer}
+ * for the {@link org.openoffice.xmerge.converter.xml.xslt.PluginFactoryImpl
+ * PluginFactoryImpl}.
+ *
+ * <p>The {@code serialize} method transforms the DOM document from the given
+ * {@code Document} object by means of a supplied Xsl Stylesheet.</p>
+ */
+
+public final class DocumentSerializerImpl
+ implements DocumentSerializer,OfficeConstants,URIResolver {
+
+ /** SXW {@code Document} object that this converter processes. */
+ private final GenericOfficeDocument sxwDoc;
+
+ private final PluginFactoryImpl pluginFactory;
+
+ /**
+ * Constructor.
+ *
+ * @param pf A {@code PluginFactoryImpl}.
+ * @param doc A SXW {@code Document} to be converted.
+ */
+ public DocumentSerializerImpl(PluginFactoryImpl pf,Document doc) {
+ pluginFactory=pf;
+ sxwDoc = (GenericOfficeDocument) doc;
+ }
+
+ /**
+ * Method to convert a {@code Document} with an xsl stylesheet.
+ *
+ * <p>It creates a {@code Document} object, which is then transformed with the
+ * Xslt processor. A {@code ConvertData} object is constructed and returned.</p>
+ *
+ * @return A {@code ConvertData} object.
+ *
+ * @throws ConvertException If any I/O error occurs.
+ * @throws IOException If any I/O error occurs.
+ */
+ public ConvertData serialize() throws ConvertException, IOException {
+ String docName = sxwDoc.getName();
+ org.w3c.dom.Document domDoc = sxwDoc.getContentDOM();
+ org.w3c.dom.Document metaDoc = sxwDoc.getMetaDOM();
+ org.w3c.dom.Document styleDoc = sxwDoc.getStyleDOM();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ ConvertData cd = new ConvertData();
+ Node offnode = domDoc.getDocumentElement();
+ if (!(offnode.getNodeName()).equals("office:document")) {
+ try {
+ DocumentBuilderFactory builderFactory = DocumentBuilderFactory
+ .newInstance();
+ DocumentBuilder builder = builderFactory.newDocumentBuilder();
+ DOMImplementation domImpl = builder.getDOMImplementation();
+ DocumentType docType = domImpl.createDocumentType(
+ "office:document",
+ "-//OpenOffice.org//DTD OfficeDocument 1.0//EN", null);
+ org.w3c.dom.Document newDoc = domImpl.createDocument(
+ "http://openoffice.org/2000/office", "office:document",
+ docType);
+
+ Element rootElement = newDoc.getDocumentElement();
+ rootElement.setAttribute("xmlns:office",
+ "http://openoffice.org/2000/office");
+ rootElement.setAttribute("xmlns:style",
+ "http://openoffice.org/2000/style");
+ rootElement.setAttribute("xmlns:text",
+ "http://openoffice.org/2000/text");
+ rootElement.setAttribute("xmlns:table",
+ "http://openoffice.org/2000/table");
+
+ rootElement.setAttribute("xmlns:draw",
+ "http://openoffice.org/2000/drawing");
+ rootElement.setAttribute("xmlns:fo",
+ "http://www.w3.org/1999/XSL/Format");
+ rootElement.setAttribute("xmlns:xlink",
+ "http://www.w3.org/1999/xlink");
+ rootElement.setAttribute("xmlns:dc",
+ "http://purl.org/dc/elements/1.1/");
+ rootElement.setAttribute("xmlns:meta",
+ "http://openoffice.org/2000/meta");
+ rootElement.setAttribute("xmlns:number",
+ "http://openoffice.org/2000/datastyle");
+ rootElement.setAttribute("xmlns:svg",
+ "http://www.w3.org/2000/svg");
+ rootElement.setAttribute("xmlns:chart",
+ "http://openoffice.org/2000/chart");
+ rootElement.setAttribute("xmlns:dr3d",
+ "http://openoffice.org/2000/dr3d");
+ rootElement.setAttribute("xmlns:math",
+ "http://www.w3.org/1998/Math/MathML");
+ rootElement.setAttribute("xmlns:form",
+ "http://openoffice.org/2000/form");
+ rootElement.setAttribute("xmlns:script",
+ "http://openoffice.org/2000/script");
+ rootElement.setAttribute("xmlns:config",
+ "http://openoffice.org/2001/config");
+ rootElement.setAttribute("office:class", "text");
+ rootElement.setAttribute("office:version", "1.0");
+
+ NodeList nodeList;
+ Node tmpNode;
+ Node rootNode = rootElement;
+ if (metaDoc != null) {
+ nodeList = metaDoc.getElementsByTagName(TAG_OFFICE_META);
+ if (nodeList.getLength() > 0) {
+ tmpNode = newDoc.importNode(nodeList.item(0), true);
+ rootNode.appendChild(tmpNode);
+ }
+ }
+ if (styleDoc != null) {
+ nodeList = styleDoc.getElementsByTagName(TAG_OFFICE_STYLES);
+ if (nodeList.getLength() > 0) {
+ tmpNode = newDoc.importNode(nodeList.item(0), true);
+ rootNode.appendChild(tmpNode);
+ }
+ }
+ nodeList = domDoc
+ .getElementsByTagName(TAG_OFFICE_AUTOMATIC_STYLES);
+ if (nodeList.getLength() > 0) {
+ tmpNode = newDoc.importNode(nodeList.item(0), true);
+ rootNode.appendChild(tmpNode);
+ }
+ nodeList = domDoc.getElementsByTagName(TAG_OFFICE_BODY);
+ if (nodeList.getLength() > 0) {
+ tmpNode = newDoc.importNode(nodeList.item(0), true);
+ rootNode.appendChild(tmpNode);
+ }
+ domDoc = newDoc;
+ } catch (Exception e) {
+ System.out
+ .println("\nAn Exception occurred with Xslt Serializer"
+ + e);
+ }
+
+ }
+
+ try {
+ baos = transform(domDoc);
+ } catch (Exception e) {
+ System.out.println("\n Error with Xslt\n");
+ }
+
+ DOMDocument resultDomDoc = (DOMDocument) pluginFactory
+ .createDeviceDocument(docName,
+ new ByteArrayInputStream(baos.toByteArray()));
+ cd.addDocument(resultDomDoc);
+ return cd;
+ }
+
+ public Source resolve(String href, String base)
+ throws TransformerException {
+ if (href != null) {
+ if (href.equals("javax.xml.transform.dom.DOMSource") || href.length() == 0) {
+ return null;
+ }
+ try {
+ ConverterInfo ci = pluginFactory.getConverterInfo();
+ String newhRef = "jar:" + ci.getJarName() + "!/" + href;
+ StreamSource sheetFile = new StreamSource(newhRef);
+ return sheetFile;
+ } catch (Exception e) {
+ System.out.println("\nException in Xslt Resolver " + e);
+ return null;
+ }
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * This method performs the xsl transformation on the supplied
+ * {@code Document} and returns a {@code DOMResult} object.
+ *
+ * <p>Xslt transformation code.</p>
+ *
+ * @return A {@code ByteArrayOutputStream} object containing the result
+ * of the Xslt transformation.
+ */
+ private ByteArrayOutputStream transform(org.w3c.dom.Document domDoc) {
+ ConverterInfo ci = pluginFactory.getConverterInfo();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ try {
+
+ DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
+ dFactory.setNamespaceAware(true);
+
+ DocumentBuilder dBuilder = dFactory.newDocumentBuilder();
+ String teststr = ci.getXsltSerial();
+
+ teststr = teststr.substring(0, 6);
+ org.w3c.dom.Document xslDoc = null;
+ if ((teststr.equals("http:/")) || (teststr.equals("file:/"))
+ || (teststr.equals("jar://"))) {
+ System.out.println(ci.getXsltSerial());
+ xslDoc = dBuilder.parse(ci.getXsltSerial());
+
+ } else {
+ xslDoc = dBuilder.parse(
+ "jar:" + ci.getJarName() + "!/" + ci.getXsltSerial());
+ }
+
+ DOMSource xslDomSource = new DOMSource(xslDoc);
+ DOMSource xmlDomSource = new DOMSource(domDoc);
+
+ //call the transformer using the XSL, Source and Result.
+ TransformerFactory tFactory = TransformerFactory.newInstance();
+ tFactory.setURIResolver(this);
+ Transformer transformer = tFactory.newTransformer(xslDomSource);
+
+ transformer.transform(xmlDomSource, new StreamResult(baos));
+ } catch (Exception e) {
+ System.out.println("An error occurred in the transformation : " + e);
+ }
+ return baos;
+ }
+}
diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/xslt/GenericOfficeDocument.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/xslt/GenericOfficeDocument.java
new file mode 100644
index 000000000..155278016
--- /dev/null
+++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/xslt/GenericOfficeDocument.java
@@ -0,0 +1,81 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+package org.openoffice.xmerge.converter.xml.xslt;
+
+import org.openoffice.xmerge.converter.xml.OfficeDocument;
+
+/**
+ * This class is an implementation of {@code OfficeDocument} for the generic
+ * office format.
+ */
+public class GenericOfficeDocument extends OfficeDocument {
+
+ /**
+ * Constructor with arguments to set {@code name}.
+ *
+ * @param name The name of the {@code Document}.
+ */
+ public GenericOfficeDocument(String name) {
+ super(name);
+ }
+
+ /**
+ * Constructor with arguments to set {@code name}, the {@code namespaceAware}
+ * flag, and the {@code validating} flag.
+ *
+ * @param name The name of the {@code Document}.
+ * @param namespaceAware The value of the {@code namespaceAware} flag.
+ * @param validating The value of the {@code validating} flag.
+ */
+ public GenericOfficeDocument(String name, boolean namespaceAware, boolean validating) {
+
+ super(name, namespaceAware, validating);
+ }
+
+ /**
+ * Returns the Office file extension for the generic format.
+ *
+ * @return The Office file extension for the generic format.
+ */
+ @Override
+ protected String getFileExtension() {
+ return "";
+ }
+
+ /**
+ * Returns the Office attribute for the generic format.
+ *
+ * @return The Office attribute for the generic format.
+ */
+ @Override
+ protected String getOfficeClassAttribute() {
+ return "";
+ }
+
+ /**
+ * Method to return the MIME type of the document.
+ *
+ * @return The document's MIME type.
+ */
+ @Override
+ protected String getDocumentMimeType() {
+ /* TODO: Determine the MIME-type from the input. */
+ return "";
+ }
+} \ No newline at end of file
diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/xslt/PluginFactoryImpl.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/xslt/PluginFactoryImpl.java
new file mode 100644
index 000000000..67432ac11
--- /dev/null
+++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/xslt/PluginFactoryImpl.java
@@ -0,0 +1,174 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+package org.openoffice.xmerge.converter.xml.xslt;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Iterator;
+import java.util.Properties;
+
+import org.openoffice.xmerge.ConvertData;
+import org.openoffice.xmerge.ConverterCapabilities;
+import org.openoffice.xmerge.Document;
+import org.openoffice.xmerge.DocumentDeserializer;
+import org.openoffice.xmerge.DocumentDeserializerFactory;
+import org.openoffice.xmerge.DocumentMerger;
+import org.openoffice.xmerge.DocumentMergerFactory;
+import org.openoffice.xmerge.DocumentSerializer;
+import org.openoffice.xmerge.DocumentSerializerFactory;
+import org.openoffice.xmerge.PluginFactory;
+import org.openoffice.xmerge.converter.dom.DOMDocument;
+import org.openoffice.xmerge.util.registry.ConverterInfo;
+
+/**
+ * Xslt implementation of the {@code PluginFactory}.
+ *
+ * <p>This encapsulates conversion of StarWriter XML format to and from a
+ * supported format.</p>
+ *
+ * <p>The superclass produces a particular {@link org.openoffice.xmerge.Document
+ * Document} object, i.e. {@link
+ * org.openoffice.xmerge.converter.xml.sxw.SxwDocument xwDocument} that the
+ * converters in this class work with. Thus, this class only implements the
+ * methods that produces the converters, i.e. {@link
+ * org.openoffice.xmerge.DocumentSerializer DocumentSerializer} and {@link
+ * org.openoffice.xmerge.DocumentDeserializer DocumentDeserializer}</p>
+ */
+public final class PluginFactoryImpl extends PluginFactory
+ implements DocumentDeserializerFactory, DocumentSerializerFactory, DocumentMergerFactory
+{
+
+ public PluginFactoryImpl (ConverterInfo ci) {
+ super(ci);
+ }
+
+ /** ConverterCapabilities object for this type of conversion. */
+ private static final ConverterCapabilities converterCap =
+ new ConverterCapabilitiesImpl();
+
+ /**
+ * Returns an instance of {@code DocumentSerializerImpl}, which is an
+ * implementation of the {@code DocumentSerializer} interface.
+ *
+ * @param doc {@code Document} object to be converted/serialized.
+ *
+ * @return A {@code DocumentSerializerImpl} object.
+ */
+ public DocumentSerializer createDocumentSerializer(Document doc) {
+ return new DocumentSerializerImpl(this,doc);
+ }
+
+ /**
+ * Returns an instance of <code>DocumentDeserializerImpl</code>,
+ * which is an implementation of the <code>DocumentDeserializer</code>
+ * interface.
+ *
+ * @param cd {@code ConvertData} object.
+ *
+ * @return A {@code DocumentDeserializerImpl} object.
+ */
+ public DocumentDeserializer createDocumentDeserializer(ConvertData cd) {
+ return new DocumentDeserializerImpl(this,cd);
+ }
+
+ @Override
+ public org.openoffice.xmerge.Document createDeviceDocument(String str,
+ java.io.InputStream inputStream) throws java.io.IOException {
+ String ext = this.getDeviceFileExtension();
+ DOMDocument domDoc = new DOMDocument(str,ext);
+ domDoc.read(inputStream);
+ return domDoc;
+ }
+
+ @Override
+ public Document createOfficeDocument(String name, InputStream is)
+ throws IOException {
+
+ // read zipped XML stream
+ GenericOfficeDocument doc = new GenericOfficeDocument(name);
+ doc.read(is);
+ return doc;
+ }
+
+ @Override
+ public Document createOfficeDocument(String name, InputStream is, boolean isZip)
+ throws IOException {
+
+ // read zipped XML stream
+ GenericOfficeDocument doc = new GenericOfficeDocument(name);
+ doc.read(is,isZip);
+ return doc;
+ }
+
+ /**
+ * Returns a {@code String} containing the file extension of a
+ * {@code Document}.
+ *
+ * <p>This method uses a properties file to determine a mapping from the
+ * device mime in the {@code ConverterInfo} to a particular file extension.
+ * If a mapping is not specified, the default is ".txt".</p>
+ *
+ * @return The file extension of a {@code Document}.
+ */
+ private String getDeviceFileExtension() {
+ Class<? extends PluginFactoryImpl> c = this.getClass();
+ InputStream is = c.getResourceAsStream("XsltPlugin.properties");
+ Properties props = new Properties();
+ String ext = ".txt";
+ String mimeType = null;
+ ConverterInfo ci = this.getConverterInfo();
+ Iterator<String> enumerate = ci.getDeviceMime();
+ while (enumerate.hasNext()) {
+ mimeType = enumerate.next();
+ }
+ try {
+ props.load(is);
+
+ String info = mimeType != null ? props.getProperty(mimeType) : null;
+ if (info != null) {
+ ext = info;
+ }
+ } catch (Exception e) {
+
+ // It is okay for the property file to not exist.
+ } finally {
+ try {
+ if (is != null) {
+ is.close();
+ }
+ } catch (IOException ex) {
+ }
+ }
+ return ext;
+ }
+
+ /**
+ * Returns an instance of {@code DocumentMergerImpl}, which is an
+ * implementation of the {@code DocumentMerger} interface.
+ *
+ * @param doc {@code Document} to merge.
+ *
+ * @return A {@code DocumentMergerImpl} object.
+ */
+ public DocumentMerger createDocumentMerger(Document doc) {
+ ConverterCapabilities cc = converterCap;
+ DocumentMergerImpl merger = new DocumentMergerImpl(doc, cc);
+ return merger;
+ }
+}
diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/xslt/XsltPlugin.properties b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/xslt/XsltPlugin.properties
new file mode 100644
index 000000000..9e1027d63
--- /dev/null
+++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/xslt/XsltPlugin.properties
@@ -0,0 +1,27 @@
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This file incorporates work covered by the following license notice:
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed
+# with this work for additional information regarding copyright
+# ownership. The ASF licenses this file to you under the Apache
+# License, Version 2.0 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.apache.org/licenses/LICENSE-2.0 .
+#
+# x-no-translate
+
+#
+# XsltPlugin.properties
+#
+
+#This file allows users to specify the mime-type to file extension mappings
+
+# e.g text/html=.html
+text/html=.html
diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/xslt/package-info.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/xslt/package-info.java
new file mode 100644
index 000000000..7351b158a
--- /dev/null
+++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/converter/xml/xslt/package-info.java
@@ -0,0 +1,58 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+/**
+ * Provides the tools for doing the conversion of StarWriter XML to and from
+ * supported formats, through the use of an XSLT transformation.
+ *
+ * <p>It follows the {@code org.openoffice.xmerge} framework for the conversion
+ * process.</p>
+ *
+ * <p>This converter does not currently support merge.</p>
+ *
+ * <h2>XSLT Transformation</h2>
+ *
+ * <p>The converter makes use of one or more XSLT style sheets, which are used
+ * in the DocumentSerializer and DocumentDeserializer, to perform the actual
+ * translations. The location of these stylesheets is extracted from the
+ * {@link org.openoffice.xmerge.util.registry.ConverterInfo ConverterInfo} data
+ * structure, and are specified using the optional converter-XSLT-serialize and
+ * converter-XSLT-deserialize tags in a plug-ins converter.xml file. Please
+ * refer to the SDK document for more information about how to implement a
+ * Plug-in Configuration XML File for a specific plug-in.
+ * A sample OpenOffice to HTML stylesheet and HTML to OpenOffice stylesheet, has
+ * been provided as a sample implementation. The converter also makes use of an
+ * XsltPlugin.properties file, which may be edited by the user to provide
+ * MIME-TYPE to file extension mappings. This file is used by the
+ * {@link org.openoffice.xmerge.converter.xml.xslt.PluginFactoryImpl
+ * getDeviceFileExtension} method.</p>
+ *
+ * <h2>TODO list</h2>
+ *
+ * <ol>
+ * <li>Expand XSLT style sheets to support more office/HTML capabilities</li>
+ * <li>Add support for certain character codes, such as {@literal &} which
+ * currently causes the transformer to break.</li>
+ * <li>Change the DocumentDeserializer transformer, so that the DOMResult is
+ * serialized using the xalan serializer and create an SxwDocument from the
+ * result</li>
+ * </ol>
+ *
+ * @see org.openoffice.xmerge.util.registry.ConverterInfo
+ */
+package org.openoffice.xmerge.converter.xml.xslt;