diff options
Diffstat (limited to 'xmerge/source')
117 files changed, 19878 insertions, 0 deletions
diff --git a/xmerge/source/bridge/XMergeBridge.component b/xmerge/source/bridge/XMergeBridge.component new file mode 100644 index 000000000..bea9a8d75 --- /dev/null +++ b/xmerge/source/bridge/XMergeBridge.component @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . + --> + +<component loader="com.sun.star.loader.Java2" + xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="XMergeBridge$_XMergeBridge"> + <service name="com.sun.star.documentconversion.XMergeBridge"/> + </implementation> +</component> diff --git a/xmerge/source/bridge/java/XMergeBridge.java b/xmerge/source/bridge/java/XMergeBridge.java new file mode 100644 index 000000000..6506326c4 --- /dev/null +++ b/xmerge/source/bridge/java/XMergeBridge.java @@ -0,0 +1,579 @@ +/* + * 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 . + */ + +/** You can find more + * information on the following web page: + * https://api.libreoffice.org/docs/common/ref/com/sun/star/module-ix.html + */ + +/*Java Uno Helper Classes*/ +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.net.URI; +import java.util.Iterator; + +import javax.xml.parsers.ParserConfigurationException; + +import org.openoffice.xmerge.Convert; +import org.openoffice.xmerge.ConvertData; +import org.openoffice.xmerge.ConverterFactory; +import org.openoffice.xmerge.Document; +import org.openoffice.xmerge.converter.xml.OfficeDocument; +import org.openoffice.xmerge.util.registry.ConverterInfo; +import org.openoffice.xmerge.util.registry.ConverterInfoMgr; +import org.openoffice.xmerge.util.registry.ConverterInfoReader; + +import com.sun.star.comp.loader.FactoryHelper; +import com.sun.star.frame.XConfigManager; +import com.sun.star.io.XInputStream; +import com.sun.star.io.XOutputStream; +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.lang.XServiceInfo; +import com.sun.star.lang.XServiceName; +import com.sun.star.lang.XSingleServiceFactory; +import com.sun.star.lang.XTypeProvider; +import com.sun.star.lib.uno.adapter.XInputStreamToInputStreamAdapter; +import com.sun.star.lib.uno.adapter.XOutputStreamToOutputStreamAdapter; +import com.sun.star.registry.XRegistryKey; +import com.sun.star.uno.AnyConverter; +import com.sun.star.uno.Type; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.xml.XExportFilter; +import com.sun.star.xml.XImportFilter; +import com.sun.star.xml.sax.InputSource; +import com.sun.star.xml.sax.XDocumentHandler; +import com.sun.star.xml.sax.XParser; + +/** This outer class provides an inner class to implement the service + * description and a method to instantiate the + * component on demand (__getServiceFactory()). + */ +public class XMergeBridge { + + private static XMultiServiceFactory xMSF; + private static XInputStream xInStream =null; + private static XOutputStream xOutStream=null; + private static String udJarPath=null; + private static XOutputStream xos = null; + private static String offMime=null; + private static String sdMime=null; + private static String sFileName=null; + private static String sURL=""; + + /** This inner class provides the component as a concrete implementation + * of the service description. It implements the needed interfaces. + */ + public static class _XMergeBridge implements + XImportFilter, + XExportFilter, + XServiceName, + XServiceInfo, + XDocumentHandler, + XTypeProvider { + + /** The component will be registered under this name. + */ + private static final String __serviceName = "com.sun.star.documentconversion.XMergeBridge"; + + public com.sun.star.uno.Type[] getTypes() { + Type[] typeReturn = {}; + + try { + typeReturn = new Type[] { + new Type( XTypeProvider.class ), + new Type( XImportFilter.class ), + new Type( XExportFilter.class ), + new Type( XServiceName.class ), + new Type( XServiceInfo.class ) }; + } + catch( Exception exception ) { + + } + + return typeReturn; + } + + private String getFileName(String origName) + { + String name; + if (origName !=null) + { + if(origName.equalsIgnoreCase("")) + name = "OutFile"; + else { + if (origName.lastIndexOf("/")>=0){ + origName=origName.substring(origName.lastIndexOf("/")+1,origName.length()); + } + if (origName.lastIndexOf(".")>=0){ + name = origName.substring(0, origName.lastIndexOf(".")); + } + else{ + name=origName; + } + } + } + else{ + name = "OutFile"; + } + return name; + } + + public boolean importer(com.sun.star.beans.PropertyValue[] aSourceData, + com.sun.star.xml.sax.XDocumentHandler xDocHandler, + String[] msUserData) throws com.sun.star.uno.RuntimeException { + + sFileName=""; + sURL=""; + udJarPath=msUserData[1]; + offMime =msUserData[4]; + sdMime = msUserData[5]; + com.sun.star.io.XInputStream xis=null; + com.sun.star.beans.PropertyValue[] pValue = aSourceData; + + for (int i = 0 ; i < pValue.length; i++) + { + + try{ + if (pValue[i].Name.equals("InputStream")){ + xis=(com.sun.star.io.XInputStream)AnyConverter.toObject(new Type(com.sun.star.io.XInputStream.class), pValue[i].Value); + } + if (pValue[i].Name.equals("FileName")){ + sFileName=(String)AnyConverter.toObject(new Type(String.class), pValue[i].Value); + } + + } + catch(com.sun.star.lang.IllegalArgumentException AnyExec){ + System.out.println("\nIllegalArgumentException "+AnyExec); + } + + } + + try{ + + Object xCfgMgrObj=xMSF.createInstance("com.sun.star.config.SpecialConfigManager"); + XConfigManager xCfgMgr = UnoRuntime.queryInterface( + XConfigManager.class , xCfgMgrObj ); + String PathString=xCfgMgr.substituteVariables("$(progurl)" ); + PathString= PathString.concat("/"); + udJarPath= PathString.concat(udJarPath); + + Object xPipeObj=xMSF.createInstance("com.sun.star.io.Pipe"); + xInStream = UnoRuntime.queryInterface( + XInputStream.class , xPipeObj ); + xOutStream = UnoRuntime.queryInterface( + XOutputStream.class , xPipeObj ); + convert (xis,xOutStream,false,udJarPath,sFileName,offMime,sdMime); + Object xSaxParserObj=xMSF.createInstance("com.sun.star.xml.sax.Parser"); + + XParser xParser = UnoRuntime.queryInterface( + XParser.class , xSaxParserObj ); + xOutStream.closeOutput(); + InputSource aInput = new InputSource(); + if (sFileName==null){ + sFileName=""; + } + aInput.sSystemId = sFileName; + aInput.aInputStream =xInStream; + xParser.setDocumentHandler ( xDocHandler ); + + xParser.parseStream ( aInput ); + xOutStream.closeOutput(); + xInStream.closeInput(); + + } + catch (IOException e){ + return false; + } + catch (Exception e){ + return false; + } + return true; + } + + public boolean exporter(com.sun.star.beans.PropertyValue[] aSourceData, + String[] msUserData) throws com.sun.star.uno.RuntimeException{ + + sFileName=null; + sURL=null; + udJarPath=msUserData[1]; + offMime =msUserData[4]; + sdMime = msUserData[5]; + + com.sun.star.beans.PropertyValue[] pValue = aSourceData; + for (int i = 0 ; i < pValue.length; i++) + { + + try{ + if (pValue[i].Name.equals("OutputStream")){ + xos=(com.sun.star.io.XOutputStream)AnyConverter.toObject(new Type(com.sun.star.io.XOutputStream.class), pValue[i].Value); + } + + if (pValue[i].Name.equals("FileName")){ + sFileName=(String)AnyConverter.toObject(new Type(String.class), pValue[i].Value); + } + + if (pValue[i].Name.equals("URL")){ + sURL=(String)AnyConverter.toObject(new Type(String.class), pValue[i].Value); + } + } + catch(com.sun.star.lang.IllegalArgumentException AnyExec){ + System.out.println("\nIllegalArgumentException "+AnyExec); + } + } + + if (sURL==null){ + sURL=""; + } + + try{ + + Object xCfgMgrObj=xMSF.createInstance("com.sun.star.config.SpecialConfigManager"); + XConfigManager xCfgMgr = UnoRuntime.queryInterface( + XConfigManager.class , xCfgMgrObj ); + + String PathString=xCfgMgr.substituteVariables("$(progurl)" ); + PathString= PathString.concat("/"); + udJarPath= PathString.concat(udJarPath); + + Object xPipeObj=xMSF.createInstance("com.sun.star.io.Pipe"); + xInStream = UnoRuntime.queryInterface( + XInputStream.class , xPipeObj ); + xOutStream = UnoRuntime.queryInterface( + XOutputStream.class , xPipeObj ); + } + catch (Exception e){ + System.out.println("Exception "+e); + return false; + } + + return true; + } + + private String needsMask(String origString){ + if (origString.contains("&")) { + origString = origString.replace("&","&"); + } + if (origString.contains("\"")) { + origString = origString.replace("\"","""); + } + if (origString.contains("<")) { + origString = origString.replace("<","<"); + } + if (origString.contains(">")) { + origString = origString.replace(">",">"); + } + return origString; + } + + public void startDocument () { + } + + public void endDocument()throws com.sun.star.uno.RuntimeException + { + + try{ + xOutStream.closeOutput(); + convert (xInStream,xos,true,udJarPath,sURL,offMime,sdMime); + + } + catch (IOException e){ + throw new com.sun.star.uno.RuntimeException(e); + + } + catch (Exception e){ + throw new com.sun.star.uno.RuntimeException(e); + + } + } + + public void startElement (String str, com.sun.star.xml.sax.XAttributeList xattribs) + { + + str="<".concat(str); + if (xattribs !=null) + { + str= str.concat(" "); + int len=xattribs.getLength(); + for (short i=0;i<len;i++) + { + str=str.concat(xattribs.getNameByIndex(i)); + str=str.concat("=\""); + str=str.concat(needsMask(xattribs.getValueByIndex(i))); + str=str.concat("\" "); + } + } + str=str.concat(">"); + + try{ + xOutStream.writeBytes(str.getBytes("UTF-8")); + } + catch (Exception e){ + System.out.println("\n"+e); + } + + } + + public void endElement(String str){ + + str="</".concat(str); + str=str.concat(">"); + try{ + xOutStream.writeBytes(str.getBytes("UTF-8")); + + } + catch (Exception e){ + System.out.println("\n"+e); + } + + } + public void characters(String str){ + str=needsMask(str); + try{ + xOutStream.writeBytes(str.getBytes("UTF-8")); + } + catch (Exception e){ + System.out.println("\n"+e); + } + + } + + public void ignorableWhitespace(String str){ + + } + public void processingInstruction(String aTarget, String aData){ + + } + + public void setDocumentLocator(com.sun.star.xml.sax.XLocator xLocator){ + + } + + private static void close(FileOutputStream c) throws IOException { + if (c == null) return; + c.close(); + } + + private void convert (com.sun.star.io.XInputStream xml,com.sun.star.io.XOutputStream device, + boolean convertFromOffice,String pluginUrl,String FileName,String offMime,String sdMime) throws com.sun.star.uno.RuntimeException, IOException { + + String jarName = pluginUrl; + String name= getFileName(FileName); + + Iterator<ConverterInfo> ciEnum= null; + + XInputStreamToInputStreamAdapter xis =new XInputStreamToInputStreamAdapter(xml); + + XOutputStreamToOutputStreamAdapter newxos =new XOutputStreamToOutputStreamAdapter(device); + + /* make sure newxos and xis get closed */ + try{ + try{ + ConverterInfoReader cir = new ConverterInfoReader(jarName,false); + ciEnum =cir.getConverterInfoEnumeration(); + } + catch (ParserConfigurationException pexc){ + System.out.println("Error:"+pexc); + } + catch ( org.xml.sax.SAXException pexc){ + System.out.println("Error:"+pexc); + } + catch(Exception e){ + System.out.println("Error:"+e); + } + ConverterInfoMgr. removeByJar(jarName); + if (convertFromOffice) + { + + try { + + //Check to see if jar contains a plugin Impl + + ConverterInfoMgr.addPlugIn(ciEnum); + ConverterFactory cf = new ConverterFactory(); + + Convert cv = cf.getConverter(ConverterInfoMgr.findConverterInfo(sdMime,offMime),false); + if (cv == null) { + System.out.println("\nNo plug-in exists to convert from <staroffice/sxw> to <specified format> "); + + } + else + { + cv.addInputStream(name,xis,false); + ConvertData dataOut = cv.convert(); + + Iterator<Object> docEnum = dataOut.getDocumentEnumeration(); + + if (docEnum.hasNext()){ + Document docOut = (Document)docEnum.next(); + docOut.write(newxos); + + newxos.flush(); + newxos.close(); + newxos = null; + + int i=1; + while (docEnum.hasNext() && sURL.startsWith("file:")) { + + URI uri=new URI(sURL); + String newFileName= getPath(uri); + + File newFile; + if (newFileName.lastIndexOf(".")!=-1){ + newFile =new File(newFileName.substring(0,newFileName.lastIndexOf("."))+String.valueOf(i)+newFileName.substring(newFileName.lastIndexOf("."))); + } + else{ + newFile =new File(newFileName.concat(String.valueOf(i))); + } + + FileOutputStream fos = null; + try { + fos = new FileOutputStream(newFile); + docOut = (Document)docEnum.next(); + docOut.write(fos); + fos.flush(); + } finally { + close(fos); + } + i++; + + } + + } + } + ConverterInfoMgr.removeByJar(jarName); + } + catch (Exception ex1) { + IOException ex2 = new IOException(); + ex2.initCause(ex1); + throw ex2; + } + } + else{ + + try { + //Check to see if jar contains a plugin Impl + ConverterInfoMgr.addPlugIn(ciEnum); + ConverterFactory cf = new ConverterFactory(); + Convert cv = cf.getConverter(ConverterInfoMgr.findConverterInfo(sdMime,offMime),true); + if (cv == null) { + System.out.println("\nNo plug-in exists to convert to <staroffice/sxw> from <specified format>"); + } + else + { + + cv.addInputStream(name,xis,false); + ConvertData dataIn = cv.convert(); + Iterator<Object> docEnum = dataIn.getDocumentEnumeration(); + while (docEnum.hasNext()) { + OfficeDocument docIn = (OfficeDocument)docEnum.next(); + + docIn.write(newxos,false); + } + newxos.close(); + newxos = null; + } + ConverterInfoMgr.removeByJar(jarName); + } + catch (StackOverflowError sOE){ + System.out.println("\nERROR : Stack Overflow. \n Increase of the JRE by adding the following line to the end of the javarc file \n \"-Xss1m\"\n"); + } + catch (Exception ex1) { + IOException ex2 = new IOException(); + ex2.initCause(ex1); + throw ex2; + } + + } + } + finally{ + if (newxos != null){ + try { + newxos.flush(); + } catch (IOException e) { + } + newxos.close(); + } + xis.close(); + } + } + + private String getPath(URI uri){ + String path = uri.getPath(); + String opSys=System.getProperty("os.name"); + if(opSys.contains("Windows")){ + path= path.replace('/','\\'); + path = path.substring(1); + } + return path; + } + + // Implement methods from interface XTypeProvider + public byte[] getImplementationId() { + return new byte[0]; + } + + // Implement method from interface XServiceName + public String getServiceName() { + return __serviceName; + } + + // Implement methods from interface XServiceInfo + public boolean supportsService(String stringServiceName) { + return stringServiceName.equals( __serviceName ); + } + + public String getImplementationName() { + return _XMergeBridge.class.getName(); + } + + public String[] getSupportedServiceNames() { + String[] stringSupportedServiceNames = { __serviceName }; + return stringSupportedServiceNames; + } + } + + /** + * Returns a factory for creating the service. + * This method is called by the <code>JavaLoader</code> + * + * @return returns a <code>XSingleServiceFactory</code> for creating the + * component + * + * @param implName the name of the implementation for which a + * service is desired + * @param multiFactory the service manager to be used if needed + * @param regKey the registryKey + * + * @see com.sun.star.comp.loader.JavaLoader + */ + public static XSingleServiceFactory __getServiceFactory(String implName, + XMultiServiceFactory multiFactory, + XRegistryKey regKey) { + xMSF= multiFactory; + XSingleServiceFactory xSingleServiceFactory = null; + if (implName.equals(_XMergeBridge.class.getName()) ) { + xSingleServiceFactory = FactoryHelper.getServiceFactory(_XMergeBridge.class, + _XMergeBridge.__serviceName, + multiFactory, + regKey); + } + + return xSingleServiceFactory; + } +} diff --git a/xmerge/source/bridge/manifest.mf b/xmerge/source/bridge/manifest.mf new file mode 100644 index 000000000..1c63d9e6d --- /dev/null +++ b/xmerge/source/bridge/manifest.mf @@ -0,0 +1,2 @@ +RegistrationClassName: XMergeBridge +UNO-Type-Path: diff --git a/xmerge/source/xmerge/converter.dtd b/xmerge/source/xmerge/converter.dtd new file mode 100644 index 000000000..f7c09f5c2 --- /dev/null +++ b/xmerge/source/xmerge/converter.dtd @@ -0,0 +1,85 @@ +<!-- + * 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 . +--> + +<!-- converter.dtd + + Author: Brian Cameron + + This DTD file is provided for documentation and development + purposes, the converter does not actually validate the + converter.xml files that it processes. Plug-ins will not + work properly, though, if the converter.xml does not + conform to this DTD specification. --> + +<!-- The root node, converters, must contain one or more + converter nodes, each corresponds to a converter plug-in. --> + +<!ELEMENT converters (converter)+> + +<!-- The converter node must contain two elements: + type - The convert-from mime-type. + version - The version of the plug-in. + + Each converter node must contain these child nodes: + converter-display-name - Name of the converter + converter-class-impl - The PluginFactory implementation for + the plugin + converter-targets - Can be one or more of these nodes. Each + contains only a "type" element. This + "type" element specifies the convert-to + mime-type. + + Each converter node may contain these child nodes: + converter-description - Descriptive description of the plug-in. + converter-vendor - Plug-in vendor name + converter-xslt-serialize - The URL of the xsl stylesheet for + serialization. This stylesheet must + exist if the xslt plugin implementation + is to be used. It is assumed that the + plug-in specified via converter-class-impl + will make use of this value. + converter-xslt-deserialize - The URL of the xsl stylesheet for + deserialization. This stylesheet must + exist if the xslt plugin implementation + is to be used. It is assumed that the + plug-in specified via converter-class-impl + will make use of this value. + --> + +<!ELEMENT converter (converter-display-name, + converter-description?, + converter-vendor?, + converter-class-impl, + converter-xslt-serialize?, + converter-xslt-deserialize?, + converter-target+)> + +<!ATTLIST converter type CDATA #REQUIRED> +<!ATTLIST converter version CDATA #REQUIRED> + +<!ELEMENT converter-display-name (#PCDATA)> +<!ELEMENT converter-description (#PCDATA)> +<!ELEMENT converter-vendor (#PCDATA)> +<!ELEMENT converter-class-impl (#PCDATA)> +<!ELEMENT converter-xslt-serialize (#PCDATA)> +<!ELEMENT converter-xslt-deserialize (#PCDATA)> + +<!ELEMENT converter-target EMPTY> + +<!ATTLIST converter-target type CDATA #REQUIRED> + diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/Convert.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/Convert.java new file mode 100644 index 000000000..728aa05ae --- /dev/null +++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/Convert.java @@ -0,0 +1,233 @@ +/* + * 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; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Iterator; + +import org.openoffice.xmerge.util.registry.ConverterInfo; + +/** + * The {@code Convert} class manages a conversion from one mime-type to another. + * + * <p>The {@code ConvertFactory} is responsible for returning the appropriate + * {@code Convert} class for a specified conversion. This class is responsible + * for all interactions with the {@code PluginFactory} implementation.</p> + * + * @see ConverterFactory + * @see PluginFactory + * @see org.openoffice.xmerge.util.registry.ConverterInfo + */ +public class Convert implements Cloneable { + + /** ConvertInfo that corresponds to the from-mime/to-mime conversion. */ + private final ConverterInfo ci; + + /** + * {@code true} if converting to the Office format, {@code false} if + * converting to the device format. + */ + private final boolean toOffice; + + /** Holds the convert input data. */ + private final ConvertData inputCD = new ConvertData(); + + /** + * Construct a Convert class with specified {@code ConvertInfo} registry + * information. + * + * @param ci A {@code ConvertInfo} object containing registry + * information corresponding to a specific plug-in. + * @param toOffice {@code true} if converting to the Office format, + * {@code false} if converting to the device format. + */ + public Convert(ConverterInfo ci, boolean toOffice) { + this.ci = ci; + this.toOffice = toOffice; + } + + /** + * Adds an {@code InputStream} to be used as input by the {@code Convert} + * class. + * + * <p>It is possible that many files need to be converted into a single + * output {@code Document}, so this function may be called more than one + * time. It is the plug-in's responsibility to know how to handle the input. + * </p> + * + * @param name The name corresponding to the {@code InputStream}. + * @param is {@code InputStream} to be used as input. + * + * @throws IOException If any I/O error occurs. + */ + public void addInputStream(String name, InputStream is) + throws IOException { + + Document inputDoc; + + if (toOffice) { + inputDoc = ci.getPluginFactory().createDeviceDocument(name, is); + } else { + inputDoc = ci.getPluginFactory().createOfficeDocument(name, is); + } + inputCD.addDocument(inputDoc); + } + + /** + * Adds an {@code InputStream} to be used as input by the {@code Convert} + * class. + * + * <p>It is possible that many files need to be converted into a single + * output {@code Document}, so this function may be called more than one + * time. It is the plug-in's responsibility to know how to handle the input. + * </p> + * + * @param name The name corresponding to the {@code InputStream}. + * @param is {@code InputStream} to be used as input. + * @param isZip {@code boolean} to identify that incoming stream is * zipped. + * + * @throws IOException If any I/O error occurs. + */ + public void addInputStream(String name, InputStream is,boolean isZip) + throws IOException { + + Document inputDoc; + + if (toOffice) { + inputDoc = ci.getPluginFactory().createDeviceDocument(name, is); + } else { + inputDoc = ci.getPluginFactory().createOfficeDocument(name, is, isZip); + } + inputCD.addDocument(inputDoc); + } + + + /** + * Returns a {@code DocumentMerger} for the given {@code Document}. + * + * @param origDoc The {@code Document} were later changes will be merged to. + * + * @return The {@code DocumentMerger} object for the given document. + * + * @throws IOException If any I/O error occurs. + */ + public DocumentMerger getDocumentMerger(Document origDoc) throws IOException { + DocumentMergerFactory myDocMergerFactory = ci.getDocMergerFactory(); + DocumentMerger merger = myDocMergerFactory.createDocumentMerger(origDoc); + return merger; + } + + /** + * Resets the input queue, so that the user can use this class to perform + * another conversion. + * + * <p>This causes the {@code addInputStream} method to accept input for the + * next conversion.</p> + */ + private void reset() { + inputCD.reset(); + } + + /** + * Clones a {@code Convert} object so another Convert object can do the same + * conversion. + * + * <p>{@code InputStream} objects passed in via calls to the + * {@code addInputStream} method are not copied.</p> + * + * @return The cloned {@code Convert} object. + */ + @Override + public Object clone() { + + Convert aClone = null; + + try { + aClone = (Convert) super.clone(); + aClone.reset(); + } + catch (CloneNotSupportedException e) { + System.out.println("Convert clone could not be created"); + } + return aClone; + } + + /** + * Convert the input specified in calls to the {@code addInputStream} + * method to the output format specified by this {@code Convert} class. + * + * @return The output data. + * + * @throws ConvertException If any conversion error occurs. + * @throws IOException If any I/O error occurs. + */ + public ConvertData convert() throws ConvertException, IOException { + + ConvertData dataOut = new ConvertData(); + + if (toOffice) { + + // From device format to Office format + + DocumentDeserializerFactory myDocDeserializerFactory = + ci.getDocDeserializerFactory(); + DocumentDeserializer deser = + myDocDeserializerFactory.createDocumentDeserializer(inputCD); + Document deviceDoc = deser.deserialize(); + + + dataOut.addDocument(deviceDoc); + return dataOut; + + } else { + + // From Office format to device format + + DocumentSerializerFactory myDocSerializerFactory = + ci.getDocSerializerFactory(); + + Iterator<Object> e = inputCD.getDocumentEnumeration(); + + Document doc = (Document) e.next(); + DocumentSerializer ser = myDocSerializerFactory.createDocumentSerializer(doc); + dataOut = ser.serialize(); + + return dataOut; + } + } + + /** + * Returns the appropriate "Office" {@code Document} object for + * this plug-in. + * + * @param name The name of the {@code Document} to create. + * @param is The {@code InputStream} corresponding to the + * {@code Document} to create. + * + * @return The appropriate "Office" {@code Document} object for + * this plug-in. + * + * @throws IOException If any I/O error occurs. + */ + public Document getOfficeDocument(String name, InputStream is) + throws IOException { + return ci.getPluginFactory().createOfficeDocument(name, is); + } +}
\ No newline at end of file diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/ConvertData.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/ConvertData.java new file mode 100644 index 000000000..d8f276bc3 --- /dev/null +++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/ConvertData.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; + +import java.util.ArrayList; +import java.util.Iterator; + +/** + * {@code ConvertData} is used as a container for passing {@code Document} + * objects in and out of the {@code Convert} class. + * + * <p>The {@code ConvertData} contains a {@code String} name and a + * {@code Vector} of {@code Document} objects.</p> + */ +public class ConvertData { + + /** + * Vector of {@code Document} objects. + */ + private final ArrayList<Object> v = new ArrayList<Object>(); + + /** + * Name of the {@code ConvertData} object. + */ + private String name; + + /** + * Resets ConvertData. + * + * <p>This empties all {@code Document} objects from this class. This allows + * reuse of a {@code ConvertData}.</p> + */ + public void reset() { + name = null; + v.clear(); + } + + /** + * Returns the {@code Document} name. + * + * @return The {@code Document} name. + */ + public String getName() { + return name; + } + + /** + * Sets the {@code Document} name. + * + * @param docName The name of the {@code Document}. + */ + public void setName(String docName) { + name = docName; + } + + /** + * Adds a {@code Document} to the vector. + * + * @param doc The {@code Document} to add. + */ + public void addDocument(Document doc) { + v.add(doc); + } + + /** + * Gets an {@code Enumeration} to access the {@code Vector} of + * {@code Document} objects. + * + * @return The {@code Enumeration} to access the {@code Vector} of + * {@code Document} objects. + */ + public Iterator<Object> getDocumentEnumeration() { + Iterator<Object> enumerate = v.iterator(); + return enumerate; + } + + /** + * Gets the number of {@code Document} objects currently stored. + * + * @return The number of {@code Document} objects currently stored. + */ + public int getNumDocuments() { + return v.size(); + } +} diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/ConvertException.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/ConvertException.java new file mode 100644 index 000000000..6d05e8d26 --- /dev/null +++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/ConvertException.java @@ -0,0 +1,34 @@ +/* + * 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; + +/** + * This {@code Exception} is thrown by convert algorithms. + */ +public class ConvertException extends Exception { + + /** + * Exception thrown by convert algorithms. + * + * @param message Message to be included in the {@code Exception}. + */ + public ConvertException(String message) { + super(message); + } +}
\ No newline at end of file diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/ConverterCapabilities.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/ConverterCapabilities.java new file mode 100644 index 000000000..da36f7da2 --- /dev/null +++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/ConverterCapabilities.java @@ -0,0 +1,54 @@ +/* + * 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; + +/** + * A {@code ConverterCapabilities} object is used by {@code DocumentMerger} + * implementations. + * + * <p>The {@code ConverterCapabilities} indicates which "Office" XML + * tags are supported by the "Device" format.</p> + * + * @see org.openoffice.xmerge.PluginFactory + * @see org.openoffice.xmerge.DocumentMerger + */ +public interface ConverterCapabilities { + + /** + * Test to see if the device document format supports the tag in question. + * + * @param tag The tag to check. + * + * @return {@code true} if the device format supports the tag, + * {@code false} otherwise. + */ + boolean canConvertTag(String tag); + + /** + * Test to see if the device document format supports the tag attribute in + * question. + * + * @param tag The tag to check. + * @param attribute The tag attribute to check. + * + * @return {@code true} if the device format supports the attribute, + * {@code false} otherwise. + */ + boolean canConvertAttribute(String tag, String attribute); +}
\ No newline at end of file diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/ConverterFactory.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/ConverterFactory.java new file mode 100644 index 000000000..5c1c246cb --- /dev/null +++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/ConverterFactory.java @@ -0,0 +1,85 @@ +/* + * 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; + +import org.openoffice.xmerge.util.registry.ConverterInfo; +import org.openoffice.xmerge.util.registry.ConverterInfoMgr; + +/** + * Factory that provides access to {@code Convert} objects, which are used to do + * a conversion. + * + * <p>The {@code ConvertFactory} does this via the {@code ConvertInfoMgr} which + * maintains a list of which {@code Convert} objects are available and their + * capabilities.</p> + * + * @see Convert + * @see org.openoffice.xmerge.util.registry.ConverterInfoMgr + */ +public class ConverterFactory { + + /** + * Returns the {@code Convert} object that converts the specified device/office + * mime type conversion. + * + * <p>If there are multiple {@code Converter} objects registered that support + * this conversion, only the first is returned.</p> + * + * @param mimeTypeIn The mime input type. + * @param mimeTypeOut The mime output type. + * + * @return The first {@code Convert} object that supports the specified + * conversion. + */ + public Convert getConverter(String mimeTypeIn, String mimeTypeOut) { + + ConverterInfo foundInfo; + boolean toOffice; + + toOffice = ConverterInfo.isValidOfficeType(mimeTypeOut); + + // findConverterInfo expects the second parameter to be the + // destination MimeType + if (toOffice) + foundInfo = ConverterInfoMgr.findConverterInfo(mimeTypeIn, mimeTypeOut); + else + foundInfo = ConverterInfoMgr.findConverterInfo(mimeTypeOut, mimeTypeIn); + + if (foundInfo != null) + return getConverter(foundInfo, toOffice); + else + return null; + } + + /** + * Returns the {@code Convert} object that is described by the + * {@code ConverterInfo} parameter. + * + * @param ci The {@code ConverterInfo} describing the converter. + * @param toOffice {@code true} to convert to office, {@code false} to + * convert to device. + * + * @return The {@code Convert} object + */ + public Convert getConverter(ConverterInfo ci, boolean toOffice) { + + Convert myConvert = new Convert(ci, toOffice); + return myConvert; + } +} diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/Document.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/Document.java new file mode 100644 index 000000000..851c1539d --- /dev/null +++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/Document.java @@ -0,0 +1,79 @@ +/* + * 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; + +import java.io.OutputStream; +import java.io.InputStream; +import java.io.IOException; + +/** + * A {@code Document} represents any {@code Document} to be converted and the + * resulting {@code Document} from any conversion. + * + * <p>It is created by the {@code PluginFactory} object's {@link + * org.openoffice.xmerge.PluginFactory#createOfficeDocument createOfficeDocument} + * method or the {@link org.openoffice.xmerge.PluginFactory#createDeviceDocument + * createDeviceDocument} method.</p> + * + * @see org.openoffice.xmerge.PluginFactory + */ +public interface Document { + + /** + * Writes out the {@code Document} content to the specified + * {@code OutputStream}. + * + * <p>This method may not be thread-safe. Implementations may or may not + * synchronize this method. User code (i.e. caller) must make sure that + * calls to this method are thread-safe.</p> + * + * @param os {@code OutputStream} to write out the {@code Document} + * content. + * + * @throws IOException If any I/O error occurs. + */ + void write(OutputStream os) throws IOException; + + /** + * Reads the content from the {@code InputStream} into the {@code Document}. + * + * <p>This method may not be thread-safe. Implementations may or may not + * synchronize this method. User code (i.e. caller) must make sure that + * calls to this method are thread-safe.</p> + * + * @param is {@code InputStream} to read in the {@code Document} content. + * + * @throws IOException If any I/O error occurs. + */ + void read(InputStream is) throws IOException; + + /** + * Returns the {@code Document} name with no file extension. + * + * @return The {@code Document} name with no file extension. + */ + String getName(); + + /** + * Returns the {@code Document} name with file extension. + * + * @return The {@code Document} name with file extension. + */ + String getFileName(); +}
\ No newline at end of file diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/DocumentDeserializer.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/DocumentDeserializer.java new file mode 100644 index 000000000..1bfadfcfe --- /dev/null +++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/DocumentDeserializer.java @@ -0,0 +1,53 @@ +/* + * 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; + +import java.io.IOException; + +/** + * A {@code DocumentDeserializer} represents a converter that converts + * "Device" {@code Document} objects into the "Office" + * {@code Document} format. + * + * <p>The {@code DocumentDeserializer} object is created by the + * {@code PluginFactory} {@link + * org.openoffice.xmerge.DocumentDeserializerFactory#createDocumentDeserializer + * createDocumentDeserializer} method. When it is constructed, a + * {@code ConvertData} object is passed in to be used as input.</p> + * + * @see org.openoffice.xmerge.PluginFactory + * @see org.openoffice.xmerge.DocumentDeserializerFactory + */ +public interface DocumentDeserializer { + + /** + * Convert the data passed into the {@code DocumentDeserializer} constructor + * into the "Office" {@code Document} format. + * + * <p>This method may or may not be thread-safe. It is expected that the + * user code does not call this method in more than one thread. And for + * most cases, this method is only done once.</p> + * + * @return The resulting {@code Document} object from conversion. + * + * @throws ConvertException If any Convert error occurs. + * @throws IOException If any I/O error occurs. + */ + Document deserialize() throws ConvertException, IOException; +}
\ No newline at end of file diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/DocumentDeserializer2.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/DocumentDeserializer2.java new file mode 100644 index 000000000..95f34413e --- /dev/null +++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/DocumentDeserializer2.java @@ -0,0 +1,61 @@ +/* + * 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; + +import java.io.IOException; + +/** + * A {@code DocumentDeserializer} represents a converter that converts + * "Device" {@code Document} objects into the "Office" + * {@code Document} format. + * + * <p>The {@code PluginFactory} {@link + * org.openoffice.xmerge.DocumentDeserializerFactory#createDocumentDeserializer + * createDocumentDeserializer} method creates a {@code DocumentDeserializer}, + * which may or may not implement {@code DocumentDeserializer2}. When it is + * constructed, a {@code ConvertData} object is passed in to be used as input. + * </p> + * + * @see org.openoffice.xmerge.PluginFactory + * @see org.openoffice.xmerge.DocumentDeserializerFactory + */ +public interface DocumentDeserializer2 extends DocumentSerializer { + + /** + * Convert the data passed into the {@code DocumentDeserializer2} + * constructor into the "Office" {@code Document} format. + * + * <p>The URL's passed may be used to resolve links and to choose the name + * of the output office document.</p> + * + * <p>This method may or may not be thread-safe. It is expected that the + * user code does not call this method in more than one thread. And for + * most cases, this method is only done once.</p> + * + * @return The resulting {@code Document} object from conversion. + * + * @param deviceURL URL of the device document (may be null if unknown) + * @param officeURL URL of the office document (may be null if unknown) + * + * @throws ConvertException If any Convert error occurs. + * @throws IOException If any I/O error occurs. + */ + Document deserialize(String deviceURL, String officeURL) throws + ConvertException, IOException; +}
\ No newline at end of file diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/DocumentDeserializerFactory.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/DocumentDeserializerFactory.java new file mode 100644 index 000000000..3d10d03bf --- /dev/null +++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/DocumentDeserializerFactory.java @@ -0,0 +1,51 @@ +/* + * 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; + +/** + * A {@code DocumentDeserializer} object is used to convert from the + * "Device" {@code Document} format to the "Office" + * {@code Document} format. + * + * <p>All plug-in implementations of the {@code PluginFactory} interface that + * also support deserialization must also implement this interface.</p> + * + * @see PluginFactory + * @see DocumentDeserializer + */ +public interface DocumentDeserializerFactory { + + /** + * The {@code DocumentDeserializer} is used to convert from the + * "Device" {@code Document} format to the "Office" + * {@code Document} format. + * + * <p>The {@code ConvertData} object is passed along to the created + * {@code DocumentDeserializer} via its constructor. The {@code ConvertData} + * is read and converted when the {@code DocumentDeserializer} object's + * {@code deserialize} method is called.</p> + * + * @param cd {@code ConvertData} object that the created + * {@code DocumentDeserializer} object uses as input. + * + * @return A {@code DocumentDeserializer} object. + */ + + DocumentDeserializer createDocumentDeserializer(ConvertData cd); +}
\ No newline at end of file diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/DocumentMerger.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/DocumentMerger.java new file mode 100644 index 000000000..3ec19e748 --- /dev/null +++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/DocumentMerger.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; + +/** + * A {@code DocumentMerger} can merge changes from a modified "Device" + * {@code Document} to the assigned original "Office" {@code Document}. + * + * <p>Merge is useful when an {@code OfficeDocument} is converted to a + * "Device" {@code Document} format, and the "Device" + * {@code Document} version is modified. Those changes can be merged back into + * the original {@code OfficeDocument} with the merger. The merger is capable + * of doing this even if the "Device" format is lossy in + * comparison to the {@code OfficeDocument} format.</p> + * + * <p>The {@code ConverterCapabilities} object is what the DocumentMerger + * utilizes to know how the "Office" {@code Document} tags are + * supported in the "Device" format.</p> + * + * <p>The {@code DocumentMerger} object is created by the + * {@code DocumentMergerFactory} {@link + * org.openoffice.xmerge.DocumentMergerFactory#createDocumentMerger + * createDocumenMerger} method. When it is constructed, the "Original + * Office" {@code Document} object is passed in to be used as input.</p> + * + * @see org.openoffice.xmerge.PluginFactory + * @see org.openoffice.xmerge.DocumentMergerFactory + * @see org.openoffice.xmerge.ConverterCapabilities + */ +public interface DocumentMerger { + + /** + * This method will find the changes that had happened in the + * {@code modifiedDoc} {@code Document} object given the designated original + * {@code Document}. + * + * <p>Note that this process may need the knowledge of the conversion + * process since some conversion process are lossy. Items/Data that are + * lost during the conversion process are not classified as changes. The + * main target of this method is to apply the changes done in + * {@code modifiedDoc} into the assigned original {@code Document} object, + * thus it also will try to preserve items that were originally in the + * original {@code Document}, but never got transferred during the + * {@link org.openoffice.xmerge.DocumentSerializer#serialize serialize} + * process/method call. After this method call, the original + * {@code Document} object will contain the changes applied.</p> + * + * <p>This method may or may not be thread-safe. Also, it is expected that + * the user uses only one instance of a {@code DocumentMerger} object per + * merge process. Create another {@code DocumentMerger} object for another + * merge process.</p> + * + * @param modifiedDoc device {@code Document} object. + * + * @throws MergeException If any merge error occurs. + */ + void merge(Document modifiedDoc) throws MergeException; +} diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/DocumentMergerFactory.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/DocumentMergerFactory.java new file mode 100644 index 000000000..e665d662a --- /dev/null +++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/DocumentMergerFactory.java @@ -0,0 +1,50 @@ +/* + * 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; + +/** + * All plug-in implementations of the {@code PluginFactory} interface that also + * support merging must also implement this interface. + * + * <p>Merge is useful when an {@code OfficeDocument} is converted to a + * "Device" {@code Document} format, and the "Device" + * {@code Document} version is modified.</p> + * + * <p>Those changes can be merged back into the original {@code OfficeDocument} + * with the merger. The merger is capable of doing this even if the + * "Device" format is lossy in comparison to the {@code OfficeDocument} + * format.</p> + * + * @see PluginFactory + * @see DocumentMerger + * @see ConverterCapabilities + */ +public interface DocumentMergerFactory { + + /** + * Create a {@code DocumentMerger} object given a {@code Document} object. + * + * @param doc {@code Document} object that the created + * {@code DocumentMerger} object uses as a base {@code Document} + * for merging changes into. + * + * @return A {@code DocumentMerger} object or {@code null} if none exists. + */ + DocumentMerger createDocumentMerger(Document doc); +}
\ No newline at end of file diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/DocumentSerializer.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/DocumentSerializer.java new file mode 100644 index 000000000..87f5da1c2 --- /dev/null +++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/DocumentSerializer.java @@ -0,0 +1,54 @@ +/* + * 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; + +import java.io.IOException; + +/** + * A {@code DocumentSerializer} represents a converter that converts a + * "Office" {@code Document} to a "Device" {@code Document} + * format. + * + * <p>The {@code DocumentSerializer} object is created by the + * {@code PluginFactory} {@link + * org.openoffice.xmerge.DocumentSerializerFactory#createDocumentSerializer + * createDocumentSerializer} method. When it is constructed, an + * "Office" {@code Document} object is passed in to be used as input. + * </p> + * + * @see org.openoffice.xmerge.PluginFactory + * @see org.openoffice.xmerge.DocumentSerializerFactory + */ +public interface DocumentSerializer { + + /** + * Convert the data passed into the {@code DocumentSerializer} constructor + * into the "Device" {@code Document} format. + * + * <p>This method may or may not be thread-safe. It is expected that the + * user code does not call this method in more than one thread. And for + * most cases, this method is only done once.</p> + * + * @return {@code ConvertData} object to pass back the converted data. + * + * @throws ConvertException If any conversion error occurs. + * @throws IOException If any I/O error occurs. + */ + ConvertData serialize() throws ConvertException, IOException; +} diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/DocumentSerializer2.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/DocumentSerializer2.java new file mode 100644 index 000000000..56a1a1116 --- /dev/null +++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/DocumentSerializer2.java @@ -0,0 +1,63 @@ +/* + * 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; + +import java.io.IOException; + +/** + * A {@code DocumentSerializer2} represents a converter that converts a + * "Office" {@code Document} to a "Device" {@code Document} + * format. + * + * <p>The {@code PluginFactory} {@link + * org.openoffice.xmerge.DocumentSerializerFactory#createDocumentSerializer + * createDocumentSerializer} method creates a {@code DocumentSerializer}, which + * may or may not implement {@code DocumentSerializer2}. When it is constructed, + * a "Office" {@code Document} object is passed in to be used as + * input.</p> + * + * @see org.openoffice.xmerge.PluginFactory + * @see org.openoffice.xmerge.DocumentSerializerFactory + */ +public interface DocumentSerializer2 extends DocumentSerializer { + + /** + * Convert the data passed into the {@code DocumentSerializer2} constructor + * into the "Device" {@code Document} format. + * + * <p>The URL's passed may be used to resolve links and to name the output + * device document(s).</p> + * + * <p>This method may or may not be thread-safe. It is expected that the + * user code does not call this method in more than one thread. And for + * most cases, this method is only done once.</p> + * + * @return {@code ConvertData} object to pass back the converted data. + * + * @param officeURL URL of the office document (may be null if + * unknown) + * @param deviceURL URL of the device document (may be null if + * unknown) + * + * @throws ConvertException If any conversion error occurs. + * @throws IOException If any I/O error occurs. + */ + ConvertData serialize(String officeURL, String deviceURL) throws + ConvertException, IOException; +}
\ No newline at end of file diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/DocumentSerializerFactory.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/DocumentSerializerFactory.java new file mode 100644 index 000000000..e4852ff1e --- /dev/null +++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/DocumentSerializerFactory.java @@ -0,0 +1,50 @@ +/* + * 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; + +/** + * A {@code DocumentSerializer} object is used to convert from the + * "Office" {@code Document} format to the "Device" + * {@code Document} format. + * + * <p>All plug-in implementations of the {@code PluginFactory} interface that + * also support serialization must also implement this interface.</p> + * + * @see PluginFactory + * @see DocumentSerializer + */ +public interface DocumentSerializerFactory { + + /** + * The {@code DocumentSerializer} is used to convert from the + * "Office" {@code Document} format to the "Device" + * {@code Document} format. + * + * <p>The {@code ConvertData} object is passed along to the created + * {@code DocumentSerializer} via its constructor. The {@code ConvertData} + * is read and converted when the {@code DocumentSerializer} object's + * {@code serialize} method is called.</p> + * + * @param doc {@code Document} object that the created + * {@code DocumentSerializer} object uses as input. + * + * @return A <code>DocumentSerializer</code> object. + */ + DocumentSerializer createDocumentSerializer(Document doc); +} diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/MergeException.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/MergeException.java new file mode 100644 index 000000000..c592bfb65 --- /dev/null +++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/MergeException.java @@ -0,0 +1,34 @@ +/* + * 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; + +/** + * This {@code Exception} is thrown by merge algorithms. + */ +public class MergeException extends Exception { + + /** + * Exception thrown by merge algorithms. + * + * @param message Message to be included in the {@code Exception}. + */ + public MergeException(String message) { + super(message); + } +}
\ No newline at end of file diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/PluginFactory.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/PluginFactory.java new file mode 100644 index 000000000..cb2e07a4a --- /dev/null +++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/PluginFactory.java @@ -0,0 +1,163 @@ +/* + * 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; + +import org.openoffice.xmerge.util.registry.ConverterInfo; +import java.io.InputStream; +import java.io.IOException; + +/** + * A {@code PluginFactory} encapsulates the conversions from one {@code Document} + * format to another. + * + * <p>It provides conversions in both directions. Refer to the + * <a href="package-summary.html#package_description"> package description</a> + * for its usage.</p> + * + * <p>Conversion from the "Office" {@code Document} format to a + * "Device" {@code Document} format may be lossy, i.e. some + * information may be lost. If a plug-in implements the + * {@code DocumentMergerFactory} interface, then there is the possibility for + * merging the changes done on the "Device" {@code Document} back to + * the original "Office" {@code Document} via the {@code DocumentMerger} + * interface.</p> + * + * <p>Plug-ins that convert from the "Device" {@code Document} format + * to the "Office" {@code Document} format must implement the + * {@code DocumentDeserializerFactory} interface. Plug-ins that convert from + * the "Office" {@code Document} format to the "Device" + * format must implement the {@code DocumentSerializerFactory} interface.</p> + * + * <p>All plug-ins should have an associated Plug-in Configuration XML File which + * describes the capabilities of the plug-in. If the plug-in is bundled in a + * jarfile, then this XML file is also bundled with the jarfile. The data in + * the XML file is managed by the {@code ConverterInfo} object. The + * {@code ConverterInfoMgr} manages a registry of all {@code ConverterInfo} + * objects. For more information about this XML file, refer to + * <a href="converter/xml/sxc/package-summary.html">org.openoffice.xmerge.util.registry</a>. + * </p> + * + * @see Document + * @see DocumentSerializer + * @see DocumentSerializerFactory + * @see DocumentDeserializer + * @see DocumentDeserializerFactory + * @see DocumentMerger + * @see DocumentMergerFactory + * @see ConverterInfo + * @see org.openoffice.xmerge.util.registry.ConverterInfoMgr + */ + +public abstract class PluginFactory { + + /** + * Cached {@code ConvertInfo} object. + */ + private final ConverterInfo ciCache; + + /** + * Constructor that caches the {@code ConvertInfo} that corresponds to the + * registry information for this plug-in. + * + * @param ci {@code ConvertInfo} object. + */ + public PluginFactory(ConverterInfo ci) { + ciCache=ci; + } + + /** + * Returns the {@code ConvertInfo} that corresponds to this plug-in. + * + * @return The {@code ConvertInfo} that corresponds to this plug-in. + */ + public ConverterInfo getConverterInfo () { + return ciCache; + } + + /** + * Create a {@code Document} object that corresponds to the Office data + * passed in via the {@code InputStream} object. + * + * <p>This abstract method must be implemented for each plug-in.</p> + * + * <p>This method will read from the given {@code InputStream} object. The + * returned {@code Document} object will contain the necessary data for the + * other objects created by the {@code PluginFactory} to process, like a + * {@code DocumentSerializer} object and a {@code DocumentMerger} object.</p> + * + * @param name The {@code Document} name. + * @param is {@code InputStream} object corresponding to the + * {@code Document}. + * + * @return A {@code Document} object representing the particular + * {@code Document} format for the {@code PluginFactory}. + * + * @throws IOException If any I/O error occurs. + */ + public abstract Document createOfficeDocument(String name, InputStream is) + throws IOException; + + /** + * Create a {@code Document} object that corresponds to the Office data + * passed in via the {@code InputStream} object. + * + * <p>This abstract method must be implemented for each plug-in.</p> + * + * <p>This method will read from the given {@code InputStream} object. The + * returned {@code Document} object will contain the necessary data for the + * other objects created by the {@code PluginFactory} to process, like a + * {@code DocumentSerializer} object and a {@code DocumentMerger} object.</p> + * + * @param name The {@code Document} name. + * @param is {@code InputStream} object corresponding to the + * {@code Document}. + * @param isZip {@code boolean} to show that the created office document + * is to be zipped. + * + * @return A {@code Document} object representing the particular + * {@code Document} format for the {@code PluginFactory}. + * + * @throws IOException If any I/O error occurs. + */ + public abstract Document createOfficeDocument(String name, InputStream is, + boolean isZip) throws IOException; + + /** + * Create a {@code Document} object that corresponds to the device data + * passed in via the {@code InputStream} object. + * + * <p>This abstract method must be implemented for each plug-in.</p> + * + * <p>This method will read from the given {@code InputStream} object. The + * returned {@code Document} object will contain the necessary data for the + * other objects created by the {@code PluginFactory} to process, like a + * {@code DocumentSerializer} object and a {@code DocumentMerger} object.</p> + * + * @param name The {@code Document} name. + * @param is {@code InputStream} object corresponding to the + * {@code Document}. + * + * @return A {@code Document} object representing the particular + * {@code Document} format for the {@code PluginFactory}. + * + * @throws IOException If any I/O error occurs. + */ + public abstract Document createDeviceDocument(String name, InputStream is) + throws IOException; +}
\ No newline at end of file diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/Version.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/Version.java new file mode 100644 index 000000000..02d16eedf --- /dev/null +++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/Version.java @@ -0,0 +1,68 @@ +/* + * 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; + +/** + * This class provides a quick utility to check the version of the jar file. + * + * <p>It has a main method that prints out the version info. It also provides + * two static methods for runtime classes to query.</p> + */ +public final class Version { + + private static final Version version = new Version(); + + private static final Package pkg = version.getClass().getPackage(); + + /** + * Private constructor to provide a singleton instance. + */ + private Version() { + } + + /** + * Returns specification version. + * + * @return The specification version. + */ + public static String getSpecificationVersion() { + return pkg.getSpecificationVersion(); + } + + /** + * Returns implementation version. + * + * @return The implementation version. + */ + public static String getImplementationVersion() { + return pkg.getImplementationVersion(); + } + + /** + * Main method for printing out version info. + * + * @param args Array of arguments, not used. + */ + public static void main(String args[]) { + System.out.println("Specification-Title: " + pkg.getSpecificationTitle()); + System.out.println("Specification-Vendor: " + pkg.getSpecificationVendor()); + System.out.println("Specification-Version: " + pkg.getSpecificationVersion()); + System.out.println("Implementation-Version: " + pkg.getImplementationVersion()); + } +}
\ No newline at end of file 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 "Office" {@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 "Device" + * {@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 "Device" {@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", "'Courier New'", + "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 "Device" + * {@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 "Device" + * {@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 "Office" {@code Document} + * tags and attributes are supported on the "Device" {@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; diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/DiffAlgorithm.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/DiffAlgorithm.java new file mode 100644 index 000000000..a39649b03 --- /dev/null +++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/DiffAlgorithm.java @@ -0,0 +1,43 @@ +/* + * 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.merger; + +/** + * This is the difference algorithm interface. + * + * <p>It is an interface so that different algorithms may be plugged-in to + * actually compute the differences.</p> + * + * <p>NOTE: this code may not be thread safe.</p> + */ +public interface DiffAlgorithm { + + /** + * Returns a {@code Difference} array. + * + * <p>This method finds out the difference between two sequences.</p> + * + * @param orgSeq The original sequence of object. + * @param modSeq The modified (or changed) sequence to compare against + * with the original. + * + * @return A {@code Difference} array. + */ + Difference[] computeDiffs(Iterator orgSeq, Iterator modSeq); +}
\ No newline at end of file diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/Difference.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/Difference.java new file mode 100644 index 000000000..74e95fb38 --- /dev/null +++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/Difference.java @@ -0,0 +1,223 @@ +/* + * 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.merger; + +/** + * This is the {@code Difference} basic unit. + * + * Used by the {@code DiffAlgorithm} as a set of difference between two + * {@code Iterators} (the original and modified {@code Iterators}). + */ +public final class Difference { + + /** Add operation. */ + public static final int ADD = 1; + + /** Delete operation. */ + public static final int DELETE = 2; + + /** Change operation. */ + public static final int CHANGE = 3; + + /** Unchange operation (i.e. no change). */ + public static final int UNCHANGE = 4; + + /** The action of the diff - either {@link #ADD} or {@link #DELETE} */ + private final int operation; + + /** + * The position of the content that should be operated on (original + * {@code iterator}). + * + * <p>For {@code ADD}, the {@code orgPosition} is the position of the + * original sequence where the diff will insert (the element count is + * starting from 0, and always insert before the element). The + * {@code modPosition} is the position of the diff in the modified sequence + * (also starting from 0).</p> + * + * <blockquote><pre>{@literal example: + * + * diff - <B D> and <A B C D E F> + * note: <B D> is original sequence and <A B C D E F> + * is the modified one. + * + * and here is the position of those sequence: + * <B D> <A B C D E F> + * 0 1 0 1 2 3 4 5 + * + * result: + * <diff orgPos=0 modPos=0 operation=ADD> <-- element A + * <diff orgPos=1 modPos=2 operation=ADD> <-- element C + * <diff orgPos=2 modPos=4 operation=ADD> <-- element E + * <diff orgPos=2 modPos=5 operation=ADD> <-- element F}</pre></blockquote> + * + * <p>One can notice the add operation is inserted before the position. + * Hence, in order to append an element, we will have a position of original + * sequence length + 1 to denote an append.</p> + * + * <p>For {@code DELETE}, {@code orgPosition} is the position that the + * element will be deleted (starting from 0) and {@code modPosition} is the + * position where the deleted element should be (consider as an {@code ADD}). + * </p> + * + * <p>The {@code modPosition} is less useful and it is difficult to + * understand how the position is calculated. One can just skip this piece + * of information. It is useful if one wants to reverse the role of original + * sequence and modified sequence and find out the diff easily (just change + * add to delete and delete to add for operation and swap the + * {@code orgPosition} and {@code modPosition}).</p> + * + * <blockquote><pre>{@literal example: + * + * diff - <A B C D E F> and <B D> + * note: <A B C D E F> is original sequence and <B D> + * is the modified one. + * + * and here is the position of those sequence: + * <A B C D E F> <B D> + * 0 1 2 3 4 5 0 1 + * + * result: + * <diff orgPos=0 modPos=0 operation=DELETE> <-- element A + * <diff orgPos=2 modPos=1 operation=DELETE> <-- element C + * <diff orgPos=4 modPos=2 operation=DELETE> <-- element E + * <diff orgPos=5 modPos=2 operation=DELETE> <-- element F}</pre></blockquote> + */ + private final int orgPosition; + + /** + * The position of the content that should be operated (modified iterator). + * + * <p>For explanation and examples, see {@link #orgPosition}</p>. + */ + private final int modPosition; + + /** + * Constructor. + * + * This is the standard way to create a {@code Difference} object. + * + * @param operation Either {@link #ADD} or {@link #DELETE}. + * @param orgPosition The position in the original (first) {@code Iterator}. + * @param modPosition The position in the modified (second) {@code Iterator}. + */ + public Difference(int operation, int orgPosition, + int modPosition) { + this.operation = operation; + this.orgPosition = orgPosition; + this.modPosition = modPosition; + } + + /** + * Get the operation of the {@code Difference}. + * + * @return the operation of the {@code Difference}, either {@link #ADD} or + * {@link #DELETE}. + */ + public int getOperation() { + return operation; + } + + /** + * Get the original {@code Iterator} position. + * + * @return The position in the original (first) {@code Iterator}. + */ + public int getOrgPosition() { + return orgPosition; + } + + /** + * Get the modified {@code Iterator} position. + * + * @return The position in the modified (second) {@code Iterator}. + */ + public int getModPosition() { + return modPosition; + } + + /** + * Two {@code Difference} objects will equal if and only if all + * {@code operation}, {@code orgPosition}, {@code modPosition} and + * {@code content} are equal. + * + * @param obj {@code Object} to compare. + * + * @return {@code true} if equal, {@code false} otherwise. + */ + @Override + public boolean equals(Object obj) { + if (obj instanceof Difference) { + Difference diff = (Difference) obj; + if ((operation == diff.operation) && + (orgPosition == diff.orgPosition) && + (modPosition == diff.modPosition)) { + return true; + } + } + + return false; + } + + @Override + public int hashCode() { + return 0; + } + + /** + * Display debug information. + * + * @return Debug string. + */ + public String debug() { + + String opStr = ""; + + switch (operation) { + case ADD: + opStr = "add"; + break; + case DELETE: + opStr = "del"; + break; + case CHANGE: + opStr = "chg"; + break; + case UNCHANGE: + opStr = "uch"; + break; + default: + break; + } + return "<diff orgPos=" + orgPosition + " modPos=" + modPosition + + " op=" + opStr + ">"; + } + + /** + * Returns position and operation values as a single string. + * + * @return {@code orgPosition}, {@code modPosition} and {@code operation} + * as a single {@code String}. + */ + @Override + public String toString() { + + return orgPosition + " " + modPosition + " " + operation; + } +} diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/Iterator.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/Iterator.java new file mode 100644 index 000000000..c53988076 --- /dev/null +++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/Iterator.java @@ -0,0 +1,85 @@ +/* + * 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.merger; + +/** + * This is an interface used by the {@link + * org.openoffice.xmerge.merger.DiffAlgorithm DiffAlgorithm} and {@link + * org.openoffice.xmerge.merger.MergeAlgorithm MergeAlgorithm} to access a + * {@code Document}. + */ +public interface Iterator { + + /** + * Move to next element in the sequence. + * + * @return The {@code Object} of the next element in the sequence. If there + * is no next element, then return {@code null}. + */ + Object next(); + + /** + * Move to the beginning of the sequence. + * + * @return The {@code Object} of the first element in the sequence. If it + * is empty, then return {@code null}. + */ + Object start(); + + /** + * Return the current element {@code Object} content. + * + * @return The {@code Object} at current position. + */ + Object currentElement(); + + /** + * Return the total element count in the sequence. + * + * @return The total element count. + */ + int elementCount(); + + /** + * A method to allow the difference algorithm to test whether the {@code obj1} + * and {@code obj2} in the {@code Iterator} are considered equal. + * + * <p>As not every {@code Object} in the {@code Iterator} can implement its + * own equal method, with this equivalent method, we can allow flexibility + * for the {@code Iterator} to choose a custom way to compare two objects. + * Two objects can even be compared based on the position in the + * {@code Iterator} rather than by the content via this option. + * + * @param obj1 The first {@code Object} + * @param obj2 The second {@code Object}. + * + * @return {@code true} if equal, {@code false} otherwise. + */ + boolean equivalent(Object obj1, Object obj2); + + /** + * A method to force the {@code Iterator} to traverse the tree again to + * refresh the content. + * + * <p>It is used mainly for {@code Iterator} objects which take a snapshot + * instead of dynamically traversing the tree. The current position will + * be set to the beginning.</p> + */ + void refresh(); +} diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/MergeAlgorithm.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/MergeAlgorithm.java new file mode 100644 index 000000000..11e337b57 --- /dev/null +++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/MergeAlgorithm.java @@ -0,0 +1,46 @@ +/* + * 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.merger; + +import org.openoffice.xmerge.MergeException; + +/** + * This is the {@code MergeAlgorithm} interface. + * + * <p>It is an interface so that different merge algorithms may be plugged-in + * to actually merge the diffs back to an original document.</p> + */ +public interface MergeAlgorithm { + + /** + * This method is to merge the difference to an {@code Iterator}. + * + * <p>The original {@code Iterator} will be modified after the call.</p> + * + * @param orgSeq The original sequence which the difference will be + * applied. It will be modified. + * @param modSeq The modified sequence where the difference content + * will be extracted. + * @param differences The {@code Difference} array. + * + * @throws MergeException If an error occurs during the merge. + */ + void applyDifference(Iterator orgSeq, Iterator modSeq, + Difference[] differences) throws MergeException; +}
\ No newline at end of file diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/NodeMergeAlgorithm.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/NodeMergeAlgorithm.java new file mode 100644 index 000000000..98a9a8bd1 --- /dev/null +++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/NodeMergeAlgorithm.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 . + */ + +package org.openoffice.xmerge.merger; + +import org.w3c.dom.Node; + +/** + * This is an interface for a {@link org.openoffice.xmerge.merger.MergeAlgorithm + * MergeAlgorithm} to merge two {@code Node} objects. + * + * <p>It is an interface so that different merge algorithms may be plugged-in.</p> + */ +public interface NodeMergeAlgorithm { + + /** + * This method is used to merge two given {@code Node} objects. + * + * <p>Note: the original {@code Node} may be modified.</p> + * + * @param originalNode The original {@code Node}. + * @param modifyNode The {@code Node} to be merged. It may be modified. + */ + void merge(Node originalNode, Node modifyNode); +}
\ No newline at end of file diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/diff/CellNodeIterator.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/diff/CellNodeIterator.java new file mode 100644 index 000000000..791c809f9 --- /dev/null +++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/diff/CellNodeIterator.java @@ -0,0 +1,96 @@ +/* + * 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.merger.diff; + +import org.w3c.dom.Node; +import org.w3c.dom.Element; + +import org.openoffice.xmerge.ConverterCapabilities; +import org.openoffice.xmerge.converter.xml.OfficeConstants; + +/** + * This is an implementations of the {@code Iterator} interface. + * + * <p>It will traverse the tree and find cell {@code Node} sequences.</p> + * + * <p>Note: Once the XML Tree is parsed, then the {@code Iterator} will be a + * snapshot of that tree. That means even the tree is modified later, then the + * cached paragraph {@code Node} list will not be updated accordingly. For this + * reason and for performance reasons this {@code Iterator} does not support any + * operation methods such as insert, remove or replace. The main purpose of this + * {@code Iterator} is to be used with difference, not with merge.</p> + */ +public final class CellNodeIterator extends NodeIterator { + + // can be expanded to an array in the future, not necessary right now + private static final String SUPPORTED_TAG1 = OfficeConstants.TAG_TABLE_CELL; + + /** + * The standard constructor. + * + * @param cc The {@code ConverterCapabilities}. + * @param node The initial root {@code Node}. + */ + public CellNodeIterator(ConverterCapabilities cc, Node node) { + super(cc, node); + } + + /** + * Overwrite the parent {@code nodeSupported} method. + * + * <p>Only cell {@code Node} objects are supported.</p> + * + * @param node The {@code Node} to check. + * + * @return {@code true} if the {@code Node} is supported, {@code false} + * otherwise. + */ + @Override + protected boolean nodeSupported(Node node) { + + // can use an array later to check all possible tags for + // future expansion + return node.getNodeType() == Node.ELEMENT_NODE && + node.getNodeName().equals(SUPPORTED_TAG1); + } + + @Override + protected boolean childrenEqual(Node node1, Node node2) { + + boolean equal = false; + + if (node1.hasChildNodes() && node2.hasChildNodes()) { + Element cell1 = (Element)node1; + Element cell2 = (Element)node2; + + // only need compare the first <text:p> children node, don't want + // to compare any non-supported features + // TODO: need to confirm whether all the text string is the + // first <text:p>, though I checked with the openoffice 619 build + Node paraNode1 = cell1.getElementsByTagName( + OfficeConstants.TAG_PARAGRAPH).item(0); + Node paraNode2 = cell2.getElementsByTagName( + OfficeConstants.TAG_PARAGRAPH).item(0); + + equal = super.compareNode(paraNode1, paraNode2); + } + + return equal; + } +} diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/diff/CharArrayLCSAlgorithm.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/diff/CharArrayLCSAlgorithm.java new file mode 100644 index 000000000..9e9eaf369 --- /dev/null +++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/diff/CharArrayLCSAlgorithm.java @@ -0,0 +1,188 @@ +/* + * 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.merger.diff; + +import java.util.ArrayList; +import org.openoffice.xmerge.merger.Difference; + +/** + * This is an implementations of {@code DiffAlgorithm} interface which will + * difference char arrays. + * + * <p>It also use Longest Common Subsequence (LCS). The algorithm is based on + * the book "Introduction to Algorithms" by Thomas H.Cormen, Charles E.Leiserson, + * and Ronald L.Riverst (MIT Press 1990) page 314.</p> + */ +public class CharArrayLCSAlgorithm { + + /** + * Return an {@code Difference} array. + * + * <p>This method finds out the difference between two sequences.</p> + * + * @param orgSeq The original sequence. + * @param modSeq The modified (or changed) sequence to compare against + * the original. + * + * @return A {@code Difference} array. + */ + public Difference[] computeDiffs(char[] orgSeq, char[] modSeq) { + + int orgSeqlen = orgSeq.length; + int modSeqlen = modSeq.length; + + // Diff table is used to keep track which element is the same or not + // in those 2 sequences + int[][] diffTable = createDiffTable(orgSeq, modSeq); + + ArrayList<Difference> diffResult = new ArrayList<Difference>(); + + generateResult(diffTable, orgSeqlen, modSeqlen, diffResult); + + // convert the vector to array, it has to do it here as + // generateResult is called recursively + Difference[] diffArray = new Difference[diffResult.size()]; + diffResult.toArray(diffArray); + + return diffArray; + } + + /** + * Create the difference table. + * + * <p>The difference table is used internal to keep track what elements are + * common or different in the two sequences.</p> + * + * @param orgSeq The original sequence to be used as a base. + * @param modSeq The modified sequence to compare. + * + * @return A difference table as a two-dimensional array of integers. + */ + private int[][] createDiffTable(char[] orgSeq, char[] modSeq) { + int orgSeqlen = orgSeq.length + 1; + int modSeqlen = modSeq.length + 1; + int[][] diffTable; + + // initialize the diffTable (it needs to be 1 row/col bigger + // than the original str) + diffTable = new int[orgSeqlen][]; + for (int i = 0; i < orgSeqlen; i++) { + diffTable[i] = new int[modSeqlen]; + } + + // compute the diff Table using LCS algorithm, refer to the book + // mentioned at the top of the program + for (int i = 1; i < orgSeqlen; i++) { + for (int j = 1; j < modSeqlen; j++) { + + if (orgSeq[i-1] == modSeq[j-1]) { + diffTable[i][j] = diffTable[i-1][j-1]+1; + } else { + if (diffTable[i-1][j] >= diffTable[i][j-1]) { + diffTable[i][j] = diffTable[i-1][j]; + } else { + diffTable[i][j] = diffTable[i][j-1]; + } + } + } + } + + return diffTable; + } + + /** + * Generate the {@code Difference} result vector. + * + * <p>This method will be called recursively to backtrack the difference + * table to get the difference result (and also the LCS).</p> + * + * @param diffTable The difference table containing the {@code Difference} + * result. + * @param i The nth element in original sequence to compare. This + * method is called recursively with {@code i} and + * {@code j} decreased until {@code 0}. + * @param j The nth element in modified sequence to compare. + * @param diffVector A vector to output the {@code Difference} result. + * Can not use a return variable as it is a recursive + * method. The vector will contain {@code Difference} + * objects with operation and positions filled in. + */ + private void generateResult(int[][] diffTable, int i, int j, + ArrayList<Difference> diffVector) { + + // handle the first element + if (i == 0 || j == 0) { + if (i == 0 && j == 0) { + // return + } else if (j == 0) { + for (int cnt = 0; cnt < i; cnt++) { + Difference diff = + new Difference(Difference.DELETE, cnt, j); + diffVector.add(diff); + } + } else { + for (int cnt = 0; cnt < j; cnt++) { + Difference diff = + new Difference(Difference.ADD, i, cnt); + diffVector.add(diff); + } + } + return; + } + + // for the detail of this algorithm, refer to the book mentioned on + // the top and page 317 and 318. + if ((diffTable[i-1][j-1] == diffTable[i][j] -1) && + (diffTable[i-1][j-1] == diffTable[i-1][j]) && + (diffTable[i-1][j-1] == diffTable[i][j-1])) { + + // the element of ith and jth in org and mod sequence is the same + generateResult(diffTable, i-1, j-1, diffVector); + } else { + if (diffTable[i-1][j] > diffTable[i][j-1]) { + + // recursively call first, then add the result so that + // the beginning of the diffs will be stored first + generateResult(diffTable, i-1, j, diffVector); + + Difference diff = + new Difference(Difference.DELETE, i-1, j); + diffVector.add(diff); + } else if (diffTable[i-1][j] < diffTable[i][j-1]) { + + // recursively call first, then add the result so that + // the beginning of the diffs will be stored first + generateResult(diffTable, i, j-1, diffVector); + + Difference diff = + new Difference(Difference.ADD, i, j-1); + diffVector.add(diff); + } else { // diffTable[i-1][j] == diffTable[i][j-1] + // recursively call first, then add the result so that + // the beginning of the diffs will be stored first + generateResult(diffTable, i-1, j-1, diffVector); + + Difference diff = + new Difference(Difference.CHANGE, i-1, j-1); + diffVector.add(diff); + + } + } + } +} diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/diff/CharacterParser.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/diff/CharacterParser.java new file mode 100644 index 000000000..622fedf9e --- /dev/null +++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/diff/CharacterParser.java @@ -0,0 +1,127 @@ +/* + * 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.merger.diff; + +import org.w3c.dom.Node; + +import org.openoffice.xmerge.converter.xml.OfficeConstants; + +import java.util.ArrayList; +import java.util.List; + +/** + * This is a parser to return a character array for difference purpose. + * + * <p>It will use depth first search to traverse all the characters inside the + * text {@code Node} under a given {@code Node} (most likely to be a paragraph + * {@code Node}).</p> + * + * <p>Note: Once the XML Tree is parsed, then the {@code Iterator} will be a + * snapshot of that tree. That means even the tree is modified later, then + * the cached paragraph {@code Node} list will not be updated accordingly. For + * this reason and for performance reasons this {@code Iterator} does not + * support any operation methods such as insert, remove or replace. The main + * purpose of this {@code Iterator} is to be used with difference, not with + * merge.</p> + */ +public class CharacterParser { + + private final TextNodeIterator textNodes; + private int currentPosition = 0; + private final List<TextNodeEntry> nodeList_; + private char[] charArray; + + /** + * Standard constructor. + * + * @param node The initial root {@code Node}. + */ + public CharacterParser(Node node) { + textNodes = new TextNodeIterator(node); + nodeList_ = new ArrayList<TextNodeEntry>(); + + parseNodes(); + } + + /** + * Returns the {@code Node} pointer with the given character position. + * + * @return The {@code Node} pointer with the given character position. + */ + public List<TextNodeEntry> getNodeList() { + // will go through the nodeList to find the corresponding node + return nodeList_; + } + + /** + * Returns the character array representation of the text. + * + * @return The character array representation of the text. + */ + public char[] getCharArray() { + return charArray; + } + + private void parseNodes() { + + StringBuffer strBuf = new StringBuffer(); + + /* create the character array by iterate the textnode iterator */ + Node currentNode = (Node)(textNodes.start()); + for (; + currentNode != null; + currentNode = (Node)(textNodes.next())) { + + // add the text value into the array + String textValue = null; + String nodeName = currentNode.getNodeName(); + + // TODO: Space node have a count attribute which is not handled! + if (currentNode.getNodeType() == Node.TEXT_NODE) { + textValue = currentNode.getNodeValue(); + } else if (nodeName.equals(OfficeConstants.TAG_SPACE)) { + textValue = " "; + } else if (nodeName.equals(OfficeConstants.TAG_TAB_STOP)) { + textValue = "\t"; + } + + if (textValue != null) { + strBuf.append(textValue); + addNewNodeEntry(textValue.length(), currentNode); + } + } + + charArray = strBuf.toString().toCharArray(); + } + + /** + * Adds a new {@code Node} entry. + * + * @param textLen The text length. + * @param node The {@code Node}. + */ + private void addNewNodeEntry(int textLen, Node node) { + + TextNodeEntry nodeEntry = new TextNodeEntry(currentPosition, + currentPosition + textLen - 1, node); + currentPosition = currentPosition + textLen; + + nodeList_.add(nodeEntry); + } +} diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/diff/IteratorLCSAlgorithm.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/diff/IteratorLCSAlgorithm.java new file mode 100644 index 000000000..a5298df19 --- /dev/null +++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/diff/IteratorLCSAlgorithm.java @@ -0,0 +1,210 @@ +/* + * 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.merger.diff; + +import java.util.ArrayList; +import org.openoffice.xmerge.merger.DiffAlgorithm; +import org.openoffice.xmerge.merger.Difference; +import org.openoffice.xmerge.merger.Iterator; +import org.openoffice.xmerge.util.Debug; + +/** + * This is one of the implementations of {@code DiffAlgorithm} interface. + * + * <p>Using Longest Common Subsequence (LCS). The algorithm here is based on the + * book "Introduction to Algorithms" by Thomas H.Cormen, Charles E.Leiserson and + * Ronald L.Riverst (MIT Press 1990) page 314.</p> + */ +public class IteratorLCSAlgorithm implements DiffAlgorithm { + + public Difference[] computeDiffs(Iterator orgSeq, Iterator modSeq) { + + int orgSeqlen = orgSeq.elementCount(); + int modSeqlen = modSeq.elementCount(); + + // Diff table is used to keep track which element is the same or not + // in those 2 sequences + int[][] diffTable = createDiffTable(orgSeq, modSeq); + + // debug purpose... + if (Debug.isFlagSet(Debug.INFO)) { + printDiffTable(diffTable); + } + + ArrayList<Difference> diffResult = new ArrayList<Difference>(); + + generateResult(diffTable, orgSeqlen, modSeqlen, diffResult); + + // convert the vector to array, it has to do in here as + // generateResult is called recursively + Difference[] diffArray = new Difference[diffResult.size()]; + diffResult.toArray(diffArray); + + return diffArray; + } + + /** + * Debug function used to print out the nicely formatted difference table. + * + * @param diffTable The difference table to display. + */ + private void printDiffTable(int[][] diffTable) { + + for (int i = 0; i < diffTable.length; i++) { + StringBuilder sb = new StringBuilder(); + for (int j = 0; j < diffTable[i].length; j++) { + sb.append(" ").append(diffTable[i][j]).append(" "); + } + Debug.log(Debug.INFO, sb.toString()); + } + } + + /** + * Create the difference table. + * + * <p>The difference table is used internal to keep track what elements are + * common or different in the two sequences.</p> + * + * @param orgSeq The original sequence to be used as a base. + * @param modSeq The modified sequence to compare. + * + * @return A difference table as a two-dimensional array of integers. + */ + private int[][] createDiffTable(Iterator orgSeq, Iterator modSeq) { + int orgSeqlen = orgSeq.elementCount() + 1; + int modSeqlen = modSeq.elementCount() + 1; + int[][] diffTable; + + // initialize the diffTable + diffTable = new int[orgSeqlen][]; + for (int i = 0; i < orgSeqlen; i++) { + diffTable[i] = new int[modSeqlen]; + } + + // compute the diff Table using LCS algorithm, refer to the book + // mentioned at the top of the program + + int i, j; + + Object orgSeqObject, modSeqObject; + + for (orgSeqObject = orgSeq.start(), i = 1; + orgSeqObject != null; + orgSeqObject = orgSeq.next(), i++) { + + for (modSeqObject = modSeq.start(), j = 1; + modSeqObject != null; + modSeqObject = modSeq.next(), j++) { + + if (orgSeq.equivalent(orgSeqObject, modSeqObject)) { + diffTable[i][j] = diffTable[i-1][j-1]+1; + } else { + if (diffTable[i-1][j] >= diffTable[i][j-1]) { + diffTable[i][j] = diffTable[i-1][j]; + } else { + diffTable[i][j] = diffTable[i][j-1]; + } + } + } + } + + return diffTable; + } + + /** + * Generate the {@code Difference} object result vector. + * + * <p>This method will be called recursively to backtrack the difference + * table to get the difference result (and also the LCS).</p> + * + * @param diffTable The difference table containing the {@code Difference} + * result. + * @param i The nth element in original sequence to compare. This + * method is called recursively with {@code i} and + * {@code j} decreased until {@code 0}. + * @param j The nth element in modified sequence to compare. + * @param diffVector A vector to output the {@code Difference} result. + * Can not use a return variable as it is a recursive + * method. The vector will contain {@code Difference} + * objects with operation and positions fill in. + */ + private void generateResult(int[][] diffTable, + int i, int j, ArrayList<Difference> diffVector) { + + // handle the first element + if (i == 0 && j == 0) { + return; + + } else if (j == 0) { + for (int cnt = 0; cnt < i; cnt++) { + Difference diff = + new Difference(Difference.DELETE, cnt, j); + diffVector.add(diff); + } + return; + + } else if (i == 0) { + for (int cnt = 0; cnt < j; cnt++) { + Difference diff = + new Difference(Difference.ADD, i, cnt); + diffVector.add(diff); + } + return; + } + + // for the detail of this algorithm, refer to the book mentioned on + // the top and page 317 and 318. + if ((diffTable[i-1][j-1] == diffTable[i][j] -1) && + (diffTable[i-1][j-1] == diffTable[i-1][j]) && + (diffTable[i-1][j-1] == diffTable[i][j-1])) { + + // the element of ith and jth in org and mod sequence is the same + generateResult(diffTable, i-1, j-1, diffVector); + } else { + if (diffTable[i-1][j] > diffTable[i][j-1]) { + + // recursively call first, then add the result so that + // the beginning of the diffs will be stored first + generateResult(diffTable, i-1, j, diffVector); + + Difference diff = + new Difference(Difference.DELETE, i-1, j); + diffVector.add(diff); + } else if (diffTable[i-1][j] < diffTable[i][j-1]) { + + // recursively call first, then add the result so that + // the beginning of the diffs will be stored first + generateResult(diffTable, i, j-1, diffVector); + + Difference diff = + new Difference(Difference.ADD, i, j-1); + diffVector.add(diff); + } else { // diffTable[i-1][j] == diffTable[i][j-1] + // recursively call first, then add the result so that + // the beginning of the diffs will be stored first + generateResult(diffTable, i-1, j-1, diffVector); + + Difference diff = + new Difference(Difference.CHANGE, i-1, j-1); + diffVector.add(diff); + + } + } + } +} diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/diff/IteratorRowCompare.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/diff/IteratorRowCompare.java new file mode 100644 index 000000000..ce1644002 --- /dev/null +++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/diff/IteratorRowCompare.java @@ -0,0 +1,229 @@ +/* + * 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.merger.diff; + +import org.w3c.dom.Node; +import org.w3c.dom.Element; + +import java.util.ArrayList; +import org.openoffice.xmerge.merger.DiffAlgorithm; +import org.openoffice.xmerge.merger.Difference; +import org.openoffice.xmerge.merger.Iterator; +import org.openoffice.xmerge.converter.xml.OfficeConstants; + +/** + * A very simple and direct difference algorithm for row {@code Node} objects in + * a spreadsheet. + * + * <p>Basically, it will compare objects in sequence and does not look ahead + * (unlike LCS).</p> + * + * <ol> + * <li> + * If two objects are the same, skip to next one. + * </li><li> + * Otherwise check whether the row repeated attribute is the same. + * </li><li> + * If the row repeated attribute is the same, then compare two rows and mark + * it as <i>change</i> if those rows are different. + * </li><li> + * If the row repeated attribute is different, then split the rows and + * continue to compare. + * </li><li> + * If there are more objects in the modseq than the original sequence, then + * all of the extra ones in the modified sequence are marked as add. + * </li><li> + * If there are more objects in the original sequence than the modified + * sequence, then all the extra one in the modified sequence are marked as + * delete. + * </li> + * </ol> + * + * <p>NOTE: The algorithm will have potential side effect to split rows.</p> + */ + +public class IteratorRowCompare implements DiffAlgorithm { + + /** + * Compute the differences of the given two sequences. + * + * <p>Refer to the class description.</p> + * + * <p>Return an array of {@code Difference} objects. This method finds out + * the difference between two sequences.</p> + * + * @param orgSeq The original sequence. + * @param modSeq The modified (or changed) sequence to compare against + * with the original. + * + * @return An array of Difference objects. + */ + public Difference[] computeDiffs(Iterator orgSeq, Iterator modSeq) { + + ArrayList<Difference> diffVector = new ArrayList<Difference>(); + + // i and j are counters to keep track the current position in the + // iterator + int i = 0; + int j = 0; + Object orgSeqObject = orgSeq.start(); + Object modSeqObject = modSeq.start(); + Element orgRow, modRow; + boolean different = false; + boolean orgSplited = false; + boolean modSplited = false; + + while (orgSeqObject != null) { + + different = true; + + if (modSeqObject == null) { + // no more modsequence, all the remaining org sequence objs + // should consider as a delete. + Difference diff = new Difference(Difference.DELETE, i, j); + diffVector.add(diff); + orgSeqObject = orgSeq.next(); + + } else { + if (!orgSeq.equivalent(orgSeqObject, modSeqObject)) { + + orgRow = (Element)orgSeqObject; + modRow = (Element)modSeqObject; + + // check whether the original Row with multiple row + // if so, need to split one out for merge + String orgRowRepeated = orgRow.getAttribute( + OfficeConstants.ATTRIBUTE_TABLE_NUM_ROWS_REPEATED); + String modRowRepeated = modRow.getAttribute( + OfficeConstants.ATTRIBUTE_TABLE_NUM_ROWS_REPEATED); + + + int orgRowNum = 1; + int modRowNum = 1; + + if (orgRowRepeated.length() > 0) { + orgRowNum = Integer.parseInt(orgRowRepeated); + } + if (modRowRepeated.length() > 0) { + modRowNum = Integer.parseInt(modRowRepeated); + } + + // try to find out the common number of repeated Rows + if (orgRowNum == modRowNum) { + orgSeqObject = orgSeq.next(); + modSeqObject = modSeq.next(); + + // cut the original row into two halves, first half + // have the repeated attribute = modify row attr + } else if (orgRowNum > modRowNum) { + Element orgSplitRow = splitRepeatedRow( + orgRow, modRowNum, + orgRowNum - modRowNum); + // it may equal after the split! + if (orgSeq.equivalent(orgSplitRow, modRow)) { + different = false; + } + orgSplited = true; + modSeqObject = modSeq.next(); + + // cut the modified Row into two halves, first half + // have the repeated attribute = original Row attr + } else { + Element modSplitRow = splitRepeatedRow( + modRow, orgRowNum, + modRowNum - orgRowNum); + + // check whether rows are equal after the split + if (modSeq.equivalent(orgRow, modSplitRow)) { + different = false; + } + modSplited = true; + orgSeqObject = orgSeq.next(); + } + + if (different) { + Difference diff = new Difference(Difference.CHANGE, + i, j); + diffVector.add(diff); + } + + } else { + // Rows are equivalent, move on to next one. + orgSeqObject = orgSeq.next(); + modSeqObject = modSeq.next(); + } // end if-else + j++; + } // end if-else + i++; + } // end while loop + + // any extra objects in modify sequence should consider as an add + // to the original sequence + for (; modSeqObject != null; modSeqObject = modSeq.next(), j++) { + Difference diff = new Difference(Difference.ADD, i, j); + diffVector.add(diff); + } + + // need to refresh the iterator if we split the rows + if (orgSplited) { + orgSeq.refresh(); + } + + if (modSplited) { + modSeq.refresh(); + } + + // convert the vector to array + Difference[] diffArray = new Difference[diffVector.size()]; + diffVector.toArray(diffArray); + + return diffArray; + } + + private Element splitRepeatedRow(Element orgRow, int splitNum, int orgNum) { + // NOTE: should we really want to do deep clone? + // in most the case, it is an empty Row, but the + // specification didn't forbid any node to use multiple + // column attributes. i.e. the node can contain text + // nodes or other things under it. + Element splitRow = (Element)(orgRow.cloneNode(true)); + + if (splitNum > 1) { + splitRow.setAttribute( + OfficeConstants.ATTRIBUTE_TABLE_NUM_ROWS_REPEATED, + String.valueOf(splitNum)); + } else if (splitNum == 1) { + splitRow.removeAttribute( + OfficeConstants.ATTRIBUTE_TABLE_NUM_ROWS_REPEATED); + } + if (orgNum > 1) { + orgRow.setAttribute( + OfficeConstants.ATTRIBUTE_TABLE_NUM_ROWS_REPEATED, + String.valueOf(orgNum)); + } else if (orgNum == 1) { + orgRow.removeAttribute( + OfficeConstants.ATTRIBUTE_TABLE_NUM_ROWS_REPEATED); + } + + Node parentNode = orgRow.getParentNode(); + parentNode.insertBefore(splitRow, orgRow); + + return splitRow; + } +} diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/diff/NodeIterator.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/diff/NodeIterator.java new file mode 100644 index 000000000..fe5bfdd08 --- /dev/null +++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/diff/NodeIterator.java @@ -0,0 +1,358 @@ +/* + * 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.merger.diff; + +import java.util.ArrayList; +import java.util.List; + +import org.openoffice.xmerge.ConverterCapabilities; +import org.openoffice.xmerge.merger.Iterator; +import org.openoffice.xmerge.util.Debug; +import org.openoffice.xmerge.util.Resources; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +/** + * This is an implementation of the {@code Iterator} interface. + * + * <p>It will traverse the tree and find {@code Node} sequences.</p> + * + * <p>Note: Once the XML Tree is parsed, then the {@code Iterator} will be a + * snapshot of that tree. That means even the tree is modified later, then the + * cached paragraph {@code Node} list will not be updated accordingly. For this + * reason and for performance reasons this {@code Iterator} does not support any + * operation methods such as insert, remove or replace. The main purpose of this + * {@code Iterator} is to be used with difference, not with merge.</p> + */ +public abstract class NodeIterator implements Iterator { + + private List<Node> nodeList = null; + private int currentPosition = 0; + private final Node root; + private final ConverterCapabilities cc_; + + /** + * Standard constructor. + * + * @param cc The {@code ConverterCapabilities}. + * @param node The initial root {@code Node}. + */ + public NodeIterator(ConverterCapabilities cc, Node node) { + cc_ = cc; + nodeList = new ArrayList<Node>(); + root = node; + markTree(node); + } + + public Object next() { + if (currentPosition < nodeList.size() - 1) { + currentPosition++; + return currentElement(); + } else { + return null; + } + } + + public Object previous() { + if (currentPosition > 0) { + currentPosition--; + return currentElement(); + } else { + return null; + } + } + + public Object start() { + currentPosition = 0; + return currentElement(); + } + + public Object end() { + int size = nodeList.size(); + + if (size > 0) { + currentPosition = size - 1; + return currentElement(); + } else { + return null; + } + } + + public Object currentElement() { + if (currentPosition < 0 || currentPosition >= nodeList.size()) { + return null; + } + return nodeList.get(currentPosition); + } + + public int elementCount() { + return nodeList.size(); + } + + public boolean equivalent(Object obj1, Object obj2) { + boolean equal = false; + if (!(obj1 instanceof Node && obj2 instanceof Node)) { + String errMsg = Resources.getInstance().getString("NOT_NODE_ERROR"); + Debug.log(Debug.ERROR, errMsg); + } else { + Node node1 = (Node)obj1; + Node node2 = (Node)obj2; + + equal = compareNode(node1, node2); + } + return equal; + } + + public void refresh() { + nodeList = new ArrayList<Node>(); + markTree(root); + currentPosition = 0; + } + + /** + * Used to compare two {@code Node} objects (type/name/value) and all their + * children {@code Node} objects. + * + * @param node1 The first {@code Node} to compare. + * @param node2 The second {@code Node} to compare. + * + * @return {@code true} if {@code Node} is equal, {@code false} otherwise. + */ + protected boolean compareNode(Node node1, Node node2) { + boolean equal = false; + + nodeCheck: { + + if (node1 == null || node2 == null) { + break nodeCheck; + } + + // nodevalue is short + if (node1.getNodeType() != node2.getNodeType()) { + break nodeCheck; + } + + // nodeName will not be null + if (!node1.getNodeName().equals(node2.getNodeName())) { + break nodeCheck; + } + + // nodeValue can be null for a lot of type of cells + if (node1.getNodeValue() == null && node2.getNodeValue() == null) { + // empty + } else if (node1.getNodeValue() == null || + node2.getNodeValue() == null) { + break nodeCheck; + } else if (!node1.getNodeValue().equals(node2.getNodeValue())) { + break nodeCheck; + } + + // try to compare attributes + if (!attributesEqual(node1, node2)) { + break nodeCheck; + } + + // don't need to compare if both node do not have children + if (!node1.hasChildNodes() && !node2.hasChildNodes()) { + equal = true; + break nodeCheck; + // don't need to compare if one node has children but not the other + } else if (!node1.hasChildNodes() || !node2.hasChildNodes()) { + equal = false; + break nodeCheck; + // need to compare if both node has children + } else if (!childrenEqual(node1, node2)) { + break nodeCheck; + } + + equal = true; + } + + return equal; + } + + /** + * Compare the children of two {@code Node} objects. + * + * <p>This method can be intentionally overridden by any class that extend + * from {@code NodeIterator} so that it can have its own children comparison + * if necessary.</p> + * + * @param node1 The first {@code Node} to compare. + * @param node2 The second {@code Node} to compare. + * + * @return {@code true} if children are equal, {@code false} otherwise. + */ + protected boolean childrenEqual(Node node1, Node node2) { + + boolean equal = false; + + childrenCheck: { + NodeList node1Children = node1.getChildNodes(); + NodeList node2Children = node2.getChildNodes(); + + if (node1Children == null || node2Children == null) { + break childrenCheck; + } + + if (node1Children.getLength() != node2Children.getLength()) { + break childrenCheck; + } + + // compare all the children + equal = true; + + for (int i = 0; i < node1Children.getLength(); i++) { + if (!compareNode(node1Children.item(i), + node2Children.item(i))) { + equal = false; + break childrenCheck; + } + } + } + + return equal; + } + + /** + * Compare attributes of two {@code Node} objects. + * + * <p>This method can be intentionally overridden by any class that extends + * from {@code NodeIterator} so that it can have its own attribute comparison. + * </p> + * + * @param node1 The first {@code Node} to compare. + * @param node2 The second {@code Node} to compare. + * + * @return {@code true} if attributes are equal, {@code false} otherwise. + */ + private boolean attributesEqual(Node node1, Node node2) { + + boolean equal = false; + String nodeName = node1.getNodeName(); + NamedNodeMap attrNode[] = new NamedNodeMap[2]; + attrNode[0] = node1.getAttributes(); + attrNode[1] = node2.getAttributes(); + + // Attribute node will be null if node is not an element node + // and attribute nodes are equal if both are not element node + if (attrNode[0] == null || attrNode[1] == null) { + if (attrNode[0] == null && attrNode[1] == null) { + equal = true; + } + return equal; + } + + // Compare the attributes from node1 vs node2 and node2 vs node1 + // though it's a little inefficient for the duplication of comparison + // as the number of attributes is not so many, it should not be + // a big problem. + int len [] = new int[2]; + int src, dst; + + attrCheck: { + for (int i = 0; i < 2; i++) { + + if (i == 0) { + src = 0; + dst = 1; + } else { + src = 1; + dst = 0; + } + + len[src] = attrNode[src].getLength(); + + for (int j = 0; j < len[src]; j++) { + Node srcAttr = attrNode[src].item(j); + String srcAttrName = srcAttr.getNodeName(); + + // copy the supported attrs + if (cc_ == null || + cc_.canConvertAttribute(nodeName, srcAttrName)) { + + // check whether the attribute exist in dst node + Node dstAttr = attrNode[dst].getNamedItem(srcAttrName); + + if (dstAttr == null) { + Debug.log(Debug.INFO, + "[NodeIterator] Attr not exist in dst - " + + srcAttrName); + break attrCheck; + } + + // then compare the attribute values + if (!srcAttr.getNodeValue().equals( + dstAttr.getNodeValue())) { + Debug.log(Debug.INFO, + "[NodeIterator] Attr diff src: " + + srcAttr.getNodeValue() + " dst: "+ + dstAttr.getNodeValue()); + break attrCheck; + } + } // end if cc_ loop + } // end for j loop + } // end for i loop + + // the whole checking is done smoothly and all attributes are equal + equal = true; + } + + return equal; + } + + /** + * Check whether a {@code Node} is supported. + * + * <p>This method can be intentionally overridden by any class that extends + * from {@code NodeIterator} so that it can specify which {@code Node} to + * support.</p> + * + * @param node {@code Node} to check. + * + * @return {@code true} if <code>Node</code> is supported, {@code false} + * otherwise. + */ + protected abstract boolean nodeSupported(Node node); + + // doing a depth first search for the tree and mark all supported nodes + private void markTree(Node node) { + + // if this is a supported node, then we add it to our cache table + if (nodeSupported(node)) { + nodeList.add(node); + } else { + // or we go through all children nodes recursively + // (can be optimized in future) + String nodeName = node.getNodeName(); + if ( cc_ == null || cc_.canConvertTag(nodeName)) { + NodeList auxNodeList = node.getChildNodes(); + int nodeListLength = auxNodeList.getLength(); + for (int i = 0; i < nodeListLength; i++) { + markTree(auxNodeList.item(i)); + } + } + else { + Debug.log(Debug.INFO, " [NodeIterator::markTree] Skipping node " + + nodeName); + } + } + } +} diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/diff/ObjectArrayIterator.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/diff/ObjectArrayIterator.java new file mode 100644 index 000000000..ecfe89786 --- /dev/null +++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/diff/ObjectArrayIterator.java @@ -0,0 +1,179 @@ +/* + * 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.merger.diff; + +import org.openoffice.xmerge.merger.Iterator; + +/** + * This is an implementation of the {@code Iterator} interface. + * + * <p>It is based upon a simple {@code Object} array.</p> + * + * <p>Note: this class is not thread safe for performance reasons.</p> + */ +public final class ObjectArrayIterator implements Iterator { + + /** The {@code Object} array. */ + private Object [] objArray; + private int currentPosition; + + /** + * Standard constructor. + * + * @param objArray The {@code Object} array. + */ + public ObjectArrayIterator(Object [] objArray) { + if (objArray != null) { + this.objArray = new Object[objArray.length]; + System.arraycopy(objArray, 0, this.objArray, 0, objArray.length); + currentPosition = 0; + } else { + this.objArray = new Object[0]; + } + } + + public Object next() { + if (currentPosition < objArray.length - 1) { + currentPosition++; + return currentElement(); + } else { + return null; + } + } + + public Object previous() { + if (currentPosition > 0) { + currentPosition--; + return currentElement(); + } else { + return null; + } + } + + public Object start() { + currentPosition = 0; + return currentElement(); + } + + public Object end() { + if (objArray.length > 0) { + currentPosition = objArray.length - 1; + } + return currentElement(); + } + + public Object currentElement() { + if (objArray.length > 0) { + return objArray[currentPosition]; + } else { + return null; + } + } + + /** + * Replace current {@code Object}. + * + * @param object {@code Object} to replace. + */ + public void replace(Object object) { + objArray[currentPosition] = object; + } + + /** + * Insert {@code Object} after current {@code Object}. + * + * @param object {@code Object} to insert. + */ + public void insert(Object object) { + Object [] objArray2 = new Object[objArray.length+1]; + + // copy the array content up before the currentposition + if (currentPosition > 0) { + System.arraycopy(objArray, 0, objArray2, 0, currentPosition); + } + + objArray2[currentPosition] = object; + + // copy the array content up after the currentposition + System.arraycopy(objArray, currentPosition, objArray2, + currentPosition + 1, objArray.length - currentPosition); + + objArray = objArray2; + currentPosition++; + } + + /** + * Append {@code Object} after current {@code Object}. + * + * @param object {@code Object} to append. + */ + public void append(Object object) { + Object [] objArray2 = new Object[objArray.length + 1]; + + int newPosition = currentPosition + 1; + + // copy the array content up to the currentposition + System.arraycopy(objArray, 0, objArray2, 0, newPosition); + + objArray2[newPosition] = object; + + // copy the array content up after the currentposition + if (currentPosition < objArray.length - 1) { + System.arraycopy(objArray, newPosition, objArray2, + newPosition + 1, objArray.length - newPosition); + } + + objArray = objArray2; + } + + /** + * Remove current {@code Object}. + */ + public void remove() { + Object [] objArray2 = new Object[objArray.length - 1]; + + // copy the array content up before the currentposition + if (currentPosition > 0) { + System.arraycopy(objArray, 0, objArray2, 0, currentPosition); + } + + // copy the array content up after the currentposition + if (currentPosition < objArray.length - 1) { + System.arraycopy(objArray, currentPosition + 1, objArray2, + currentPosition, objArray.length - currentPosition - 1); + } + + objArray = objArray2; + + if (currentPosition == objArray.length) + currentPosition--; + } + + public int elementCount() { + return objArray.length; + } + + public boolean equivalent(Object obj1, Object obj2) { + return obj1.equals(obj2); + } + + public void refresh() { + // do nothing + } +}
\ No newline at end of file diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/diff/ParaNodeIterator.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/diff/ParaNodeIterator.java new file mode 100644 index 000000000..0ab96e9b8 --- /dev/null +++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/diff/ParaNodeIterator.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.merger.diff; + +import org.w3c.dom.Node; + +import org.openoffice.xmerge.ConverterCapabilities; +import org.openoffice.xmerge.converter.xml.OfficeConstants; + +/** + * This is an implementation of the {@code Iterator} interface. + * + * <p>It will traverse the tree and find the Paragraph/Heading {@code Node} + * sequences.</p> + * + * <p>Note: Once the XML Tree is parsed, then the {@code Iterator} will be a + * snapshot of that tree. That means even the tree is modified later, then the + * cached paragraph {@code Node} list will not be updated accordingly. For this + * reason and for performance reasons this {@code Iterator} does not support any + * operation methods such as insert, remove or replace. The main purpose of this + * {@code Iterator} is to be used with difference, not with merge.</p> + */ +public final class ParaNodeIterator extends NodeIterator { + + /** + * Standard constructor. + * + * @param cc The {@code ConverterCapabilities}. + * @param node The initial root {@code Node}. + */ + public ParaNodeIterator(ConverterCapabilities cc, Node node) { + // not using convertercapabilities unless it's needed in future. + super(cc, node); + } + + /** + * Overwrite the parent <code>nodeSupported</code> method. + * + * @param node {@code Node} to check. + * + * @return {@code true} if the {@code Node} is supported, {@code false} + * otherwise. + */ + @Override + protected boolean nodeSupported(Node node) { + // can use an array later to check all possible tags for + // future expansion + String nodeName = node.getNodeName(); + return node.getNodeType() == Node.ELEMENT_NODE && + (nodeName.equals(OfficeConstants.TAG_PARAGRAPH) || + nodeName.equals(OfficeConstants.TAG_HEADING)); + } +} diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/diff/RowIterator.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/diff/RowIterator.java new file mode 100644 index 000000000..972e38802 --- /dev/null +++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/diff/RowIterator.java @@ -0,0 +1,66 @@ +/* + * 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.merger.diff; + +import org.w3c.dom.Node; + +import org.openoffice.xmerge.ConverterCapabilities; +import org.openoffice.xmerge.converter.xml.OfficeConstants; + +/** + * This is an implementation of the {@code Iterator} interface and extends + * {@code NodeIterator}. + * + * <p>It will traverse the tree and find row sequences.</p> + */ +public final class RowIterator extends NodeIterator { + + // TODO: should compare the ConverterCapabilities supported feature only! + // otherwise even though one with a chart, one without, will still be + // considered to be not equivalent. + + /** + * Standard constructor. + * + * @param cc The {@code ConverterCapabilities}. + * @param node The initial root {@code Node}. + */ + public RowIterator(ConverterCapabilities cc, Node node) { + super(cc, node); + } + + /** + * Overwrite the parent {@code nodeSupported} method. + * + * <p>Only row {@code Node} objects are supported.</p> + * + * @param node {@code Node} to check. + * + * @return {@code true} if the {@code Node} is supported, {@code false} + * otherwise. + */ + @Override + protected boolean nodeSupported(Node node) { + + // can use an array later to check all possible tags for + // future expansion + return node.getNodeType() == Node.ELEMENT_NODE && + node.getNodeName().equals(OfficeConstants.TAG_TABLE_ROW); + } +}
\ No newline at end of file diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/diff/TextNodeEntry.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/diff/TextNodeEntry.java new file mode 100644 index 000000000..cbfb9e93f --- /dev/null +++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/diff/TextNodeEntry.java @@ -0,0 +1,75 @@ +/* + * 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.merger.diff; + +import org.w3c.dom.Node; + +/** + * A small class to hold the start/end character position and the {@code Node} + * pointer in a text {@code Node}. + * + * <p>It is mainly used for character parser to make a list of text {@code Node} + * cache entries.</p> + */ +public class TextNodeEntry { + + private final int startChar_; + private final int endChar_; + private final Node node_; + + /** + * Constructor + * + * @param startChar The start character position. + * @param endChar The end character position. + * @param node The text {@code Node}. + */ + public TextNodeEntry(int startChar, int endChar, Node node) { + startChar_ = startChar; + endChar_ = endChar; + node_ = node; + } + + /** + * Returns the start character. + * + * @return The start character. + */ + public int startChar() { + return startChar_; + } + + /** + * Returns the end character. + * + * @return The end character. + */ + public int endChar() { + return endChar_; + } + + /** + * Returns the {@code Node}. + * + * @return The {@code Node}. + */ + public Node node() { + return node_; + } +}
\ No newline at end of file diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/diff/TextNodeIterator.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/diff/TextNodeIterator.java new file mode 100644 index 000000000..aa1e14cbb --- /dev/null +++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/diff/TextNodeIterator.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.merger.diff; + +import org.w3c.dom.Node; + +import org.openoffice.xmerge.converter.xml.OfficeConstants; + +/** + * This is an implementation of the {@code Iterator} interface. + * + * <p>It will traverse the tree and find text/space/tab {@code Node} sequences. + * </p> + * + * <p>Note: Once the XML Tree is parsed, then the {@code Iterator} will be a + * snapshot of that tree. That means even the tree is modified later, then the + * cached paragraph {@code Node} list will not be updated accordingly. For this + * reason and for performance reasons this {@code Iterator} does not support + * any operation methods such as insert, remove or replace. The main purpose of + * this {@code Iterator} is to be used with difference, not with merge.</p> + */ +public final class TextNodeIterator extends NodeIterator { + + /** + * Standard constructor. + * + * @param node The initial root {@code Node}. + */ + public TextNodeIterator(Node node) { + super(null, node); + } + + /** + * Overwrite the parent {@code nodeSupported} method. + * + * <p>Only text {@code Node} objects are supported.</p> + * + * @param node {@code Node} to check. + * + * @return {@code true} if the {@code Node} is supported, {@code false} + * otherwise. + */ + @Override + protected boolean nodeSupported(Node node) { + // can use an array later to check all possible tags for + // future expansion + String nodeName = node.getNodeName(); + return node.getNodeType() == Node.TEXT_NODE || + nodeName.equals(OfficeConstants.TAG_SPACE) || + nodeName.equals(OfficeConstants.TAG_TAB_STOP) || + nodeName.equals(OfficeConstants.TAG_LINE_BREAK); + } +} diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/diff/package-info.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/diff/package-info.java new file mode 100644 index 000000000..17a66ec6a --- /dev/null +++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/diff/package-info.java @@ -0,0 +1,26 @@ +/* + * 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 implementations for the {@link org.openoffice.xmerge.merger.Iterator + * Iterator} interface and related support classes. + * + * <p>These are used by the {@link org.openoffice.xmerge.merger.DiffAlgorithm + * DiffAlgorithm} interface.</p> + */ +package org.openoffice.xmerge.merger.diff; diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/merge/CharacterBaseParagraphMerge.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/merge/CharacterBaseParagraphMerge.java new file mode 100644 index 000000000..f2db38b65 --- /dev/null +++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/merge/CharacterBaseParagraphMerge.java @@ -0,0 +1,281 @@ +/* + * 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.merger.merge; + +import java.util.List; +import org.w3c.dom.Node; +import org.openoffice.xmerge.merger.Difference; +import org.openoffice.xmerge.merger.NodeMergeAlgorithm; +import org.openoffice.xmerge.merger.diff.CharacterParser; +import org.openoffice.xmerge.merger.diff.CharArrayLCSAlgorithm; +import org.openoffice.xmerge.merger.diff.TextNodeEntry; +import org.openoffice.xmerge.util.Debug; + +/** + * This is an implementation of the {@code NodeMergeAlgorithm} interface. + * + * <p>It is used to merge two paragraph {@code Node} objects based on character + * comparisons.</p> + */ +public final class CharacterBaseParagraphMerge implements NodeMergeAlgorithm { + + /** + * Merge two paragraphs {@code Node} by using Longest Common Subsequence + * (LCS) character algorithm defined in {@link + * org.openoffice.xmerge.merger.diff.CharArrayLCSAlgorithm + * CharArrayLCSAlgorithm}. + * + * @param orgPara The original paragraph {@code Node}. + * @param modPara The modified paragraph {@code Node}. + */ + public void merge(Node orgPara, Node modPara) { + CharacterParser orgParser = new CharacterParser(orgPara); + CharacterParser modParser = new CharacterParser(modPara); + + char[] orgCharArray = orgParser.getCharArray(); + char[] modCharArray = modParser.getCharArray(); + + CharArrayLCSAlgorithm diffAlgo = new CharArrayLCSAlgorithm(); + + Difference[] diffResult = diffAlgo.computeDiffs(orgCharArray, + modCharArray); + // debug use + System.out.println("Diff Result: "); + for (int i = 0; i < diffResult.length; i++) { + Debug.log(Debug.INFO, diffResult[i].debug()); + } + + applyDifference(orgParser, modParser, diffResult); + } + + private void applyDifference(CharacterParser orgParser, + CharacterParser modParser, + Difference[] diffs) { + + List<TextNodeEntry> orgNodeList = orgParser.getNodeList(); + int diffCount = 0; + int numNode = orgNodeList.size(); + + for (int i = 0; i < numNode; i++) { + + int extraChar = 0; + int orgDiffCount = diffCount; + TextNodeEntry orgTextNode = orgNodeList.get(i); + + Debug.log(Debug.INFO, "checking node " + (i + 1) + " of " + numNode); + + // check any difference in this node and estimate the new char num + for (; diffCount < diffs.length; diffCount++) { + + Debug.log(Debug.INFO, " checking diff " + (diffCount + 1) + + " of " + diffs.length); + Debug.log(Debug.INFO, " OrgPosision <" + + diffs[diffCount].getOrgPosition() + "> diffCount <" + + diffCount + "> orgDiffCount <" + orgDiffCount + ">"); + + // don't need to check and diffs beyond the current node text + // range except the last node + if (diffs[diffCount].getOrgPosition() > orgTextNode.endChar() && + i < numNode - 1) { + Debug.log(Debug.INFO, " breaking!"); + break; + } + + if (diffs[diffCount].getOrgPosition() + >= orgTextNode.startChar()) { + if (diffs[diffCount].getOperation() == Difference.DELETE) { + extraChar--; + } else if (diffs[diffCount].getOperation() + == Difference.ADD) { + extraChar++; + } + + } + } + + Debug.log(Debug.INFO, " final diffCount <" + diffCount + + "> final orgDiffCount <" + orgDiffCount + ">"); + + // will only try to merge if there is a difference in this node + if (diffCount > orgDiffCount) { + + Debug.log(Debug.INFO, " There is a difference, doing merge"); + Debug.log(Debug.INFO, " TextNode name <" + + orgTextNode.node().getNodeName() + ">"); + Debug.log(Debug.INFO, " TextNode value <" + + orgTextNode.node().getNodeValue() + ">"); + Debug.log(Debug.INFO, " TextNode start char <" + + orgTextNode.startChar() + "> TextNode end char <" + + orgTextNode.endChar() + ">"); + Debug.log(Debug.INFO, " extraChar value <" + extraChar + ">"); + + coreMerge(orgDiffCount, diffCount, diffs, + modParser, orgTextNode, extraChar); + } + } + } + + private void coreMerge(int startDiffNum, int endDiffNum, Difference[] diffs, + CharacterParser modParser, + TextNodeEntry orgTextNode, int extraChar) { + + Node orgNode = orgTextNode.node(); + char[] modTextArray = modParser.getCharArray(); + String tmpString; + + // Handle situation where getNodeValue returns null + if (orgNode.getNodeValue() != null) + tmpString = orgNode.getNodeValue(); + else + tmpString = ""; + + char[] orgNodeText = tmpString.toCharArray(); + char[] newNodeText; + + if (orgNodeText.length + extraChar > 0) + newNodeText = new char[orgNodeText.length + extraChar]; + else + newNodeText = new char[0]; + + int orgTextPosition = orgTextNode.startChar(); // used for block copy + int newTextPosition = 0; // used for block copy + int unChangedTextLength = 0; + + char[] cacheCharArray = new char[endDiffNum - startDiffNum]; + int cacheLength = 0; + int lastDiffOperation = Difference.UNCHANGE; + int lastDiffPosition = -1; + + // starting to diff + for (int j = startDiffNum; j < endDiffNum; j++) { + + // copy any contents before the diff + if (diffs[j].getOrgPosition() > orgTextPosition) { + // need to flush first + if (cacheLength > 0) { + System.arraycopy(cacheCharArray, 0, + newNodeText, newTextPosition, cacheLength); + newTextPosition += cacheLength; + + // reset the markers + lastDiffPosition = -1; + lastDiffOperation = Difference.UNCHANGE; + cacheLength = 0; + } + + // find out the length how many characters are + // untouched by the diff + unChangedTextLength = diffs[j].getOrgPosition() - + orgTextPosition; + System.arraycopy(orgNodeText, + orgTextPosition - orgTextNode.startChar(), + newNodeText, newTextPosition, + unChangedTextLength); + orgTextPosition += unChangedTextLength; + newTextPosition += unChangedTextLength; + } + + // for any deleted characters, just skip without copy + // but still need to take care the cached characters + if (diffs[j].getOperation() == Difference.DELETE) { + orgTextPosition++; + + // flush out the cache and copy the content to new Text + if (cacheLength > 0) { + System.arraycopy(cacheCharArray, 0, + newNodeText, newTextPosition, cacheLength); + newTextPosition += cacheLength; + + // reset the markers + lastDiffPosition = -1; + lastDiffOperation = Difference.UNCHANGE; + cacheLength = 0; + } + + continue; + + // check whether we should flush the cache. + // For changed diffs, only continuous changes can be cached + // For Add diffs, only same insertion point can be cached + // and for both changed/add diffs, need to have same operation + // as last cached diffs. + + } else { + if (lastDiffOperation != diffs[j].getOperation() || + (diffs[j].getOperation() == Difference.CHANGE && + diffs[j].getOrgPosition() != lastDiffPosition + 1) || + (diffs[j].getOperation() == Difference.ADD && + diffs[j].getOrgPosition() != lastDiffPosition)) { + + // flush the cache + if (cacheLength > 0) { + System.arraycopy(cacheCharArray, 0, newNodeText, + newTextPosition, cacheLength); + newTextPosition += cacheLength; + + // reset the markers + cacheLength = 0; + } + } + + // add the diffs to the cache, now the diffs will be either + // a new 'changed' char or is an adjacent following change of + // last difference + cacheCharArray[cacheLength] = + modTextArray[diffs[j].getModPosition()]; + cacheLength++; + lastDiffOperation = diffs[j].getOperation(); + lastDiffPosition = diffs[j].getOrgPosition(); + + // need to increment the original text position + // after we cached it + if (lastDiffOperation == Difference.CHANGE) { + orgTextPosition++; + } + } + } + + // flush any contents remaining in the cache + if (cacheLength > 0) { + System.arraycopy(cacheCharArray, 0, newNodeText, + newTextPosition, cacheLength); + newTextPosition += cacheLength; + // no need to reset any cache-related info as this is a last flush + } + + // copy any contents after all the diffs + int orgStartPosition = orgTextNode.startChar(); + if (orgNodeText.length + orgStartPosition > orgTextPosition) { + unChangedTextLength = orgNodeText.length + orgStartPosition + - orgTextPosition; + System.arraycopy(orgNodeText, orgTextPosition - orgStartPosition, + newNodeText, newTextPosition, + unChangedTextLength); + } + + // set the text to the original node if there are any diffs processed. + // can't use newNodeText.length to check as even it is empty, we may + // process a whole bunch of deletion already (i.e. the whole + // orgNodeText deleted). + if (endDiffNum > startDiffNum) { + String newString = new String(newNodeText); + orgNode.setNodeValue(newString); + } + } +} diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/merge/DocumentMerge.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/merge/DocumentMerge.java new file mode 100644 index 000000000..2c208dba0 --- /dev/null +++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/merge/DocumentMerge.java @@ -0,0 +1,218 @@ +/* + * 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.merger.merge; + +import org.w3c.dom.Element; +import org.w3c.dom.Node; + +import org.openoffice.xmerge.ConverterCapabilities; +import org.openoffice.xmerge.MergeException; +import org.openoffice.xmerge.merger.MergeAlgorithm; +import org.openoffice.xmerge.merger.NodeMergeAlgorithm; +import org.openoffice.xmerge.merger.Iterator; +import org.openoffice.xmerge.merger.Difference; +import org.openoffice.xmerge.util.XmlUtil; + +/** + * This is an implementation of the {@code MergeAlgorithm} interface. + * + * <p>This class will merge two {@code Document} classes. It utilizes the + * appropriate class which implements {@link + * org.openoffice.xmerge.merger.NodeMergeAlgorithm NodeMergeAlgorithm} to + * perform the merge.</p> + */ +public class DocumentMerge implements MergeAlgorithm { + + private final NodeMergeAlgorithm subDocumentMerge; + + /** The capabilities of this converter. */ + protected ConverterCapabilities cc_; + + /** + * Constructor. + * + * @param cc The {@code ConverterCapabilities}. + * @param merge The {@code NodeMergeAlgorithm}. + */ + public DocumentMerge(ConverterCapabilities cc, NodeMergeAlgorithm merge) { + cc_ = cc; + subDocumentMerge = merge; + } + + public void applyDifference(Iterator orgSeq, Iterator modSeq, + Difference[] differences) throws MergeException { + + // a quick test whether the differences array is in ascending order + int currentPosition = -1; + boolean haveDeleteOperation = false; + + for (Difference difference : differences) { + if (difference.getOrgPosition() > currentPosition) { + currentPosition = difference.getOrgPosition(); + haveDeleteOperation = difference.getOperation() == Difference.DELETE; + } else if (difference.getOrgPosition() == currentPosition) { + if (difference.getOperation() == Difference.DELETE) { + haveDeleteOperation = true; + } else if (difference.getOperation() == Difference.ADD && + haveDeleteOperation) { + throw new MergeException( + "Differences array is not sorted. Delete before Add"); + } + } else { + throw new MergeException("Differences array need to be sorted."); + } + } + + // reset sequence counters + orgSeq.start(); + int orgSeqCounter = 0; + + modSeq.start(); + int modSeqCounter = 0; + + // check for each diff unit in the diff array to apply the diff + for (Difference currentDiff : differences) { + + int operation = currentDiff.getOperation(); + + switch (operation) { + + case Difference.DELETE: + // loop through the original sequence up to the expected + // position. note that we use delta (see above comment) + // also. we will just continue the counter without reset it. + for (; + orgSeqCounter < currentDiff.getOrgPosition(); + orgSeqCounter++, orgSeq.next()) { + // empty + } + + // remove the Node. note that it will NOT affect the + // iterator sequence as ParaNodeIterator is a static one. + removeNode((Node)(orgSeq.currentElement())); + + break; + + // if it's an add operation, then get content from original seq + case Difference.ADD: + // loop through the modified sequence up to the expected + // position to get the content. As we don't need to modify + // the sequence. we don't need to use delta to do adjustment. + for (; + modSeqCounter < currentDiff.getModPosition(); + modSeqCounter++, modSeq.next()) { + // empty + } + + for (; + orgSeqCounter < currentDiff.getOrgPosition(); + orgSeqCounter++, orgSeq.next()) { + // empty + } + + if (orgSeqCounter > orgSeq.elementCount() - 1) { + // append the element to the end of the original sequence + appendNode((Node)(orgSeq.currentElement()), + (Node)(modSeq.currentElement())); + } else { + // insert the element BEFORE the current element + insertNode((Node)(orgSeq.currentElement()), + (Node)(modSeq.currentElement())); + } + + break; + + case Difference.CHANGE: + for (; + modSeqCounter < currentDiff.getModPosition(); + modSeqCounter++, modSeq.next()) { + // empty + } + + for (; + orgSeqCounter < currentDiff.getOrgPosition(); + orgSeqCounter++, orgSeq.next()) { + // empty + } + + if (subDocumentMerge == null) { + // use a simple replace if no row merge algorithm supply + replaceElement((Element)orgSeq.currentElement(), + (Element)modSeq.currentElement()); + } else { + subDocumentMerge.merge((Element)orgSeq.currentElement(), + (Element)modSeq.currentElement()); + + } + break; + + default: + break; + } + } + } + + /** + * Removes the specified {@code Node}. + * + * @param node {@code Node} to remove. + */ + protected void removeNode(Node node) { + + Node parent = node.getParentNode(); + parent.removeChild(node); + } + + /** + * Appends {@code Node} after the specified {@code Node}. + * + * @param oldNode {@code Node} to append after. + * @param newNode {@code Node} to append. + */ + private void appendNode(Node oldNode, Node newNode) { + Node clonedNode = XmlUtil.deepClone(oldNode, newNode); + Node parent = oldNode.getParentNode(); + parent.appendChild(clonedNode); + } + + /** + * Insert {@code Node} before the specified {@code Node}. + * + * @param oldNode {@code Node} to insert before. + * @param newNode {@code Node} to insert. + */ + private void insertNode(Node oldNode, Node newNode) { + Node clonedNode = XmlUtil.deepClone(oldNode, newNode); + Node parent = oldNode.getParentNode(); + parent.insertBefore(clonedNode, oldNode); + } + + /** + * Replace <code>Element</code>. + * + * @param currElem {@code Element} to be replaced. + * @param newElem {@code Element} to replace. + */ + private void replaceElement(Element currElem, Element newElem) { + + Node clonedNode = XmlUtil.deepClone(currElem, newElem); + Node parent = currElem.getParentNode(); + parent.replaceChild(clonedNode, currElem); + } +}
\ No newline at end of file diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/merge/PositionBaseRowMerge.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/merge/PositionBaseRowMerge.java new file mode 100644 index 000000000..afc03935e --- /dev/null +++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/merge/PositionBaseRowMerge.java @@ -0,0 +1,242 @@ +/* + * 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.merger.merge; + +import org.w3c.dom.Node; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import org.w3c.dom.NamedNodeMap; + +import org.openoffice.xmerge.ConverterCapabilities; +import org.openoffice.xmerge.merger.Iterator; +import org.openoffice.xmerge.merger.NodeMergeAlgorithm; +import org.openoffice.xmerge.merger.diff.CellNodeIterator; +import org.openoffice.xmerge.converter.xml.OfficeConstants; +import org.openoffice.xmerge.util.XmlUtil; + +/** + * This is an implementation of the {@code NodeMergeAlgorithm} interface. + * + * <p>It is used to merge two rows using a positional comparison base method. + * </p> + */ +public final class PositionBaseRowMerge implements NodeMergeAlgorithm { + + /** The capabilities of this converter. */ + private final ConverterCapabilities cc_; + + /** + * Constructor. + * + * @param cc The {@code ConverterCapabilities}. + */ + public PositionBaseRowMerge(ConverterCapabilities cc) { + cc_ = cc; + } + + public void merge(Node orgRow, Node modRow) { + + Iterator orgCells = new CellNodeIterator(cc_, orgRow); + Iterator modCells = new CellNodeIterator(cc_, modRow); + + mergeCellSequences(orgCells, modCells); + } + + // used to compare the cell 1 by 1 + private void mergeCellSequences(Iterator orgSeq, Iterator modSeq) { + + boolean needMerge = true; + Element orgCell, modCell; + + Object orgSeqObject = orgSeq.start(); + Object modSeqObject = modSeq.start(); + + while (orgSeqObject != null) { + + needMerge = true; + + if (modSeqObject == null) { + // no corresponding cell in the target, empty out the cell + SheetUtil.emptyCell(cc_, (Node)orgSeqObject); + orgSeqObject = orgSeq.next(); + + } else { + + // compare the cell directly + if (!orgSeq.equivalent(orgSeqObject, modSeqObject)) { + + orgCell = (Element)orgSeqObject; + modCell = (Element)modSeqObject; + + // check whether the original cell with multiple column + // if so, need to split one out for merge + String orgColRepeated = orgCell.getAttribute( + OfficeConstants.ATTRIBUTE_TABLE_NUM_COLUMNS_REPEATED); + String modColRepeated = modCell.getAttribute( + OfficeConstants.ATTRIBUTE_TABLE_NUM_COLUMNS_REPEATED); + + int orgColNum = 1; + int modColNum = 1; + + if (orgColRepeated.length() > 0) { + orgColNum = Integer.parseInt(orgColRepeated); + } + if (modColRepeated.length() > 0) { + modColNum = Integer.parseInt(modColRepeated); + } + + // try to find out the common number of repeated cols + if (orgColNum == modColNum) { + orgSeqObject = orgSeq.next(); + modSeqObject = modSeq.next(); + + // cut the original cell into 2 half, first half + // have the repeated attribute = modify cell attr + } else if (orgColNum > modColNum) { + Element orgSplitCell = splitColRepeatedCell( + orgCell, modColNum, + orgColNum - modColNum); + // it may equal after the split! + if (orgSeq.equivalent(orgSplitCell, modCell)) { + needMerge = false; + } + orgCell = orgSplitCell; + modSeqObject = modSeq.next(); + + // cut the modified cell into 2 half, first half + // have the repeated attribute = original cell attr + } else { + Element modSplitCell = splitColRepeatedCell( + modCell, orgColNum, + modColNum - orgColNum); + // it may equal after the split! + if (modSeq.equivalent(orgCell, modSplitCell)) { + needMerge = false; + } + modCell = modSplitCell; + orgSeqObject = orgSeq.next(); + } + + if (needMerge) { + mergeCells(orgCell, modCell); + } + + } else { + // cells are equivalent, move on to next one. + orgSeqObject = orgSeq.next(); + modSeqObject = modSeq.next(); + } // end if-else + } // end if-else + } // end while loop + + // get the one of the original cell, so that the cloned node + // can base it to find the document node + orgCell = (Element)orgSeq.start(); + + // add any extra cells to the original cell sequence. + for (; modSeqObject != null; modSeqObject = modSeq.next()) { + Node clonedNode = XmlUtil.deepClone(orgCell, (Node)modSeqObject); + Node parent = orgCell.getParentNode(); + parent.appendChild(clonedNode); + } + } + + private Element splitColRepeatedCell(Element orgCell, int splitNum, + int orgNum) { + // NOTE: should we really want to do deep clone? + // in most the case, it is an empty cell, but the + // specification didn't forbid any node to use multiple + // column attributes. i.e. the node can contain text + // nodes or other things under it. + Element splitCell = (Element)(orgCell.cloneNode(true)); + + if (splitNum > 1) { + splitCell.setAttribute( + OfficeConstants.ATTRIBUTE_TABLE_NUM_COLUMNS_REPEATED, + String.valueOf(splitNum)); + } else if (splitNum == 1) { + splitCell.removeAttribute( + OfficeConstants.ATTRIBUTE_TABLE_NUM_COLUMNS_REPEATED); + } + if (orgNum > 1) { + orgCell.setAttribute( + OfficeConstants.ATTRIBUTE_TABLE_NUM_COLUMNS_REPEATED, + String.valueOf(orgNum)); + } else if (orgNum == 1) { + orgCell.removeAttribute( + OfficeConstants.ATTRIBUTE_TABLE_NUM_COLUMNS_REPEATED); + } + + Node parentNode = orgCell.getParentNode(); + parentNode.insertBefore(splitCell, orgCell); + + return splitCell; + } + + private void mergeCells(Element orgCell, Element modCell) { + + // remove all the supported attributes and possible text child for + // string cells + SheetUtil.emptyCell(cc_, orgCell); + + // copy all the supported attributes and possible text child from + // the modified cell + NamedNodeMap attrNodes = modCell.getAttributes(); + + if (attrNodes != null) { + + // copy the first text:p node. As it's not necessary only string + // type cell can have a text:p section. + NodeList paraNodes = + modCell.getElementsByTagName(OfficeConstants.TAG_PARAGRAPH); + + Node firstParaNode = paraNodes.item(0); + + // try to clone the node + if (firstParaNode != null) { + + Node clonedNode = XmlUtil.deepClone(orgCell, firstParaNode); + + // insert as the first child of the original cell + Node firstChild = orgCell.getFirstChild(); + if (firstChild != null) { + orgCell.insertBefore(clonedNode, firstChild); + } else { + orgCell.appendChild(clonedNode); + } + } + + // check all the attributes and copy those we supported in + // converter + // NOTE: for attribute list, refer to section 4.7.2 in specification + int len = attrNodes.getLength(); + + for (int i = 0; i < len; i++) { + Node attr = attrNodes.item(i); + + // copy the supported attrs + if (cc_.canConvertAttribute(OfficeConstants.TAG_TABLE_CELL, + attr.getNodeName())) { + orgCell.setAttribute(attr.getNodeName(), + attr.getNodeValue()); + } + } + } + } +} diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/merge/SheetMerge.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/merge/SheetMerge.java new file mode 100644 index 000000000..f21be1591 --- /dev/null +++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/merge/SheetMerge.java @@ -0,0 +1,76 @@ +/* + * 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.merger.merge; + +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import org.openoffice.xmerge.ConverterCapabilities; +import org.openoffice.xmerge.merger.NodeMergeAlgorithm; + +/** + * This class extends the {@code DocumentMerge} class. + * + * <p>This class will merge two spreadsheet documents.</p> + * + * <p>The main difference between this implementation and {@code DocumentMerge} + * is that this merge will try to maintain unsupported features by examining the + * cell {@code node} objects one by one when it removes a node from the original + * {@code Iterator}.</p> + */ +public final class SheetMerge extends DocumentMerge { + + /** + * Constructor. + * + * @param cc The {@code ConverterCapabilities}. + * @param merge The {@code NodeMergeAlgorithm}. + */ + public SheetMerge(ConverterCapabilities cc, NodeMergeAlgorithm merge) { + super(cc, merge); + } + + /** + * Remove specified {@code Node}. + * + * @param node {@code Node} to remove. + */ + @Override + protected void removeNode(Node node) { + clearRow(node); + } + + /** + * Clear the row corresponding to the {@code Node}. + * + * @param node {@code Node} containing the row to clear. + */ + private void clearRow(Node node) { + NodeList children = node.getChildNodes(); + int numOfChildren = children.getLength(); + + // clear all the cells under the row node but maintain any unsupported + // features + // TODO: we can actually check anything left after the clear up. + // if there is nothing left, then we can even delete the cell nodes + for (int i = 0; i < numOfChildren; i++) { + SheetUtil.emptyCell(cc_, children.item(i)); + } + } +}
\ No newline at end of file diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/merge/SheetUtil.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/merge/SheetUtil.java new file mode 100644 index 000000000..2f5a8ebe9 --- /dev/null +++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/merge/SheetUtil.java @@ -0,0 +1,99 @@ +/* + * 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.merger.merge; + +import org.w3c.dom.Node; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import org.w3c.dom.NamedNodeMap; + +import org.openoffice.xmerge.ConverterCapabilities; +import org.openoffice.xmerge.converter.xml.OfficeConstants; + +/** + * Utility methods to handle sheet XML tree. + */ +public class SheetUtil { + + /** + * Empty the content of a cell value. + * + * <p>This includes the following:</p> + * + * <ul> + * <li> + * Remove all of the supported attributes. + * </li><li> + * Remove the first <i>text:p</i> {@code Node} for most of the cells. + * </li> + * </ul> + * + * @param cc The {@code ConverterCapabilities}. + * @param node The {@code Node}. + */ + public static void emptyCell(ConverterCapabilities cc, Node node) { + + NamedNodeMap attrNodes = node.getAttributes(); + + if (attrNodes != null) { + + // empty the first text:p node. + // Note: it's not necessary only string type cell contain text:p + // basically, all different type of cell will contain one + Element cell = (Element)node; + + // get the paragraph node list + NodeList paraNodes = + cell.getElementsByTagName(OfficeConstants.TAG_PARAGRAPH); + + Node firstParaNode = paraNodes.item(0); + + // remove the first paragraph element node + if (firstParaNode != null) { + Node parent = firstParaNode.getParentNode(); + parent.removeChild(firstParaNode); + } + + // check all the attributes and remove those we supported in + // converter + // NOTE: for attribute list, refer to section 4.7.2 in specification + int len = attrNodes.getLength(); + + for (int i = 0; i < len; ) { + Node attr = attrNodes.item(i); + + // when we hit the end of the attribute nodes, return + // it may happen sooner as we keep on removing nodes + if (attr == null) { + break; + } + // remove the supported attr except columns repeated attribute + if (cc.canConvertAttribute(OfficeConstants.TAG_TABLE_CELL, + attr.getNodeName()) && + !attr.getNodeName().equals( + OfficeConstants.ATTRIBUTE_TABLE_NUM_COLUMNS_REPEATED)) { + + attrNodes.removeNamedItem(attr.getNodeName()); + } else { + i++; + } + } + } + } +}
\ No newline at end of file diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/merge/package-info.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/merge/package-info.java new file mode 100644 index 000000000..6f65cb62c --- /dev/null +++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/merge/package-info.java @@ -0,0 +1,25 @@ +/* + * 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 implementations for the {@link + * org.openoffice.xmerge.merger.MergeAlgorithm MergeAlgorithm} interface, the + * {@link org.openoffice.xmerge.merger.NodeMergeAlgorithm NodeMergeAlgorithm} + * interface, and related support classes. + */ +package org.openoffice.xmerge.merger.merge; diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/package-info.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/package-info.java new file mode 100644 index 000000000..8b51dcbe7 --- /dev/null +++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/merger/package-info.java @@ -0,0 +1,56 @@ +/* + * 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 . + */ + +/** + * The {@code DiffAlgorithm} and {@code MergeAlgorithm} are used to provide + * the merge capabilities of this project. + * + * <p>Merge is useful when an {@code OfficeDocument} is converted to a + * {@literal "Device"} {@code Document} format, and the {@literal "Device"} + * {@code Document} version is modified. + * Those changes can be merged back into the original {@code OfficeDocument} + * with the merger. The merger is capable of doing this even if the + * {@literal "Device"} format is lossy in comparison to the + * {@code OfficeDocument} format.</p> + * + * <p>The {@code DiffAlgorithm} generates a list of {@code Difference} objects + * that represent the differences between two {@code OfficeDocument} objects. + * It is assumed that one is the original {@code OfficeDocument} object and the + * other is a "lossy" version of the same {@code Document} with edits + * to be merged. Typically the {@literal "lossy"} version is created by + * converting a {@literal "Device"} {@code Document} back into an + * {@code OfficeDocument}.</p> + * + * <p>The {@code MergeAlgorithm} takes the {@code Difference} objects as input, + * and creates a merged {@code OfficeDocument}. + * A merged {@code OfficeDocument} has the following features:</p> + * + * <ul> + * <li>Tags in the {@code OfficeDocument} that are not supported in the device + * format are not altered or removed.</li> + * <li>Changes made to the device format are merged back into the + * {@code OfficeDocument} in the location determined by the + * {@code DiffAlgorithm}.</li> + * </ul> + * + * <p>Each converter provides an implementation of the {@link + * org.openoffice.xmerge.ConverterCapabilities ConverterCapabilities} which + * specifies which {@code OfficeDocument} tags are supported for the device + * format.</p> + */ +package org.openoffice.xmerge.merger; diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/package-info.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/package-info.java new file mode 100644 index 000000000..8a50a4919 --- /dev/null +++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/package-info.java @@ -0,0 +1,89 @@ +/* + * 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 interfaces for converting between two {@code Document} formats, and + * supports a {@literal "merge"} interface for merging back changes from a + * {@literal "lossy"} format back into a rich format. + * + * <p>The {@link org.openoffice.xmerge.Convert Convert} object encapsulates the + * conversion of one format to/from another format. The user requests a + * {@code Convert} object via the {@code ConverterFactory}.</p> + * + * <p> The {@code Convert} class encapsulates a specific plug-in. + * A plug-in can support deserialization (convert from {@literal "Device"} to + * {@literal "Office"}) and/or serialization (convert from {@literal "Office"} + * to {@literal "Device"}). If a plug-in supports both deserialization and + * serialization, then it can also support {@literal "merge"}.</p> + * + * <p>To support conversions where a single input {@code Document} can create + * multiple output {@code Document} objects, data is passed in and out of the + * conversion functions via a {@code ConvertData"} object. + * This {@code ConvertData} can contain one or more {@code Document} objects. + * It is assumed that the client will know when to pass multiple files into a + * specific plug-in, and that the plug-in will know how to handle the multiple + * files.</p> + * + * <p>Merging is useful when converting from a rich {@code Document} format to + * a more lossy format. Then the user may modify the {@code Document} in the + * lossy format, and {@literal "merge"} those changes back into the original + * {@literal "rich"} {@code Document}. + * Each merge implementation provides a {@code ConverterCapabilities} + * implementation so that the merge logic knows what changes from the + * {@literal "lossy"} format to merge into the original {@literal "rich"} + * {@code Document}.</p> + * + * <p>Each plug-in must be registered via the singleton {@code ConverterInfoMgr} + * object via its {@link + * org.openoffice.xmerge.util.registry.ConverterInfoMgr#addPlugIn addPlugIn} + * method.</p> + * + * <h2>Providing implementations</h2> + * + * <p>The plug-in implementation must include the {@code getDeviceDocument} and + * {@code getOfficeDocument} methods. These functions need to return the + * appropriate type of {@code Document} for the plug-in. It may be necessary to + * create a new implementation of the {@code Document} interface if one does not + * exist that meets the needs of the plug-in.</p> + * + * <p>Currently, base implementations for working with StarWriter XML + * {@code Document} objects are available via the + * <a href="converter/xml/sxc/package-summary.html#package_description"> + * org.openoffice.xmerge.xml.sxw</a> package, and StarCalc XML {@code Document} + * objects via the + * <a href="converter/xml/sxw/package-summary.html#package_description"> + * org.openoffice.xmerge.xml.sxc</a> package.</p> + * + * <h2>TODO/IDEAS list</h2> + * + * <ol> + * <li>An idea is to combine the {@code ConvertData} and the {@code Convert} + * classes, so that a {@code ConvertData} knows what it can convert into and + * whether or not it can merge. + * Then a user would call convert/merge methods on the {@code ConvertData} + * class, which returns a {@code ConvertData} object that likewise knows what + * it can convert/merge into.</li> + * <li>{@code DocumentSerialize} constructors and the + * {@code DocumentDeserializer.deserializer} method could pass in a + * {@code ConvertData} object rather than assuming a single {@code Document} + * will represent a {@literal "rich"} {@code Document}.</li> + * <li>May need to add a {@code PluginFactory.setProperties} method for adding + * properties specific to each converter.</li> + * </ol> + */ +package org.openoffice.xmerge;
\ No newline at end of file diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/test/ConverterInfoList.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/test/ConverterInfoList.java new file mode 100644 index 000000000..3c9caf197 --- /dev/null +++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/test/ConverterInfoList.java @@ -0,0 +1,91 @@ +/* + * 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.test; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.Properties; + +/** + * Loads a properties file so that registry knows which plug-ins it needs to + * load. + */ +public class ConverterInfoList { + + private static String defaultPropsFile = "ConverterInfoList.properties"; + private ArrayList<String> jars; + + /** + * This constructor loads and reads the default properties file. + * + * <p>The default property file name is: + * {@literal "ConverterInfoList.properties"}. + * + * @throws IOException If any I/O error occurs. + */ + public ConverterInfoList() throws IOException { + this(defaultPropsFile); + } + + /** + * This constructor loads and reads the properties file. + * + * @param propsFile The properties file to load. + * + * @throws IOException If any I/O error occurs. + */ + public ConverterInfoList(String propsFile) throws IOException { + + Class<? extends ConverterInfoList> c = this.getClass(); + InputStream is = c.getResourceAsStream(propsFile); + BufferedInputStream bis = new BufferedInputStream(is); + Properties props = new Properties(); + try { + props.load(bis); + } finally { + bis.close(); + } + + int i = 1; + String jarFileName = ""; + jars = new ArrayList<String>(); + + while ((jarFileName = props.getProperty("jarname" + i)) != null) { + jars.add(jarFileName); + i++; + } + } + + /** + * Returns an {@code Iterator} of {@code String} objects. + * + * <p>Each {@code String} describes a plug-in to be loaded into the registry. + * + * @return An {@code Iterator} of {@code String} objects. + * Each {@code String} describes a plug-in to be loaded into the + * registry. + */ + public Iterator<String> getJarFileEnum() { + + return jars.iterator(); + } +} diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/test/ConverterInfoList.properties b/xmerge/source/xmerge/java/org/openoffice/xmerge/test/ConverterInfoList.properties new file mode 100644 index 000000000..4611114ac --- /dev/null +++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/test/ConverterInfoList.properties @@ -0,0 +1,28 @@ +# +# 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 + +# +# Jarfiles to be loaded +# +jarname1=file:///jarDirectory/htmlsoff.jar + +# +# Pocket Word plugin +# +jarname2=file:///jarDirectory/pocketword.jar diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/test/Driver.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/test/Driver.java new file mode 100644 index 000000000..43555e88d --- /dev/null +++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/test/Driver.java @@ -0,0 +1,300 @@ +/* + * 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.test; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.util.ArrayList; +import java.util.Iterator; + +import org.openoffice.xmerge.Convert; +import org.openoffice.xmerge.ConvertData; +import org.openoffice.xmerge.ConverterFactory; +import org.openoffice.xmerge.Document; +import org.openoffice.xmerge.DocumentMerger; +import org.openoffice.xmerge.util.registry.ConverterInfo; +import org.openoffice.xmerge.util.registry.ConverterInfoMgr; +import org.openoffice.xmerge.util.registry.ConverterInfoReader; + +/** + * This class is a command-line driver for the converter framework. + * + * <p>It is expected that this code will be later called by the device server. + * It does some basic validation of the command-line parameters.</p> + */ +public final class Driver { + + /** Command-line parameter. */ + private String fromMime = null; + + /** Command-line parameter. */ + private String toMime = null; + + /** {@code mergeFile} name. */ + private String mergeFile = null; + + /** Command-line parameter. */ + private final ArrayList<String> deviceFiles = new ArrayList<String>(); + + /** Command-line parameter shortcuts. */ + private final String mimeTypes[] = { + "sxc", "staroffice/sxc", + "sxw","staroffice/sxw" + }; + + /** + * Main. + * + * @param args The argument passed on the command line. + */ + public static void main(String args[]) { + try { + // Register jarfiles + String propFile = "ConverterInfoList.properties"; + ConverterInfoList cil = new ConverterInfoList(propFile); + + Iterator<String> jarFileEnum = cil.getJarFileEnum(); + while (jarFileEnum.hasNext()) { + String jarName = jarFileEnum.next(); + try { + ConverterInfoReader cir = new ConverterInfoReader(jarName, false); + Iterator<ConverterInfo> jarInfoEnumeration = cir.getConverterInfoEnumeration(); + ConverterInfoMgr.addPlugIn(jarInfoEnumeration); + } catch (Exception e) { + System.out.println("\nCannot not load <" + jarName + + "> from the <" + propFile + "> property file"); + } + } + + Driver app = new Driver(); + app.parseCommandLine(args); + app.doConversion(); + } catch (IllegalArgumentException ex) { + + String msg = ex.getMessage(); + if (msg != null) System.out.println("\n" + msg); + showUsage(); + + } catch (Exception ex) { + + String msg = ex.getMessage(); + if (msg != null) System.out.println("\n" + msg); + ex.printStackTrace(); + } + } + + private static void close(FileOutputStream c) { + if (c == null) return; + try { + c.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * Gets a {@code Convert} object using the {@code ConverterFactory} and does + * the conversion using this object. + * + * @throws IllegalArgumentException If an argument is invalid. + */ + private void doConversion() throws IllegalArgumentException { + + ConverterFactory cf = new ConverterFactory(); + Convert myConvert = cf.getConverter(fromMime, toMime); + String processFile = null; + + if (myConvert == null) { + System.out.println("\nNo plug-in exists to convert from <" + + fromMime + "> to <" + toMime + ">"); + throw new IllegalArgumentException(); + } + + try { + Iterator<String> dfEnum = deviceFiles.iterator(); + while (dfEnum.hasNext()) { + processFile = dfEnum.next(); + File f = new File(processFile); + + // Make sure the input file actually exists before using it + if (!f.exists()) { + System.out.println(processFile + " does not exist!"); + System.exit(0); + } + FileInputStream fis = new FileInputStream(f); + myConvert.addInputStream(f.getName(), fis); + } + } catch (Exception addExcept) { + throw new IllegalArgumentException("\nFile <" + processFile + "> is not in <" + + fromMime + "> format", addExcept); + } + + ConvertData dataOut = null; + + try { + dataOut = myConvert.convert(); + } catch (Exception convertExcept) { + System.out.println("\nThere was an error in the conversion"); + convertExcept.printStackTrace(); + } + + if (dataOut != null ) { + + if (mergeFile == null) { + Iterator<Object> docEnum = dataOut.getDocumentEnumeration(); + while (docEnum.hasNext()) { + Document docOut = (Document)docEnum.next(); + String fileName = docOut.getFileName(); + FileOutputStream fos = null; + try { + fos = new FileOutputStream(fileName); + docOut.write(fos); + fos.flush(); + } catch (Exception writeExcept) { + System.out.println("\nThere was a writing out file <" + + fileName + ">"); + writeExcept.printStackTrace(); + } finally { + close(fos); + } + } + } else { + try { + FileInputStream mergeIS = new FileInputStream(mergeFile); + Document mergeDoc = myConvert.getOfficeDocument(mergeFile, mergeIS); + DocumentMerger merger = myConvert.getDocumentMerger(mergeDoc); + Iterator<Object> mergeDocEnum = dataOut.getDocumentEnumeration(); + Document convertedFile = (Document)mergeDocEnum.next(); + + merger.merge(convertedFile); + mergeIS.close(); + + FileOutputStream fos = null; + try { + fos = new FileOutputStream(mergeFile); + mergeDoc.write(fos); + fos.flush(); + } finally { + close(fos); + } + } catch (Exception mergeExcept) { + System.out.println("\nThere was an error in the merge"); + mergeExcept.printStackTrace(); + } + } + } + } + + /** + * Display usage. + */ + private static void showUsage() { + System.out.println("\nUsage:"); + System.out.println("\n java org.openoffice.xmerge.test.Driver <args>"); + System.out.println("\n where <args> is as follows:"); + System.out.println(" -from <MIMETYPE> -to <MIMETYPE> [ -merge <OrigDoc ] <document>\n"); + } + + /** + * Parse command-line arguments. + * + * @param args Array of command line arguments. + * + * @throws IllegalArgumentException If an argument is invalid. + */ + private void parseCommandLine(String args[]) + throws IllegalArgumentException { + + if (args.length == 0) { + throw new IllegalArgumentException(); + } + + for (int i = 0; i < args.length; i++) { + String arg = args[i]; + + if ("-to".equals(arg)) { + toMime = extractArg(i, args); + for (int j = 0; j < mimeTypes.length; j+=2) { + if(mimeTypes[j].equals(extractArg(i, args))) + toMime = mimeTypes[j+1]; + } + i++; + } else if ("-from".equals(arg)) { + fromMime = extractArg(i, args); + for (int j = 0; j < mimeTypes.length; j+=2) { + if(mimeTypes[j].equals(extractArg(i, args))) + fromMime = mimeTypes[j+1]; + } + i++; + } else if ("-merge".equals(arg)) { + mergeFile = extractArg(i, args); + if (!isZip(mergeFile)) { + throw new + IllegalArgumentException("Arg " + i + + ": expected zip, got " + + mergeFile); + } + i++; + } else { + deviceFiles.add(arg); + } + } + + System.out.println("\nConverting from " + fromMime + " to " + toMime + + ((mergeFile != null) ? " with merge " : " ")); + } + + /** + * Extract the next argument from the array, while checking to see that the + * array size is not exceeded. + * + * <p>Throw a friendly error message in case the {@code args[i+1]} is + * missing.</p> + * + * @param i Argument index. + * @param args Array of command line arguments. + * + * @return The argument with the specified index. + * + * @throws IllegalArgumentException If an argument is invalid. + */ + private String extractArg(int i, String args[]) + throws IllegalArgumentException { + + if (i+1 < args.length) + return args[i+1]; + else throw new + IllegalArgumentException("Arg " + i + + ": expected arg for " + args[i]); + } + + /** + * Simple validation for Office ZIP files. + * + * @param zipName The name of the ZIP file. + * + * @return {@code true} if {@code zipName} is valid, {@code false} otherwise. + */ + private boolean isZip(String zipName) { + + String str = zipName.toLowerCase(); + return str.endsWith("sxw") || zipName.endsWith("sxc"); + } +} diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/util/ActiveSyncDriver.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/util/ActiveSyncDriver.java new file mode 100644 index 000000000..829963283 --- /dev/null +++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/util/ActiveSyncDriver.java @@ -0,0 +1,143 @@ +/* + * 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.util; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.FileInputStream; +import java.io.IOException; + +import java.util.StringTokenizer; +import java.util.NoSuchElementException; + +import org.openoffice.xmerge.Convert; +import org.openoffice.xmerge.Document; +import org.openoffice.xmerge.ConvertData; +import org.openoffice.xmerge.ConvertException; +import org.openoffice.xmerge.ConverterFactory; +import org.openoffice.xmerge.util.registry.ConverterInfoMgr; +import org.openoffice.xmerge.util.registry.ConverterInfoReader; + +public class ActiveSyncDriver { + public static void main(String[] args) { + if (args.length != 4) { + return; + } + + ActiveSyncDriver asd = new ActiveSyncDriver(); + + try { + // At the moment can't really signal back to the calling DLL + asd.Convert(args[0], args[1], args[2], args[3]); + } + catch (Exception e) { + return; + } + } + + private boolean Convert(String srcMime, String dstMime, String srcFile, + String dstFile) throws Exception { + /* + * The classpath passed in by XMergeSync.dll should contain all of the + * jar files, but at the least it will contain xmerge.jar, so strip off + * the xmerge.jar portion and use the remainder to provide a root for + * the Pocket Word and Pocket Excel plugins. + */ + String ooClassDir = null; + String strClassPath = System.getProperty("java.class.path"); + + StringTokenizer st = new StringTokenizer(strClassPath, ";"); + + // There should be at least one element, but just in case + while (st.hasMoreTokens()) { + String s = st.nextToken(); + + if (s.endsWith("xmerge.jar")) { + ooClassDir = s.substring(0, s.indexOf("xmerge.jar")); + } + } + + if (ooClassDir == null) { + return true; + } + + /* + * The XMergeSync.dll should will have checked for the presence of the + * jars at the same location already. + * + * Because they can be installed separately, though, the MIME types need + * to be check to see which one to load. + */ + File pluginJar; + if (srcMime.equals("staroffice/sxw") || srcMime.equals("application/x-pocket-word")) { + pluginJar = new File(ooClassDir + "pocketWord.jar"); + } else { + if (srcMime.equals("staroffice/sxc") || srcMime.equals("application/x-pocket-excel")) { + pluginJar = new File(ooClassDir + "pexcel.jar"); + } else { + return false; + } + } + + ConverterInfoReader cirPlugin = new ConverterInfoReader(pluginJar.toURI().toURL().toString(), false); + + ConverterInfoMgr.addPlugIn(cirPlugin.getConverterInfoEnumeration()); + + ConverterFactory cf = new ConverterFactory(); + Convert conv = cf.getConverter(srcMime, dstMime); + + if (conv == null) { + return false; + } + + // Everything is registered so do the conversion + boolean bOK = true; + FileInputStream fis = null; + FileOutputStream fos = null; + try { + fis = new FileInputStream(srcFile); + conv.addInputStream(srcFile, fis); + try { + fos = new FileOutputStream(dstFile); + ConvertData dataOut = conv.convert(); + + // Get the document and write it out. + Document doc = (Document)dataOut.getDocumentEnumeration().next(); + doc.write(fos); + fos.flush(); + } finally { + if (fos != null) + fos.close(); + } + } catch (IOException e) { + bOK = false; + } catch (NullPointerException e) { + bOK = false; + } catch (ConvertException e) { + bOK = false; + } catch (NoSuchElementException e) { + bOK = false; + } finally { + if (fis != null) + fis.close(); + } + + return bOK; + } +}
\ No newline at end of file diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/util/ColourConverter.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/util/ColourConverter.java new file mode 100644 index 000000000..aefb3f966 --- /dev/null +++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/util/ColourConverter.java @@ -0,0 +1,421 @@ +/* + * 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.util; + +import java.awt.Color; + +/** + * Utility class mapping RGB colour specifications to the colour indices used + * in the Pocket PC. + * + * <p>The original converter was written for use with Pocket Word it was later + * put into the utils so Pocket excel could use this code also. For this reason + * the default values are those used by Pocket Word but a colour table can be + * passed in through the constructor to map the 16 values to a colour table.</p> + * + * <p>These colour indices are based on the Windows VGA 16 colour palette, which + * later was used as the basis for the named colours in the HTML 3.2 + * specification.</p> + * + * <p>In Pocket Word's case, the match to the VGA 16 palette is not exact as it + * swaps Grey and Silver, with Silver being the darker colour (i.e. having the + * lower RGB value).</p> + */ + +public class ColourConverter { + + /** Colour table index for Black */ + private static final short BLACK = 0; + + /** Colour table index for Silver */ + private static final short SILVER = 1; + + /** Colour table index for Grey */ + private static final short GREY = 2; + + /** Colour table index for White */ + private static final short WHITE = 3; + + /** Colour table index for Red */ + private static final short RED = 4; + + /** Colour table index for Lime */ + private static final short LIME = 5; + + /** Colour table index for Blue */ + private static final short BLUE = 6; + + /** Colour table index for Aqua */ + private static final short AQUA = 7; + + /** Colour table index for Fuchsia */ + private static final short FUCHSIA = 8; + + /** Colour table index for Yellow */ + private static final short YELLOW = 9; + + /** Colour table index for Maroon */ + private static final short MAROON = 10; + + /** Colour table index for Green */ + private static final short GREEN = 11; + + /** Colour table index for Navy */ + private static final short NAVY = 12; + + /** Colour table index for Teal */ + private static final short TEAL = 13; + + /** Colour table index for Purple */ + private static final short PURPLE = 14; + + /** Colour table index for Olive */ + public static final short OLIVE = 15; + + private short tableLookup[] = null; + + /** + * Default constructor used in the case where a lookup table is not required. + */ + public ColourConverter() { + + } + + /** + * Constructor that passes in the colour lookup table. + * + * <p>This is required in cases where the 16 colour values are something + * other than there default values (e.g. in the case of pocket Excel).</p> + * + * @param lookup a 16 bit array mapping the 16 colours to their values. + */ + public ColourConverter(short lookup[]) { + tableLookup = lookup; + } + + /** + * Uses the colour table if it exists to translate default values to values + * in the colorTable. + */ + private short colourLookup(short colour) { + if(tableLookup!=null) { + return tableLookup[colour]; + } else { + return colour; + } + } + + /** + * Uses the colour table if it exists to translate default values to values + * in the colorTable. + */ + private short indexLookup(short index) { + + short result = 0; + + if(tableLookup!=null) { + for(short i = 0;i < tableLookup.length;i++) { + if(tableLookup[i]==index) + result = i; + } + } else { + result = index; + } + + return result; + } + /** + * This method maps a Pocket Word colour index value to an RGB value as used + * by OpenOffice. + * + * @param colour The index into Pocket Word's colour table. + * + * @return A {@code Color} object representing the RGB value of the Pocket + * Word colour. + */ + public Color convertToRGB (short colour) { + + short index = indexLookup(colour); + + int r = 0; + int g = 0; + int b = 0; + + switch (index) { + case SILVER: + r = g = b = 128; + break; + + case GREY: + r = g = b = 192; + break; + + case WHITE: + r = g = b = 255; + break; + + case RED: + r = 255; + break; + + case LIME: + g = 255; + break; + + case BLUE: + b = 255; + break; + + case AQUA: + g = b = 255; + break; + + case FUCHSIA: + r = b = 255; + break; + + case YELLOW: + r = g = 255; + break; + + case MAROON: + r = 128; + break; + + case GREEN: + g = 128; + break; + + case NAVY: + b = 128; + break; + + case TEAL: + b = g = 128; + break; + + case PURPLE: + r = b = 128; + break; + + case OLIVE: + r = g = 128; + break; + + case BLACK: + default: + r = g = b = 0; + break; + } + + return new Color(r, g, b); + } + + /** + * This method approximates an RGB value (as used by Writer) to one of the + * 16 available colours. + * + * <p>Most of the supported colours have their components set to either 0, + * 128 or 255. The exception is 'Grey' which is {@literal 0xC0C0C0}.</p> + * + * @param colour {@code Color} object representing the RGB value of the + * colour. + * + * @return Index into the Pocket Word colour table which represents the + * closest match to the specified colour. + */ + public short convertFromRGB (Color colour) { + int matchedRGB = 0; + short indexColour = 0; + int reducedMap[] = new int[] { 0, 0, 128 }; + + int red = colour.getRed(); + int green = colour.getGreen(); + int blue = colour.getBlue(); + + // We need to convert the pale colours to their base color rather than + // white so we modify the rgb values if the colour is sufficiently white. + if(red>0xC0 && green>0xC0 && blue>0xC0) { + + if(red!=0xFF) + red = getClosest(red, reducedMap); + if(green!=0xFF) + green = getClosest(green, reducedMap); + if(blue!=0xFF) + blue = getClosest(blue, reducedMap); + } + + // Need to derive an RGB value that has been rounded to match the ones + // Pocket Word knows about. + matchedRGB += getClosest(red) << 16; + matchedRGB += getClosest(green) << 8; + matchedRGB += getClosest(blue); + + // The colour map used by Pocket Word doesn't have any combinations of + // values beyond 0 and any other value. A value of 255 in any RGB code + // indicates a dominant colour. Other colours are only modifiers to the + // principal colour(s). Thus, for this conversion, modifiers can be + // dropped. + if ((matchedRGB & 0xFF0000) == 0xFF0000 || (matchedRGB & 0xFF00) == 0xFF00 + || (matchedRGB & 0xFF) == 0xFF) { + if ((matchedRGB & 0xFF0000) == 0x800000) { + matchedRGB ^= 0x800000; + } + if ((matchedRGB & 0xFF00) == 0x8000) { + matchedRGB ^= 0x8000; + } + if ((matchedRGB & 0xFF) == 0x80) { + matchedRGB ^= 0x80; + } + } + + /* + * And now for the actual matching ... + * + * Colours are based on the Windows VGA 16 palette. One difference + * though is that Pocket Word seems to switch the RGB codes for Grey + * and Silver. In Pocket Word Silver is the darker colour leaving Grey + * is closest to White. + * + * Shades of grey will be converted to either Silver or White, where + * Grey may be a more appropriate colour. This is handled specially + * only for Silver and White matches. + */ + switch (matchedRGB) { + case 0x000000: + indexColour = BLACK; + break; + + case 0x808080: + if (!isGrey(colour)) { + indexColour = SILVER; + } + else { + indexColour = GREY; + } + break; + + case 0xFFFFFF: + if (!isGrey(colour)) { + indexColour = WHITE; + } + else { + indexColour = GREY; + } + break; + + case 0xFF0000: + indexColour = RED; + break; + + case 0x00FF00: + indexColour = LIME; + break; + + case 0x0000FF: + indexColour = BLUE; + break; + + case 0x00FFFF: + indexColour = AQUA; + break; + + case 0xFF00FF: + indexColour = FUCHSIA; + break; + + case 0xFFFF00: + indexColour = YELLOW; + break; + + case 0x800000: + indexColour = MAROON; + break; + + case 0x008000: + indexColour = GREEN; + break; + + case 0x000080: + indexColour = NAVY; + break; + + case 0x008080: + indexColour = TEAL; + break; + + case 0x800080: + indexColour = PURPLE; + break; + + case 0x808000: + indexColour = OLIVE; + break; + + default: // Just in case! + indexColour = BLACK; + break; + } + + return colourLookup(indexColour); + } + + /** + * Default implementation, checks for the closest of value to 0, 128 or 255. + */ + private int getClosest(int value) { + int points[] = new int[] { 0, 128, 255 }; + + return getClosest(value, points); + } + + /** + * Utility method that returns the closest of the three points to the value + * supplied. + */ + private int getClosest(int value, int[] points) { + + if (value == points[0] || value == points[1] || value == points[2]) { + return value; + } + + if (value < points[1]) { + int x = value - points[0]; + return (Math.round((float)x / (points[1] - points[0])) == 1 ? points[1] : points[0]); + } + else { + int x = value - points[1]; + return (Math.round((float)x / (points[2] - points[1])) >= 1 ? points[2] : points[1]); + } + } + + /** + * Checks to see if the supplied colour can be considered to be grey. + */ + private boolean isGrey(Color c) { + int matchedRGB = 0; + int points[] = new int[] { 128, 192, 255 }; + + matchedRGB += getClosest(c.getRed(), points) << 16; + matchedRGB += getClosest(c.getGreen(), points) << 8; + matchedRGB += getClosest(c.getBlue(), points); + + return matchedRGB == 0xC0C0C0; + } +}
\ No newline at end of file diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/util/Debug.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/util/Debug.java new file mode 100644 index 000000000..ed95031f8 --- /dev/null +++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/util/Debug.java @@ -0,0 +1,230 @@ +/* + * 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.util; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.FileWriter; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.io.Writer; +import java.util.Properties; + +/** + * This class is used for logging debug messages. + * + * <p>Currently, there are three types of logging: {@link #INFO}, {@link #TRACE} + * {@literal &} {@link #ERROR}. Use the {@code Debug.properties} file to set or + * unset each type. Also use {@code Debug.properties} to set the writer to + * either {@code System.out}, {@code System.err}, or to a file.</p> + */ +public final class Debug { + + /** Informational messages. */ + public static final int INFO = 0x0001; + /** Error messages. */ + public static final int ERROR = 0x0002; + /** Trace messages. */ + public static final int TRACE = 0x0004; + + /** To set a flag. */ + private static final boolean SET = true; + + private static int flags = 0; + private static PrintWriter writer = null; + + static { + + InputStream is = null; + try { + try { + is = Debug.class.getResourceAsStream("Debug.properties"); + Properties props = new Properties(); + props.load(is); + + String info = props.getProperty("debug.info", "false"); + info = info.toLowerCase(); + + if (info.equals("true")) { + setFlags(Debug.INFO, Debug.SET); + } + + String trace = props.getProperty("debug.trace", "false"); + trace = trace.toLowerCase(); + + if (trace.equals("true")) { + setFlags(Debug.TRACE, Debug.SET); + } + + String error = props.getProperty("debug.error", "false"); + error = error.toLowerCase(); + + if (error.equals("true")) { + setFlags(Debug.ERROR, Debug.SET); + } + + String w = props.getProperty("debug.output", "System.out"); + setOutput(w); + + } finally { + if (is !=null) + is.close(); + } + } catch (Throwable ex) { + ex.printStackTrace(System.err); + } + } + + /** + * Private constructor so as not to allow any instances of this class. + * + * <p>This serves as a singleton class.</p> + */ + private Debug() { + } + + /** + * Set the output to the specified argument. + * + * <p>This method is only used internally to prevent invalid string + * parameters.</p> + * + * @param str Output specifier. + */ + private static void setOutput(String str) { + if (writer == null) { + if (str.equals("System.out")) { + setOutput(System.out); + } else if (str.equals("System.err")) { + setOutput(System.err); + } else { + try { + setOutput(new FileWriter(str)); + } catch (IOException e) { + e.printStackTrace(System.err); + } + } + } + } + + /** + * Set the output to an {@code OutputStream} object. + * + * @param stream {@code OutputStream} object. + */ + private static void setOutput(OutputStream stream) { + setOutput(new OutputStreamWriter(stream)); + } + + /** + * Set the {@code Writer} object to manage the output. + * + * @param w {@code Writer} object to write out. + */ + private static void setOutput(Writer w) { + if (writer != null) { + writer.close(); + } + writer = new PrintWriter(new BufferedWriter(w), true); + } + + /** + * This method sets the levels for debugging logs. + * + * <p>Example calls:</p> + * + * <blockquote><pre>{@code Debug.setFlags( Debug.INFO, Debug.SET ) + * Debug.setFlags( Debug.TRACE, Debug.SET ) + * Debug.setFlags( Debug.INFO | Debug.TRACE, Debug.SET ) + * Debug.setFlags( Debug.ERROR, Debug.UNSET )}</pre></blockquote> + * + * @param f Debug flag + * @param set Use {@code Debug.SET} to set, and {@code Debug.UNSET} to + * unset the given flag. + */ + private static void setFlags(int f, boolean set) { + if (set) { + flags |= f; + } else { + flags &= ~f; + } + } + + /** + * Checks if flag is set. + * + * @return {@code true} if info logging is on, otherwise {@code false}. + */ + public static boolean isFlagSet(int f) { + return ((flags & f) != 0); + } + + /** + * Log message based on the flag type. + * + * <p>Example 1:</p> + * + * <blockquote><pre>{@code Debug.log(Debug.INFO, "info string here");}</pre> + * </blockquote> + * + * <p>This logs the message during runtime if {@code debug.info} in the + * properties file is set to true.</p> + * + * <p>Example 2:</p> + * + * <blockquote> + * <pre>{@code Debug.log(Debug.INFO | Debug.TRACE, "info string here");}</pre> + * </blockquote> + * + * <p>This logs the message during runtime if debug.info or debug.trace in + * the properties file is set to true.</p> + * + * @param flag Log type, one of the Debug constants {@link #INFO}, + * {@link #TRACE}, {@link #ERROR} or a combination of which + * or'ed together. + * @param msg The message. + */ + public static void log(int flag, String msg) { + if (isFlagSet(flag) && writer != null) { + writer.println(msg); + } + } + + /** + * Log message based on flag type plus print out stack trace of the + * exception passed in. + * + * <p>Refer to the other log method for description.</p> + * + * @param flag Log type, one of the Debug constants {@link #INFO}, + * {@link #TRACE}, {@link #ERROR} or a combination of which + * or'ed together. + * @param msg The message. + * @param e Throwable object. + */ + public static void log(int flag, String msg, Throwable e) { + if (isFlagSet(flag) && writer != null) { + writer.println(msg); + if (e != null) + e.printStackTrace(writer); + } + } +}
\ No newline at end of file diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/util/Debug.properties b/xmerge/source/xmerge/java/org/openoffice/xmerge/util/Debug.properties new file mode 100644 index 000000000..5802cb86c --- /dev/null +++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/util/Debug.properties @@ -0,0 +1,29 @@ +# +# 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 + +# +# debug logging information and settings. +# + +debug.info=false +debug.trace=false +debug.error=false +debug.output=System.err + + diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/util/EndianConverter.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/util/EndianConverter.java new file mode 100644 index 000000000..a796806f9 --- /dev/null +++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/util/EndianConverter.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.util; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * Helper class providing static methods to convert data to/from Network Byte + * Order (Big Endian). + * + * <p>With the introduction of {@code java.nio.ByteOrder} and + * {@code java.nio.ByteBuffer} in Java 1.4, it may no longer be necessary to use + * this class in the future.</p> + * + * @version 1.1 + */ +public class EndianConverter { + + /** + * Convert a {@code short} to a Little Endian representation. + * + * @param value The {@code short} to be converted. + * + * @return Two element {@code byte} array containing the converted value. + */ + public static byte[] writeShort (short value) { + return ByteBuffer.allocate(2).order(ByteOrder.LITTLE_ENDIAN) + .putShort(value).array(); + } + + /** + * Convert an integer to a Little Endian representation. + * + * @param value The {@code int} to be converted. + * + * @return Four element {@code byte} array containing the converted value. + */ + public static byte[] writeInt (int value) { + return ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN) + .putInt(value).array(); + } + + /** + * Converts a {@code double} to a Little Endian representation of a float in + * IEEE-754 format. + * + * <p>An array with more than eight elements can be used, but only the first + * eight elements will be read.</p> + * + * @param value {@code double} containing the value to be converted. + * + * @return {@code byte} array containing the LE representation of a + * IEEE-754 float. + */ + public static byte[] writeDouble(double value) { + return ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putLong( + Double.doubleToLongBits(value)).array(); + } + + /** + * Convert a Little Endian representation of a short to a Java {@code short} + * (Network Byte Order). + * + * <p>An array with more than two elements can be used, but only the first + * two elements will be read.</p> + * + * @param value {@code byte} array containing the LE representation of + * the value. + * + * @return {@code short} containing the converted value. + */ + public static short readShort (byte[] value) { + return ByteBuffer.allocate(2).order(ByteOrder.LITTLE_ENDIAN) + .put(value).getShort(0); + } + + /** + * Convert a Little Endian representation of an integer to a Java {@code int} + * (Network Byte Order). + * + * <p>An array with more than four elements can be used, but only the first + * four elements will be read.</p> + * + * @param value {@code byte} array containing the LE representation of + * the value. + * + * @return {@code int} containing the converted value. + */ + public static int readInt(byte[] value) { + return ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN) + .put(value).getInt(0); + } + + /** + * Convert a Little Endian representation of a float in IEEE-754 Little + * Endian to a Java {@code double} (Network Byte Order). + * + * <p>An array with more than eight elements can be used, but only the first + * eight elements will be read.</p> + * + * @param value {@code byte} array containing the LE representation of an + * IEEE-754 float. + * + * @return {@code double} containing the converted value. + */ + public static double readDouble(byte[] value) { + return Double.longBitsToDouble( + ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).put(value) + .getLong(0)); + } +}
\ No newline at end of file diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/util/IntArrayList.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/util/IntArrayList.java new file mode 100644 index 000000000..56b8212b6 --- /dev/null +++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/util/IntArrayList.java @@ -0,0 +1,125 @@ +/* + * 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.util; + +import java.util.ArrayList; + +/** + * This is a convenience class used to create an {@code ArrayList} of integers. + */ +public class IntArrayList { + + /** The list to hold our integers. */ + private final ArrayList<Integer> list; + + /** + * Constructor. + * + * <p>Creates the list with 0 length.</p> + */ + public IntArrayList() { + list = new ArrayList<Integer>(); + } + + /** + * Constructor. + * + * @param initialCapacity Initial capacity of the list. + */ + public IntArrayList(int initialCapacity) { + list = new ArrayList<Integer>(initialCapacity); + } + + /** + * This method ensures that the list is large enough for {@code minCapacity} + * elements. + * + * @param minCapacity The minimum capacity of the list. + */ + public void ensureCapacity(int minCapacity) { + list.ensureCapacity(minCapacity); + } + + /** + * This method ensures that the list is large enough for {@code minCapacity} + * elements. + * + * <p>It also fills in the new slots in the list with the integer value + * input as {@code fillValue}.</p> + * + * @param minCapacity The minimum capacity of the list. + * @param fillValue This method adds in an integer for each slot that was + * added to ensure that the list meets the minimum + * capacity. {@code fillValue} is the value used as the + * initial value of these added elements. + */ + public void ensureCapacityAndFill(int minCapacity, int fillValue) { + + list.ensureCapacity(minCapacity); + + int needToAdd = minCapacity - list.size(); + if (needToAdd > 0) { + for (int i = 0; i < needToAdd; i++) { + list.add(Integer.valueOf(fillValue)); + } + } + } + + /** + * This method sets an element of the list to the input integer value. + * + * @param index The index in the list of the element we wish to set. + * @param value The integer value that we assign to the selected element + * of the list. + */ + public void set(int index, int value) { + list.set(index, Integer.valueOf(value)); + } + + /** + * This method appends an element to the list. + * + * @param value The integer value that we assign to the element that we + * are appending to the list. + */ + public void add(int value) { + list.add(Integer.valueOf(value)); + } + + /** + * This method gets the integer value stored in element index. + * + * @param index The index in the list of the element we wish to get the + * value from. + * + * @return The value of the data stored in element index. + */ + public int get(int index) { + return list.get(index).intValue(); + } + + /** + * This method gets the size of the list. + * + * @return The size of the list. + */ + public int size() { + return list.size(); + } +}
\ No newline at end of file diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/util/OfficeUtil.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/util/OfficeUtil.java new file mode 100644 index 000000000..305100772 --- /dev/null +++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/util/OfficeUtil.java @@ -0,0 +1,123 @@ +/* + * 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.util; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.Text; + +import org.openoffice.xmerge.converter.xml.OfficeConstants; + +import java.util.ArrayList; + +/** + * Class providing utility methods for OpenOffice plug-ins. + * + * @version 1.1 + */ +public class OfficeUtil implements OfficeConstants { + + /** + * Method to replace whitespace character within text with appropriate + * OpenOffice tags. + * + * @param text The text to parse for whitespace. + * + * @return {@code Node} array containing OpenOffice XML nodes representing + * the text. + */ + public static Node[] parseText(String text, Document parentDoc) { + ArrayList<Node> nodeVec = new ArrayList<Node>(); + + /* + * Break up the text from the text run into Open + * Office text runs. There may be more runs in OO because + * runs of 2 or more spaces map to nodes. + */ + while ((text.indexOf(" ") != -1) || (text.indexOf('\t') != 1)) { + + /* + * Find the indices of tabs and multiple spaces, and + * figure out which of them occurs first in the string. + */ + int spaceIndex = text.indexOf(" "); + int tabIndex = text.indexOf('\t'); + if ((spaceIndex == -1) && (tabIndex == -1)) + break; // DJP This should not be necessary. What is wrong + // with the while() stmt up above? + int closerIndex; // Index of the first of these + if (spaceIndex == -1) + closerIndex = tabIndex; + else if (tabIndex == -1) + closerIndex = spaceIndex; + else + closerIndex = (spaceIndex > tabIndex) ? tabIndex : spaceIndex; + + /* + * If there is any text prior to the first occurrence of a + * tab or spaces, create a text node from it, then chop it + * off the string we're working with. + */ + if (closerIndex > 0) { + String beginningText = text.substring(0, closerIndex); + Text textNode = parentDoc.createTextNode(beginningText); + nodeVec.add(textNode); + } + text = text.substring(closerIndex); + + /* + * Handle either tab character or space sequence by creating + * an element for it, and then chopping out the text that + * represented it in "text". + */ + if (closerIndex == tabIndex) { + Element tabNode = parentDoc.createElement(TAG_TAB_STOP); + nodeVec.add(tabNode); + text = text.substring(1); // tab is always a single character + } else { + // Compute length of space sequence. + int nrSpaces = 2; + while ((nrSpaces < text.length()) + && text.substring(nrSpaces, nrSpaces + 1).equals(" ")) { + nrSpaces++; + } + + Element spaceNode = parentDoc.createElement(TAG_SPACE); + spaceNode.setAttribute(ATTRIBUTE_SPACE_COUNT, + Integer.toString(nrSpaces)); + nodeVec.add(spaceNode); + text = text.substring(nrSpaces); + } + } + + /* + * No more tabs or space sequences. If there's any remaining + * text create a text node for it. + */ + if (text.length() > 0) { + Text textNode = parentDoc.createTextNode(text); + nodeVec.add(textNode); + } + + // Now create and populate an array to return the nodes in. + Node nodes[] = nodeVec.toArray(new Node[nodeVec.size()]); + return nodes; + } +} diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/util/Resources.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/util/Resources.java new file mode 100644 index 000000000..aa7dd7c10 --- /dev/null +++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/util/Resources.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.util; + +import java.util.ResourceBundle; + +/** + * Provides a singleton resource class for converter messages. + * + * <p>By default, this class will search for a {@code ResourceBundle} class file + * or properties file based on the default locale.</p> + * + * <p>A properties file resources.properties will be provided.</p> + * + * <p>Note that if the resource bundle object is not loaded, the construction of + * the singleton object will throw a {@code MissingResourceException}, which is + * a {@code RuntimeException}, thus I opted to not explicitly declare it. If it + * does throw {@code MissingResourceException}, it may be due to a packaging + * problem.</p> + */ +public final class Resources +{ + private final ResourceBundle rb; + private static Resources instance = null; + + /** + * This method returns the singleton instance of this class. + * + * @return The singleton {@code Resources} instance. + */ + public synchronized static Resources getInstance() { + if (instance == null) { + instance = new Resources(); + } + + return instance; + } + + /** + * Default constructor is only accessible within this class. + * + * <p>Load the resource bundle that contains the resource {@code String} + * values.</p> + */ + private Resources() { + rb = ResourceBundle.getBundle("org.openoffice.xmerge.util.resources"); + } + + /** + * This method returns the corresponding {@code String} given the key. + * + * @param key Key string for getting the message {@code String}. + * @return Message {@code String} corresponding to the key. + */ + public String getString(String key) { + return rb.getString(key); + } +}
\ No newline at end of file diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/util/TwipsConverter.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/util/TwipsConverter.java new file mode 100644 index 000000000..a8501f8b9 --- /dev/null +++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/util/TwipsConverter.java @@ -0,0 +1,84 @@ +/* + * 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.util; + +/** + * Helper class providing static methods to convert data to/from twips. + */ +public class TwipsConverter { + + /** + * Convert from twips to cm's. + * + * @param value The {@code short} to be converted. + * + * @return {@code float} containing the converted. + */ + public static float twips2cm(int value) { + float inches = (float) value/1440; + float cm = inches*(float)2.54; + return cm; + } + + /** + * Convert from cm's to twips. + * + * @param value {@code float} containing the representation of the value. + * + * @return {@code int} containing the converted value. + */ + private static int cm2twips(float value) { + int twips = (int) ((value/2.54)*1440); + return twips; + } + + /** + * Convert from cm's to twips. + * + * @param value {@code float} containing the representation of the value. + * + * @return {@code int} containing the converted value. + */ + private static int inches2twips(float value) { + return (int) (value*1440); + } + + /** + * Convert {@code String} to twips. + * + * @param value {@code String} in the form {@literal "1.234cm"} or + * {@literal "1.234inch"}. + * @param defaultValue the default value. + * @return the converted value if {@code value} is a well-formatted {@code + * String}, {@code defaultValue} otherwise. + */ + public static int convert2twips(String value, int defaultValue) { + int posi; + + if ((posi = value.indexOf("cm")) != -1) { + float cm = Float.parseFloat(value.substring(0,posi)); + return cm2twips(cm); + } else if ((posi = value.indexOf("inch")) != -1) { + float inches = Float.parseFloat(value.substring(0,posi)); + return inches2twips(inches); + } + + return defaultValue; + } +}
\ No newline at end of file diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/util/XmlUtil.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/util/XmlUtil.java new file mode 100644 index 000000000..fd39536b3 --- /dev/null +++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/util/XmlUtil.java @@ -0,0 +1,171 @@ +/* + * 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.util; + +import org.w3c.dom.Node; +import org.w3c.dom.Document; +import org.w3c.dom.NodeList; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; + +/** + * Class containing static utility methods for handling XML trees. + */ +public final class XmlUtil { + + /** + * Perform a deep clone of certain {@code Node} which will base on the + * document {@code Node} of the old {@code Node}. + * + * @param oldNode The {@code Document} of this {@code Node} is used to + * clone the {@code Node}. + * @param newNode The <code>Node</code> to clone. + * + * @return The cloned {@code Node}. + */ + public static Node deepClone(Node oldNode, Node newNode) { + Document docNode = oldNode.getOwnerDocument(); + + // clone the starting node + Node clonedNode = cloneNode(docNode, newNode); + + if (clonedNode != null) { + // then clone the sub-tree recursively + cloneTree(docNode, clonedNode, newNode); + } + + return clonedNode; + } + + /** + * Clone the sub-tree under certain given {@code Node}. + * + * @param docNode The {@code Document} used to clone the {@code Node}. + * @param oldNode The {@code Node} to clone. + * @param newNode The destination {@code Node}. + */ + private static void cloneTree(Document docNode, Node oldNode, Node newNode) { + + NodeList nodeList = newNode.getChildNodes(); + int nodeListLen = nodeList.getLength(); + + for (int i = 0; i < nodeListLen; i++) { + Node newClonedChild = cloneNode(docNode, nodeList.item(i)); + if (newClonedChild != null) { + oldNode.appendChild(newClonedChild); + cloneTree(docNode, newClonedChild, nodeList.item(i)); + } + } + } + + /** + * Clone a {@code Node} (either text or element). + * + * @param docNode The {@code Document} used to clone the {@code Node}. + * @param newNode The {@code Node}to clone. + * + * @return The cloned {@code Node}. + */ + private static Node cloneNode(Document docNode, Node newNode) { + + Node clonedNode = null; + + // only support text node and element node (will copy the attributes) + switch (newNode.getNodeType()) { + case Node.TEXT_NODE: + String textStr = newNode.getNodeValue(); + clonedNode = docNode.createTextNode(textStr); + break; + case Node.ELEMENT_NODE: + Element oldElem = (Element)newNode; + String tagName = newNode.getNodeName(); + Element newElem = docNode.createElement(tagName); + + // copy the attributes + NamedNodeMap attrs = oldElem.getAttributes(); + + for (int i = 0; i < attrs.getLength(); i++) { + newElem.setAttribute(attrs.item(i).getNodeName(), + attrs.item(i).getNodeValue()); + } + clonedNode = newElem; + break; + } + return clonedNode; + } + + /** + * Returns the name and type of an XML DOM {@code Node}. + * + * @param node {@code Node} to query. + * + * @return Name and type of XML DOM {@code Node}. + */ + public static String getNodeInfo(Node node) { + + String str = null; + switch (node.getNodeType()) { + + case Node.ELEMENT_NODE: + str = "ELEMENT"; + break; + case Node.ATTRIBUTE_NODE: + str = "ATTRIBUTE"; + break; + case Node.TEXT_NODE: + str = "TEXT"; + break; + case Node.CDATA_SECTION_NODE: + str = "CDATA_SECTION"; + break; + case Node.ENTITY_REFERENCE_NODE: + str = "ENTITY_REFERENCE"; + break; + case Node.ENTITY_NODE: + str = "ENTITY"; + break; + case Node.PROCESSING_INSTRUCTION_NODE: + str = "PROCESSING_INSTRUCTION"; + break; + case Node.COMMENT_NODE: + str = "COMMENT"; + break; + case Node.DOCUMENT_NODE: + str = "DOCUMENT"; + break; + case Node.DOCUMENT_TYPE_NODE: + str = "DOCUMENT_TYPE"; + break; + case Node.DOCUMENT_FRAGMENT_NODE: + str = "DOCUMENT_FRAGMENT"; + break; + case Node.NOTATION_NODE: + str = "NOTATION"; + break; + } + + StringBuffer buffer = new StringBuffer("name=\""); + buffer.append(node.getNodeName()); + buffer.append("\" type=\""); + buffer.append(str); + buffer.append("\""); + + return buffer.toString(); + } +} diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/util/package-info.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/util/package-info.java new file mode 100644 index 000000000..db76b1cc9 --- /dev/null +++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/util/package-info.java @@ -0,0 +1,22 @@ +/* + * 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 general purpose utilities. + */ +package org.openoffice.xmerge.util; diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/util/registry/ConverterInfo.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/util/registry/ConverterInfo.java new file mode 100644 index 000000000..2190ab27c --- /dev/null +++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/util/registry/ConverterInfo.java @@ -0,0 +1,406 @@ +/* + * 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.util.registry; + +import java.lang.reflect.Constructor; +import java.net.URL; +import java.net.URLClassLoader; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.ArrayList; +import java.util.Iterator; + +import org.openoffice.xmerge.DocumentDeserializerFactory; +import org.openoffice.xmerge.DocumentMergerFactory; +import org.openoffice.xmerge.DocumentSerializerFactory; +import org.openoffice.xmerge.PluginFactory; + +/** + * Class for storing the information about a converter plug-in. + */ +public class ConverterInfo { + + /** Keep track of the valid Office mime types. */ + private static final String[] validOfficeTypes = new String[] { + // This needs to be updated to reflect all valid office types. + "staroffice/sxw", + "staroffice/sxc" + }; + + private final String piJarName; + private final String piOfficeMime; + private final ArrayList<String> piDeviceMime; + private final String piDisplayName; + private final String piDescription; + private final String piVersion; + private final String piVendor; + private final String piClassImpl; + private String piXsltSerial; + private String piXsltDeserial; + private boolean piCanSerialize = false; + private boolean piCanDeserialize = false; + private boolean piCanMerge = false; + private final ClassLoader piClassLoader; + private PluginFactory piPluginFactory; + + + /** + * The constructor builds a ConverterInfo structure. + * + * @param jarName The URL of the jarfile. + * @param officeMime The office mime-type. + * @param deviceMime The device mime-type. + * @param displayName The display name. + * @param description The description. + * @param version The version. + * @param vendor The vendor name. + * @param impl The implementation class name of + * {@code PluginFactory}. + * @param xsltSerial The URL of the serializer XSL stylesheet + * @param xsltDeserial The URL of the deserializer XSL stylesheet + * + * @throws RegistryException If {@code ci} cannot be loaded. + */ + public ConverterInfo(String jarName, String officeMime, + ArrayList<String> deviceMime, String displayName, + String description, String version, String vendor, + String impl,String xsltSerial, String xsltDeserial) + throws RegistryException { + + if (!isValidOfficeType(officeMime.trim())) { + RegistryException re = new RegistryException( "Invalid office type"); + throw re; + } + + piJarName = jarName.trim(); + piOfficeMime = officeMime.trim(); + piDeviceMime = deviceMime; + piDisplayName = displayName.trim(); + piDescription = description.trim(); + piVersion = version.trim(); + piVendor = vendor.trim(); + piXsltSerial = xsltSerial.trim(); + piXsltDeserial= xsltDeserial.trim(); + piClassImpl = impl.trim(); + piClassLoader = this.getClass().getClassLoader(); + + // Get instance of the PluginFactory. + + try { + final URL jarURL = new URL(jarName); + final URL[] urls = new URL[] { jarURL }; + URLClassLoader loader = AccessController.doPrivileged( + new PrivilegedAction<URLClassLoader>() { + public URLClassLoader run() { + return new URLClassLoader(urls, piClassLoader); + } + }); + Class<?> clas = loader.loadClass(piClassImpl); + Class<?>[] argumentTypes = { org.openoffice.xmerge.util.registry.ConverterInfo.class }; + Constructor<?> construct = clas.getConstructor(argumentTypes); + + Object[] arguments = { this }; + piPluginFactory = ( PluginFactory ) construct.newInstance(arguments); + + // See which interfaces the plug-in PluginFactory supports. + + Class<?>[] cl = piPluginFactory.getClass().getInterfaces(); + for (int i=0; i < cl.length; i++) { + + if (cl[i].getName().equals("org.openoffice.xmerge.DocumentSerializerFactory")) { + piCanSerialize = true; + } + if (cl[i].getName().equals("org.openoffice.xmerge.DocumentDeserializerFactory")) { + piCanDeserialize = true; + } + if (cl[i].getName().equals("org.openoffice.xmerge.DocumentMergerFactory")) { + piCanMerge = true; + } + } + + } catch (Exception e) { + throw new RegistryException( + "Class implementation of the plug-in cannot be loaded.", e); + } + } + + /** + * The constructor builds a ConverterInfo structure. + * + * @param jarName The URL of the jarfile. + * @param officeMime The office mime-type. + * @param deviceMime The device mime-type. + * @param displayName The display name. + * @param description The description. + * @param version The version. + * @param vendor The vendor name. + * @param impl The implementation class name of + * {@code PluginFactory}. + * + * @throws RegistryException If {@code ci} cannot be loaded. + */ + public ConverterInfo(String jarName, String officeMime, + ArrayList<String> deviceMime, String displayName, String description, + String version, String vendor, String impl) + throws RegistryException { + + if (officeMime == null || displayName == null || description == null || + version == null || vendor == null || impl == null) + throw new IllegalArgumentException("arguments unexpected null"); + + if (!isValidOfficeType(officeMime.trim())) { + RegistryException re = new RegistryException( + "Invalid office type"); + throw re; + } + + piJarName = jarName.trim(); + piOfficeMime = officeMime.trim(); + piDeviceMime = deviceMime; + piDisplayName = displayName.trim(); + piDescription = description.trim(); + piVersion = version.trim(); + piVendor = vendor.trim(); + piClassImpl = impl.trim(); + piClassLoader = this.getClass().getClassLoader(); + + // Get instance of the PluginFactory. + + try { + final URL jarURL = new URL(jarName); + final URL[] urls = new URL[] { jarURL }; + URLClassLoader loader = AccessController.doPrivileged( + new PrivilegedAction<URLClassLoader>() { + public URLClassLoader run() { + return new URLClassLoader(urls, piClassLoader); + } + }); + Class<?> clas = loader.loadClass(piClassImpl); + Class<?>[] argumentTypes = { org.openoffice.xmerge.util.registry.ConverterInfo.class }; + Constructor<?> construct = clas.getConstructor(argumentTypes); + + Object[] arguments = { this }; + piPluginFactory = ( PluginFactory ) construct.newInstance(arguments); + + // See which interfaces the plug-in PluginFactory supports. + + Class<?>[] cl = piPluginFactory.getClass().getInterfaces(); + for (int i=0; i < cl.length; i++) { + + if (cl[i].getName().equals("org.openoffice.xmerge.DocumentSerializerFactory")) { + piCanSerialize = true; + } + if (cl[i].getName().equals("org.openoffice.xmerge.DocumentDeserializerFactory")) { + piCanDeserialize = true; + } + if (cl[i].getName().equals("org.openoffice.xmerge.DocumentMergerFactory")) { + piCanMerge = true; + } + } + + } catch (Exception e) { + throw new RegistryException( + "Class implementation of the plug-in cannot be loaded.", e); + } + } + + /** + * Returns an instance of the {@code DocumentDeserializerFactory} interface. + * + * @return instance of the {@code DocumentDeserializer} for this + * {@code ConverterInfo}. + */ + public DocumentSerializerFactory getDocSerializerFactory() { + return (DocumentSerializerFactory)piPluginFactory; + } + + /** + * Returns an instance of the {@code DocumentSerializerFactory} interface. + * + * @return instance of the {@code DocumentSerializer} for this + * {@code ConverterInfo}. + */ + public DocumentDeserializerFactory getDocDeserializerFactory() { + return (DocumentDeserializerFactory)piPluginFactory; + } + + /** + * Returns an instance of the DocumentMergerFactory interface. + * + * @return instance of the {@code DocumentMergerFactory} for this + * {@code ConverterInfo}. + */ + public DocumentMergerFactory getDocMergerFactory() { + return (DocumentMergerFactory)piPluginFactory; + } + + /** + * Returns the jar file name. + * + * @return The jar file name, {@code null} if none exists. + */ + public String getJarName() { + return piJarName; + } + + /** + * Returns the office mime-type. + * + * @return The office mime-type, {@code null} if none exists. + */ + public String getOfficeMime() { + return piOfficeMime; + } + + /** + * Returns an {@code Enumeration} of {@code String} objects indicating the + * device mime-type. + * + * @return An {@code Enumeration} of {@code String} objects indicating the + * device mime-type. + */ + public Iterator<String> getDeviceMime() { + return piDeviceMime.iterator(); + } + + /** + * Returns the display name. + * + * @return The display name, {@code null} if none exists. + */ + public String getDisplayName() { + return piDisplayName; + } + + /** + * Returns the description. + * + * @return The description, {@code null} if none exists. + */ + public String getDescription() { + return piDescription; + } + + /** + * Returns the version. + * + * @return The version, {@code null} if none exists. + */ + public String getVersion() { + return piVersion; + } + + /** + * Returns the vendor name. + * + * @return The vendor name, {@code null} if none exists. + */ + public String getVendor() { + return piVendor; + } + + /** + * Returns the implementation class name of PluginFactory. + * + * @return The implementation class name of {@code PluginFactory}, + * {@code null} if none exists. + */ + public String getClassImpl() { + return piClassImpl; + } + + /** + * Returns the {@code PluginFactory} instance for this plug-in. + * + * @return The {@code PluginFactory} instance for this plug-in. + */ + public PluginFactory getPluginFactory() { + return piPluginFactory; + } + + /** + * Returns {@code true} if this plug-in has a serializer, {@code false} + * otherwise. + * + * @return {@code true} if this plug-in has a serializer, {@code false} + * otherwise. + */ + public boolean canSerialize() { + return piCanSerialize; + } + + /** + * Returns {@code true} if this plug-in has a deserializer, {@code false} + * otherwise. + * + * @return {@code true} if this plug-in has a deserializer, {@code false} + * otherwise. + */ + public boolean canDeserialize() { + return piCanDeserialize; + } + + /** + * Returns {@code true} if this plug-in has a merger, {@code false} + * otherwise. + * + * @return {@code true} if this plug-in has a merger, {@code false} + * otherwise. + */ + public boolean canMerge() { + return piCanMerge; + } + + /** + * Returns {@code true} if the officeMime is a valid Office mime type. + * + * @return {@code true} if the officeMime is a valid Office mime type. + */ + public static boolean isValidOfficeType(String officeMime) { + + boolean rc = false; + for (String validOfficeType : validOfficeTypes) { + if (officeMime.equals(validOfficeType)) { + rc = true; + } + } + + return rc; + } + + /** + * Returns a {@code String} containing the Xslt stylesheet URL that is to be + * used by the Xslt Plug-in Serializer. + * + * @return {@code String}. + */ + public String getXsltSerial() { + return piXsltSerial; + } + + /** + * Returns a {@code String} containing the xslt stylesheet URL that is to be + * used by the Xslt Plug-in Deserializer. + * + * @return {@code String}. + */ + public String getXsltDeserial() { + return piXsltDeserial; + } +} diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/util/registry/ConverterInfoMgr.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/util/registry/ConverterInfoMgr.java new file mode 100644 index 000000000..9cea2df38 --- /dev/null +++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/util/registry/ConverterInfoMgr.java @@ -0,0 +1,477 @@ +/* + * 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.util.registry; + +import java.util.*; +import java.io.*; + +/** + * Manages the converter plug-ins that are currently active. + * + * <p>This class allows plug-ins to be added or removed dynamically.</p> + * + * <p>This class is a singleton (static) class, so that only one manager can + * exist at a time. It is final, so it may not be sub-classed.</p> + */ +public final class ConverterInfoMgr { + + private static final ArrayList<ConverterInfo> converterInfoList = new ArrayList<ConverterInfo>(); + + /** + * Adds a converter plug-in to the registry. + * + * <p>The {@code ConverterInfo} must have a unique DisplayName and must have + * non-null values for DisplayName, ClassImpl, OfficeMime, and DeviceMime.</p> + * + * @param ci A {@code ConverterInfo} object describing a plug-in. + * + * @throws RegistryException If the {@code ConverterInfo} is not valid. + */ + private static void addPlugIn(ConverterInfo ci) throws RegistryException { + + // Validate + if (ci.getDisplayName() == null) { + RegistryException re = new RegistryException( + "Converter must have valid name."); + throw re; + } + if (ci.getClassImpl() == null) { + RegistryException re = new RegistryException( + "Converter must have valid class implementation specified."); + throw re; + } + if (ci.getOfficeMime() == null) { + RegistryException re = new RegistryException( + "Converter must have valid office mime specified."); + throw re; + } + if (! ci.getDeviceMime().hasNext()) { + RegistryException re = new RegistryException( + "Converter must have valid device mime specified."); + throw re; + } + + // Verify there is no converter with the same Display Name in the + // registry. + for (ConverterInfo converterInfo : converterInfoList) { + if (ci.getDisplayName().equals(converterInfo.getDisplayName())) { + RegistryException re = new RegistryException( + "Converter with specified display name already exists."); + throw re; + } + } + + // Since this is adding to a static Vector, make sure this add method + // call is synchronized. + synchronized (converterInfoList) { + converterInfoList.add(ci); + } + } + + /** + * Adds a list of converter plug-ins to the registry. + * + * <p>Each {@code ConverterInfo} in the list must have a unique DisplayName + * and must have non-null values for DisplayName, ClassImpl, OfficeMime, and + * DeviceMime.</p> + * + * @param jarEnum An {@code Enumeration} of {@code ConverterInfo} objects + * describing one or more plug-in(s). + * + * @throws RegistryException If a {@code ConverterInfo} in the + * {@code Vector} is not valid. + */ + public static void addPlugIn(Iterator<ConverterInfo> jarEnum) + throws RegistryException { + + while (jarEnum.hasNext()) { + ConverterInfo converterInfo = jarEnum.next(); + addPlugIn(converterInfo); + } + } + + /** + * Returns an {@code Enumeration} of registered {@code ConverterInfo} objects. + * + * @return An {@code Enumeration} containing the currently registered + * {@code ConverterInfo} objects, an empty {@code Vector} if none + * exist. + */ + private static Iterator<ConverterInfo> getConverterInfoEnumeration() { + return converterInfoList.iterator(); + } + + /** + * Removes any {@code ConverterInfo} object from the registry that have the + * specified jar name value. + * + * @param jar The name of the jarfile. + * + * @return {@code true} if a {@code ConverterInfo} object was removed, + * {@code false} otherwise. + */ + public static boolean removeByJar(String jar) { + + boolean rc = false; + + for (Iterator<ConverterInfo> it = converterInfoList.iterator(); it.hasNext();) { + ConverterInfo converterInfo = it.next(); + if (jar.equals(converterInfo.getJarName())) { + it.remove(); + rc = true; + } + } + return rc; + } + + /** + * Removes any {@code ConverterInfo} object from the registry that have the + * specified display name value. + * + * @param name The display name. + * + * @return {@code true} if a {@code ConverterInfo} object was removed, + * {@code false} otherwise. + */ + private static boolean removeByName(String name) { + + boolean rc = false; + + for (Iterator<ConverterInfo> it = converterInfoList.iterator(); it.hasNext();) { + ConverterInfo converterInfo = it.next(); + if (name.equals(converterInfo.getDisplayName())) { + it.remove(); + rc = true; + } + } + return rc; + } + + /** + * Returns the {@code ConverterInfo} object that supports the specified + * device/office mime type conversion. + * + * <p>If there are multiple {@code ConverterInfo} objects registered that + * support this conversion, only the first is returned.</p> + * + * @param deviceMime The device mime. + * @param officeMime The office mime. + * + * @return The first plug-in that supports the specified conversion. + */ + public static ConverterInfo findConverterInfo(String deviceMime, String officeMime) { + + if (deviceMime == null || + !ConverterInfo.isValidOfficeType(officeMime)) { + return null; + } + + // Loop over elements comparing with deviceFromMime + for (ConverterInfo converterInfo : converterInfoList) { + String toDeviceInfo = converterInfo.getOfficeMime(); + Iterator<String> fromEnum = converterInfo.getDeviceMime(); + + // Loop over the deviceMime types. + while (fromEnum.hasNext()) { + String fromDeviceInfo = fromEnum.next(); + if (deviceMime.trim().equals(fromDeviceInfo) && + officeMime.trim().equals(toDeviceInfo)) { + return converterInfo; + } + } + } + return null; + } + + /** + * Returns an array of two {@code ConverterInfo} objects that can be chained + * to perform the specified mime type conversion. + * + * <p>If there are multiple {@code ConverterInfo} objects that support this + * conversion, only the first is returned.</p> + * + * @param deviceFromMime The device from mime. + * @param deviceToMime The device to mime. + * + * @return An array of two {@code ConverterInfo} objects that can be chained + * to perform the specified conversion. + */ + private static ConverterInfo[] findConverterInfoChain(String deviceFromMime, String deviceToMime) { + + if (deviceFromMime == null || deviceToMime == null) { + return null; + } + + ConverterInfo[] converterInfo = new ConverterInfo[2]; + + // Loop over elements comparing with deviceFromMime + Iterator<ConverterInfo> cifEnum = converterInfoList.iterator(); + while (cifEnum.hasNext()) { + + converterInfo[0] = cifEnum.next(); + String fromOfficeInfo = converterInfo[0].getOfficeMime(); + Iterator<String> fromEnum = converterInfo[0].getDeviceMime(); + + // Loop over the deviceMime types looking for a deviceFromMime + // match. + while (fromEnum.hasNext()) { + String fromDeviceInfo = fromEnum.next(); + + if (deviceFromMime.trim().equals(fromDeviceInfo)) { + + // Found a match for deviceFrom. Now loop over the + // elements comparing with deviceToMime + Iterator<ConverterInfo> citEnum = converterInfoList.iterator(); + while (citEnum.hasNext()) { + + converterInfo[1] = citEnum.next(); + String toOfficeInfo = converterInfo[1].getOfficeMime(); + Iterator<String> toEnum = converterInfo[1].getDeviceMime(); + + // Loop over deviceMime types looking for a + // deviceToMime match. + while (toEnum.hasNext()) { + String toDeviceInfo = toEnum.next(); + if (deviceToMime.trim().equals(toDeviceInfo) && + fromOfficeInfo.equals(toOfficeInfo)) { + + // Found a match + return converterInfo; + } + } + } + } + } + } + return null; + } + + static String readLine(BufferedReader br) throws IOException{ + String ret = br.readLine(); + if (ret == null) { + throw new IOException("short read"); + } + return ret; + } + + /** + * Main to let the user specify what plug-ins to register from jarfiles and + * to display the currently registered plug-ins. + * + * @param args Not used. + */ + public static void main(String args[]) { + + ConverterInfoReader cir = null; + boolean validate = false; + InputStreamReader isr = new InputStreamReader(System.in); + BufferedReader br = new BufferedReader(isr); + char c = ' '; + + boolean exitFlag = false; + while (!exitFlag) { + + System.out.println("\nMenu:"); + System.out.println("(L)oad plug-ins from a jar file"); + System.out.println("(D)isplay name unload"); + System.out.println("(J)ar name unload"); + System.out.println("(F)ind ConverterInfo"); + System.out.println("(C)ind ConverterInfo chain"); + System.out.println("(V)iew plug-ins"); + System.out.println("(T)oggle Validation"); + System.out.println("(Q)uit\n"); + + try { + c = readLine(br).toUpperCase().trim().charAt(0); + } catch(Exception e) { + System.out.println("Invalid entry"); + System.out.println("Error msg: " + e.getMessage()); + continue; + } + + System.out.println(""); + + // Quit + if (c == 'Q') { + exitFlag = true; + + // Load by Jarfile + } else if (c == 'L') { + + System.out.println("Enter path to jarfile: "); + try { + String jarname = readLine(br).trim(); + cir = new ConverterInfoReader(jarname,validate); + } catch (RegistryException e) { + System.out.println("Cannot load plug-in ConverterFactory implementation."); + System.out.println("Error msg: " + e.getMessage()); + } catch (Exception e) { + System.out.println("Error adding data to registry"); + System.out.println("Error msg: " + e.getMessage()); + } + + if (cir != null) { + Iterator<ConverterInfo> jarInfoEnum = cir.getConverterInfoEnumeration(); + try { + ConverterInfoMgr.addPlugIn(jarInfoEnum); + } catch (Exception e) { + System.out.println("Error adding data to registry"); + System.out.println("Error msg: " + e.getMessage()); + } + } + + // Unload by Display Name or Jarfile + } else if (c == 'T') { + if (validate){ + System.out.println("Validation switched off"); + validate=false; + } else { + System.out.println("Validation switched on"); + validate=true; + } + } else if (c == 'D' || c == 'J') { + + if (c == 'D') { + System.out.println("Enter display name: "); + } else { + System.out.println("Enter path to jarfile: "); + } + + try { + String name = readLine(br).trim(); + boolean rc = false; + + if (c == 'D') { + rc = ConverterInfoMgr.removeByName(name); + } else { + rc = ConverterInfoMgr.removeByJar(name); + } + + if (rc) { + System.out.println("Remove successful."); + } else { + System.out.println("Remove failed."); + } + + } catch (Exception e) { + System.out.println("Error removing value from registry"); + System.out.println("Error msg: " + e.getMessage()); + } + + // Find Office Mime + + } else if (c == 'F' || c == 'C') { + + String findMimeOne = null; + String findMimeTwo = null; + + if (c == 'F') { + System.out.println("Enter device mime: "); + } else { + System.out.println("Enter device from mime: "); + } + + try { + findMimeOne = readLine(br).trim(); + } catch (Exception e) { + System.out.println("Error adding data to registry"); + System.out.println("Error msg: " + e.getMessage()); + } + + if (c == 'F') { + System.out.println("Enter office mime: "); + } else { + System.out.println("Enter device to mime: "); + } + + try { + findMimeTwo = readLine(br).trim(); + } catch (Exception e) { + System.out.println("Error adding data to registry"); + System.out.println("Error msg: " + e.getMessage()); + } + + if (c == 'F') { + ConverterInfo foundInfo = ConverterInfoMgr.findConverterInfo(findMimeOne, findMimeTwo); + if (foundInfo != null) { + System.out.println(" Found ConverterInfo"); + System.out.println(" DisplayName : " + foundInfo.getDisplayName()); + } else { + System.out.println(" Did not find ConverterInfo"); + } + } else { + ConverterInfo[] foundInfo = ConverterInfoMgr.findConverterInfoChain(findMimeOne, + findMimeTwo); + if (foundInfo != null && foundInfo[0] != null && foundInfo[1] != null ) { + System.out.println(" Found ConverterInfo Chain"); + System.out.println(" DisplayName : " + foundInfo[0].getDisplayName()); + System.out.println(" DisplayName : " + foundInfo[1].getDisplayName()); + } else { + System.out.println(" Did not find ConverterInfo"); + } + } + + // View + + } else if (c == 'V') { + + Iterator<ConverterInfo> ciEnum = ConverterInfoMgr.getConverterInfoEnumeration(); + + int ciCnt = 0; + while (ciEnum.hasNext()) + { + System.out.println(""); + System.out.println(" Displaying converter number " + ciCnt); + ConverterInfo converterInfo = ciEnum.next(); + System.out.println(" DisplayName : " + converterInfo.getDisplayName()); + System.out.println(" JarFile : " + converterInfo.getJarName()); + System.out.println(" Description : " + converterInfo.getDescription()); + System.out.println(" Version : " + converterInfo.getVersion()); + System.out.println(" OfficeMime : " + converterInfo.getOfficeMime()); + Iterator<String> fromEnum = converterInfo.getDeviceMime(); + int feCnt = 1; + while (fromEnum.hasNext()) + { + System.out.println(" DeviceMime : (#" + feCnt + ") : " + + fromEnum.next()); + feCnt++; + } + if (feCnt == 1) { + System.out.println(" DeviceMime : None specified"); + } + + System.out.println(" Vendor : " + converterInfo.getVendor()); + System.out.println(" ClassImpl : " + converterInfo.getClassImpl()); + System.out.println(" XsltSerial : " + converterInfo.getXsltSerial()); + System.out.println(" XsltDeserial : " + converterInfo.getXsltDeserial()); + System.out.println(" Serialize : " + converterInfo.canSerialize()); + System.out.println(" Deserialize : " + converterInfo.canDeserialize()); + System.out.println(" Merge : " + converterInfo.canMerge()); + ciCnt++; + } + + if (ciCnt == 0) { + System.out.println("No converters registered"); + } + } else { + System.out.println("Invalid input"); + } + } + } +} diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/util/registry/ConverterInfoReader.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/util/registry/ConverterInfoReader.java new file mode 100644 index 000000000..21831a691 --- /dev/null +++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/util/registry/ConverterInfoReader.java @@ -0,0 +1,245 @@ +/* + * 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.util.registry; + +import java.io.*; +import java.util.*; +import java.util.jar.*; +import org.xml.sax.*; +import org.w3c.dom.*; +import javax.xml.parsers.*; +import java.net.URL; +import java.net.JarURLConnection; + +/** + * The {@code ConverterInfoReader} pulls a {@code META-INF/converter.xml} file + * out of a jar file and parses it, providing access to this information in a + * {@code Vector} of {@code ConverterInfo} objects. + */ +public class ConverterInfoReader { + + private static final String TAG_CONVERTER = "converter"; + private static final String ATTRIB_OFFICE_TYPE = "type"; + private static final String ATTRIB_VERSION = "version"; + private static final String TAG_NAME = "converter-display-name"; + private static final String TAG_DESC = "converter-description"; + private static final String TAG_VENDOR = "converter-vendor"; + private static final String TAG_CLASS_IMPL = "converter-class-impl"; + private static final String TAG_TARGET = "converter-target"; + private static final String ATTRIB_DEVICE_TYPE = "type"; + private static final String TAG_XSLT_DESERIAL = "converter-xslt-deserialize"; + private static final String TAG_XSLT_SERIAL = "converter-xslt-serialize"; + private final String jarfilename; + private final Document document; + private final ArrayList<ConverterInfo> converterInfoList; + + /** + * Constructor. + * + * <p>A jar file is passed in. The jar file is parsed and the {@code Vector} + * of {@code ConverterInfo} objects is built.</p> + * + * @param jar The URL of the jar file to process. + * @param shouldvalidate Boolean to enable or disable xml validation. + * + * @throws IOException If the jar file cannot be read or + * if the META-INF/converter.xml can + * not be read in the jar file. + * @throws ParserConfigurationException If the {@code DocumentBuilder} + * can not be built. + * @throws org.xml.sax.SAXException If the converter.xml file can not + * be parsed. + * @throws RegistryException If the {@code ConverterFactory} + * implementation of a plug-in cannot + * be loaded. + */ + public ConverterInfoReader(String jar,boolean shouldvalidate) throws IOException, + ParserConfigurationException, org.xml.sax.SAXException, + RegistryException { + + converterInfoList = new ArrayList<ConverterInfo>(); + jarfilename = jar; + + // Get Jar via URL + URL url = new URL("jar:" + jar + "!/META-INF/converter.xml"); + JarURLConnection jarConnection = (JarURLConnection)url.openConnection(); + JarEntry jarentry = jarConnection.getJarEntry(); + JarFile jarfile = jarConnection.getJarFile(); + + if (jarfile == null || jarentry == null) { + throw new IOException("Missing jar entry"); + } + + // Build the InputSource + InputStream istream = jarfile.getInputStream(jarentry); + InputSource isource = new InputSource(istream); + + // Get the DOM builder and build the document. + + DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance(); + + //DTD validation + + if (shouldvalidate) { + System.out.println("Validating xml..."); + builderFactory.setValidating(true); + } + + DocumentBuilder builder = builderFactory.newDocumentBuilder(); + document = builder.parse(isource); + + // Parse the document. + + parseDocument(); + } + + /** + * Loops over the <i>converter</i> {@code Node} in the converter.xml file + * and processes them. + * + * @throws RegistryException If the plug-in associated with a specific + * <i>converter</i> {@code Node} cannot be + * loaded. + */ + private void parseDocument() throws RegistryException { + + Node converterNode; + NodeList converterNodes = document.getElementsByTagName(TAG_CONVERTER); + + for (int i=0; i < converterNodes.getLength(); i++) { + converterNode = converterNodes.item(i); + if (converterNode.getNodeType() == Node.ELEMENT_NODE) { + parseConverterNode((Element)converterNode); + } + } + } + + /** + * Parses a <i>converter</i> node, pulling the information out of the + * {@code Node} and placing it in a {@code ConverterInfo} object, and adds + * that object to a {@code Vector} of {@code ConverterInfo} objects. + * + * @param e The {@code Element} corresponding to the <i>converter</i> + * XML tag. + * + * @throws RegistryException If the plug-in cannot be loaded. + */ + private void parseConverterNode(Element e) throws RegistryException { + + Element detailElement; + Node detailNode; + String elementTagName; + String officeMime = null; + ArrayList<String> deviceMime = new ArrayList<String>(); + String name = null; + String desc = null; + String version = null; + String vendor = null; + String classImpl = null; + String xsltSerial = null; + String xsltDeserial = null; + String temp; + + temp = e.getAttribute(ATTRIB_OFFICE_TYPE); + if (temp.length() != 0) { + officeMime = temp; + } + + temp = e.getAttribute(ATTRIB_VERSION); + if (temp.length() != 0) { + version = temp; + } + + NodeList detailNodes = e.getChildNodes(); + for (int i=0; i < detailNodes.getLength(); i++) { + + detailNode = detailNodes.item(i); + if (detailNode.getNodeType() == Node.ELEMENT_NODE) { + + detailElement = (Element)detailNode; + elementTagName = detailElement.getTagName(); + + if (TAG_NAME.equalsIgnoreCase(elementTagName)) { + name = getTextValue(detailElement); + } else if (TAG_DESC.equalsIgnoreCase(elementTagName)) { + desc = getTextValue(detailElement); + } else if (TAG_VENDOR.equalsIgnoreCase(elementTagName)) { + vendor = getTextValue(detailElement); + } else if (TAG_XSLT_SERIAL.equalsIgnoreCase(elementTagName)) { + xsltSerial = getTextValue(detailElement); + } else if (TAG_XSLT_DESERIAL.equalsIgnoreCase(elementTagName)) { + xsltDeserial = getTextValue(detailElement); + } else if (TAG_CLASS_IMPL.equalsIgnoreCase(elementTagName)) { + classImpl = getTextValue(detailElement); + } else if (TAG_TARGET.equalsIgnoreCase(elementTagName)) { + temp = detailElement.getAttribute(ATTRIB_DEVICE_TYPE); + if (temp.length() != 0) { + deviceMime.add(temp); + } + } + } + } + + ConverterInfo converterInfo; + if ((xsltSerial == null) || (xsltDeserial == null)) { + converterInfo = new ConverterInfo(jarfilename, + officeMime, deviceMime, name, + desc, version, vendor, classImpl); + } else { + converterInfo = new ConverterInfo(jarfilename, + officeMime, deviceMime, name, + desc, version, vendor, classImpl, + xsltSerial, xsltDeserial); + } + converterInfoList.add(converterInfo); + } + + /** + * Helper function to get the text value of an {@code Element}. + * + * @param e The {@code Element} to process. + * + * @return The text value of the {@code Element}. + */ + private String getTextValue(Element e) { + + NodeList tempNodes = e.getChildNodes(); + String text = null; + Node tempNode; + + for (int j=0; j < tempNodes.getLength(); j++) { + tempNode = tempNodes.item(j); + if (tempNode.getNodeType() == Node.TEXT_NODE) { + text = tempNode.getNodeValue().trim(); + break; + } + } + + return text; + } + + /** + * Returns an {@code Enumeration} of {@code ConverterInfo} objects. + * + * @return An {@code Enumeration} of {@code ConverterInfo} objects. + */ + public Iterator<ConverterInfo> getConverterInfoEnumeration() { + return converterInfoList.iterator(); + } +} diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/util/registry/RegistryException.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/util/registry/RegistryException.java new file mode 100644 index 000000000..6452256d2 --- /dev/null +++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/util/registry/RegistryException.java @@ -0,0 +1,38 @@ +/* + * 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.util.registry; + +/** + * This {@code Exception} is thrown by converter registry algorithms. + */ +public class RegistryException extends Exception { + + /** + * Exception thrown by merge algorithms. + * + * @param message Message to be included in the {@code Exception}. + */ + public RegistryException(String message) { + super(message); + } + + public RegistryException(String message, Throwable cause) { + super(message, cause); + } +}
\ No newline at end of file diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/util/registry/package-info.java b/xmerge/source/xmerge/java/org/openoffice/xmerge/util/registry/package-info.java new file mode 100644 index 000000000..8634ab0a9 --- /dev/null +++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/util/registry/package-info.java @@ -0,0 +1,59 @@ +/* + * 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 an interface for plug-in registration. + * + * <p>Each plug-in must have a corresponding Plug-in Configuration XML File + * which is named converter.xml. If the plug-in is stored in a jarfile, this + * converter.xml file is typically stored in the following location in the + * jarfile:</p> + * + * <blockquote>META-INF/converter.xml</blockquote> + * + * <p>The Plug-in Configuration XML File must validate against the converter.dtd + * file provided with this package. Since a jarfile can contain multiple + * plug-ins, this DTD supports specifying multiple plug-ins per jarfile. Please + * refer to the SDK document for more information about how to implement a + * Plug-in Configuration XML File for a specific plug-in.</p> + * + * <p>All information in the Plug-in Configuration XML File is bundled into one + * or more {@code ConverterInfo} object. The {@code ConverterInfoReader} object + * is used to build a {@code Vector} of {@code ConverterInfo} objects from a + * jarfile.</p> + * + * <p>The {@code ConverterInfoMgr} manages the registry of {@code ConverterInfo}. + * It is a singleton class, so that only one registry manager will ever exist. + * It is the client program's responsibility to register {@code ConverterInfo} + * objects that correspond to the plug-ins that are to be used.</p> + * + * <h2>TODO/IDEAS list</h2> + * <ol> + * <li>The {@code ConverterInfo} object could contain + * {@code org.w3c.dom.Document} fragments that are accessed in a generic + * fashion rather than get/set methods for each item in the DTD. This would + * provide a more flexible approach, especially for adding custom tags to a + * specific Plug-in Configuration XML file (tags that are only used by its + * associated plug-in).</li> + * <li>{@code ConverterInfo} should allow the merge/serialize/deserialize logic + * to be included in separate plug-ins, if desired.</li> + * <li>{@code ConverterInfoMgr} could use the Java Activation Framework (JAF) + * to manage registration.</li> + * </ol> + */ +package org.openoffice.xmerge.util.registry; diff --git a/xmerge/source/xmerge/java/org/openoffice/xmerge/util/resources.properties b/xmerge/source/xmerge/java/org/openoffice/xmerge/util/resources.properties new file mode 100644 index 000000000..95e10b9e6 --- /dev/null +++ b/xmerge/source/xmerge/java/org/openoffice/xmerge/util/resources.properties @@ -0,0 +1,59 @@ +# +# 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 + +# +# resources.properties +# +# resources for org.openoffice.xmerge.converter package. +# +NULL_MIME_EXCEPTION=Cannot specify null MIME types +EMPTY_MIME_EXCEPTION=Cannot specify empty MIME types +CANNOT_LOAD_CLASS=Unable to load class +CANNOT_INST_CLASS=Unable to instantiate class +NOT_AN_INSTANCE= is not an instance of +CANNOT_FIND_REGISTERED=Cannot find registered class +PARSE_ERROR=Parse Error +LINE=Line +COLUMN=Column +PUBLIC_ID=PublicId +SYSTEM_ID=SystemId +INVALID_LOG_LEVEL=Invalid log level specified +OPERATION_NOT_SUPPORTED=Operation not supported +TEMPLATE_FILE_LOAD_ERROR=Error in loading template file - + +# +# diff/merge algorithm error messages +# +EMPTY_NODE_EXCEPTION=Current Node is empty +NOT_LEAFNODE_EXCEPTION=Current Node is not a leaf node +ROOTNODE_EXCEPTION=Cannot perform insert/append/remove on root node +NOT_TEXTNODE_EXCEPTION=The target Node is not a TEXT_NODE, it is - +NULL_NODE_EXCEPTION=The initial Xmldocument node is null +CELL_NODE_EXCEPTION1=Cell node do not have only 1 child <text:p> nodes, will skip the merge of this node.Num of PARA child nodes: +CELL_NODE_EXCEPTION2=Cell node have a non Element child nodes - +CELL_NODE_EXCEPTION2=There is a child node under an expected empty cell node. +NOT_ELEM_NODE_ERROR=The compared nodes are not an Element Node +NOT_PARA_NODE_ERROR=The compared nodes are not a Paragraph or Heading node - +NOT_NODE_ERROR=The compared nodes are not a Node +# +# SXW to/from DOC conversion error messages. +# +UNKNOWN_DOC_VERSION=Unknown DOC version. +DOC_TEXT_LENGTH_EXCEEDED=DOC text length exceeds maximum value. +DOC_TEXT_RECORD_SIZE_EXCEEDED=DOC text record exceeds size limit. diff --git a/xmerge/source/xmerge/xmerge.mf b/xmerge/source/xmerge/xmerge.mf new file mode 100644 index 000000000..58afc82bf --- /dev/null +++ b/xmerge/source/xmerge/xmerge.mf @@ -0,0 +1,6 @@ +Main-Class: org.openoffice.xmerge.test.Driver +Specification-Title: OpenOffice XMerge Framework +Specification-Vendor: OpenOffice.org +Specification-Version: 0.6.0 +Implementation-Version: #IMPL-VERSION# + |