Started modifying project structure

This commit is contained in:
Georgy Litvinov 2020-12-13 14:03:25 +01:00
parent 4e16ed01c2
commit 1e4ee37f89
566 changed files with 3340 additions and 176 deletions

View file

@ -0,0 +1,441 @@
/************************************************************************
*
* Application.java
*
* Copyright: 2002-2015 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.6 (2015-01-09)
*
*/
package writer2latex;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileNotFoundException;
//import java.io.FileOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Set;
import java.util.Vector;
import writer2latex.api.BatchConverter;
import writer2latex.api.Converter;
import writer2latex.api.ConverterFactory;
import writer2latex.api.ConverterResult;
import writer2latex.api.MIMETypes;
//import writer2latex.api.OutputFile;
import writer2latex.util.Misc;
/**
* <p>Command line utility to convert an OpenOffice.org Writer XML file into XHTML/LaTeX/BibTeX</p>
* <p>The utility is invoked with the following command line:</p>
* <pre>java -jar writer2latex.jar [options] source [target]</pre>
* <p>Where the available options are
* <ul>
* <li><code>-latex</code>, <code>-bibtex</code>, <code>-html5</code>, <code>-xhtml</code>,
<code>-xhtml+mathml</code>, <code>-epub</code>, <code>-epub3</code>
* <li><code>-recurse</code>
* <li><code>-ultraclean</code>, <code>-clean</code>, <code>-pdfscreen</code>,
* <code>-pdfprint</code>, <code>-cleanxhtml</code>
* <li><code>-config[=]filename</code>
* <li><code>-template[=]filename</code>
* <li><code>-stylesheet[=]filename</code>
* <li><code>-resource[=]filename[::media type]</code>
* <li><code>-option[=]value</code>
* </ul>
* <p>where <code>option</code> can be any simple option known to Writer2LaTeX
* (see documentation for the configuration file).</p>
*/
public final class Application {
/* Based on command-line parameters. */
private String sTargetMIME = MIMETypes.LATEX;
private boolean bRecurse = false;
private Vector<String> configFileNames = new Vector<String>();
private String sTemplateFileName = null;
private String sStyleSheetFileName = null;
private Set<String> resources = new HashSet<String>();
private Hashtable<String,String> options = new Hashtable<String,String>();
private String sSource = null;
private String sTarget = null;
/**
* Main method
*
* @param args The argument passed on the command line.
*/
public static final void main (String[] args){
try {
//long time = System.currentTimeMillis();
Application app = new Application();
app.parseCommandLine(args);
app.doConversion();
//System.out.println("Total conversion time was "+(System.currentTimeMillis()-time)+" miliseconds");
} catch (IllegalArgumentException ex) {
String msg = ex.getMessage();
showUsage(msg);
}
}
// Convert the directory or file
private void doConversion() {
// Step 1: Say hello...
String sOutputFormat;
if (MIMETypes.LATEX.equals(sTargetMIME)) { sOutputFormat = "LaTeX"; }
else if (MIMETypes.BIBTEX.equals(sTargetMIME)) { sOutputFormat = "BibTeX"; }
else { sOutputFormat = "xhtml"; }
System.out.println();
System.out.println("This is Writer2" + sOutputFormat +
", Version " + ConverterFactory.getVersion() +
" (" + ConverterFactory.getDate() + ")");
System.out.println();
System.out.println("Starting conversion...");
// Step 2: Examine source
File source = new File(sSource);
if (!source.exists()) {
System.out.println("I'm sorry, I can't find "+sSource);
System.exit(1);
}
if (!source.canRead()) {
System.out.println("I'm sorry, I can't read "+sSource);
System.exit(1);
}
boolean bBatch = source.isDirectory();
// Step 3: Examine target
File target;
if (bBatch) {
if (sTarget==null) {
target=source;
}
else {
target = new File(sTarget);
}
}
else {
if (sTarget==null) {
target = new File(source.getParent(),Misc.removeExtension(source.getName()));
}
else {
target = new File(sTarget);
if (sTarget.endsWith(File.separator)) {
target = new File(target,Misc.removeExtension(source.getName()));
}
}
}
// Step 4: Create converters
Converter converter = ConverterFactory.createConverter(sTargetMIME);
if (converter==null) {
System.out.println("Failed to create converter for "+sTargetMIME);
System.exit(1);
}
BatchConverter batchCv = null;
if (bBatch) {
batchCv = ConverterFactory.createBatchConverter(MIMETypes.XHTML);
if (batchCv==null) {
System.out.println("Failed to create batch converter");
System.exit(1);
}
batchCv.setConverter(converter);
}
// Step 5a: Read template
if (sTemplateFileName!=null) {
try {
System.out.println("Reading template "+sTemplateFileName);
byte [] templateBytes = Misc.inputStreamToByteArray(new FileInputStream(sTemplateFileName));
converter.readTemplate(new ByteArrayInputStream(templateBytes));
if (batchCv!=null) {
// Currently we use the same template for the directory and the files
batchCv.readTemplate(new ByteArrayInputStream(templateBytes));
}
}
catch (FileNotFoundException e) {
System.out.println("--> This file does not exist!");
System.out.println(" "+e.getMessage());
}
catch (IOException e) {
System.out.println("--> Failed to read the template file!");
System.out.println(" "+e.getMessage());
}
}
// Step 5b: Read style sheet
if (sStyleSheetFileName!=null) {
try {
System.out.println("Reading style sheet "+sStyleSheetFileName);
byte [] styleSheetBytes = Misc.inputStreamToByteArray(new FileInputStream(sStyleSheetFileName));
converter.readStyleSheet(new ByteArrayInputStream(styleSheetBytes));
}
catch (FileNotFoundException e) {
System.out.println("--> This file does not exist!");
System.out.println(" "+e.getMessage());
}
catch (IOException e) {
System.out.println("--> Failed to read the style sheet file!");
System.out.println(" "+e.getMessage());
}
}
// Step 5c: Read style resources
for (String sResource : resources) {
String sMediaType;
String sFileName;
int nSeparator = sResource.indexOf("::");
if (nSeparator>-1) {
sFileName = sResource.substring(0,nSeparator);
sMediaType = sResource.substring(nSeparator+2);
}
else {
sFileName = sResource;
sMediaType = null;
}
System.out.println("Reading resource file "+sFileName);
try {
byte [] resourceBytes = Misc.inputStreamToByteArray(new FileInputStream(sFileName));
converter.readResource(new ByteArrayInputStream(resourceBytes),sFileName,sMediaType);
} catch (IOException e) {
System.out.println("--> Failed to read the resource file!");
System.out.println(" "+e.getMessage());
}
}
// Step 6: Read config
for (int i=0; i<configFileNames.size(); i++) {
String sConfigFileName = (String) configFileNames.get(i);
if (sConfigFileName.startsWith("*")) {
sConfigFileName = sConfigFileName.substring(1);
System.out.println("Reading default configuration "+sConfigFileName);
try {
converter.getConfig().readDefaultConfig(sConfigFileName);
}
catch (IllegalArgumentException e) {
System.err.println("--> This configuration is unknown!");
System.out.println(" "+e.getMessage());
}
}
else {
System.out.println("Reading configuration file "+sConfigFileName);
try {
byte[] configBytes = Misc.inputStreamToByteArray(new FileInputStream(sConfigFileName));
converter.getConfig().read(new ByteArrayInputStream(configBytes));
if (bBatch) {
// Currently we use the same configuration for the directory and the files
batchCv.getConfig().read(new ByteArrayInputStream(configBytes));
}
}
catch (IOException e) {
System.err.println("--> Failed to read the configuration!");
System.out.println(" "+e.getMessage());
}
}
}
// Step 7: Set options from command line
Enumeration<String> keys = options.keys();
while (keys.hasMoreElements()) {
String sKey = keys.nextElement();
String sValue = (String) options.get(sKey);
converter.getConfig().setOption(sKey,sValue);
if (batchCv!=null) {
batchCv.getConfig().setOption(sKey,sValue);
}
}
// Step 8: Perform conversion
if (bBatch) {
batchCv.convert(source,target,bRecurse, new BatchHandlerImpl());
}
else {
System.out.println("Converting "+source.getPath());
ConverterResult dataOut = null;
try {
dataOut = converter.convert(source,target.getName());
}
catch (FileNotFoundException e) {
System.out.println("--> The file "+source.getPath()+" does not exist!");
System.out.println(" "+e.getMessage());
System.exit(1);
}
catch (IOException e) {
System.out.println("--> Failed to convert the file "+source.getPath()+"!");
System.out.println(" "+e.getMessage());
System.out.println(" Please make sure the file is in OpenDocument format");
System.exit(1);
}
// TODO: Should do some further checking on the feasability of writing
// the directory and the files.
File targetDir = target.getParentFile();
if (targetDir!=null && !targetDir.exists()) { targetDir.mkdirs(); }
try {
dataOut.write(targetDir);
}
catch (IOException e) {
System.out.println("--> Error writing out file!");
System.out.println(" "+e.getMessage());
System.exit(1);
}
}
// Step 9: Say goodbye!
System.out.println("Done!");
}
/**
* Display usage.
*/
private static void showUsage(String msg) {
System.out.println();
System.out.println("This is Writer2LaTeX, Version " + ConverterFactory.getVersion()
+ " (" + ConverterFactory.getDate() + ")");
System.out.println();
if (msg != null) System.out.println(msg);
System.out.println();
System.out.println("Usage:");
System.out.println(" java -jar <path>/writer2latex.jar <options> <source file/directory> [<target file/directory>]");
System.out.println("where the available options are:");
System.out.println(" -latex");
System.out.println(" -bibtex");
System.out.println(" -xhtml");
System.out.println(" -xhtml11");
System.out.println(" -xhtml+mathml");
System.out.println(" -html5");
System.out.println(" -epub");
System.out.println(" -epub3");
System.out.println(" -recurse");
System.out.println(" -template[=]<template file>");
System.out.println(" -stylesheet[=]<style sheet file>");
System.out.println(" -resource[=]<resource file>[::<media type>]");
System.out.println(" -ultraclean");
System.out.println(" -clean");
System.out.println(" -pdfprint");
System.out.println(" -pdfscreen");
System.out.println(" -cleanxhtml");
System.out.println(" -config[=]<configuration file>");
System.out.println(" -<configuration option>[=]<value>");
System.out.println("See the documentation for the available configuration options");
}
/**
* Parse command-line arguments.
*
* @param args Array of command line arguments.
*
* @throws IllegalArgumentException If an argument is invalid.
*/
private void parseCommandLine(String sArgs[])
throws IllegalArgumentException {
int i = 0;
while (i<sArgs.length) {
String sArg = getArg(i++,sArgs);
if (sArg.startsWith("-")) { // found an option
if ("-latex".equals(sArg)) { sTargetMIME = MIMETypes.LATEX; }
else if ("-bibtex".equals(sArg)) { sTargetMIME = MIMETypes.BIBTEX; }
else if ("-html5".equals(sArg)) { sTargetMIME = MIMETypes.HTML5; }
else if ("-xhtml".equals(sArg)) { sTargetMIME = MIMETypes.XHTML; }
else if ("-xhtml11".equals(sArg)) { sTargetMIME = MIMETypes.XHTML11; }
else if ("-xhtml+mathml".equals(sArg)) { sTargetMIME = MIMETypes.XHTML_MATHML; }
else if ("-epub".equals(sArg)) { sTargetMIME = MIMETypes.EPUB; }
else if ("-epub3".equals(sArg)) { sTargetMIME = MIMETypes.EPUB3; }
else if ("-recurse".equals(sArg)) { bRecurse = true; }
else if ("-ultraclean".equals(sArg)) { configFileNames.add("*ultraclean.xml"); }
else if ("-clean".equals(sArg)) { configFileNames.add("*clean.xml"); }
else if ("-pdfprint".equals(sArg)) { configFileNames.add("*pdfprint.xml"); }
else if ("-pdfscreen".equals(sArg)) { configFileNames.add("*pdfscreen.xml"); }
else if ("-cleanxhtml".equals(sArg)) { configFileNames.add("*cleanxhtml.xml"); }
else { // option with argument
int j=sArg.indexOf("=");
String sArg2;
if (j>-1) { // argument is separated by =
sArg2 = sArg.substring(j+1);
sArg = sArg.substring(0,j);
}
else { // argument is separated by space
sArg2 = getArg(i++,sArgs);
}
if ("-config".equals(sArg)) { configFileNames.add(sArg2); }
else if ("-template".equals(sArg)) { sTemplateFileName = sArg2; }
else if ("-stylesheet".equals(sArg)) { sStyleSheetFileName = sArg2; }
else if ("-resource".equals(sArg)) { resources.add(sArg2); }
else { // configuration option
options.put(sArg.substring(1),sArg2);
}
}
}
else { // not an option, so this must be the source
sSource = sArg;
// Possibly followed by the target
if (i<sArgs.length) {
String sArgument = getArg(i++,sArgs);
if (sArgument.length()>0) { sTarget = sArgument; }
}
// Skip any trailing empty arguments and signal an error if there's more
while (i<sArgs.length) {
String sArgument = getArg(i++,sArgs);
if (sArgument.length()>0) {
throw new IllegalArgumentException("I didn't expect "+sArgument+"?");
}
}
}
}
if (sSource==null) {
throw new IllegalArgumentException("Please specify a source document/directory!");
}
// Parsing of command line ended successfully!
}
/**
* Extract the next argument from the array, while checking to see
* that the array size is not exceeded. Throw a friendly error
* message in case the arg is missing.
*
* @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 getArg(int i, String args[])
throws IllegalArgumentException {
if (i < args.length) {
return args[i];
}
else throw new
IllegalArgumentException("I'm sorry, the commandline ended abnormally");
}
}

View file

@ -0,0 +1,93 @@
/************************************************************************
*
* BatchHandlerImpl.java
*
* Copyright: 2002-2008 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.0 (2008-10-03)
*
*/
package writer2latex;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import writer2latex.api.BatchHandler;
/** This class implements a <code>BatchHandler</code> for command line usage
*/
public class BatchHandlerImpl implements BatchHandler {
private int nIndent = 0;
private void writeMessage(String sMsg) {
for (int i=0; i<nIndent; i++) {
System.out.print(" ");
}
System.out.println(sMsg);
}
public void startConversion() {
System.out.println("Press Enter to cancel the conversion");
}
public void endConversion() {
// No message
}
public void startDirectory(String sName) {
writeMessage("Converting directory "+sName);
nIndent++;
}
public void endDirectory(String sName, boolean bSuccess) {
nIndent--;
if (!bSuccess) {
writeMessage("--> Conversion of the directory "+sName+" failed!");
}
}
public void startFile(String sName) {
writeMessage("Converting file "+sName);
nIndent++;
}
public void endFile(String sName, boolean bSuccess) {
nIndent--;
if (!bSuccess) {
writeMessage("--> Conversion of the file "+sName+" failed!");
}
}
public boolean cancel() {
try {
if (System.in.available()>0) {
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
in.readLine();
System.out.print("Do you want to cancel the conversion (y/n)? ");
String s = in.readLine();
if (s!= null && s.toLowerCase().startsWith("y")) { return true; }
}
}
catch (IOException e) {
}
return false;
}
}

View file

@ -0,0 +1,95 @@
/************************************************************************
*
* BatchConverter.java
*
* Copyright: 2002-2008 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.0 (2008-11-23)
*
*/
package writer2latex.api;
import java.io.File;
import java.io.InputStream;
import java.io.IOException;
/** This is an interface for a converter, which offers conversion of
* all OpenDocument (or OpenOffice.org 1.x) documents in a directory
* (and optionally subdirectories), creating index pages in a specific format.
* Instances of this interface are created using the
* {@link ConverterFactory}
*/
public interface BatchConverter {
/** Get the configuration interface for this batch converter
*
* @return the configuration
*/
public Config getConfig();
/** Define a <code>Converter</code> implementation to use for
* conversion of the individual documents.
* If no converter is given, the <code>convert</code> method cannot
* convert documents (but can still create index pages).
*
* @param converter the <code>Converter</code> to use
*/
public void setConverter(Converter converter);
/** Read a template to use as a base for the index pages.
* The format of the template depends on the <code>BatchConverter</code>
* implementation.
*
* @param is an <code>InputStream</code> from which to read the template
* @throws IOException if some exception occurs while reading the template
*/
public void readTemplate(InputStream is) throws IOException;
/** Read a template to use as a base for the index pages.
* The format of the template depends on the <code>BatchConverter</code>
* implementation.
*
* @param file the file from which to read the template
* @throws IOException if the file does not exist or some exception occurs
* while reading the template
*/
public void readTemplate(File file) throws IOException;
/** Create an index page with specific entries
*
* @param sHeading a heading describing the index page
* @param entries an array of <code>IndexPageEntry</code> objects (null entries
* are allowed, and will be ignored) describing the individual directories
* and documents
*/
public OutputFile createIndexFile(String sHeading, IndexPageEntry[] entries);
/** Convert a directory using the given <code>Converter</code> (if none is given,
* all files will be ignored).
* This method fails silently if you haven't set a converter.
*
* @param source a <code>File</code> representing the directory to convert
* @param target a <code>File</code> representing the directory to contain
* the converted documents
* @param bRecurse determines wether or not to recurse into subdirectories
* @param handler a </code>BatchHandler</code>
*/
public void convert(File source, File target, boolean bRecurse, BatchHandler handler);
}

View file

@ -0,0 +1,76 @@
/************************************************************************
*
* BatchHandler.java
*
* Copyright: 2002-2008 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.0 (2008-11-23)
*
*/
package writer2latex.api;
/** This is a call back interface to handle user interaction during a
* batch conversion with a {@link BatchConverter}
*/
public interface BatchHandler {
/** Notification that the conversion is started */
public void startConversion();
/** Notification that the conversion has finished */
public void endConversion();
/** Notification that a directory conversion starts
*
* @param sName the name of the directory to convert
*/
public void startDirectory(String sName);
/** Notification that a directory conversion has finished
*
* @param sName the name of the directory
* @param bSuccess true if the conversion was successful (this only means
* that the index page was created with success, not that the conversion
* of files and subdirectories was successful)
*/
public void endDirectory(String sName, boolean bSuccess);
/** Notification that a file conversion starts
*
* @param sName the name of the file to convert
*/
public void startFile(String sName);
/** Notification that a file conversion has finished
*
* @param sName the name of the file
* @param bSuccess true if the conversion of this file was successful
*/
public void endFile(String sName, boolean bSuccess);
/** Notification that the conversion may be cancelled. The
* {@link BatchConverter} fires this event once per document.
* Cancelling the conversion does not delete files that was already
* converted
*
* @return true if the handler wants to cancel the conversion
*/
public boolean cancel();
}

View file

@ -0,0 +1,120 @@
/************************************************************************
*
* Config.java
*
* Copyright: 2002-2009 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.2 (2009-11-19)
*
*/
package writer2latex.api;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/** A complex option is a set of named keys, each pointing to a set of named attributes
*/
public class ComplexOption {
private Map<String,Map<String,String>> options = new HashMap<String,Map<String,String>>();
/** Clear the contents of the set
*
*/
public void clear() {
options.clear();
}
/** Remove an option from the set, if it exists
*
* @param sName the name of the key to remove
*/
public void remove(String sName) {
if (options.containsKey(sName)) {
options.remove(sName);
}
}
/** Define a key. If the key already exists, the old value will be replaced
*
* @param sName the name of the key. The name must be non-empty, otherwise the request will be ignored.
* @param attributes
*/
public void put(String sName, Map<String,String> attributes) {
if (sName!=null && sName.length()>0) {
options.put(sName, attributes);
}
}
/** Define a key using a <i>copy</i> of a the provided attributes.
* If the key already exists, the old value will be replaced
*
* @param sName the name of the key. The name must be non-empty, otherwise the request will be ignored.
* @param attributes
*/
public void copy(String sName, Map<String,String> attributes) {
if (sName!=null && sName.length()>0) {
Map<String,String> newAttributes = new HashMap<String,String>();
for (String sAttrName : attributes.keySet()) {
newAttributes.put(sAttrName, attributes.get(sAttrName));
}
put(sName, newAttributes);
}
}
/** Get the value belonging to a key
*
* @param sName the name of the key
* @return the attributes, or null if the option doesn't exist
*/
public Map<String,String> get(String sName) {
return options.get(sName);
}
/** Copy all values from another <code>ComplexOption</code>
* (overwrites existing values)
* @param co another instance of <code>ComplexOption</code>
*/
public void copyAll(ComplexOption co) {
for (String sName : co.keySet()) {
copy(sName, co.get(sName));
}
}
/** Get the names of all options that are currently defined by this complex option
*
* @return all names as a <code>Set</code>
*/
public Set<String> keySet() {
return options.keySet();
}
/** Test if this complex options contains a specific option name
*
* @param sName the name to test
* @return true if the name exists
*/
public boolean containsKey(String sName) {
return options.containsKey(sName);
}
}

View file

@ -0,0 +1,105 @@
/************************************************************************
*
* Config.java
*
* Copyright: 2002-2009 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.2 (2009-09-22)
*
*/
package writer2latex.api;
import java.io.File;
import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.IllegalArgumentException;
/** This is an interface for configuration of a {@link Converter}.
* A configuration always supports simple name/value options.
* In addition, you can read and write configurations using streams
* or abstract file names. The format depends on the {@link Converter}
* implementation, cf. the user's manual.
*/
public interface Config {
/** Read a default configuration: The available configurations depend on the
* {@link Converter} implementation
*
* @param sName the name of the configuration
* @throws IllegalArgumentException if the configuration does not exist
*/
public void readDefaultConfig(String sName) throws IllegalArgumentException;
/** Read a configuration (stream based version)
*
* @param is the <code>InputStream</code> to read from
* @throws IOException if an error occurs reading the stream, or the data
* is not in the right format
*/
public void read(InputStream is) throws IOException;
/** Read a configuration (file based version)
*
* @param file the <code>File</code> to read from
* @throws IOException if the file does not exist, an error occurs reading
* the file, or the data is not in the right format
*/
public void read(File file) throws IOException;
/** Write the configuration (stream based version)
*
* @param os the <code>OutputStream</code> to write to
* @throws IOException if an error occurs writing to the stream
*/
public void write(OutputStream os) throws IOException;
/** Write the configuration (file based version)
*
* @param file the <code>File</code> to write to
* @throws IOException if an error occurs writing to the file
*/
public void write(File file) throws IOException;
/** Set a name/value option. Options that are not defined by the
* {@link Converter} implementation as well as null values are
* silently ignored
*
* @param sName the name of the option
* @param sValue the value of the option
*/
public void setOption(String sName, String sValue);
/** Get a named option
*
* @param sName the name of the option
* @return the value of the option, or <code>null</code> if the option does
* not exist or the given name is null
*/
public String getOption(String sName);
/** Get a complex option
*
* @param sName the name of the complex option
* @return the option
*/
public ComplexOption getComplexOption(String sName);
}

View file

@ -0,0 +1,60 @@
/************************************************************************
*
* ContentEntry.java
*
* Copyright: 2002-2010 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.2 (2010-03-15)
*
*/
package writer2latex.api;
/** This interface represents a content entry, that is a named reference
* to a position within the output document.
*/
public interface ContentEntry {
/** Get the outline level of this <code>ContentEntry</code>.
* The top level is 1 (entries corresponding to indexes are considered
* top level).
* Note that intermediate levels may be missing (e.g. a heading of
* level 3 may follow immediately after a heading of level 1).
*
* @return the outline level
*/
public int getLevel();
/** Get the title for this entry
*
* @return the title
*/
public String getTitle();
/** Get the file associated with the entry
*
* @return the output file
*/
public OutputFile getFile();
/** Get the name of a target within the file, if any
*
* @return the target name, or null if no target is needed
*/
public String getTarget();
}

View file

@ -0,0 +1,153 @@
/************************************************************************
*
* Converter.java
*
* Copyright: 2002-2014 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.4 (2014-08-28)
*
*/
package writer2latex.api;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.IOException;
/** This is an interface for a converter, which offers conversion of
* OpenDocument (or OpenOffice.org 1.x) documents into a specific format.
* Instances of this interface are created using the
* <code>ConverterFactory</code>
*/
public interface Converter {
/** Get the interface for the configuration of this converter
*
* @return the configuration
*/
public Config getConfig();
/** Define a <code>GraphicConverter</code> implementation to use for
* conversion of graphic files. If no converter is specified, graphic
* files will not be converted into other formats.
*
* @param gc the <code>GraphicConverter</code> to use
*/
public void setGraphicConverter(GraphicConverter gc);
/** Read a template to use as a base for the converted document.
* The format of the template depends on the <code>Converter</code>
* implementation.
*
* @param is an <code>InputStream</code> from which to read the template
* @throws IOException if some exception occurs while reading the template
*/
public void readTemplate(InputStream is) throws IOException;
/** Read a template to use as a base for the converted document.
* The format of the template depends on the <code>Converter</code>
* implementation.
*
* @param file a file from which to read the template
* @throws IOException if the file does not exist or some exception occurs
* while reading the template
*/
public void readTemplate(File file) throws IOException;
/** Read a style sheet to <em>include</em> with the converted document.
* The format of the style sheet depends on the <code>Converter</code>
* implementation.
*
* @param is an <code>InputStream</code> from which to read the style sheet
* @throws IOException if some exception occurs while reading the style sheet
*/
public void readStyleSheet(InputStream is) throws IOException;
/** Read a style sheet to <em>include</em> with the converted document.
* The format of the style sheet depends on the <code>Converter</code>
* implementation.
*
* @param file a file from which to read the style sheet
* @throws IOException if the file does not exist or some exception occurs
* while reading the style sheet
*/
public void readStyleSheet(File file) throws IOException;
/** Read a resource to <em>include</em> with the converted document.
* A resource can be any (binary) file and will be placed in the same directory as
* the style sheet
*
* @param is an <code>InputStream</code> from which to read the resource
* @param sFileName the file name to use for the resource
* @param sMediaType the media type of the resource, if null the media type will be guessed from the file name
* @throws IOException if some exception occurs while reading the resource
*/
public void readResource(InputStream is, String sFileName, String sMediaType) throws IOException;
/** Read a style sheet to <em>include</em> with the converted document.
* A resource can be any (binary) file and will be placed in the same directory as
* the style sheet
*
* @param file a file from which to read the style sheet
* @param sFileName the file name to use for the resource
* @param sMediaType the media type of the resource, if null the media type will be guessed from the file name
* @throws IOException if the file does not exist or some exception occurs
* while reading the resource
*/
public void readResource(File file, String sFileName, String sMediaType) throws IOException;
/** Convert a document
*
* @param is an <code>InputStream</code> from which to read the source document.
* @param sTargetFileName the file name to use for the converted document
* (if the converted document is a compound document consisting consisting
* of several files, this name will be used for the master document)
* @return a <code>ConverterResult</code> containing the converted document
* @throws IOException if some exception occurs while reading the document
*/
public ConverterResult convert(InputStream is, String sTargetFileName)
throws IOException;
/** Convert a document
*
* @param source a <code>File</code> from which to read the source document.
* @param sTargetFileName the file name to use for the converted document
* (if the converted document is a compound document consisting consisting
* of several files, this name will be used for the master document)
* @return a <code>ConverterResult</code> containing the converted document
* @throws FileNotFoundException if the file does not exist
* @throws IOException if some exception occurs while reading the document
*/
public ConverterResult convert(File source, String sTargetFileName)
throws FileNotFoundException, IOException;
/** Convert a document
*
* @param dom a DOM tree representing the document as flat XML
* @param sTargetFileName the file name to use for the converted document
* (if the converted document is a compound document consisting consisting
* of several files, this name will be used for the master document)
* @param bDestructive set to true if the converter is allowed to remove contents from the DOM tree (to save memory)
* @return a <code>ConverterResult</code> containing the converted document
* @throws IOException if some exception occurs while reading the document
*/
public ConverterResult convert(org.w3c.dom.Document dom, String sTargetFileName, boolean bDestructive)
throws IOException;
}

View file

@ -0,0 +1,143 @@
/************************************************************************
*
* ConverterFactory.java
*
* Copyright: 2002-2018 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 2.0 (2018-03-06)
*
*/
package writer2latex.api;
/** This is a factory class which provides static methods to create converters
* for documents in OpenDocument (or OpenOffice.org 1.x) format into a specific MIME type
*/
public class ConverterFactory {
// Version information
private static final String VERSION = "1.9.1";
private static final String DATE = "2018-03-06";
/** Return the Writer2LaTeX version in the form
* (major version).(minor version).(patch level)<br/>
* Development versions have an odd minor version number
* @return the version number
*/
public static String getVersion() { return VERSION; }
/** Return date information
* @return the release date for this Writer2LaTeX version
*/
public static String getDate() { return DATE; }
/** <p>Create a <code>Converter</code> implementation which supports
* conversion into the specified MIME type.</p>
* <p>Currently supported MIME types are:</p>
* <ul>
* <li><code>application/x-latex</code> for LaTeX format</li>
* <li><code>application/x-bibtex</code> for BibTeX format</li>
* <li><code>text/html</code> for XHTML 1.0 strict format</li>
* <li><code>application/xhtml11</code> for XHTML 1.1 format
* Note that this is <em>not</em> the recommended media type for XHTML 1.1
* (see http://www.w3.org/TR/xhtml-media-types/), but it is used internally
* by Writer2xhtml to distinguish from XHTML+MathML</li>
* <li><code>application/xhtml+xml</code> for XHTML+MathML</li>
* <li><code>text/html5</code> for HTML5 documents
* Note that this is <em>not</em> the recommended media type for HTML5
* (see http://wiki.whatwg.org/), but it is used internally
* by Writer2xhtml to distinguish from HTML5</li>
* <li><code>application/epub+zip</code></li> for EPUB format</li>
* <li><code>epub3</code> for EPUB 3 format</li>
* </ul>
*
* @param sMIME the MIME type of the target format
* @return the required <code>Converter</code> or null if a converter for
* the requested MIME type could not be created
*/
public static Converter createConverter(String sMIME) {
Object converter = null;
if (MIMETypes.LATEX.equals(sMIME)) {
converter = createInstance("writer2latex.latex.ConverterPalette");
}
else if (MIMETypes.BIBTEX.equals(sMIME)) {
converter = createInstance("writer2latex.bibtex.Converter");
}
else if (MIMETypes.XHTML.equals(sMIME)) {
converter = createInstance("writer2latex.xhtml.Xhtml10Converter");
}
else if (MIMETypes.XHTML11.equals(sMIME)) {
converter = createInstance("writer2latex.xhtml.Xhtml11Converter");
}
else if (MIMETypes.XHTML_MATHML.equals(sMIME)) {
converter = createInstance("writer2latex.xhtml.XhtmlMathMLConverter");
}
else if (MIMETypes.HTML5.equals(sMIME)) {
converter = createInstance("writer2latex.xhtml.Html5Converter");
}
else if (MIMETypes.EPUB3.equals(sMIME)) {
converter = createInstance("writer2latex.epub.EPUB3Converter");
}
else if (MIMETypes.EPUB.equals(sMIME)) {
converter = createInstance("writer2latex.epub.EPUBConverter");
}
return converter instanceof Converter ? (Converter) converter : null;
}
/** <p>Create a <code>BatchConverter</code> implementation which supports
* conversion into the specified MIME type</p>
* <p>The only currently supported MIME type is <code>text/html</code>
* (XHTML 1.0 strict)</p>
*
* @param sMIME the MIME type of the target format
* @return the required <code>BatchConverter</code> or null if a converter
* for the requested MIME type could not be created
*/
public static BatchConverter createBatchConverter(String sMIME) {
Object converter = null;
if (MIMETypes.XHTML.equals(sMIME)) {
converter = createInstance("writer2latex.xhtml.BatchConverterImpl");
}
return converter instanceof BatchConverter ? (BatchConverter) converter : null;
}
/** Create a <code>StarMathConverter</code> implementation
*
* @return the converter
*/
public static StarMathConverter createStarMathConverter() {
Object converter = createInstance("writer2latex.latex.StarMathConverter");
return converter instanceof StarMathConverter ? (StarMathConverter) converter : null;
}
private static Object createInstance(String sClassName) {
try {
return Class.forName(sClassName).newInstance();
}
catch (java.lang.ClassNotFoundException e) {
return null;
}
catch (java.lang.InstantiationException e) {
return null;
}
catch (java.lang.IllegalAccessException e) {
return null;
}
}
}

View file

@ -0,0 +1,131 @@
/************************************************************************
*
* ConverterResult.java
*
* Copyright: 2002-2011 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.2 (2011-07-20)
*
*/
package writer2latex.api;
import java.io.File;
import java.io.IOException;
import java.util.Iterator;
import java.util.List;
/** A <code>ConverterResult</code> represent a document, which is the result
* of a conversion performed by a <code>Converter</code>implementation.
*/
public interface ConverterResult {
/** Get the master document
* Deprecated as of Writer2LaTeX 1.2: The master document is always the first document
* returned by the <code>iterator</code>
*
* @return <code>OutputFile</code> the master document
*/
@Deprecated public OutputFile getMasterDocument();
/** Gets an <code>Iterator</code> to access all files in the
* <code>ConverterResult</code>. The iterator will return the master documents first
* in logical order (starting with the primary master document)
* @return an <code>Iterator</code> of all files
*/
public Iterator<OutputFile> iterator();
/** Get the meta data associated with the source document
* @return the meta data
*/
public MetaData getMetaData();
/** Get the content table (based on headings) for this <code>ConverterResult</code>
*
* @return list view of the content
*/
public List<ContentEntry> getContent();
/** Get the entry which contains the table page
*
* @return the entry or null if there is no title page
*/
public ContentEntry getTitlePageFile();
/** Get the entry which contains the start of the actual text (the first chapter, or simply the start of
* the document if there are no headings)
*
* @return the entry
*/
public ContentEntry getTextFile();
/** Get the entry which contains the table of contents
*
* @return the entry or null if a table of content does not exist
*/
public ContentEntry getTocFile();
/** Get the entry which contains the list of tables
*
* @return the entry or null if a list of tables does not exist
*/
public ContentEntry getLotFile();
/** Get the entry which contains the list of figures
*
* @return the entry or null if a list of figures does not exist
*/
public ContentEntry getLofFile();
/** Get the entry which contains the alphabetical index
*
* @return the entry or null if an alphabetical index does not exist
*/
public ContentEntry getIndexFile();
/** Get the entry which contains the bibliography
*
* @return the entry or null if a bibliography does not exist
*/
public ContentEntry getBibliographyFile();
/** Get the entry which contains the cover (which usually will contain a cover image)
*
* @return the entry or null if a cover does not exist
*/
public ContentEntry getCoverFile();
/** Get the entry which contains the actual cover image
*
* @return the entry or null if a cover image does not exist
*/
public ContentEntry getCoverImageFile();
/** Write all files of the <code>ConverterResult</code> to a directory.
* Subdirectories are created as required by the individual
* <code>OutputFile</code>s.
* @param dir the directory to write to (this directory must exist).
If the parameter is null, the default directory is used
* @throws IOException if the directory does not exist or one or more files
* could not be written
*/
public void write(File dir) throws IOException;
}

View file

@ -0,0 +1,57 @@
/************************************************************************
*
* GraphicConverter.java
*
* Copyright: 2002-2008 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.0 (2008-11-23)
*
*/
package writer2latex.api;
/** A simple interface for a graphic converter which converts between various
* graphics formats
*/
public interface GraphicConverter {
/** Check whether a certain conversion is supported by the converter
*
* @param sSourceMime a string containing the source Mime type
* @param sTargetMime a string containing the target Mime type
* @param bCrop true if the target graphic should be cropped
* @param bResize true if the target graphic should be resized
* (the last two parameters are for future use)
* @return true if the conversion is supported
*/
public boolean supportsConversion(String sSourceMime, String sTargetMime, boolean bCrop, boolean bResize);
/** Convert a graphics file from one format to another
*
* @param source a byte array containing the source graphic
* @param sSourceMime a string containing the Mime type of the source
* @param sTargetMime a string containing the desired Mime type of the target
* @return a byte array containing the converted graphic. Returns null
* if the conversion failed.
*/
public byte[] convert(byte[] source, String sSourceMime, String sTargetMime);
}

View file

@ -0,0 +1,140 @@
/************************************************************************
*
* IndexPageEntry.java
*
* Copyright: 2002-2009 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.0 (2009-02-08)
*
*/
package writer2latex.api;
/** This class represents a single entry on an index page created by a batch converter
*/
public class IndexPageEntry {
private String sFile;
private String sDisplayName;
private String sDescription = null;
private String sPdfFile = null;
private String sOriginalFile = null;
private boolean bIsDirectory;
/** Construct a new <code>IndexPageEntry</code> based on a file name.
* The file name is also used as display name.
*
* @param sFile the file name for this entry
* @param bIsDirectory true if this is a directory, false if it is a file
*/
public IndexPageEntry(String sFile, boolean bIsDirectory) {
this.sFile = sFile;
this.sDisplayName = sFile;
this.bIsDirectory = bIsDirectory;
}
/** Set the file name
*
* @param sFile the file name
*/
public void setFile(String sFile) {
this.sFile = sFile;
}
/** Set the display name for this entry. The display name is the
* name presented on the index page.
*
* @param sDisplayName the display name
*/
public void setDisplayName(String sDisplayName) {
this.sDisplayName = sDisplayName;
}
/** Set the description of this file (additional information about the file)
*
* @param sDescription the description
*/
public void setDescription(String sDescription) {
this.sDescription = sDescription;
}
/** Set the file name for a pdf file associated with this file
*
* @param sPdfFile the file name
*/
public void setPdfFile(String sPdfFile) {
this.sPdfFile = sPdfFile;
}
/** Set the file name for the original file
*
* @param sOriginalFile the origianl file name
*/
public void setOriginalFile(String sOriginalFile) {
this.sOriginalFile = sOriginalFile;
}
/** Get the file name
*
* @return the file name
*/
public String getFile() {
return sFile;
}
/** Get the display name
*
* @return the display name
*/
public String getDisplayName() {
return sDisplayName;
}
/** Get the description
*
* @return the description, or null if there is no description
*/
public String getDescription() {
return sDescription;
}
/** Get the pdf file name
*
* @return the file name or null if there is none
*/
public String getPdfFile() {
return sPdfFile;
}
/** Get the original file name
*
* @return the file name or null if there is none
*/
public String getOriginalFile() {
return sOriginalFile;
}
/** Check whether this is a file or a directory
*
* @return true for a directory, false for a file
*/
public boolean isDirectory() {
return bIsDirectory;
}
}

View file

@ -0,0 +1,62 @@
/************************************************************************
*
* MIMETypes.java
*
* Copyright: 2002-2015 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.6 (2015-01-09)
*
*/
package writer2latex.api;
/* Some helpers to handle the MIME types used by OOo and Writer2LaTeX
*/
public class MIMETypes {
// Various graphics formats, see
// http://api.openoffice.org/docs/common/ref/com/sun/star/graphic/MediaProperties.html#MimeType
public static final String PNG="image/png";
public static final String JPEG="image/jpeg";
public static final String GIF="image/gif";
public static final String TIFF="image/tiff";
public static final String BMP="image/bmp";
public static final String EMF="image/x-emf";
public static final String WMF="image/x-wmf";
public static final String EPS="image/x-eps";
public static final String SVG="image/svg+xml";
// MIME type for SVM has changed
//public static final String SVM="image/x-svm";
public static final String SVM="application/x-openoffice-gdimetafile;windows_formatname=\"GDIMetaFile\"";
public static final String PDF="application/pdf";
// Destination formats
public static final String XHTML="text/html";
/** This is a fake MIME type, for internal use only */
public static final String XHTML11="application/xhtml11";
public static final String XHTML_MATHML="application/xhtml+xml";
/** This is a fake MIME type, for internal use only */
public static final String HTML5="text/html5";
public static final String EPUB="application/epub+zip";
/** This is not a MIME type either */
public static final String EPUB3="epub3";
public static final String LATEX="application/x-latex";
public static final String BIBTEX="application/x-bibtex";
public static final String TEXT="text";
}

View file

@ -0,0 +1,81 @@
/************************************************************************
*
* MetaData.java
*
* Copyright: 2002-2010 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.2 (2010-12-15)
*
*/
package writer2latex.api;
import java.util.Map;
/** This interface provides access to the predefined meta data of the
* source document (currently incomplete)
*/
public interface MetaData {
/** Get the title of the source document
*
* @return the title (may return an empty string)
*/
public String getTitle();
/** Get the subject of the source document
*
* @return the subject (may return an empty string)
*/
public String getSubject();
/** Get the keywords of the source document
*
* @return the keywords as a comma separated list (may return an empty string)
*/
public String getKeywords();
/** Get the description of the source document
*
* @return the description (may return an empty string)
*/
public String getDescription();
/** Get the creator of the source document (or the initial creator if none is specified)
*
* @return the creator (may return an empty string)
*/
public String getCreator();
/** Get the (main) language of the document
*
* @return the language
*/
public String getLanguage();
/** Get the date of the source document
*
* @return the date (may return an empty string)
*/
public String getDate();
/** Get the user-defined meta data
*
* @return the user-defined meta data as a name-value map
*/
public Map<String,String> getUserDefinedMetaData();
}

View file

@ -0,0 +1,70 @@
/************************************************************************
*
* OutputFile.java
*
* Copyright: 2002-2015 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.6 (2015-05-05)
*
*/
package writer2latex.api;
import java.io.OutputStream;
import java.io.IOException;
/** An <code>OutputFile</code> represents a single file in a
* {@link ConverterResult}, which is output from a {@link Converter}
* implementation.
*/
public interface OutputFile {
/** Writes the <code>OutputFile</code> to an <code>OutputStream</code>.
*
* @param os <code>OutputStream</code> to which the content should be written
* @throws IOException if any I/O error occurs
*/
public void write(OutputStream os) throws IOException;
/** Returns the file name of the <code>OutputFile</code>. This includes
* the file extension and may also include a relative path, always using
* / as separator.
*
* @return the file name of this <code>OutputFile</code>
*/
public String getFileName();
/** Get the MIME type of the <code>OutputFile</code>.
*
* @return string representation of the MIME type
*/
public String getMIMEType();
/** Test whether this document is part of the main document flow (master documents) or
* an auxiliary document
*
* @return true if this document is a master document
*/
public boolean isMasterDocument();
/** Test whether this document contains mathematical formulas
*
* @return true if the document contains formulas
*/
public boolean containsMath();
}

View file

@ -0,0 +1,13 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title>The package writer2latex.api</title>
</head>
<body>
<p>This package contains the api for using Writer2LaTeX.</p>
<p>The api consitst of a factory class and a number of interfaces, and is
used by the command line application as well as by the filters.
</body>
</html>

View file

@ -0,0 +1,60 @@
/************************************************************************
*
* StarMathConverter.java
*
* Copyright: 2002-2008 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.0 (2008-11-23)
*
*/
package writer2latex.api;
//import java.io.InputStream;
//import java.io.IOException;
/** This is an interface for a converter, which offers conversion of
* a StarMath formula into LaTeX
* Instances of this interface are created using the
* {@link ConverterFactory}
*/
public interface StarMathConverter {
/** Get the configuration used when converting.
*
* @return the configuration used by this converter
*/
public Config getConfig();
/** Convert a StarMath formula
*
* @param sStarMathFormula is a string containing the StarMath formula
* @return a string containing the converted LaTeX formula
*/
public String convert(String sStarMathFormula);
/** Create a suitable LaTeX preamble to process the formulas converted so far
*
* @return a string containg the entire LaTeX preamble
*/
public String getPreamble();
}

View file

@ -0,0 +1,181 @@
/************************************************************************
*
* BatchConverterBase.java
*
* Copyright: 2002-2014 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.4 (2014-08-27)
*
*/
package writer2latex.base;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import writer2latex.api.BatchConverter;
import writer2latex.api.BatchHandler;
import writer2latex.api.Converter;
import writer2latex.api.ConverterResult;
import writer2latex.api.IndexPageEntry;
import writer2latex.api.OutputFile;
import writer2latex.util.Misc;
/**
* Abstract base implementation of <code>writer2latex.api.BatchConverter</code>.
* The base implementation handles the traversal of directories and files, and
* leaves the handling of indexpages to the subclass.
*/
public abstract class BatchConverterBase implements BatchConverter {
private Converter converter;
public BatchConverterBase() {
converter = null;
}
// Partial implementation of the interface
public void setConverter(Converter converter) {
this.converter = converter;
}
public void convert(File source, File target, boolean bRecurse, BatchHandler handler) {
handler.startConversion();
convertDirectory(source, target, bRecurse, source.getName(), handler);
handler.endConversion();
}
protected abstract String getIndexFileName();
// Convert files and directories in the directory indir
// (return false if conversion has been cancelled by the BatchHandler)
private boolean convertDirectory(File indir, File outdir, boolean bRecurse, String sHeading, BatchHandler handler) {
handler.startDirectory(indir.getPath());
// Step 1: Get the directory
File[] contents = indir.listFiles();
int nLen = contents.length;
IndexPageEntry[] entries = new IndexPageEntry[nLen];
// Step 2: Traverse subdirectories, if allowed
if (bRecurse) {
String sUplink = getConfig().getOption("uplink");
for (int i=0; i<nLen; i++) {
if (contents[i].isDirectory()) {
getConfig().setOption("uplink","../"+getIndexFileName());
File newOutdir = new File(outdir,contents[i].getName());
String sNewHeading = sHeading + " - " + contents[i].getName();
boolean bResult = convertDirectory(contents[i],newOutdir,bRecurse,sNewHeading,handler);
getConfig().setOption("uplink", sUplink);
if (!bResult) { return false; }
// Create entry for this subdirectory
IndexPageEntry entry = new IndexPageEntry(Misc.makeHref(contents[i].getName()+"/"+getIndexFileName()),true);
entry.setDisplayName(contents[i].getName());
entries[i]=entry;
}
}
}
// Step 3: Traverse documents, if we have a converter
if (converter!=null) {
String sUplink = getConfig().getOption("uplink");
for (int i=0; i<nLen; i++) {
if (contents[i].isFile()) {
getConfig().setOption("uplink",getIndexFileName());
String sLinkFile = convertFile(contents[i],outdir,handler);
getConfig().setOption("uplink", sUplink);
if (sLinkFile!=null) {
// Create entry for this file
IndexPageEntry entry = new IndexPageEntry(Misc.makeHref(sLinkFile),false);
entry.setDisplayName(Misc.removeExtension(sLinkFile));
entries[i]=entry;
if (handler.cancel()) { return false; }
}
}
}
}
// Step 4: Create and write out the index file
OutputFile indexFile = createIndexFile(sHeading, entries);
if (!outdir.exists()) { outdir.mkdirs(); }
boolean bSuccess = true;
File outfile = new File(outdir,indexFile.getFileName());
try {
FileOutputStream fos = new FileOutputStream(outfile);
indexFile.write(fos);
fos.flush();
fos.close();
} catch (Exception writeExcept) {
bSuccess = false;
}
handler.endDirectory(indir.getPath(), bSuccess);
return !handler.cancel();
}
// Convert a single file, returning the name of the master file
// Returns null if conversion fails
private String convertFile(File infile, File outdir, BatchHandler handler) {
handler.startFile(infile.getPath());
// Currently we discriminate based on file extension
if (!(infile.getName().endsWith(".odt") || infile.getName().endsWith(".ods") || infile.getName().endsWith(".odp"))) {
handler.endFile(infile.getPath(),false);
return null;
}
// Do conversion
ConverterResult dataOut = null;
try {
// The target file name is always the same as the source
dataOut = converter.convert(infile,Misc.removeExtension(infile.getName()));
}
catch (FileNotFoundException e) {
handler.endFile(infile.getPath(),false);
return null;
}
catch (IOException e) {
handler.endFile(infile.getPath(),false);
return null;
}
// Write out files
if (!outdir.exists()) { outdir.mkdirs(); }
try {
dataOut.write(outdir);
}
catch (IOException e) {
handler.endFile(infile.getPath(),false);
return null;
}
handler.endFile(infile.getPath(),true);
return dataOut.iterator().next().getFileName();
}
}

View file

@ -0,0 +1,292 @@
/************************************************************************
*
* BibliographyGenerator.java
*
* Copyright: 2002-2018 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.6 (2018-03-06)
*
*/
package writer2latex.base;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import writer2latex.office.OfficeReader;
import writer2latex.office.XMLString;
import writer2latex.util.Misc;
import writer2latex.util.StringComparator;
/** This class is used to generate bibliographic references and a bibliography.
* Bibliographies are generated from a list of items (text:bibliograpy-mark), a global configuration
* (text:bibliography-configuration) and a formatting template (text:bibliography-source)
*/
public abstract class BibliographyGenerator {
// Bibliography configuration data
private String sPrefix = "[";
private String sSuffix = "]";
// The sorted list of bibliography marks
private List<Element> bibMarkList = new ArrayList<Element>();
// Map from key to label
private Map<String,String> bibMarkLabel = new HashMap<String,String>();
// Flag to identify numbering
private boolean bNumberedEntries = false;
// Flag to identify truncation of templates
private boolean bSkipKey = false;
/** Create a new bibliography generator based on a bibliography configuration and a list of bibliography marks
*
* @param ofr the office reader used to access the source document
* @param bSkipKey set to true if the key should be excluded when applying templates
*/
protected BibliographyGenerator(OfficeReader ofr, boolean bSkipKey) {
this.bSkipKey = bSkipKey;
Element bibConfig = ofr.getBibliographyConfiguration();
if (bibConfig!=null) {
if (bibConfig.hasAttribute(XMLString.TEXT_PREFIX)) {
sPrefix = bibConfig.getAttribute(XMLString.TEXT_PREFIX);
}
if (bibConfig.hasAttribute(XMLString.TEXT_SUFFIX)) {
sSuffix = bibConfig.getAttribute(XMLString.TEXT_SUFFIX);
}
}
collectBibMarks(ofr.getBibliographyMarks());
sortBibMarks(bibConfig);
createLabels(bibConfig);
}
// Collect the bibliography marks from the raw list, removing any duplicates
private void collectBibMarks(List<Element> bibMarks) {
Set<String> keys = new HashSet<String>();
for (Element bibMark : bibMarks) {
String sKey = bibMark.getAttribute(XMLString.TEXT_IDENTIFIER);
if (!keys.contains(sKey)) {
bibMarkList.add(bibMark);
keys.add(sKey);
}
}
}
// Sort the bibliography marks based on the settings in the bibliography configuration
private void sortBibMarks(Element bibConfig) {
if (bibConfig!=null && "false".equals(bibConfig.getAttribute(XMLString.TEXT_SORT_BY_POSITION))) {
// Get the sort algorithm
//String sSortAlgorithm = "alphanumeric";
//if (bibConfig.hasAttribute(XMLString.TEXT_SORT_ALGORITHM)) {
// sSortAlgorithm = bibConfig.getAttribute(XMLString.TEXT_SORT_ALGORITHM);
//}
// Get the sort keys
List<String> sortKeys = new ArrayList<String>();
List<Boolean> sortAscending = new ArrayList<Boolean>();
Node child = bibConfig.getFirstChild();
while (child!=null) {
if (child.getNodeType()==Node.ELEMENT_NODE && child.getNodeName().equals(XMLString.TEXT_SORT_KEY)) {
String sKey = Misc.getAttribute(child, XMLString.TEXT_KEY);
if (sKey!=null) {
sortKeys.add(sKey);
sortAscending.add(!"false".equals(Misc.getAttribute(child, XMLString.TEXT_SORT_ASCENDING)));
}
}
child = child.getNextSibling();
}
// Sort the list
Comparator<Element> comparator = new StringComparator<Element>(
Misc.getAttribute(bibConfig,XMLString.FO_LANGUAGE),
Misc.getAttribute(bibConfig, XMLString.FO_COUNTRY)) {
private List<String> sortKeys = null;
private List<Boolean> sortAscending = null;
Comparator<Element> setSortKeys(List<String> sortKeys, List<Boolean> sortAscending) {
this.sortKeys = sortKeys;
this.sortAscending = sortAscending;
return this;
}
public int compare(Element a, Element b) {
int nCount = sortKeys.size();
for (int i=0; i<nCount; i++) {
String sWorda = a.getAttribute("text:"+sortKeys.get(i));
String sWordb = b.getAttribute("text:"+sortKeys.get(i));
int nCompare = getCollator().compare(sWorda, sWordb)*(sortAscending.get(i) ? 1 : -1);
if (nCompare!=0) { return nCompare; }
}
return 0;
}
}.setSortKeys(sortKeys, sortAscending);
Collections.sort(bibMarkList, comparator);
}
}
private void createLabels(Element bibConfig) {
bNumberedEntries = bibConfig!=null && "true".equals(bibConfig.getAttribute(XMLString.TEXT_NUMBERED_ENTRIES));
int nCount = bibMarkList.size();
for (int i=0; i<nCount; i++) {
Element item = bibMarkList.get(i);
String sKey = item.getAttribute(XMLString.TEXT_IDENTIFIER);
if (bNumberedEntries) {
bibMarkLabel.put(sKey, Integer.toString(i+1));
}
else {
bibMarkLabel.put(sKey, sKey);
}
}
}
/** Get all labels used in the bibliography
*
* @return the set of labels
*/
protected Collection<String> getLabels() {
return bibMarkLabel.values();
}
/** Check whether entries are numbered rather than labeled with the key
*
* @return true if the entries are numbered
*/
protected boolean isNumberedEntries() {
return bNumberedEntries;
}
/** Get citation text for a reference to the bibliography
*
* @param sKey the key of the bibliography item
* @return the citation text to be shown in the document
*/
public String generateCitation(String sKey) {
return sPrefix+bibMarkLabel.get(sKey)+sSuffix;
}
/** Generate a bibliography
*
* @param bibSource a text:bibliography-source element
*/
protected void generateBibliography(Element bibSource) {
Map<String,Element> bibEntryTemplate = collectTemplates(bibSource);
for (Element bibMark : bibMarkList) {
String sKey = bibMark.getAttribute(XMLString.TEXT_IDENTIFIER);
String sType = bibMark.getAttribute(XMLString.TEXT_BIBLIOGRAPHY_TYPE);
if (bibEntryTemplate.containsKey(sType)) {
Element template = bibEntryTemplate.get(sType);
String sStyleName = template.getAttribute(XMLString.TEXT_STYLE_NAME);
insertBibliographyItem(sStyleName,sKey);
applyTemplate(template,bibMark);
}
else { // Use a default template (identical with the default template in LO)
String sAuthor = bibMark.getAttribute(XMLString.TEXT_AUTHOR);
String sTitle = bibMark.getAttribute(XMLString.TEXT_TITLE);
String sYear = bibMark.getAttribute(XMLString.TEXT_YEAR);
insertBibliographyItem(null,sKey);
if (!bSkipKey) {
insertBibliographyItemElement(null,bibMarkLabel.get(sKey));
insertBibliographyItemElement(null,": ");
}
insertBibliographyItemElement(null,sAuthor);
insertBibliographyItemElement(null,", ");
insertBibliographyItemElement(null,sTitle);
insertBibliographyItemElement(null,", ");
insertBibliographyItemElement(null,sYear);
}
}
}
private Map<String,Element> collectTemplates(Element bibSource) {
Map<String,Element> bibEntryTemplate = new HashMap<String,Element>();
Node child = bibSource.getFirstChild();
while (child!=null) {
if (child.getNodeType()==Node.ELEMENT_NODE
&& child.getNodeName().equals(XMLString.TEXT_BIBLIOGRAPHY_ENTRY_TEMPLATE)) {
String sType = Misc.getAttribute(child, XMLString.TEXT_BIBLIOGRAPHY_TYPE);
if (sType!=null) {
bibEntryTemplate.put(sType, (Element)child);
}
}
child = child.getNextSibling();
}
return bibEntryTemplate;
}
private void applyTemplate(Element template, Element bibMark) {
boolean bSkip = bSkipKey;
Node child = template.getFirstChild();
while (child!=null) {
if (child.getNodeType()==Node.ELEMENT_NODE) {
if (child.getNodeName().equals(XMLString.TEXT_INDEX_ENTRY_BIBLIOGRAPHY)) {
String sField = Misc.getAttribute(child, XMLString.TEXT_BIBLIOGRAPHY_DATA_FIELD);
if (sField!=null) {
String sValue = bibMark.getAttribute("text:"+sField);
if (sField.equals("identifier")) {
sValue = bibMarkLabel.get(sValue);
}
else {
bSkip = false;
}
if (!bSkip) {
String sElementStyleName = Misc.getAttribute(child,XMLString.TEXT_STYLE_NAME);
insertBibliographyItemElement(sElementStyleName,sValue);
}
}
}
else if (child.getNodeName().equals(XMLString.TEXT_INDEX_ENTRY_SPAN)) {
if (!bSkip) {
String sValue = Misc.getPCDATA(child);
String sElementStyleName = Misc.getAttribute(child,XMLString.TEXT_STYLE_NAME);
insertBibliographyItemElement(sElementStyleName,sValue);
}
}
}
child = child.getNextSibling();
}
}
/** Insert a new bibliography item
*
* @param sStyleName a paragraph style to apply to the item
* @param sKey the key of the bibliography item
*/
protected abstract void insertBibliographyItem(String sStyleName, String sKey);
/** Insert an element of a bibliography item
*
* @param sStyleName a character style to apply to the element
* @param sText the element text
*/
protected abstract void insertBibliographyItemElement(String sStyleName, String sText);
}

View file

@ -0,0 +1,179 @@
/************************************************************************
*
* BinaryGraphicsDocument.java
*
* Copyright: 2002-2015 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.6 (2015-05-05)
*
*/
package writer2latex.base;
import java.io.OutputStream;
import java.io.IOException;
import writer2latex.api.OutputFile;
/** This class is used to represent a binary graphics document to be included in the converter result.
* I may also represent a linked image, which should <em>not</em> be included (and will produce an empty file
* if it is).
*/
public class BinaryGraphicsDocument implements OutputFile {
private String sFileName;
private String sMimeType;
private boolean bAcceptedFormat;
private boolean bRecycled = false;
// Data for an embedded image
private byte[] blob = null;
private int nOff = 0;
private int nLen = 0;
/**Constructs a new graphics document.
* Until data is added using the <code>read</code> methods, the document is considered a link to
* the image given by the file name.
*
* @param sFileName The name or URL of the <code>GraphicsDocument</code>.
* @param sMimeType the MIME type of the document
*/
public BinaryGraphicsDocument(String sFileName, String sMimeType) {
this.sFileName = sFileName;
this.sMimeType = sMimeType;
bAcceptedFormat = false; // or rather "don't know"
}
/** Construct a new graphics document which is a recycled version of the supplied one.
* This implies that all information is identical, but the recycled version does not contain any data.
* This is for images that are used more than once in the document.
*
* @param bgd the source document
*/
public BinaryGraphicsDocument(BinaryGraphicsDocument bgd) {
this.sFileName = bgd.getFileName();
this.sMimeType = bgd.getMIMEType();
this.bAcceptedFormat = bgd.isAcceptedFormat();
this.bRecycled = true;
}
/** Is this graphics document recycled?
*
* @return true if this is the case
*/
public boolean isRecycled() {
return bRecycled;
}
/** Set image contents to a byte array
*
* @param data the image data
* @param bIsAcceptedFormat flag to indicate that the format of the image is acceptable for the converter
*/
public void setData(byte[] data, boolean bIsAcceptedFormat) {
setData(data,0,data.length,bIsAcceptedFormat);
}
/** Set image contents to part of a byte array
*
* @param data the image data
* @param nOff the offset into the byte array
* @param nLen the number of bytes to use
* @param bIsAcceptedFormat flag to indicate that the format of the image is acceptable for the converter
*/
public void setData(byte[] data, int nOff, int nLen, boolean bIsAcceptedFormat) {
this.blob = data;
this.nOff = nOff;
this.nLen = nLen;
this.bAcceptedFormat = bIsAcceptedFormat;
}
/** Does this <code>BinaryGraphicsDocument</code> represent a linked image?
*
* @return true if so
*/
public boolean isLinked() {
return blob==null && !bRecycled;
}
/** Is this image in an acceptable format for the converter?
*
* @return true if so (always returns false for linked images)
*/
public boolean isAcceptedFormat() {
return bAcceptedFormat;
}
/** Get the data of the image
*
* @return the image data as a byte array - or null if this is a linked image
*/
public byte[] getData() {
return blob;
}
// Implement OutputFile
/** Writes out the content to the specified <code>OutputStream</code>.
* Linked images will not write any data.
*
* @param os <code>OutputStream</code> to write out the content.
*
* @throws IOException If any I/O error occurs.
*/
public void write(OutputStream os) throws IOException {
if (blob!=null) {
os.write(blob, nOff, nLen);
}
}
/** Get the document name or URL</p>
*
* @return The document name or URL
*/
public String getFileName() {
return sFileName;
}
/** Get the MIME type of the document.
*
* @return The MIME type or null if this is unknown
*/
public String getMIMEType() {
return sMimeType;
}
/** Is this document a master document?
*
* @return false - a graphics file is never a master document
*/
public boolean isMasterDocument() {
return false;
}
/** Does this document contain formulas?
*
* @return false - a graphics file does not contain formulas
*/
public boolean containsMath() {
return false;
}
}

View file

@ -0,0 +1,43 @@
/************************************************************************
*
* BooleanOption.java
*
* Copyright: 2002-2008 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.0 (2008-09-08)
*
*/
package writer2latex.base;
// A BooleanOption interprets the values as booleans
public class BooleanOption extends Option {
private boolean bValue;
public boolean getValue() { return bValue; }
public void setString(String sValue) {
super.setString(sValue);
bValue = "true".equals(sValue);
}
public BooleanOption(String sName, String sDefaultValue) {
super(sName,sDefaultValue);
}
}

View file

@ -0,0 +1,187 @@
/************************************************************************
*
* ConfigBase.java
*
* Copyright: 2002-2014 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.4 (2014-08-26)
*
*/
package writer2latex.base;
/** Base implementation of writer2latex.api.Config
*/
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.Element;
import org.w3c.dom.DOMImplementation;
import writer2latex.api.ComplexOption;
public abstract class ConfigBase implements writer2latex.api.Config {
protected abstract int getOptionCount();
protected abstract String getDefaultConfigPath();
protected Option[] options;
protected Map<String,ComplexOption> optionGroups;
public ConfigBase() {
options = new Option[getOptionCount()];
optionGroups = new HashMap<String,ComplexOption>();
}
public void setOption(String sName,String sValue) {
if (sName!=null && sValue!=null) {
for (int j=0; j<getOptionCount(); j++) {
if (sName.equals(options[j].getName())) {
options[j].setString(sValue);
break;
}
}
}
}
public String getOption(String sName) {
if (sName!=null) {
for (int j=0; j<getOptionCount(); j++) {
if (sName.equals(options[j].getName())) {
return options[j].getString();
}
}
}
return null;
}
public ComplexOption getComplexOption(String sGroup) {
return optionGroups.get(sGroup);
}
// The subclass may use this method to define option groups
protected ComplexOption addComplexOption(String sGroup) {
optionGroups.put(sGroup, new ComplexOption());
return optionGroups.get(sGroup);
}
public void readDefaultConfig(String sName) throws IllegalArgumentException {
InputStream is = this.getClass().getResourceAsStream(getDefaultConfigPath()+sName);
if (is==null) {
throw new IllegalArgumentException("The internal configuration '"+sName+ "' does not exist");
}
try {
read(is);
}
catch (IOException e) {
// This would imply a bug in the configuration file!
throw new IllegalArgumentException("The internal configuration '"+sName+ "' is invalid");
}
}
/** <p>Read configuration from a specified input stream</p>
* @param is the input stream to read the configuration from
*/
public void read(InputStream is) throws IOException {
DOMDocument doc = new DOMDocument("config",".xml");
doc.read(is); // may throw an IOException
Document dom = doc.getContentDOM();
if (dom==null) {
throw new IOException("Failed to parse configuration");
}
Node root = dom.getDocumentElement();
Node child = root.getFirstChild();
while (child!=null) {
if (child.getNodeType()==Node.ELEMENT_NODE) {
Element elm = (Element)child;
if (elm.getTagName().equals("option")) {
String sName = elm.getAttribute("name");
String sValue = elm.getAttribute("value");
if (sName.length()>0) { setOption(sName,sValue); }
}
else {
readInner(elm);
}
}
child = child.getNextSibling();
}
}
public void read(File file) throws IOException {
read(new FileInputStream(file));
}
/** Read configuration information from an xml element.
* The subclass must define this to read richer configuration data
*/
protected abstract void readInner(Element elm);
public void write(OutputStream os) throws IOException {
DOMDocument doc = new DOMDocument("config",".xml");
Document dom = null;
try {
DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = builderFactory.newDocumentBuilder();
DOMImplementation domImpl = builder.getDOMImplementation();
dom = domImpl.createDocument("","config",null);
} catch (ParserConfigurationException e) {
// This will not happen
e.printStackTrace();
return;
}
Element rootElement = dom.getDocumentElement();
for (int i=0; i<getOptionCount(); i++) {
Element optionNode = dom.createElement("option");
optionNode.setAttribute("name",options[i].getName());
optionNode.setAttribute("value",options[i].getString());
rootElement.appendChild(optionNode);
}
writeInner(dom);
doc.setContentDOM(dom);
doc.write(os); // may throw an IOException
}
public void write(File file) throws IOException {
write(new FileOutputStream(file));
}
/** Write configuration information to an xml document.
* The subclass must define this to write richer configuration data
*/
protected abstract void writeInner(Document dom);
}

View file

@ -0,0 +1,59 @@
/************************************************************************
*
* ConverterResultImpl.java
*
* Copyright: 2002-2010 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.2 (2010-03-24)
*
*/
package writer2latex.base;
import writer2latex.api.ContentEntry;
import writer2latex.api.OutputFile;
public class ContentEntryImpl implements ContentEntry {
private String sTitle;
private int nLevel;
private OutputFile file;
private String sTarget;
public ContentEntryImpl(String sTitle, int nLevel, OutputFile file, String sTarget) {
this.sTitle = sTitle;
this.nLevel = nLevel;
this.file = file;
this.sTarget = sTarget;
}
public String getTitle() {
return sTitle;
}
public int getLevel() {
return nLevel;
}
public OutputFile getFile() {
return file;
}
public String getTarget() {
return sTarget;
}
}

View file

@ -0,0 +1,183 @@
/************************************************************************
*
* ConverterBase.java
*
* Copyright: 2002-2014 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.4 (2014-09-03)
*
*/
package writer2latex.base;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.IOException;
import org.w3c.dom.Element;
import writer2latex.api.GraphicConverter;
import writer2latex.api.Converter;
import writer2latex.api.ConverterResult;
import writer2latex.api.OutputFile;
import writer2latex.office.EmbeddedObject;
import writer2latex.office.MetaData;
import writer2latex.office.OfficeDocument;
import writer2latex.office.OfficeReader;
import writer2latex.office.XMLString;
import writer2latex.util.Misc;
/**<p>Abstract base implementation of <code>writer2latex.api.Converter</code></p>
*/
public abstract class ConverterBase implements Converter {
public enum TexMathsStyle {inline, display, latex};
// Helper
protected GraphicConverter graphicConverter;
// The source document
protected OfficeDocument odDoc;
protected OfficeReader ofr;
protected MetaData metaData;
protected ImageConverter imageConverter;
// The output file(s)
protected String sTargetFileName;
protected ConverterResultImpl converterResult;
// Constructor
public ConverterBase() {
graphicConverter = null;
converterResult = new ConverterResultImpl();
}
// Implement the interface
public void setGraphicConverter(GraphicConverter graphicConverter) {
this.graphicConverter = graphicConverter;
}
// Provide a do noting fallback method
public void readTemplate(InputStream is) throws IOException { }
// Provide a do noting fallback method
public void readTemplate(File file) throws IOException { }
// Provide a do noting fallback method
public void readStyleSheet(InputStream is) throws IOException { }
// Provide a do noting fallback method
public void readStyleSheet(File file) throws IOException { }
// Provide a do noting fallback method
public void readResource(InputStream is, String sFileName, String sMediaType) throws IOException { }
// Provide a do noting fallback method
public void readResource(File file, String sFileName, String sMediaType) throws IOException { }
public ConverterResult convert(File source, String sTargetFileName) throws FileNotFoundException,IOException {
return convert(new FileInputStream(source), sTargetFileName);
}
public ConverterResult convert(InputStream is, String sTargetFileName) throws IOException {
// Read document
odDoc = new OfficeDocument();
odDoc.read(is);
return convert(sTargetFileName,true);
}
public ConverterResult convert(org.w3c.dom.Document dom, String sTargetFileName, boolean bDestructive) throws IOException {
// Read document
odDoc = new OfficeDocument();
odDoc.read(dom);
return convert(sTargetFileName,bDestructive);
}
private ConverterResult convert(String sTargetFileName, boolean bDestructive) throws IOException {
ofr = new OfficeReader(odDoc,false);
metaData = new MetaData(odDoc);
imageConverter = new ImageConverter(ofr,bDestructive,true);
imageConverter.setGraphicConverter(graphicConverter);
// Prepare output
this.sTargetFileName = sTargetFileName;
converterResult.reset();
converterResult.setMetaData(metaData);
if (metaData.getLanguage()==null || metaData.getLanguage().length()==0) {
metaData.setLanguage(ofr.getMajorityLanguage());
}
convertInner();
return converterResult;
}
// The subclass must provide the implementation
public abstract void convertInner() throws IOException;
public MetaData getMetaData() { return metaData; }
public ImageConverter getImageCv() { return imageConverter; }
public void addDocument(OutputFile doc) { converterResult.addDocument(doc); }
public EmbeddedObject getEmbeddedObject(String sHref) {
return odDoc.getEmbeddedObject(sHref);
}
/** Get a TexMaths equation from a draw:frame (PNG formula) or draw:g element (SVG)
* Such an element is a TexMaths equation if it contains an svg:title element with content "TexMaths"
* The actual formula is the content of an svg:desc element
*
* @param node the draw:frame or draw:g element to check
* @return the TexMaths equation, or null if this is not a TexMaths equation
*/
public Element getTexMathsEquation(Element node) {
Element svgTitle = Misc.getChildByTagName(node, XMLString.SVG_TITLE);
if (svgTitle!=null && "TexMaths".equals(Misc.getPCDATA(svgTitle))) {
return Misc.getChildByTagName(node, XMLString.SVG_DESC);
}
return null;
}
public TexMathsStyle getTexMathsStyle(String s) {
String[] sContent = s.split("\u00a7");
if (sContent.length>=3) { // we only need 3 items of 6
if ("display".equals(sContent[1])) {
return TexMathsStyle.display;
}
else if ("latex".equals(sContent[1]) || "text".equals(sContent[1])) { // text is for OOoLaTeX
return TexMathsStyle.latex;
}
}
return TexMathsStyle.inline;
}
public String getTexMathsEquation(String s) {
String[] sContent = s.split("\u00a7");
if (sContent.length>=3) { // we only need 3 items of 6
return sContent[2];
}
else {
return "";
}
}
}

View file

@ -0,0 +1,282 @@
/************************************************************************
*
* ConverterResultImpl.java
*
* Copyright: 2002-2011 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.2 (2011-07-20)
*
*/
package writer2latex.base;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Vector;
import java.util.Iterator;
import writer2latex.api.ConverterResult;
import writer2latex.api.MetaData;
import writer2latex.api.OutputFile;
import writer2latex.api.ContentEntry;
/** <code>ConverterResultImpl</code> is a straightforward implementation of <code>ConverterResult</code>
*/
public class ConverterResultImpl implements ConverterResult {
private List<OutputFile> files;
private List<ContentEntry> content;
private ContentEntry titlePageFile;
private ContentEntry textFile;
private ContentEntry tocFile;
private ContentEntry lofFile;
private ContentEntry lotFile;
private ContentEntry indexFile;
private ContentEntry bibliographyFile;
private ContentEntry coverFile;
private ContentEntry coverImageFile;
private MetaData metaData = null;
private int nMasterCount;
/** Construct a new <code>ConverterResultImpl</code> with empty content
*/
public ConverterResultImpl() {
reset();
}
/** Resets all data. This empties all <code>OutputFile</code> and <code>ContentEntry</code> objects
* objects from this class. This allows reuse of a <code>ConvertResult</code> object.
*/
public void reset() {
files = new Vector<OutputFile>();
content = new Vector<ContentEntry>();
titlePageFile = null;
textFile = null;
tocFile = null;
lofFile = null;
lotFile = null;
indexFile = null;
bibliographyFile = null;
coverImageFile = null;
metaData = null;
nMasterCount = 0;
}
/** Adds an <code>OutputFile</code> to the list
*
* @param file The <code>OutputFile</code> to add.
*/
public void addDocument(OutputFile file) {
if (file.isMasterDocument()) {
files.add(nMasterCount++, file);
}
else {
files.add(file);
}
}
/** Get the first master document
*
* @return <code>OutputFile</code> the master document
*/
public OutputFile getMasterDocument() {
return files.size()>0 ? files.get(0) : null;
}
/**
* Gets an <code>Iterator</code> to access the <code>List</code>
* of <code>OutputFile</code> objects
*
* @return The <code>Iterator</code> to access the
* <code>List</code> of <code>OutputFile</code> objects.
*/
public Iterator<OutputFile> iterator() {
return files.iterator();
}
/** Add an entry to the "external" table of contents
*
* @param entry the entry to add
*/
public void addContentEntry(ContentEntry entry) {
content.add(entry);
}
public List<ContentEntry> getContent() {
return Collections.unmodifiableList(content);
}
/** Define the entry which contains the title page
*
* @param entry the entry
*/
public void setTitlePageFile(ContentEntry entry) {
titlePageFile = entry;
}
public ContentEntry getTitlePageFile() {
return titlePageFile;
}
/** Define the entry which contains the main text file
*
* @param entry the entry
*/
public void setTextFile(ContentEntry entry) {
textFile = entry;
}
public ContentEntry getTextFile() {
return textFile;
}
/** Define the entry which contains the table of contents
*
* @param entry the entry
*/
public void setTocFile(ContentEntry entry) {
tocFile = entry;
}
public ContentEntry getTocFile() {
return tocFile;
}
/** Define the entry which contains the list of figures
*
* @param entry the entry
*/
public void setLofFile(ContentEntry entry) {
lofFile = entry;
}
public ContentEntry getLofFile() {
return lofFile;
}
/** Define the entry which contains the list of tables
*
* @param entry the entry
*/
public void setLotFile(ContentEntry entry) {
lotFile = entry;
}
public ContentEntry getLotFile() {
return lotFile;
}
/** Define the entry which contains the alphabetical index
*
* @param entry the entry
*/
public void setIndexFile(ContentEntry entry) {
indexFile = entry;
}
public ContentEntry getIndexFile() {
return indexFile;
}
/** Define the entry which contains the bibliography
*
* @param entry the entry
*/
public void setBibliographyFile(ContentEntry entry) {
bibliographyFile = entry;
}
public ContentEntry getBibliographyFile() {
return bibliographyFile;
}
/** Define the entry which contains the cover
*
* @param entry the entry
*/
public void setCoverFile(ContentEntry entry) {
coverFile = entry;
}
public ContentEntry getCoverFile() {
return coverFile;
}
/** Define the entry which contains the cover image
*
* @param entry the entry
*/
public void setCoverImageFile(ContentEntry entry) {
coverImageFile = entry;
}
public ContentEntry getCoverImageFile() {
return coverImageFile;
}
/** Set the meta data of this <code>ConverterResult</code>
*
* @param metaData the meta data
*/
public void setMetaData(MetaData metaData) {
this.metaData = metaData;
}
/** Get the meta data of this <code>ConverterResult</code>
*
* @return the meta data
*/
public MetaData getMetaData() {
return metaData;
}
/** Write all files to a given directory
*
* @param dir the directory to use
*/
public void write(File dir) throws IOException {
if (dir!=null && !dir.exists()) throw new IOException("Directory does not exist");
Iterator<OutputFile> docEnum = iterator();
while (docEnum.hasNext()) {
OutputFile docOut = docEnum.next();
String sDirName = "";
String sFileName = docOut.getFileName();
File subdir = dir;
int nSlash = sFileName.indexOf("/");
if (nSlash>-1) {
sDirName = sFileName.substring(0,nSlash);
sFileName = sFileName.substring(nSlash+1);
subdir = new File(dir,sDirName);
if (!subdir.exists()) { subdir.mkdir(); }
}
File outfile = new File (subdir,sFileName);
FileOutputStream fos = new FileOutputStream(outfile);
docOut.write(fos);
fos.flush();
fos.close();
}
}
}

View file

@ -0,0 +1,369 @@
/************************************************************************
*
* DOMDocument.java
*
* Copyright: 2002-2015 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.6 (2015-05-05)
*
*/
package writer2latex.base;
import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.Element;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
//import org.xml.sax.SAXParseException;
import writer2latex.api.OutputFile;
/**
* This class represents XML-based documents. It is loosely based on a class from the former xmerge project
* from OOo.
*/
public class DOMDocument implements OutputFile {
/** Factory for <code>DocumentBuilder</code> objects. */
private static DocumentBuilderFactory factory =
DocumentBuilderFactory.newInstance();
/** DOM <code>Document</code> of content.xml. */
private Document contentDoc = null;
/** DOM <code>Document</code> of styles.xml. */
//private Document styleDoc = null;
private String documentName = null;
private String fileName = null;
private String fileExt = null;
/** Resources object. */
//private Resources res = null;
/**
* Default constructor.
*
* @param name <code>Document</code> name.
* @param ext <code>Document</code> extension.
*/
public DOMDocument(String name,String ext)
{
this(name,ext,true, false);
}
/**
* Returns the file extension of the <code>Document</code>
* represented.
*
* @return file extension of the <code>Document</code>.
*/
protected String getFileExtension() {
return fileExt;
}
/**
* Constructor with arguments to set <code>namespaceAware</code>
* and <code>validating</code> flags.
*
* @param name <code>Document</code> name (may or may not
* contain extension).
* @param ext <code>Document</code> extension.
* @param namespaceAware Value for <code>namespaceAware</code> flag.
* @param validating Value for <code>validating</code> flag.
*/
public DOMDocument(String name, String ext,boolean namespaceAware, boolean validating) {
//res = Resources.getInstance();
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</code>
* name.
*
* @param name Full <code>Document</code> name with extension.
*
* @return Name of <code>Document</code> 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</code> object of the document content
* file. Note that a content DOM is not created when the constructor
* is called. So, either the <code>read</code> method or the
* <code>initContentDOM</code> method will need to be called ahead
* on this object before calling this method.
*
* @return DOM <code>Document</code> object.
*/
public Document getContentDOM() {
return contentDoc;
}
/**
* Sets the Content of the <code>Document</code> to the contents of the
* supplied <code>Node</code> list.
*
* @param newDom DOM <code>Document</code> object.
*/
public void setContentDOM( Node newDom) {
contentDoc=(Document)newDom;
}
/**
* Return the name of the <code>Document</code>.
*
* @return The name of <code>Document</code>.
*/
public String getName() {
return documentName;
}
/**
* Return the file name of the <code>Document</code>, possibly
* with the standard extension.
*
* @return The file name of <code>Document</code>.
*/
public String getFileName() {
return fileName;
}
/**
* Read the Office <code>Document</code> from the specified
* <code>InputStream</code>.
*
* @param is Office document <code>InputStream</code>.
*
* @throws IOException If any I/O error occurs.
*/
public void read(InputStream is) throws IOException {
DocumentBuilder builder = null;
try {
builder = factory.newDocumentBuilder();
} catch (ParserConfigurationException ex) {
throw new IOException(ex.getMessage());
}
try {
contentDoc= builder.parse(is);
} catch (SAXException ex) {
throw new IOException(ex.getMessage());
}
}
/**
* Write out content to the supplied <code>OutputStream</code>.
* (with pretty printing)
* @param os XML <code>OutputStream</code>.
* @throws IOException If any I/O error occurs.
*/
public void write(OutputStream os) throws IOException {
OutputStreamWriter osw = new OutputStreamWriter(os,"UTF-8");
osw.write("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n");
write(getContentDOM().getDocumentElement(),0,osw);
osw.flush();
osw.close();
}
// Write nodes; we only need element, text and comment nodes
private void write(Node node, int nLevel, OutputStreamWriter osw) throws IOException {
short nType = node.getNodeType();
switch (nType) {
case Node.ELEMENT_NODE:
if (node.hasChildNodes()) {
// Block pretty print from this node?
NodeList list = node.getChildNodes();
int nLen = list.getLength();
boolean bBlockPrettyPrint = false;
if (nLevel>=0) {
for (int i = 0; i < nLen; i++) {
bBlockPrettyPrint |= list.item(i).getNodeType()==Node.TEXT_NODE;
}
}
// Print start tag
if (nLevel>=0) { writeSpaces(nLevel,osw); }
osw.write("<"+node.getNodeName());
writeAttributes(node,osw);
osw.write(">");
if (nLevel>=0 && !bBlockPrettyPrint) { osw.write("\n"); }
// Print children
for (int i = 0; i < nLen; i++) {
int nNextLevel;
if (bBlockPrettyPrint || nLevel<0) { nNextLevel=-1; }
else { nNextLevel=nLevel+1; }
write(list.item(i),nNextLevel,osw);
}
// Print end tag
if (nLevel>=0 && !bBlockPrettyPrint) { writeSpaces(nLevel,osw); }
osw.write("</"+node.getNodeName()+">");
if (nLevel>=0) { osw.write("\n"); }
}
else { // empty element
if (nLevel>=0) { writeSpaces(nLevel,osw); }
osw.write("<"+node.getNodeName());
writeAttributes(node,osw);
osw.write(" />");
if (nLevel>=0) { osw.write("\n"); }
}
break;
case Node.TEXT_NODE:
write(node.getNodeValue(),osw);
break;
case Node.COMMENT_NODE:
if (nLevel>=0) { writeSpaces(nLevel,osw); }
osw.write("<!-- ");
write(node.getNodeValue(),osw);
osw.write(" -->");
if (nLevel>=0) { osw.write("\n"); }
}
}
private void writeAttributes(Node node, OutputStreamWriter osw) throws IOException {
NamedNodeMap attr = node.getAttributes();
int nLen = attr.getLength();
for (int i=0; i<nLen; i++) {
Node item = attr.item(i);
osw.write(" ");
write(item.getNodeName(),osw);
osw.write("=\"");
write(item.getNodeValue(),osw);
osw.write("\"");
}
}
private void writeSpaces(int nCount, OutputStreamWriter osw) throws IOException {
for (int i=0; i<nCount; i++) { osw.write(" "); }
}
private void write(String s, OutputStreamWriter osw) throws IOException {
int nLen = s.length();
char c;
for (int i=0; i<nLen; i++) {
c = s.charAt(i);
switch (c) {
case ('<'): osw.write("&lt;"); break;
case ('>'): osw.write("&gt;"); break;
case ('&'): osw.write("&amp;"); break;
case ('"'): osw.write("&quot;"); break;
case ('\''): osw.write( "&apos;"); break;
default: osw.write(c);
}
}
}
/**
* Initializes a new DOM <code>Document</code> with the content
* containing minimum XML tags.
*
* @throws IOException If any I/O error occurs.
*/
public final void initContentDOM() throws IOException {
contentDoc = createDOM("");
}
/**
* <p>Creates a new DOM <code>Document</code> containing minimum
* OpenOffice XML tags.</p>
*
* <p>This method uses the subclass
* <code>getOfficeClassAttribute</code> 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) {
// This will not happen
System.err.println("Error:"+ ex);
throw new IOException(ex);
}
Element root = (Element) doc.createElement(rootName);
doc.appendChild(root);
return doc;
}
// We need these because we implement OutputFile
public String getMIMEType() {
return "";
}
public boolean isMasterDocument() {
return false;
}
public boolean containsMath() {
return false;
}
}

View file

@ -0,0 +1,354 @@
/************************************************************************
*
* ImageConverter.java
*
* Copyright: 2002-2014 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.6 (2014-11-18)
*
*/
package writer2latex.base;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import javax.xml.bind.DatatypeConverter;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import writer2latex.api.GraphicConverter;
import writer2latex.office.EmbeddedBinaryObject;
import writer2latex.office.EmbeddedObject;
import writer2latex.office.MIMETypes;
import writer2latex.office.OfficeReader;
import writer2latex.office.SVMReader;
import writer2latex.office.XMLString;
import writer2latex.util.Misc;
/** This class extracts and converts images from an office document.
* The images are returned as <code>BinaryGraphicsDocument</code>.
* The image converter can be configured as destructive. In this case, the returned
* graphics documents will contain the only reference to the image (the original data
* will be removed).
*/
public final class ImageConverter {
private OfficeReader ofr;
private boolean bDestructive;
// Data for file name generation
private String sBaseFileName = "";
private String sSubDirName = "";
private int nImageCount = 0;
private NumberFormat formatter;
// should EPS be extracted from SVM?
private boolean bExtractEPS;
// Data for image conversion
private GraphicConverter gcv = null;
private boolean bAcceptOtherFormats = true;
private String sDefaultFormat = null;
private String sDefaultVectorFormat = null;
private HashSet<String> acceptedFormats = new HashSet<String>();
// In the package format, the same image file may be used more than once in the document
// Hence we keep information of all documents for potential
private HashMap<String,BinaryGraphicsDocument> recycledImages = new HashMap<String,BinaryGraphicsDocument>();
/** Construct a new <code>ImageConverter</code> referring to a specific document
*
* @param ofr the office reader to use
* @param bExtractEPS set true if EPS content should be extracted from SVM files
*/
public ImageConverter(OfficeReader ofr, boolean bDestructive, boolean bExtractEPS) {
this.ofr = ofr;
this.bDestructive = bDestructive;
this.bExtractEPS = bExtractEPS;
this.formatter = new DecimalFormat("000");
}
/** Define the base file name to use for generating file names
*
* @param sBaseFileName the base file name
*/
public void setBaseFileName(String sBaseFileName) {
this.sBaseFileName = sBaseFileName;
}
/** Define the name of a sub directory to prepend to file names
*
* @param sSubDirName the sub directory
*/
public void setUseSubdir(String sSubDirName) {
this.sSubDirName = sSubDirName+"/";
}
/** Specify that the <code>ImageConverter</code> should return an image even if it was not possible
* to convert it to an acceptable format.
*
* @param b true if other formats should be accepted
*/
public void setAcceptOtherFormats(boolean b) {
bAcceptOtherFormats = b;
}
/** Define the default format for raster graphics
*
* @param sMime the MIME type of the default raster format
*/
public void setDefaultFormat(String sMime) {
addAcceptedFormat(sMime);
sDefaultFormat = sMime;
}
/** Define the default format for vector graphics
*
* @param sMime the MIME type for the default vector format
*/
public void setDefaultVectorFormat(String sMime) {
addAcceptedFormat(sMime);
sDefaultVectorFormat = sMime;
}
/** Define an accepted graphics format
*
* @param sMime the MIME type of the format
*/
public void addAcceptedFormat(String sMime) {
acceptedFormats.add(sMime);
}
/** Is a given format accepted?
*
* @param sMime the MIME type to query
* @return true if this is an accepted format
*/
private boolean isAcceptedFormat(String sMime) {
return acceptedFormats.contains(sMime);
}
/** Define the <code>GraphicConverter</code> to use for image conversion
*
* @param gcv the graphics converter
*/
public void setGraphicConverter(GraphicConverter gcv) {
this.gcv = gcv;
}
/** Get an image from a <code>draw:image</code> element. If the converter is destructive, the returned
* <code>BinaryGraphicsDocument</code> will hold the only reference to the image data (the original
* data will be removed).
*
* @param node the image element
* @return a document containing the (converted) image, or null if it was not possible to read the image
* or convert it to an accepted format
*/
public BinaryGraphicsDocument getImage(Element node) {
String sName = sSubDirName+sBaseFileName+formatter.format(++nImageCount);
BinaryGraphicsDocument bgd = getImage(node,sName);
if (bgd!=null) {
if (!bgd.isAcceptedFormat() || (sDefaultVectorFormat!=null && !sDefaultVectorFormat.equals(bgd.getMIMEType()))) {
// We may have better luck with an alternative image
Element sibling = getAlternativeImage(node);
if (sibling!=null) {
BinaryGraphicsDocument altBgd = getImage(sibling,sName);
if (altBgd!=null && altBgd.isAcceptedFormat()) {
if (!bgd.isAcceptedFormat() ||
(sDefaultVectorFormat!=null && !sDefaultVectorFormat.equals(bgd.getMIMEType()) &&
sDefaultVectorFormat.equals(altBgd.getMIMEType()))) {
bgd = altBgd;
}
}
}
}
}
if (bgd==null || bgd.isLinked() || bgd.isRecycled()) {
// The file name was not used
nImageCount--;
}
return bgd;
}
private BinaryGraphicsDocument getImage(Element node, String sName) {
assert(XMLString.DRAW_IMAGE.equals(node.getTagName()));
// Image data
String sExt = null;
String sMIME = null;
byte[] blob = null;
String sId = null;
// First try to extract the image using the xlink:href attribute
if (node.hasAttribute(XMLString.XLINK_HREF)) {
String sHref = node.getAttribute(XMLString.XLINK_HREF);
if (sHref.length()>0) {
// We may have seen this image before, return the recycled version
if (recycledImages.containsKey(sHref)) {
return recycledImages.get(sHref);
}
// Image may be embedded in package:
String sPath = sHref;
if (sPath.startsWith("#")) { sPath = sPath.substring(1); }
if (sPath.startsWith("./")) { sPath = sPath.substring(2); }
EmbeddedObject obj = ofr.getEmbeddedObject(sPath);
if (obj!=null && obj instanceof EmbeddedBinaryObject) {
EmbeddedBinaryObject object = (EmbeddedBinaryObject) obj;
blob = object.getBinaryData();
sMIME = object.getType();
if (sMIME.length()==0) {
// If the manifest provides a media type, trust that
// Otherwise determine it by byte inspection
sMIME = MIMETypes.getMagicMIMEType(blob);
}
sExt = MIMETypes.getFileExtension(sMIME);
if (bDestructive) {
object.dispose();
}
// We got an image, define ID for recycling
sId = sHref;
}
else {
// This is a linked image
// TODO: Add option to download image from the URL?
String sFileName = ofr.fixRelativeLink(sHref);
BinaryGraphicsDocument bgd
= new BinaryGraphicsDocument(sFileName,null);
return bgd;
}
}
}
// If there is no suitable xlink:href attribute, the image must be contained in an office:binary-element as base64
if (blob==null) {
Node obd = Misc.getChildByTagName(node,XMLString.OFFICE_BINARY_DATA);
if (obd!=null) {
StringBuilder buf = new StringBuilder();
NodeList nl = obd.getChildNodes();
int nLen = nl.getLength();
for (int i=0; i<nLen; i++) {
if (nl.item(i).getNodeType()==Node.TEXT_NODE) {
buf.append(nl.item(i).getNodeValue());
}
}
//blob = Base64.decode(buf.toString());
blob = DatatypeConverter.parseBase64Binary(buf.toString());
// We may have seen this image before, return the recycled version
String sId1 = createId(blob);
if (recycledImages.containsKey(sId1)) {
return recycledImages.get(sId1);
}
sMIME = MIMETypes.getMagicMIMEType(blob);
sExt = MIMETypes.getFileExtension(sMIME);
if (bDestructive) {
node.removeChild(obd);
}
// We got an image, define ID for recycling
sId = sId1;
}
else {
// There is no image data
return null;
}
}
// Is this an EPS file embedded in an SVM file?
// (This case is obsolete, but kept for the sake of old documents)
if (bExtractEPS && MIMETypes.SVM.equals(sMIME)) {
// Look for postscript:
int[] offlen = new int[2];
if (SVMReader.readSVM(blob,offlen)) {
String sFileName = sName+MIMETypes.EPS_EXT;
BinaryGraphicsDocument bgd
= new BinaryGraphicsDocument(sFileName, MIMETypes.EPS);
bgd.setData(blob,offlen[0],offlen[1],true);
return bgd;
}
}
// We have an embedded image.
// If we have a converter AND a default format AND this image
// is not in an accepted format AND the converter knows how to
// convert it - try to convert...
if (gcv!=null && !isAcceptedFormat(sMIME) && sDefaultFormat!=null) {
byte[] newBlob = null;
String sTargetMIME = null;
if (MIMETypes.isVectorFormat(sMIME) && sDefaultVectorFormat!=null &&
gcv.supportsConversion(sMIME,sDefaultVectorFormat,false,false)) {
// Try vector format first
newBlob = gcv.convert(blob, sMIME, sTargetMIME=sDefaultVectorFormat);
}
if (newBlob==null && gcv.supportsConversion(sMIME,sDefaultFormat,false,false)) {
// Then try bitmap format
newBlob = gcv.convert(blob,sMIME,sTargetMIME=sDefaultFormat);
}
if (newBlob!=null) {
// Conversion successful - create new data
blob = newBlob;
sMIME = sTargetMIME;
sExt = MIMETypes.getFileExtension(sMIME);
}
}
// Create the result
if (isAcceptedFormat(sMIME) || bAcceptOtherFormats) {
String sFileName = sName+sExt;
BinaryGraphicsDocument bgd = new BinaryGraphicsDocument(sFileName,sMIME);
bgd.setData(blob,isAcceptedFormat(sMIME));
if (sId!=null) {
recycledImages.put(sId, new BinaryGraphicsDocument(bgd));
}
return bgd;
}
else {
return null;
}
}
private Element getAlternativeImage(Element node) {
Node sibling = node.getNextSibling();
if (sibling!=null && Misc.isElement(sibling, XMLString.DRAW_IMAGE)) {
return (Element) sibling;
}
return null;
}
// Create a fingerprint of a blob. The fingerprint concatenates the MD5 hash with the first 10 bytes of the blob.
private String createId(byte[] blob) {
MessageDigest md;
try {
md = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
// This would be surprising
return null;
}
return DatatypeConverter.printHexBinary(md.digest(blob))
+DatatypeConverter.printHexBinary(Arrays.copyOf(blob, 10));
}
}

View file

@ -0,0 +1,39 @@
/************************************************************************
*
* IntegerOption.java
*
* Copyright: 2002-2008 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.0 (2008-09-08)
*
*/
package writer2latex.base;
// An IntegerOption must always be subclassed (must override setString)
public abstract class IntegerOption extends Option {
protected int nValue;
public int getValue() { return nValue; }
public IntegerOption(String sName, String sDefaultValue) {
super(sName,sDefaultValue);
}
}

View file

@ -0,0 +1,44 @@
/************************************************************************
*
* Option.java
*
* Copyright: 2002-2008 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.0 (2008-09-08)
*
*/
package writer2latex.base;
// The mother of all options; reads and writes string values
public class Option {
protected String sValue;
private String sName;
public void setString(String sValue) { this.sValue = sValue; }
public String getString() { return sValue; }
public String getName() { return sName; }
public Option(String sName, String sDefaultValue) {
this.sName = sName;
setString(sDefaultValue);
}
}

View file

@ -0,0 +1,17 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title>The package writer2latex.base</title>
</head>
<body>
<p>This package contains (abstract) base implementations of some of the
interfaces in writer2latex.api</p>
<p>They are intended to be subclassed by converters into specific formats
e.g. LaTeX, xhtml</p>
</body>
</html>

View file

@ -0,0 +1,167 @@
/************************************************************************
*
* BibTeXDocument.java
*
* Copyright: 2002-2015 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.6 (2015-07-01)
*
*/
package writer2latex.bibtex;
import java.util.Hashtable;
import java.util.Enumeration;
import java.util.List;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import org.w3c.dom.Element;
import writer2latex.api.ConverterFactory;
import writer2latex.api.MIMETypes;
import writer2latex.api.OutputFile;
import writer2latex.latex.LaTeXConfig;
import writer2latex.latex.i18n.ClassicI18n;
import writer2latex.latex.i18n.I18n;
import writer2latex.util.ExportNameCollection;
import writer2latex.office.BibMark;
import writer2latex.office.BibMark.EntryType;
import writer2latex.office.OfficeReader;
/** Class representing a BibTeX document
*/
public class BibTeXDocument implements OutputFile {
private static final String FILE_EXTENSION = ".bib";
private String sName;
private Hashtable<String, BibMark> entries = new Hashtable<String, BibMark>();
private ExportNameCollection exportNames = new ExportNameCollection("",true,"_-:");
private I18n i18n;
private boolean bIsMaster;
/** Constructs a new BibTeX Document based on an office document
*
* @param sName The name of the document
* @param bIsMaster is this a master document?
* @param ofr the office document
*/
public BibTeXDocument(String sName, boolean bIsMaster, OfficeReader ofr) {
this.sName = sName;
this.bIsMaster = bIsMaster;
loadEntries(ofr);
// Use default config (only ascii, no extra font packages)
i18n = new ClassicI18n(new LaTeXConfig());
}
private void loadEntries(OfficeReader ofr) {
List<Element> bibMarks = ofr.getBibliographyMarks();
for (Element bibMark : bibMarks) {
BibMark entry = new BibMark(bibMark);
entries.put(entry.getIdentifier(),entry);
exportNames.addName(entry.getIdentifier());
}
}
// Methods to query the content
/** Test whether or not this BibTeX document contains any entries
*
* @return true if there is one or more entries in the document
*/
public boolean isEmpty() {
return entries.size()==0;
}
/** Get export name for an identifier
*
* @param sIdentifier the identifier
* @return the export name
*/
public String getExportName(String sIdentifier) {
return exportNames.getExportName(sIdentifier);
}
/** Returns the document name without file extension
*
* @return the document name without file extension
*/
public String getName() {
return sName;
}
// Implement writer2latex.api.OutputFile
public String getFileName() {
return new String(sName + FILE_EXTENSION);
}
public String getMIMEType() {
return MIMETypes.BIBTEX;
}
public boolean isMasterDocument() {
return bIsMaster;
}
public boolean containsMath() {
return false;
}
public void write(OutputStream os) throws IOException {
// BibTeX files are plain ascii
OutputStreamWriter osw = new OutputStreamWriter(os,"ASCII");
osw.write("%% This file was converted to BibTeX by Writer2BibTeX ver. "+ConverterFactory.getVersion()+".\n");
osw.write("%% See http://writer2latex.sourceforge.net for more info.\n");
osw.write("\n");
Enumeration<BibMark> enumeration = entries.elements();
while (enumeration.hasMoreElements()) {
BibMark entry = enumeration.nextElement();
osw.write("@");
osw.write(entry.getEntryType().toUpperCase());
osw.write("{");
osw.write(exportNames.getExportName(entry.getIdentifier()));
osw.write(",\n");
for (EntryType entryType : EntryType.values()) {
String sValue = entry.getField(entryType);
if (sValue!=null) {
if (entryType==EntryType.author || entryType==EntryType.editor) {
// OOo uses ; to separate authors and editors - BibTeX uses and
sValue = sValue.replaceAll(";" , " and ");
}
osw.write(" ");
osw.write(BibTeXEntryMap.getFieldName(entryType).toUpperCase());
osw.write(" = {");
for (int j=0; j<sValue.length(); j++) {
String s = i18n.convert(Character.toString(sValue.charAt(j)),false,"en");
if (s.charAt(0)=='\\') { osw.write("{"); }
osw.write(s);
if (s.charAt(0)=='\\') { osw.write("}"); }
}
osw.write("},\n");
}
}
osw.write("}\n\n");
}
osw.flush();
osw.close();
}
}

View file

@ -0,0 +1,109 @@
/************************************************************************
*
* BibTeXEntryMap.java
*
* Copyright: 2002-2014 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.6 (2014-12-16)
*
*/
package writer2latex.bibtex;
import java.util.HashMap;
import java.util.Map;
import writer2latex.office.BibMark.EntryType;
/**
* This class provides static methods to map the the entry types of an ODF
* bibliography mark to and from BibTeX field names
*
*/
public class BibTeXEntryMap {
private static Map<EntryType, String> bibTeXFields = null;
private static Map<String, EntryType> entryTypes = null;
private static void createMaps() {
// Note the BibTeX fileds key and crossref are not supported in ODF
bibTeXFields = new HashMap<EntryType, String>();
bibTeXFields.put(EntryType.address, "address");
bibTeXFields.put(EntryType.annote, "annote");
bibTeXFields.put(EntryType.author, "author");
bibTeXFields.put(EntryType.booktitle, "booktitle");
bibTeXFields.put(EntryType.chapter, "chapter");
bibTeXFields.put(EntryType.edition, "edition");
bibTeXFields.put(EntryType.editor, "editor");
bibTeXFields.put(EntryType.howpublished, "howpublished");
bibTeXFields.put(EntryType.institution, "institution");
bibTeXFields.put(EntryType.journal, "journal");
bibTeXFields.put(EntryType.month, "month");
bibTeXFields.put(EntryType.note, "note");
bibTeXFields.put(EntryType.number, "number");
bibTeXFields.put(EntryType.organizations, "organization");
bibTeXFields.put(EntryType.pages, "pages");
bibTeXFields.put(EntryType.publisher, "publisher");
bibTeXFields.put(EntryType.school, "school");
bibTeXFields.put(EntryType.series, "series");
bibTeXFields.put(EntryType.title, "title");
bibTeXFields.put(EntryType.report_type, "type");
bibTeXFields.put(EntryType.volume, "volume");
bibTeXFields.put(EntryType.year, "year");
bibTeXFields.put(EntryType.url, "url");
bibTeXFields.put(EntryType.custom1, "custom1");
bibTeXFields.put(EntryType.custom2, "custom2");
bibTeXFields.put(EntryType.custom3, "custom3");
bibTeXFields.put(EntryType.custom4, "custom4");
bibTeXFields.put(EntryType.custom5, "custom5");
bibTeXFields.put(EntryType.isbn, "isbn");
entryTypes = new HashMap<String, EntryType>();
for (EntryType entryType : bibTeXFields.keySet()) {
entryTypes.put(bibTeXFields.get(entryType), entryType);
}
}
/**
* Return BibTeX field name corresponding to and entry type
*
* @param entryType
* the entry type
* @return the BibTeX field name, or null if there is no corresponding
* BibTeX field
*/
public static final String getFieldName(EntryType entryType) {
if (bibTeXFields == null) {
createMaps();
}
return bibTeXFields.containsKey(entryType) ? bibTeXFields.get(entryType) : null;
}
/**
* Return entry type corresponding to a BibTeX field
*
* @param sFieldName
* the BibTeX field name
* @return the entry type, or null if there is no corresponding entry type
*/
public static final EntryType getEntryType(String sFieldName) {
if (bibTeXFields == null) {
createMaps();
}
sFieldName = sFieldName.toLowerCase();
return entryTypes.containsKey(sFieldName) ? entryTypes.get(sFieldName) : null;
}
}

View file

@ -0,0 +1,67 @@
/************************************************************************
*
* Converter.java
*
* Copyright: 2001-2015 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.6 (2015-06-22)
*
*/
package writer2latex.bibtex;
import writer2latex.api.Config;
import writer2latex.base.ConverterBase;
import writer2latex.latex.LaTeXConfig;
import writer2latex.util.Misc;
import java.io.IOException;
/** This class exports bibliographic information from an OpenDocument text file to a BibTeX data file
*/
public final class Converter extends ConverterBase {
// Implement converter API
// TODO: Doesn't really use the configuration - should use some fake config
private LaTeXConfig config;
public Converter() {
super();
config = new LaTeXConfig();
}
public Config getConfig() {
return config;
}
// Extend converter base
/** Convert the document into BibTeX format.</p>
*
* @throws IOException If any I/O error occurs.
*/
@Override public void convertInner() throws IOException {
sTargetFileName = Misc.trimDocumentName(sTargetFileName,".bib");
BibTeXDocument bibDoc = new BibTeXDocument(sTargetFileName,true,ofr);
converterResult.addDocument(bibDoc);
}
}

View file

@ -0,0 +1,14 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title>The package writer2latex.bibtex</title>
</head>
<body>
<p>This package contains BibTeX specific code.</p>
<p>It contains a <code>writerlatex.api.Converter</code> implementation for
conversion into BibTeX, as well as code to convert to BibTeX as part of a
conversion into LaTeX.</p>
</body>
</html>

View file

@ -0,0 +1,76 @@
/************************************************************************
*
* ContainerWriter.java
*
* Copyright: 2001-2014 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* version 1.4 (2014-08-26)
*
*/
package writer2latex.epub;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Document;
import org.w3c.dom.DocumentType;
import org.w3c.dom.Element;
import writer2latex.base.DOMDocument;
/** This class creates the required META-INF/container.xml file for an EPUB package
* (see http://www.idpf.org/ocf/ocf1.0/download/ocf10.htm).
*/
public class ContainerWriter extends DOMDocument {
public ContainerWriter() {
super("container", "xml");
// create DOM
Document contentDOM = null;
try {
DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = builderFactory.newDocumentBuilder();
DOMImplementation domImpl = builder.getDOMImplementation();
DocumentType doctype = domImpl.createDocumentType("container","","");
contentDOM = domImpl.createDocument("urn:oasis:names:tc:opendocument:xmlns:container","container",doctype);
}
catch (ParserConfigurationException t) { // this should never happen
throw new RuntimeException(t);
}
// Populate the DOM tree
Element container = contentDOM.getDocumentElement();
container.setAttribute("version", "1.0");
container.setAttribute("xmlns","urn:oasis:names:tc:opendocument:xmlns:container");
Element rootfiles = contentDOM.createElement("rootfiles");
container.appendChild(rootfiles);
Element rootfile = contentDOM.createElement("rootfile");
rootfile.setAttribute("full-path", "OEBPS/book.opf");
rootfile.setAttribute("media-type", "application/oebps-package+xml");
rootfiles.appendChild(rootfile);
setContentDOM(contentDOM);
}
}

View file

@ -0,0 +1,64 @@
/************************************************************************
*
* EPUB3Converter.java
*
* Copyright: 2002-2015 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* version 1.6 (2015-01-15)
*
*/
package writer2latex.epub;
import java.io.IOException;
import java.io.InputStream;
import writer2latex.api.ConverterResult;
import writer2latex.base.ConverterResultImpl;
import writer2latex.xhtml.Html5Converter;
/** This class converts an OpenDocument file to an EPUB 3 document.
*/
public final class EPUB3Converter extends Html5Converter {
// Constructor
public EPUB3Converter() {
super();
}
@Override public ConverterResult convert(InputStream is, String sTargetFileName) throws IOException {
setOPS(true);
ConverterResult xhtmlResult = super.convert(is, "chapter");
return createPackage(xhtmlResult,sTargetFileName);
}
@Override public ConverterResult convert(org.w3c.dom.Document dom, String sTargetFileName, boolean bDestructive) throws IOException {
setOPS(true);
ConverterResult xhtmlResult = super.convert(dom, "chapter", bDestructive);
return createPackage(xhtmlResult,sTargetFileName);
}
private ConverterResult createPackage(ConverterResult xhtmlResult, String sTargetFileName) {
ConverterResultImpl epubResult = new ConverterResultImpl();
epubResult.addDocument(new EPUBWriter(xhtmlResult,sTargetFileName,3,getXhtmlConfig()));
epubResult.setMetaData(xhtmlResult.getMetaData());
return epubResult;
}
}

View file

@ -0,0 +1,64 @@
/************************************************************************
*
* EPUBConverter.java
*
* Copyright: 2002-2015 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* version 1.6 (2015-01-15)
*
*/
package writer2latex.epub;
import java.io.IOException;
import java.io.InputStream;
import writer2latex.api.ConverterResult;
import writer2latex.base.ConverterResultImpl;
import writer2latex.xhtml.Xhtml11Converter;
/** This class converts an OpenDocument file to an EPUB document.
*/
public final class EPUBConverter extends Xhtml11Converter {
// Constructor
public EPUBConverter() {
super();
}
@Override public ConverterResult convert(InputStream is, String sTargetFileName) throws IOException {
setOPS(true);
ConverterResult xhtmlResult = super.convert(is, "chapter");
return createPackage(xhtmlResult,sTargetFileName);
}
@Override public ConverterResult convert(org.w3c.dom.Document dom, String sTargetFileName, boolean bDestructive) throws IOException {
setOPS(true);
ConverterResult xhtmlResult = super.convert(dom, "chapter", bDestructive);
return createPackage(xhtmlResult,sTargetFileName);
}
private ConverterResult createPackage(ConverterResult xhtmlResult, String sTargetFileName) {
ConverterResultImpl epubResult = new ConverterResultImpl();
epubResult.addDocument(new EPUBWriter(xhtmlResult,sTargetFileName,2,getXhtmlConfig()));
epubResult.setMetaData(xhtmlResult.getMetaData());
return epubResult;
}
}

View file

@ -0,0 +1,152 @@
/************************************************************************
*
* EPUBWriter.java
*
* Copyright: 2002-2015 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* version 1.6 (2015-06-24)
*
*/
package writer2latex.epub;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Iterator;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import writer2latex.api.ConverterResult;
import writer2latex.api.OutputFile;
import writer2latex.util.Misc;
import writer2latex.xhtml.XhtmlConfig;
/** This class repackages an XHTML document into EPUB format.
* Some filenames are hard wired in this implementation: The main directory is OEBPS and
* the OPF and NCX files are book.opf and book.ncx respectively; finally the EPUB 3 navigation
* document is nav.xhtml
*/
public class EPUBWriter implements OutputFile {
private static final byte[] mimeBytes = { 'a', 'p', 'p', 'l', 'i', 'c', 'a', 't', 'i', 'o', 'n', '/',
'e', 'p', 'u', 'b', '+', 'z', 'i', 'p'};
private ConverterResult xhtmlResult;
private String sFileName;
private int nVersion;
private XhtmlConfig config;
/** Create a new <code>EPUBWriter</code> based on a <code>ConverterResult</code>.
*
* @param xhtmlResult the converter result to repackage
* @param sFileName the file name to use for the result
* @param nVersion the EPUB version number. Can be either 2 or 3, other values are interpreted as 2.
* @param config the configuration to use
*/
public EPUBWriter(ConverterResult xhtmlResult, String sFileName, int nVersion, XhtmlConfig config) {
this.xhtmlResult = xhtmlResult;
this.sFileName = Misc.removeExtension(sFileName);
this.nVersion = nVersion;
this.config = config;
}
// Implement OutputFile
public String getFileName() {
return sFileName+".epub";
}
public String getMIMEType() {
return "application/epub+zip";
}
public boolean isMasterDocument() {
return true;
}
public boolean containsMath() {
// We don't really care about this
return nVersion==3;
}
public void write(OutputStream os) throws IOException {
ZipOutputStream zos = new ZipOutputStream(os);
// Write uncompressed MIME type as first entry
ZipEntry mimeEntry = new ZipEntry("mimetype");
mimeEntry.setMethod(ZipEntry.STORED);
mimeEntry.setCrc(0x2CAB616F);
mimeEntry.setSize(mimeBytes.length);
zos.putNextEntry(mimeEntry);
zos.write(mimeBytes, 0, mimeBytes.length);
zos.closeEntry();
// Write container entry next
OutputFile containerWriter = new ContainerWriter();
ZipEntry containerEntry = new ZipEntry("META-INF/container.xml");
zos.putNextEntry(containerEntry);
writeZipEntry(containerWriter,zos);
zos.closeEntry();
// Then manifest
OPFWriter manifest = new OPFWriter(xhtmlResult,sFileName,nVersion,config);
ZipEntry manifestEntry = new ZipEntry("OEBPS/book.opf");
zos.putNextEntry(manifestEntry);
writeZipEntry(manifest,zos);
zos.closeEntry();
// And content table
if (nVersion==3) {
OutputFile navigation = new NavigationWriter(xhtmlResult);
ZipEntry navigationEntry = new ZipEntry("OEBPS/nav.xhtml");
zos.putNextEntry(navigationEntry);
writeZipEntry(navigation,zos);
zos.closeEntry();
}
if (nVersion!=3 || config.includeNCX()) {
OutputFile ncx = new NCXWriter(xhtmlResult, manifest.getUid());
ZipEntry ncxEntry = new ZipEntry("OEBPS/book.ncx");
zos.putNextEntry(ncxEntry);
writeZipEntry(ncx,zos);
zos.closeEntry();
}
// Finally XHTML content
Iterator<OutputFile> iter = xhtmlResult.iterator();
while (iter.hasNext()) {
OutputFile file = iter.next();
ZipEntry entry = new ZipEntry("OEBPS/"+file.getFileName());
zos.putNextEntry(entry);
writeZipEntry(file, zos);
zos.closeEntry();
}
zos.close();
}
private void writeZipEntry(OutputFile file, ZipOutputStream zos) throws IOException {
// Unfortunately we cannot simply do file.write(zos) because the write method of OutputFile
// closes the OutputStream. Hence this suboptimal solution
ByteArrayOutputStream baos = new ByteArrayOutputStream();
file.write(baos);
byte[] content = baos.toByteArray();
zos.write(content, 0, content.length);
}
}

View file

@ -0,0 +1,162 @@
/************************************************************************
*
* NCXWriter.java
*
* Copyright: 2001-2014 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* version 1.4 (2014-08-26)
*
*/
package writer2latex.epub;
import java.util.Iterator;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Document;
import org.w3c.dom.DocumentType;
import org.w3c.dom.Element;
import writer2latex.api.ContentEntry;
import writer2latex.api.ConverterResult;
import writer2latex.base.DOMDocument;
import writer2latex.util.Misc;
/** This class creates the required NXC file for an EPUB document
* (see http://www.idpf.org/2007/opf/OPF_2.0_final_spec.html#Section2.4).
*/
public class NCXWriter extends DOMDocument {
public NCXWriter(ConverterResult cr, String sUUID) {
super("book", "ncx");
// create DOM
Document contentDOM = null;
try {
DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = builderFactory.newDocumentBuilder();
DOMImplementation domImpl = builder.getDOMImplementation();
DocumentType doctype = domImpl.createDocumentType("ncx","","");
contentDOM = domImpl.createDocument("http://www.daisy.org/z3986/2005/ncx/","ncx",doctype);
}
catch (ParserConfigurationException t) { // this should never happen
throw new RuntimeException(t);
}
// Populate the DOM tree
Element ncx = contentDOM.getDocumentElement();
ncx.setAttribute("version", "2005-1");
ncx.setAttribute("xml:lang", cr.getMetaData().getLanguage());
ncx.setAttribute("xmlns","http://www.daisy.org/z3986/2005/ncx/");
// The head has four required meta data items
Element head = contentDOM.createElement("head");
ncx.appendChild(head);
Element uid = contentDOM.createElement("meta");
uid.setAttribute("name","dtb:uid");
uid.setAttribute("content", sUUID);
head.appendChild(uid);
Element depth = contentDOM.createElement("meta");
depth.setAttribute("name","dtb:depth");
// Setting the content attribute later
head.appendChild(depth);
Element totalPageCount = contentDOM.createElement("meta");
totalPageCount.setAttribute("name","dtb:totalPageCount");
totalPageCount.setAttribute("content", "0");
head.appendChild(totalPageCount);
Element maxPageNumber = contentDOM.createElement("meta");
maxPageNumber.setAttribute("name","dtb:maxPageNumber");
maxPageNumber.setAttribute("content", "0");
head.appendChild(maxPageNumber);
// The ncx must contain a docTitle element
Element docTitle = contentDOM.createElement("docTitle");
ncx.appendChild(docTitle);
Element docTitleText = contentDOM.createElement("text");
docTitle.appendChild(docTitleText);
docTitleText.appendChild(contentDOM.createTextNode(cr.getMetaData().getTitle()));
// Build the navMap from the content table in the converter result
Element navMap = contentDOM.createElement("navMap");
ncx.appendChild(navMap);
Element currentContainer = ncx;
int nCurrentLevel = 0;
int nCurrentEntryLevel = 0; // This may differ from nCurrentLevel if the heading levels "jump" in then document
int nDepth = 0;
int nPlayOrder = 0;
Iterator<ContentEntry> content = cr.getContent().iterator();
while (content.hasNext()) {
ContentEntry entry = content.next();
int nEntryLevel = Math.max(entry.getLevel(), 1);
if (nEntryLevel<nCurrentLevel) {
// Return to higher level
for (int i=nEntryLevel; i<nCurrentLevel; i++) {
currentContainer = (Element) currentContainer.getParentNode();
}
nCurrentLevel = nEntryLevel;
}
else if (nEntryLevel>nCurrentEntryLevel) {
// To lower level (always one step; a jump from e.g. heading 1 to heading 3 in the document
// is considered an error)
currentContainer = (Element) currentContainer.getLastChild();
nCurrentLevel++;
}
nCurrentEntryLevel = nEntryLevel;
Element navPoint = contentDOM.createElement("navPoint");
navPoint.setAttribute("playOrder", Integer.toString(++nPlayOrder));
navPoint.setAttribute("id", "text"+nPlayOrder);
currentContainer.appendChild(navPoint);
Element navLabel = contentDOM.createElement("navLabel");
navPoint.appendChild(navLabel);
Element navLabelText = contentDOM.createElement("text");
navLabel.appendChild(navLabelText);
navLabelText.appendChild(contentDOM.createTextNode(entry.getTitle()));
Element navPointContent = contentDOM.createElement("content");
String sHref = Misc.makeHref(entry.getFile().getFileName());
if (entry.getTarget()!=null) { sHref+="#"+entry.getTarget(); }
navPointContent.setAttribute("src", sHref);
navPoint.appendChild(navPointContent);
nDepth = Math.max(nDepth, nCurrentLevel);
}
if (nDepth==0) {
// TODO: We're in trouble: The document has no headings
}
depth.setAttribute("content", Integer.toString(nDepth));
setContentDOM(contentDOM);
}
}

View file

@ -0,0 +1,130 @@
/************************************************************************
*
* NavigationWriter.java
*
* Copyright: 2001-2015 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* version 1.6 (2015-04-21)
*
*/
package writer2latex.epub;
import java.util.Iterator;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Document;
import org.w3c.dom.DocumentType;
import org.w3c.dom.Element;
import writer2latex.api.ContentEntry;
import writer2latex.api.ConverterResult;
import writer2latex.base.DOMDocument;
import writer2latex.util.Misc;
/** This class writes the EPUB Navigation Document as defined in EPUB 3
*/
public class NavigationWriter extends DOMDocument {
public NavigationWriter(ConverterResult cr) {
super("nav", "xhtml");
// create DOM
Document contentDOM = null;
try {
DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = builderFactory.newDocumentBuilder();
DOMImplementation domImpl = builder.getDOMImplementation();
DocumentType doctype = domImpl.createDocumentType("xhtml","","");
contentDOM = domImpl.createDocument("http://www.w3.org/1999/xhtml","html",doctype);
}
catch (ParserConfigurationException t) { // this should never happen
throw new RuntimeException(t);
}
// Populate the DOM tree
Element doc = contentDOM.getDocumentElement();
doc.setAttribute("xmlns","http://www.w3.org/1999/xhtml");
doc.setAttribute("xmlns:epub","http://www.idpf.org/2007/ops");
doc.setAttribute("xml:lang", cr.getMetaData().getLanguage());
doc.setAttribute("lang", cr.getMetaData().getLanguage());
// Create and populate the head
Element head = contentDOM.createElement("head");
doc.appendChild(head);
Element title = contentDOM.createElement("title");
head.appendChild(title);
title.appendChild(contentDOM.createTextNode("EPUB 3 Navigation Document"));
// Or use the document title cr.getMetaData().getTitle()?
// Create the body
Element body = contentDOM.createElement("body");
doc.appendChild(body);
// Create nav element
Element nav = contentDOM.createElement("nav");
nav.setAttribute("epub:type", "toc");
body.appendChild(nav);
// Populate the nav element from the content table in the converter result
Element currentContainer = body;
int nCurrentLevel = 0;
int nCurrentEntryLevel = 0; // This may differ from nCurrentLevel if the heading levels "jump" in then document
Iterator<ContentEntry> content = cr.getContent().iterator();
while (content.hasNext()) {
ContentEntry entry = content.next();
int nEntryLevel = Math.max(entry.getLevel(), 1);
if (nEntryLevel<nCurrentLevel) {
// Return to higher level
for (int i=nEntryLevel; i<nCurrentLevel; i++) {
currentContainer = (Element) currentContainer.getParentNode().getParentNode();
}
nCurrentLevel = nEntryLevel;
}
else if (nEntryLevel>nCurrentEntryLevel) {
// To lower level (always one step; a jump from e.g. heading 1 to heading 3 in the document
// is considered an error)
currentContainer = (Element) currentContainer.getLastChild();
Element ol = contentDOM.createElement("ol");
currentContainer.appendChild(ol);
currentContainer = ol;
nCurrentLevel++;
}
nCurrentEntryLevel = nEntryLevel;
// Create the actual toc entry
Element li = contentDOM.createElement("li");
currentContainer.appendChild(li);
Element a = contentDOM.createElement("a");
li.appendChild(a);
String sHref = Misc.makeHref(entry.getFile().getFileName());
if (entry.getTarget()!=null) { sHref+="#"+entry.getTarget(); }
a.setAttribute("href", sHref);
a.appendChild(contentDOM.createTextNode(entry.getTitle()));
}
setContentDOM(contentDOM);
}
}

View file

@ -0,0 +1,419 @@
/************************************************************************
*
* OPFWriter.java
*
* Copyright: 2001-2015 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* version 1.6 (2015-06-24)
*
*/
package writer2latex.epub;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.TimeZone;
import java.util.UUID;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Document;
import org.w3c.dom.DocumentType;
import org.w3c.dom.Element;
import writer2latex.api.ContentEntry;
import writer2latex.api.ConverterResult;
import writer2latex.api.OutputFile;
import writer2latex.base.DOMDocument;
import writer2latex.util.Misc;
import writer2latex.xhtml.XhtmlConfig;
/** This class writes an OPF-file for an EPUB document (see http://www.idpf.org/2007/opf/OPF_2.0_final_spec.html).
*/
public class OPFWriter extends DOMDocument {
private String sUID=null;
public OPFWriter(ConverterResult cr, String sFileName, int nVersion, XhtmlConfig config) {
super("book", "opf");
// create DOM
Document contentDOM = null;
try {
DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = builderFactory.newDocumentBuilder();
DOMImplementation domImpl = builder.getDOMImplementation();
DocumentType doctype = domImpl.createDocumentType("package","","");
contentDOM = domImpl.createDocument("http://www.idpf.org/2007/opf","package",doctype);
}
catch (ParserConfigurationException t) { // this should never happen
throw new RuntimeException(t);
}
// Populate the DOM tree
Element pack = contentDOM.getDocumentElement();
if (nVersion==3) {
pack.setAttribute("version", "3.0");
}
else {
pack.setAttribute("version", "2.0");
}
pack.setAttribute("xmlns","http://www.idpf.org/2007/opf");
pack.setAttribute("unique-identifier", "BookId");
// Meta data, at least dc:title, dc:language and dc:identifier are required by the specification
// For EPUB 3, also dcterms:modified is required
Element metadata = contentDOM.createElement("metadata");
metadata.setAttribute("xmlns:dc", "http://purl.org/dc/elements/1.1/");
if (nVersion!=3) {
metadata.setAttribute("xmlns:opf", "http://www.idpf.org/2007/opf");
}
pack.appendChild(metadata);
// Title and language (required; use file name if title is empty)
String sTitle = cr.getMetaData().getTitle();
appendElement(contentDOM, metadata, "dc:title", sTitle.trim().length()>0 ? sTitle : sFileName);
appendElement(contentDOM, metadata, "dc:language", cr.getMetaData().getLanguage());
// Modification (required in EPUB 3)
if (nVersion==3) {
appendElement(contentDOM, metadata, "meta", getCurrentDateTime())
.setAttribute("property", "dcterms:modified");
}
// Subject and keywords in ODF both map to Dublin core subjects
if (cr.getMetaData().getSubject().length()>0) {
appendElement(contentDOM, metadata, "dc:subject", cr.getMetaData().getSubject());
}
if (cr.getMetaData().getKeywords().length()>0) {
String[] sKeywords = cr.getMetaData().getKeywords().split(",");
for (String sKeyword : sKeywords) {
appendElement(contentDOM, metadata, "dc:subject", sKeyword.trim());
}
}
if (cr.getMetaData().getDescription().length()>0) {
appendElement(contentDOM, metadata, "dc:description", cr.getMetaData().getDescription());
}
// User defined meta data
// The identifier, creator, contributor and date has an optional attribute and there may be multiple instances of
// the latter three. The key must be in the form name[id][.attribute]
// where the id is some unique id amongst the instances with the same name
// Furthermore the instances will be sorted on the id
// Thus you can have e.g. creator1.aut="John Doe" and creator2.aut="Jane Doe", and "John Doe" will be the first author
boolean bHasIdentifier = false;
boolean bHasCreator = false;
boolean bHasDate = false;
// First rearrange the user-defined meta data
Map<String,String> userDefinedMetaData = cr.getMetaData().getUserDefinedMetaData();
Map<String,String[]> dc = new HashMap<String,String[]>();
for (String sKey : userDefinedMetaData.keySet()) {
if (sKey.length()>0) {
String[] sValue = new String[2];
sValue[0] = userDefinedMetaData.get(sKey);
String sNewKey;
int nDot = sKey.indexOf(".");
if (nDot>0) {
sNewKey = sKey.substring(0, nDot).toLowerCase();
sValue[1] = sKey.substring(nDot+1);
}
else {
sNewKey = sKey.toLowerCase();
sValue[1] = null;
}
dc.put(sNewKey, sValue);
}
}
// Then export it
String[] sKeys = Misc.sortStringSet(dc.keySet());
for (String sKey : sKeys) {
String sValue = dc.get(sKey)[0];
String sAttributeValue = dc.get(sKey)[1];
if (sKey.startsWith("identifier")) {
Element identifier = appendElement(contentDOM, metadata, "dc:identifier", sValue);
if (!bHasIdentifier) { // The first identifier is the unique ID
identifier.setAttribute("id", "BookId");
sUID = sValue;
}
if (sAttributeValue!=null) {
if (nVersion==3) {
Element meta = appendElement(contentDOM, metadata, "meta", sAttributeValue);
meta.setAttribute("refines", "#BookId");
meta.setAttribute("property", "identifier-type");
}
else {
identifier.setAttribute("opf:scheme", sAttributeValue);
}
}
bHasIdentifier = true;
}
else if (sKey.startsWith("creator")) {
Element creator = appendElement(contentDOM, metadata, "dc:creator", sValue);
creator.setAttribute("id", sKey);
if (nVersion==3) {
Element fileas = appendElement(contentDOM, metadata, "meta", fileAs(sValue));
fileas.setAttribute("refines", "#"+sKey);
fileas.setAttribute("property", "file-as");
if (sAttributeValue!=null) {
Element role = appendElement(contentDOM, metadata, "meta", sAttributeValue);
role.setAttribute("refines", "#"+sKey);
role.setAttribute("property", "role");
}
}
else {
creator.setAttribute("opf:file-as", fileAs(sValue));
if (sAttributeValue!=null) {
creator.setAttribute("opf:role", sAttributeValue);
}
}
bHasCreator = true;
}
else if (sKey.startsWith("contributor")) {
Element contributor = appendElement(contentDOM, metadata, "dc:contributor", sValue);
contributor.setAttribute("id", sKey);
if (nVersion==3) {
Element fileas = appendElement(contentDOM, metadata, "meta", fileAs(sValue));
fileas.setAttribute("refines", "#"+sKey);
fileas.setAttribute("property", "file-as");
if (sAttributeValue!=null) {
Element role = appendElement(contentDOM, metadata, "meta", sAttributeValue);
role.setAttribute("refines", "#"+sKey);
role.setAttribute("property", "role");
}
}
else {
contributor.setAttribute("opf:file-as", fileAs(sValue));
if (sAttributeValue!=null) {
contributor.setAttribute("opf:role", sAttributeValue);
}
}
}
else if (sKey.startsWith("date")) {
Element date = appendElement(contentDOM, metadata, "dc:date", sValue);
date.setAttribute("id", sKey);
if (nVersion==3) {
if (sAttributeValue!=null) {
Element event = appendElement(contentDOM, metadata, "meta", sAttributeValue);
event.setAttribute("refines", "#"+sKey);
event.setAttribute("property", "event");
}
}
else {
if (sAttributeValue!=null) {
date.setAttribute("opf:event", sAttributeValue);
}
}
bHasDate = true;
}
// Remaining properties must be unique and has not attributes, hence
else if (sAttributeValue==null) {
if ("publisher".equals(sKey)) {
appendElement(contentDOM, metadata, "dc:publisher", sValue);
}
else if ("type".equals(sKey)) {
appendElement(contentDOM, metadata, "dc:type", sValue);
}
else if ("format".equals(sKey)) {
appendElement(contentDOM, metadata, "dc:format", sValue);
}
else if ("source".equals(sKey)) {
appendElement(contentDOM, metadata, "dc:source", sValue);
}
else if ("relation".equals(sKey)) {
appendElement(contentDOM, metadata, "dc:relation", sValue);
}
else if ("coverage".equals(sKey)) {
appendElement(contentDOM, metadata, "dc:coverage", sValue);
}
else if ("rights".equals(sKey)) {
appendElement(contentDOM, metadata, "dc:rights", sValue);
}
}
}
// Fall back values for identifier, creator and date
if (!bHasIdentifier) {
// Create a universal unique ID
sUID = UUID.randomUUID().toString();
Element identifier = appendElement(contentDOM, metadata, "dc:identifier", sUID);
identifier.setAttribute("id", "BookId");
if (nVersion==3) {
Element meta = appendElement(contentDOM, metadata, "meta", "UUID");
meta.setAttribute("refines", "#BookId");
meta.setAttribute("property", "identifier-type");
}
else {
identifier.setAttribute("opf:scheme", "UUID");
}
}
if (!bHasCreator && cr.getMetaData().getCreator().length()>0) {
Element creator = appendElement(contentDOM, metadata, "dc:creator", cr.getMetaData().getCreator());
creator.setAttribute("id", "creator");
if (nVersion==3) {
Element fileas = appendElement(contentDOM, metadata, "meta", fileAs(cr.getMetaData().getCreator()));
fileas.setAttribute("refines", "#creator");
fileas.setAttribute("property", "file-as");
}
else {
creator.setAttribute("opf:file-as", fileAs(cr.getMetaData().getCreator()));
}
}
if (!bHasDate && cr.getMetaData().getDate().length()>0) {
// TODO: Support meta:creation-date?
appendElement(contentDOM, metadata, "dc:date", Misc.dateOnly(cr.getMetaData().getDate()));
}
// Manifest must contain references to all the files in the XHTML converter result
// Spine should contain references to all the master documents within the converter result
Element manifest = contentDOM.createElement("manifest");
pack.appendChild(manifest);
Element spine = contentDOM.createElement("spine");
if (nVersion!=3 || config.includeNCX()) { // Use old NCX file for navigation
spine.setAttribute("toc", "ncx");
}
pack.appendChild(spine);
int nMasterCount = 0;
int nResourceCount = 0;
Iterator<OutputFile> iterator = cr.iterator();
while (iterator.hasNext()) {
OutputFile file = iterator.next();
Element item = contentDOM.createElement("item");
manifest.appendChild(item);
item.setAttribute("href",Misc.makeHref(file.getFileName()));
item.setAttribute("media-type", file.getMIMEType());
// Treat cover as recommended by Threepress consulting (http://blog.threepress.org/2009/11/20/best-practices-in-epub-cover-images/)
if (cr.getCoverFile()!=null && cr.getCoverFile().getFile()==file) {
item.setAttribute("id", "cover");
Element itemref = contentDOM.createElement("itemref");
itemref.setAttribute("idref", "cover");
itemref.setAttribute("linear", "no"); // maybe problematic
spine.appendChild(itemref);
}
else if (cr.getCoverImageFile()!=null && cr.getCoverImageFile().getFile()==file) {
item.setAttribute("id", "cover-image");
Element meta = contentDOM.createElement("meta");
meta.setAttribute("name", "cover");
meta.setAttribute("content", "cover-image");
metadata.appendChild(meta);
}
else if (file.isMasterDocument()) {
String sId = "text"+(++nMasterCount);
item.setAttribute("id", sId);
if (nVersion==3 && file.containsMath()) {
item.setAttribute("properties","mathml");
}
Element itemref = contentDOM.createElement("itemref");
itemref.setAttribute("idref", sId);
spine.appendChild(itemref);
}
else {
item.setAttribute("id", "resource"+(++nResourceCount));
}
}
if (nVersion==3) { // Include the new Navigation Document
Element item = contentDOM.createElement("item");
item.setAttribute("href", "nav.xhtml");
item.setAttribute("media-type", "application/xhtml+xml");
item.setAttribute("id", "nav");
item.setAttribute("properties", "nav");
manifest.appendChild(item);
}
if (nVersion!=3 || config.includeNCX()) { // Include old NCX file
Element item = contentDOM.createElement("item");
item.setAttribute("href", "book.ncx");
item.setAttribute("media-type", "application/x-dtbncx+xml");
item.setAttribute("id", "ncx");
manifest.appendChild(item);
}
// The guide may contain references to some fundamental structural components
Element guide = contentDOM.createElement("guide");
pack.appendChild(guide);
addGuideReference(contentDOM,guide,"cover",cr.getCoverFile());
addGuideReference(contentDOM,guide,"title-page",cr.getTitlePageFile());
addGuideReference(contentDOM,guide,"text",cr.getTextFile());
addGuideReference(contentDOM,guide,"toc",cr.getTocFile());
addGuideReference(contentDOM,guide,"index",cr.getIndexFile());
addGuideReference(contentDOM,guide,"loi",cr.getLofFile());
addGuideReference(contentDOM,guide,"lot",cr.getLotFile());
addGuideReference(contentDOM,guide,"bibliography",cr.getBibliographyFile());
setContentDOM(contentDOM);
}
/** Get the unique ID associated with this EPUB document (either collected from the user-defined
* meta data or a generated UUID)
*
* @return the ID
*/
public String getUid() {
return sUID;
}
private String fileAs(String sName) {
int nSpace = sName.lastIndexOf(' ');
if (nSpace>-1) {
return sName.substring(nSpace+1).trim()+", "+sName.substring(0, nSpace).trim();
}
else {
return sName.trim();
}
}
private Element appendElement(Document contentDOM, Element node, String sTagName, String sContent) {
Element child = contentDOM.createElement(sTagName);
node.appendChild(child);
child.appendChild(contentDOM.createTextNode(sContent));
return child;
}
private void addGuideReference(Document contentDOM, Element guide, String sType, ContentEntry entry) {
if (entry!=null) {
Element reference = contentDOM.createElement("reference");
reference.setAttribute("type", sType);
reference.setAttribute("title", entry.getTitle());
String sHref = Misc.makeHref(entry.getFile().getFileName());
if (entry.getTarget()!=null) { sHref+="#"+entry.getTarget(); }
reference.setAttribute("href", sHref);
guide.appendChild(reference);
}
}
// Get the current date and time in the required format
private String getCurrentDateTime() {
Date date = Calendar.getInstance().getTime();
DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
formatter.setTimeZone(TimeZone.getTimeZone("UTC"));
return formatter.format(date);
}
}

View file

@ -0,0 +1,13 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title>The package writer2latex.epub</title>
</head>
<body>
<p>This package contains EPUB specific code.</p>
<p>It contains a <code>writerlatex.api.Converter</code> implementation for
conversion into EPUB.</p>
</body>
</html>

View file

@ -0,0 +1,260 @@
/************************************************************************
*
* BibConverter.java
*
* Copyright: 2002-2015 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.6 (2015-07-27)
*
*/
package writer2latex.latex;
import java.util.Collection;
import org.w3c.dom.Element;
import writer2latex.base.BibliographyGenerator;
import writer2latex.bibtex.BibTeXDocument;
import writer2latex.latex.i18n.ClassicI18n;
import writer2latex.latex.util.BeforeAfter;
import writer2latex.latex.util.Context;
import writer2latex.office.OfficeReader;
import writer2latex.office.StyleWithProperties;
import writer2latex.office.XMLString;
import writer2latex.util.Misc;
/** This class handles bibliographic citations and the bibliography. The result depends on these
* configuration options:
* <li><code>use_index</code>: If false, the bibliography will be omitted</li>
* <li><code>use_bibtex</code> true and <code>external_bibtex_files</code>
* empty: The citations will be exported to a BibTeX file, which will be used
* for the bibliography</li>
* <li><code>use_bibtex</code> true and <code>external_bibtex_files</code>
* non-empty: The citations will be not be exported to a BibTeX file, the
* files referred to by the option will be used instead</li>
* <li><code>use_bibtex</code> false: The bibliography will be exported as
* a thebibliography environment
* <li><code>bibtex_style</code> If BibTeX is used, this style will be applied
* </ul>
* The citations will always be exported as \cite commands.
*/
class BibConverter extends ConverterHelper {
private BibTeXDocument bibDoc = null;
private boolean bUseBibTeX;
private String sBibTeXEncoding = null;
private String sDocumentEncoding = null;
/** Construct a new BibConverter.
*
* @param config the configuration to use
* @param palette the ConverterPalette to use
*/
BibConverter(OfficeReader ofr,LaTeXConfig config, ConverterPalette palette) {
super(ofr,config,palette);
// We need to create a BibTeX document except if we are using external BibTeX files
if (!(config.useBibtex() && config.externalBibtexFiles().length()>0)) {
bibDoc = new BibTeXDocument(palette.getOutFileName(),false,ofr);
}
// We need to use a different encoding for the BibTeX files
if (config.externalBibtexFiles().length()>0) {
int nBibTeXEncoding = config.getBibtexEncoding();
int nDocumentEncoding = config.getInputencoding();
if (config.getBackend()!=LaTeXConfig.XETEX && nBibTeXEncoding>-1 && nBibTeXEncoding!=nDocumentEncoding) {
sBibTeXEncoding = ClassicI18n.writeInputenc(nBibTeXEncoding);
sDocumentEncoding = ClassicI18n.writeInputenc(nDocumentEncoding);
}
}
// We need to export it
bUseBibTeX = config.useBibtex();
}
/** Export the bibliography directly as a thebibliography environment (as an alternative to using BibTeX)
*/
private class ThebibliographyGenerator extends BibliographyGenerator {
// The bibliography is the be inserted in this LaTeX document portion with that context
private LaTeXDocumentPortion ldp;
private Context context;
// The current bibliography item is to formatted with this before/after pair with that context
private BeforeAfter itemBa = null;
private Context itemContext = null;
ThebibliographyGenerator(OfficeReader ofr) {
super(ofr,true);
}
void handleBibliography(Element bibliography, LaTeXDocumentPortion ldp, Context context) {
this.ldp = ldp;
this.context = context;
String sWidestLabel = "";
Collection<String> labels = getLabels();
for (String sLabel : labels) {
if (sLabel.length()>=sWidestLabel.length()) {
sWidestLabel = sLabel;
}
}
ldp.append("\\begin{thebibliography}{").append(sWidestLabel).append("}\n");
generateBibliography(bibliography);
endBibliographyItem();
ldp.append("\\end{thebibliography}\n");
}
@Override protected void insertBibliographyItem(String sStyleName, String sKey) {
endBibliographyItem();
itemBa = new BeforeAfter();
itemContext = (Context) context.clone();
// Apply paragraph style (character formatting only)
StyleWithProperties style = ofr.getParStyle(sStyleName);
if (style!=null) {
palette.getI18n().applyLanguage(style,true,true,itemBa);
palette.getCharSc().applyFont(style,true,true,itemBa,itemContext);
if (itemBa.getBefore().length()>0) {
itemBa.add(" ","");
itemBa.enclose("{", "}");
}
}
// Convert item
ldp.append(itemBa.getBefore());
palette.getI18n().pushSpecialTable(palette.getCharSc().getFontName(style));
ldp.append("\\bibitem");
if (!isNumberedEntries()) {
ldp.append("[").append(bibDoc.getExportName(sKey)).append("]");
}
ldp.append("{").append(bibDoc.getExportName(sKey)).append("} ");
}
private void endBibliographyItem() {
if (itemBa!=null) {
palette.getI18n().popSpecialTable();
ldp.append(itemBa.getAfter()).append("\n");
itemBa = null;
}
}
@Override protected void insertBibliographyItemElement(String sStyleName, String sText) {
BeforeAfter ba = new BeforeAfter();
Context elementContext = (Context) itemContext.clone();
// Apply character style
StyleWithProperties style = ofr.getTextStyle(sStyleName);
palette.getCharSc().applyTextStyle(sStyleName,ba,elementContext);
// Convert text
ldp.append(ba.getBefore());
palette.getI18n().pushSpecialTable(palette.getCharSc().getFontName(style));
ldp.append(palette.getI18n().convert(sText, false, elementContext.getLang()));
palette.getI18n().popSpecialTable();
ldp.append(ba.getAfter());
}
}
/** Append declarations needed by the <code>BibConverter</code> to the preamble.
*
* @param pack the LaTeXDocumentPortion to which declarations of packages (\\usepackage) should be added.
* @param decl the LaTeXDocumentPortion to which other declarations should be added.
*/
void appendDeclarations(LaTeXDocumentPortion pack, LaTeXDocumentPortion decl) {
// Currently nothing
}
/** Process a bibliography
*
* @param node A text:bibliography element
* @param ldp the LaTeXDocumentPortion to which LaTeX code should be added
* @param oc the current context
*/
void handleBibliography(Element node, LaTeXDocumentPortion ldp, Context oc) {
if (!config.noIndex()) {
if (config.useBibtex()) { // Export using BibTeX
handleBibliographyAsBibTeX(ldp);
}
else { // Export as thebibliography environment
ThebibliographyGenerator bibCv = new ThebibliographyGenerator(ofr);
Element source = Misc.getChildByTagName(node,XMLString.TEXT_BIBLIOGRAPHY_SOURCE);
bibCv.handleBibliography(source, ldp, oc);
}
}
}
private void handleBibliographyAsBibTeX(LaTeXDocumentPortion ldp) {
// Use the style given in the configuration
ldp.append("\\bibliographystyle{")
.append(config.bibtexStyle())
.append("}").nl();
// Use BibTeX file from configuration, or exported BibTeX file
// TODO: For XeTeX, probably use \XeTeXdefaultencoding?
if (config.externalBibtexFiles().length()>0) {
if (sBibTeXEncoding!=null) {
ldp.append("\\inputencoding{").append(sBibTeXEncoding).append("}").nl();
}
ldp.append("\\bibliography{")
.append(config.externalBibtexFiles())
.append("}").nl();
if (sBibTeXEncoding!=null) {
ldp.append("\\inputencoding{").append(sDocumentEncoding).append("}").nl();
}
}
else {
ldp.append("\\bibliography{")
.append(bibDoc.getName())
.append("}").nl();
}
}
/** Process a Bibliography Mark
* @param node a text:bibliography-mark element
* @param ldp the LaTeXDocumentPortion to which LaTeX code should be added
* @param oc the current context
*/
void handleBibliographyMark(Element node, LaTeXDocumentPortion ldp, Context oc) {
String sIdentifier = node.getAttribute(XMLString.TEXT_IDENTIFIER);
if (sIdentifier!=null) {
// Use original citation if using external files; stripped if exporting BibTeX
ldp.append("\\cite{")
.append(config.externalBibtexFiles().length()==0 ? bibDoc.getExportName(sIdentifier) : sIdentifier)
.append("}");
}
}
/** Get the BibTeX document, if any (that is if the document contains bibliographic data <em>and</em>
* the configuration does not specify external BibTeX files)
*
* @return the BiBTeXDocument, or null if no BibTeX file is needed
*/
BibTeXDocument getBibTeXDocument() {
if (bUseBibTeX && bibDoc!=null && !bibDoc.isEmpty()) {
return bibDoc;
}
return null;
}
}

View file

@ -0,0 +1,229 @@
/************************************************************************
*
* BlockConverter.java
*
* Copyright: 2002-2015 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.6 (2015-04-15)
*
*/
package writer2latex.latex;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import writer2latex.latex.util.Context;
import writer2latex.latex.util.StyleMap;
import writer2latex.office.OfficeReader;
import writer2latex.office.XMLString;
import writer2latex.util.Misc;
/**
* This class handles basic block content, such as the main text body,
* sections, tables, lists, headings and paragraphs.</p>
*/
public class BlockConverter extends ConverterHelper {
public BlockConverter(OfficeReader ofr, LaTeXConfig config, ConverterPalette palette) {
super(ofr,config,palette);
}
public void appendDeclarations(LaTeXDocumentPortion pack, LaTeXDocumentPortion decl) {
// currently do nothing..
}
/** <p> Traverse block text (eg. content of body, section, list item).
* This is traversed in logical order and dedicated handlers take care of
* each block element.</p>
* <p> (Note: As a rule, all handling of block level elements should add a
* newline to the LaTeX document at the end of the block)</p>
* @param node The element containing the block text
* @param ldp the <code>LaTeXDocumentPortion</code> to which
* LaTeX code should be added
* @param oc the current context
*/
public void traverseBlockText(Element node, LaTeXDocumentPortion ldp, Context oc) {
Context ic = (Context) oc.clone();
// The current paragraph block:
StyleMap blockMap = config.getParBlockStyleMap();
String sBlockName = null;
if (node.hasChildNodes()) {
NodeList list = node.getChildNodes();
int nLen = list.getLength();
for (int i = 0; i < nLen; i++) {
Node childNode = list.item(i);
if (childNode.getNodeType() == Node.ELEMENT_NODE) {
Element child = (Element)childNode;
String sTagName = child.getTagName();
// Start/End a paragraph block (not in tables)
if (!ic.isInTable()) {
if (sTagName.equals(XMLString.TEXT_P)) {
String sStyleName = ofr.getParStyles().getDisplayName(child.getAttribute(XMLString.TEXT_STYLE_NAME));
if (sBlockName!=null && !blockMap.isNext(sBlockName,sStyleName)) {
// end current block
String sAfter = blockMap.getAfter(sBlockName);
if (sAfter.length()>0) ldp.append(sAfter).nl();
sBlockName = null;
ic.setVerbatim(false);
}
if (sBlockName==null && blockMap.contains(sStyleName)) {
// start a new block
sBlockName = sStyleName;
String sBefore = blockMap.getBefore(sBlockName);
if (sBefore.length()>0) ldp.append(sBefore).nl();
ic.setVerbatim(blockMap.getVerbatim(sStyleName));
}
}
else if (sBlockName!=null) {
// non-paragraph: end current block
String sAfter = blockMap.getAfter(sBlockName);
if (sAfter.length()>0) ldp.append(sAfter).nl();
sBlockName = null;
ic.setVerbatim(false);
}
}
palette.getFieldCv().flushReferenceMarks(ldp,ic);
palette.getIndexCv().flushIndexMarks(ldp,ic);
palette.getInfo().addDebugInfo(child,ldp);
// Basic block content; handle by this class
if (sTagName.equals(XMLString.TEXT_P)) {
// is this a caption?
String sSequence = ofr.getSequenceName(child);
if (ofr.isFigureSequenceName(sSequence)) {
palette.getDrawCv().handleCaption(child,ldp,ic);
}
else if (ofr.isTableSequenceName(sSequence)) {
// Next node *should* be a table
if (i+1<nLen && Misc.isElement(list.item(i+1),XMLString.TABLE_TABLE)) {
// Found table with caption above
palette.getTableCv().handleTable((Element)list.item(++i),child,true,ldp,ic);
}
else {
// Found lonely caption
palette.getTableCv().handleCaption(child,ldp,ic);
}
}
else {
palette.getParCv().handleParagraph(child,ldp,ic,i==nLen-1);
}
}
else if(sTagName.equals(XMLString.TEXT_H)) {
palette.getHeadingCv().handleHeading(child,ldp,ic);
}
else if (sTagName.equals(XMLString.TEXT_LIST)) { // oasis
palette.getListCv().handleList(child,ldp,ic);
}
else if (sTagName.equals(XMLString.TEXT_UNORDERED_LIST)) {
palette.getListCv().handleList(child,ldp,ic);
}
else if (sTagName.equals(XMLString.TEXT_ORDERED_LIST)) {
palette.getListCv().handleList(child,ldp,ic);
}
else if (sTagName.equals(XMLString.TABLE_TABLE)) {
// Next node *could* be a caption
if (i+1<nLen && Misc.isElement(list.item(i+1),XMLString.TEXT_P) &&
ofr.isTableSequenceName(ofr.getSequenceName((Element)list.item(i+1)))) {
// Found table with caption below
palette.getTableCv().handleTable(child,(Element)list.item(++i),false,ldp,oc);
}
else {
// Found table without caption
palette.getTableCv().handleTable(child,null,false,ldp,oc);
}
}
else if (sTagName.equals(XMLString.TABLE_SUB_TABLE)) {
palette.getTableCv().handleTable(child,null,true,ldp,ic);
}
else if (sTagName.equals(XMLString.TEXT_SECTION)) {
palette.getSectionCv().handleSection(child,ldp,ic);
}
// Draw elements may appear in block context if they are
// anchored to page
else if (sTagName.startsWith("draw:")) {
palette.getDrawCv().handleDrawElement(child,ldp,ic);
}
// Indexes
else if (sTagName.equals(XMLString.TEXT_TABLE_OF_CONTENT)) {
palette.getIndexCv().handleTOC(child,ldp,ic);
}
else if (sTagName.equals(XMLString.TEXT_ILLUSTRATION_INDEX)) {
palette.getIndexCv().handleLOF(child,ldp,ic);
}
else if (sTagName.equals(XMLString.TEXT_TABLE_INDEX)) {
palette.getIndexCv().handleLOT(child,ldp,ic);
}
else if (sTagName.equals(XMLString.TEXT_OBJECT_INDEX)) {
palette.getIndexCv().handleObjectIndex(child,ldp,ic);
}
else if (sTagName.equals(XMLString.TEXT_USER_INDEX)) {
palette.getIndexCv().handleUserIndex(child,ldp,ic);
}
else if (sTagName.equals(XMLString.TEXT_ALPHABETICAL_INDEX)) {
palette.getIndexCv().handleAlphabeticalIndex(child,ldp,ic);
}
else if (sTagName.equals(XMLString.TEXT_BIBLIOGRAPHY)) {
palette.getBibCv().handleBibliography(child,ldp,ic);
}
// Sequence declarations appear in the main text body (before the actual content)
else if (sTagName.equals(XMLString.TEXT_SEQUENCE_DECLS)) {
palette.getFieldCv().handleSequenceDecls(child);
}
// other tags are ignored
}
}
}
if (!oc.isInTable() && sBlockName!=null) {
// end current block
String sAfter = blockMap.getAfter(sBlockName);
if (sAfter.length()>0) ldp.append(sAfter).nl();
sBlockName = null;
}
palette.getFieldCv().flushReferenceMarks(ldp,ic);
palette.getIndexCv().flushIndexMarks(ldp,ic);
}
}

View file

@ -0,0 +1,162 @@
/************************************************************************
*
* CaptionConverter.java
*
* Copyright: 2002-2008 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.0 (2008-11-23)
*
*/
package writer2latex.latex;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import writer2latex.latex.util.Context;
import writer2latex.office.OfficeReader;
import writer2latex.office.XMLString;
/**
* <p>This class converts captions (for figures and tables) to LaTeX.</p>
* <p>Packages:
* <ul><li>caption.sty is used implement non-floating captions</li></ul>
* <p>Options:
* <ul><li>use_caption is a boolean option to determine whether or not
* to use caption.sty. If this option is set to false, a simple definition of
* \captionof (borrowed from capt-of.sty) is inserted in the preamble</li></ul>
* <p>TODO: Implement formatting of captions using the features of caption.sty
* (only if formatting>=CONVERT_BASIC)
*/
public class CaptionConverter extends ConverterHelper {
private boolean bNeedCaptionOf = false;
private Element seqField = null; // the sequence field within the current caption
public CaptionConverter(OfficeReader ofr, LaTeXConfig config, ConverterPalette palette) {
super(ofr,config,palette);
}
public void appendDeclarations(LaTeXDocumentPortion pack, LaTeXDocumentPortion decl) {
if (bNeedCaptionOf) {
if (config.useCaption()) {
pack.append("\\usepackage{caption}").nl();
}
else { // use definition borrowed from capt-of.sty
decl.append("% Non-floating captions").nl()
.append("\\makeatletter").nl()
.append("\\newcommand\\captionof[1]{\\def\\@captype{#1}\\caption}").nl()
.append("\\makeatother").nl();
}
}
}
/**
* <p> Process content of a text:p tag as a caption body (inluding label)</p>
* @param node The text:p element node containing the caption
* @param ldp The <code>LaTeXDocumentPortion</code> to add LaTeX code to
* @param oc The current context
* @param bIsCaptionOf true if this is caption uses captionof
*/
public void handleCaptionBody(Element node,LaTeXDocumentPortion ldp, Context oc, boolean bIsCaptionOf) {
bNeedCaptionOf|=bIsCaptionOf;
// Get rid of the caption label before converting
removeCaptionLabel(node,0);
Element label = seqField;
seqField = null;
// Get the stylename of the paragraph and push the font used
String sStyleName = node.getAttribute(XMLString.TEXT_STYLE_NAME);
palette.getI18n().pushSpecialTable(palette.getCharSc().getFontName(ofr.getParStyle(sStyleName)));
if (palette.getHeadingCv().containsElements(node)) {
ldp.append("[");
palette.getInlineCv().traversePlainInlineText(node,ldp,oc);
ldp.append("]");
}
// Update context before traversing text
Context ic = (Context) oc.clone();
ldp.append("{");
palette.getInlineCv().traverseInlineText(node,ldp,ic);
ldp.append("}").nl();
// Insert label
palette.getFieldCv().handleSequence(label,ldp,oc);
// Flush any index marks
palette.getIndexCv().flushIndexMarks(ldp,oc);
// pop the font name
palette.getI18n().popSpecialTable();
}
// In OpenDocument a caption is an ordinary paragraph with a text:seqence
// element. For example
// Table <text:sequence>3</text:sequence>: Caption text
// The first part is the caption label which is autogenerated by LaTeX.
// Before converting, we remove this in 3 steps:
// nStep = 0: Remove all text before the text:sequence
// nStep = 1: Remove all text up to the first alphanumeric character
// after the text:sequence
// nStep = 2: Finished!
private int removeCaptionLabel(Element node, int nStep) {
if (nStep==2) { return 2; }
Node removeMe = null;
Node child = node.getFirstChild();
while (child!=null) {
if (child.getNodeType()==Node.ELEMENT_NODE) {
if (nStep==0 && child.getNodeName().equals(XMLString.TEXT_SEQUENCE)) {
removeMe = child;
seqField = (Element) child; // remember me...
nStep = 1;
}
else if (nStep<2 && !OfficeReader.isDrawElement(child)) {
// draw elements (frames) should not be touched..
nStep = removeCaptionLabel((Element)child,nStep);
}
}
else if (child.getNodeType()==Node.TEXT_NODE) {
if (nStep==0) {
child.setNodeValue("");
}
else if (nStep==1) {
String s = child.getNodeValue();
int n = s.length();
for (int j=0; j<n; j++) {
if (Character.isLetterOrDigit(s.charAt(j))) {
child.setNodeValue(s.substring(j));
nStep = 2;
break;
}
}
}
}
child = child.getNextSibling();
}
if (removeMe!=null) { node.removeChild(removeMe); }
return nStep;
}
}

View file

@ -0,0 +1,508 @@
/************************************************************************
*
* CharStyleConverter.java
*
* Copyright: 2002-2015 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.6 (2015-06-30)
*
*/
package writer2latex.latex;
import java.util.Hashtable;
import writer2latex.util.*;
import writer2latex.office.*;
import writer2latex.latex.util.BeforeAfter;
import writer2latex.latex.util.Context;
import writer2latex.latex.util.StyleMap;
/** This class creates LaTeX code from OOo character formatting
Character formatting in OOo includes font, font effects/decorations and color.
In addition it includes color and language/country information, this is however handled
by the classes <code>writer2latex.latex.ColorConverter</code> and
<code>writer2latex.latex.style.I18n</code>
*/
public class CharStyleConverter extends StyleConverter {
// Cache of converted font declarations
private Hashtable<String, String> fontDecls = new Hashtable<String, String>();
// Which formatting should we export?
private boolean bIgnoreHardFontsize;
private boolean bIgnoreFontsize;
private boolean bIgnoreFont;
private boolean bIgnoreAll;
private boolean bUseUlem;
// Do we need actually use ulem.sty or \textsubscript?
private boolean bNeedUlem = false;
private boolean bNeedSubscript = false;
/** <p>Constructs a new <code>CharStyleConverter</code>.</p>
*/
public CharStyleConverter(OfficeReader ofr, LaTeXConfig config, ConverterPalette palette) {
super(ofr,config,palette);
bUseUlem = config.useUlem();
// No character formatting at all:
bIgnoreAll = config.formatting()==LaTeXConfig.IGNORE_ALL;
// No font family or size:
bIgnoreFont = config.formatting()<=LaTeXConfig.IGNORE_MOST;
// No fontsize:
bIgnoreFontsize = config.formatting()<=LaTeXConfig.CONVERT_BASIC;
// No hard fontsize
bIgnoreHardFontsize = config.formatting()<=LaTeXConfig.CONVERT_MOST;
}
public void appendDeclarations(LaTeXDocumentPortion pack, LaTeXDocumentPortion decl) {
if (bNeedUlem) {
pack.append("\\usepackage[normalem]{ulem}").nl();
}
if (bNeedSubscript && !config.getTextAttributeStyleMap().contains("subscript")) {
decl.append("\\providecommand\\textsubscript[1]{\\ensuremath{{}_{\\text{#1}}}}").nl();
}
if (!styleNames.isEmpty()) {
decl.append("% Text styles").nl().append(declarations);
}
}
/** <p>Use a text style in LaTeX.</p>
* @param sName the name of the text style
* @param ba a <code>BeforeAfter</code> to put code into
*/
public void applyTextStyle(String sName, BeforeAfter ba, Context context) {
if (sName==null) { return; }
String sDisplayName = ofr.getTextStyles().getDisplayName(sName);
if (bIgnoreAll) {
// Even if all is ignored, we still apply style maps from config..
StyleMap sm = config.getTextStyleMap();
if (sm.contains(sDisplayName)) {
ba.add(sm.getBefore(sDisplayName),sm.getAfter(sDisplayName));
context.setVerbatim(sm.getVerbatim(sDisplayName));
context.setNoLineBreaks(sm.getVerbatim(sDisplayName));
}
return;
}
// Style already converted?
if (styleMap.contains(sName)) {
ba.add(styleMap.getBefore(sName),styleMap.getAfter(sName));
context.updateFormattingFromStyle(ofr.getTextStyle(sName));
// it's verbatim if specified as such in the configuration
StyleMap sm = config.getTextStyleMap();
boolean bIsVerbatim = sm.contains(sDisplayName) && sm.getVerbatim(sDisplayName);
context.setVerbatim(bIsVerbatim);
context.setNoLineBreaks(bIsVerbatim);
return;
}
// The style may already be declared in the configuration:
StyleMap sm = config.getTextStyleMap();
if (sm.contains(sDisplayName)) {
styleMap.put(sName,sm.getBefore(sDisplayName),sm.getAfter(sDisplayName));
applyTextStyle(sName,ba,context);
return;
}
// Get the style, if it exists:
StyleWithProperties style = ofr.getTextStyle(sName);
if (style==null) {
styleMap.put(sName,"","");
applyTextStyle(sName,ba,context);
return;
}
// Convert automatic style
if (style.isAutomatic()) {
palette.getI18n().applyLanguage(style,false,true,ba);
applyFont(style,false,true,ba,context);
applyFontEffects(style,true,ba);
context.updateFormattingFromStyle(ofr.getTextStyle(sName));
return;
}
// Convert soft style:
// This must be converted relative to a blank context!
BeforeAfter baText = new BeforeAfter();
palette.getI18n().applyLanguage(style,false,true,baText);
applyFont(style,false,true,baText,new Context());
applyFontEffects(style,true,baText);
// declare the text style (\newcommand)
String sTeXName = styleNames.getExportName(ofr.getTextStyles().getDisplayName(sName));
styleMap.put(sName,"\\textstyle"+sTeXName+"{","}");
declarations.append("\\newcommand\\textstyle")
.append(sTeXName).append("[1]{")
.append(baText.getBefore()).append("#1").append(baText.getAfter())
.append("}").nl();
applyTextStyle(sName,ba,context);
}
public String getFontName(StyleWithProperties style) {
if (style!=null) {
String sName = style.getProperty(XMLString.STYLE_FONT_NAME);
if (sName!=null) {
FontDeclaration fd = ofr.getFontDeclaration(sName);
if (fd!=null) {
return fd.getFontFamily();
}
}
}
return null;
}
// Get the font name from a char style
public String getFontName(String sStyleName) {
return getFontName(ofr.getTextStyle(sStyleName));
}
/** <p>Apply hard character formatting (no inheritance).</p>
* <p>This is used in sections and {foot|end}notes</p>
* @param style the style to use
* @param ba the <code>BeforeAfter</code> to add LaTeX code to
*/
public void applyHardCharFormatting(StyleWithProperties style, BeforeAfter ba) {
palette.getI18n().applyLanguage(style,true,false,ba);
applyFont(style,true,false,ba,new Context());
if (!ba.isEmpty()) { ba.add(" ",""); }
}
/** <p>Apply all font attributes (family, series, shape, size and color).</p>
* @param style the OOo style to read attributesfrom
* @param bDecl true if declaration form is required
* @param bInherit true if inherited properties should be used
* @param ba the <code>BeforeAfter</code> to add LaTeX code to.
*/
public void applyFont(StyleWithProperties style, boolean bDecl, boolean bInherit, BeforeAfter ba, Context context) {
// Note: if bDecl is true, nothing will be put in the "after" part of ba.
if (style==null) { return; }
applyNfssSize(style,bDecl,bInherit,ba,context);
applyNfssFamily(style,bDecl,bInherit,ba,context);
applyNfssSeries(style,bDecl,bInherit,ba,context);
applyNfssShape(style,bDecl,bInherit,ba,context);
palette.getColorCv().applyColor(style,bDecl,bInherit,ba,context);
}
/** <p>Reset to normal font, size and color.</p>
* @param ba the <code>BeforeAfter</code> to add LaTeX code to.
*/
public void applyNormalFont(BeforeAfter ba) {
ba.add("\\normalfont\\normalsize","");
palette.getColorCv().applyNormalColor(ba);
}
/** <p>Apply default font attributes (family, series, shape, size and color).</p>
* @param style the OOo style to read attributesfrom
* @param ldp the <code>LaTeXDocumentPortion</code> to add LaTeX code to.
*/
public void applyDefaultFont(StyleWithProperties style, LaTeXDocumentPortion ldp) {
if (style==null) { return; }
String s = convertFontDeclaration(style.getProperty(XMLString.STYLE_FONT_NAME));
if (s!=null){
ldp.append("\\renewcommand\\familydefault{\\")
.append(s).append("default}").nl();
} // TODO: Else read props directly from the style
s = nfssSeries(style.getProperty(XMLString.FO_FONT_WEIGHT));
if (s!=null) {
ldp.append("\\renewcommand\\seriesdefault{\\")
.append(s).append("default}").nl();
}
s = nfssShape(style.getProperty(XMLString.FO_FONT_VARIANT),
style.getProperty(XMLString.FO_FONT_STYLE));
if (s!=null) {
ldp.append("\\renewcommand\\shapedefault{\\")
.append(s).append("default}").nl();
}
palette.getColorCv().setNormalColor(style.getProperty(XMLString.FO_COLOR),ldp);
}
/** <p>Apply font effects (position, underline, crossout, change case.</p>
* @param style the OOo style to read attributesfrom
* @param bInherit true if inherited properties should be used
* @param ba the <code>BeforeAfter</code> to add LaTeX code to.
*/
public void applyFontEffects(StyleWithProperties style, boolean bInherit, BeforeAfter ba) {
if (style==null) { return; }
applyTextPosition(style, bInherit, ba);
applyUnderline(style, bInherit, ba);
applyCrossout(style, bInherit, ba);
applyChangeCase(style, bInherit, ba);
}
// Remaining methods are private
/** <p>Apply font family.</p>
* @param style the OOo style to read attributesfrom
* @param bDecl true if declaration form is required
* @param bInherit true if inherited properties should be used
* @param ba the <code>BeforeAfter</code> to add LaTeX code to.
*/
private void applyNfssFamily(StyleWithProperties style, boolean bDecl, boolean bInherit, BeforeAfter ba, Context context) {
// Note: if bDecl is true, nothing will be put in the "after" part of ba.
if (style==null || bIgnoreFont) { return; }
String sFontName=style.getProperty(XMLString.STYLE_FONT_NAME,bInherit);
if (sFontName!=null){
String sFamily = convertFontDeclaration(sFontName);
if (sFamily==null) { return; }
if (sFamily.equals(convertFontDeclaration(context.getFontName()))) { return; }
if (bDecl) { ba.add("\\"+sFamily+"family",""); }
else { ba.add("\\text"+sFamily+"{","}"); }
} // TODO: Else read props directly from the style
}
/** <p>Apply font series.</p>
* @param style the OOo style to read attributesfrom
* @param bDecl true if declaration form is required
* @param bInherit true if inherited properties should be used
* @param ba the <code>BeforeAfter</code> to add LaTeX code to.
*/
private void applyNfssSeries(StyleWithProperties style, boolean bDecl, boolean bInherit, BeforeAfter ba, Context context) {
// Note: if bDecl is true, nothing will be put in the "after" part of ba.
if (style!=null && !bIgnoreAll) {
String sSeries = nfssSeries(style.getProperty(XMLString.FO_FONT_WEIGHT,bInherit));
if (sSeries!=null) {
// Temporary: Support text-attribute style maps for this particular case
// TODO: Reimplement the CharStyleConverter to properly support this...
if (!bDecl && "bf".equals(sSeries) && config.getTextAttributeStyleMap().contains("bold")) {
ba.add(config.getTextAttributeStyleMap().getBefore("bold"),
config.getTextAttributeStyleMap().getAfter("bold"));
}
else {
if (style.isAutomatic()) { // optimize hard formatting
if (sSeries.equals(nfssSeries(context.getFontWeight()))) { return; }
if (context.getFontWeight()==null && sSeries.equals("md")) { return; }
}
if (bDecl) { ba.add("\\"+sSeries+"series",""); }
else { ba.add("\\text"+sSeries+"{","}"); }
}
}
}
}
/** <p>Apply font shape.</p>
* @param style the OOo style to read attributesfrom
* @param bDecl true if declaration form is required
* @param bInherit true if inherited properties should be used
* @param ba the <code>BeforeAfter</code> to add LaTeX code to.
*/
private void applyNfssShape(StyleWithProperties style, boolean bDecl, boolean bInherit, BeforeAfter ba, Context context) {
// Note: if bDecl is true, nothing will be put in the "after" part of ba.
if (style!=null && !bIgnoreAll) {
String sVariant = style.getProperty(XMLString.FO_FONT_VARIANT, bInherit);
String sStyle = style.getProperty(XMLString.FO_FONT_STYLE, bInherit);
String sShape = nfssShape(sVariant,sStyle);
if (sShape!=null) {
// Temporary: Support text-attribute style maps for this particular case
// TODO: Reimplement the CharStyleConverter to properly support this...
if (!bDecl && "sc".equals(sShape) && config.getTextAttributeStyleMap().contains("small-caps")) {
ba.add(config.getTextAttributeStyleMap().getBefore("small-caps"),
config.getTextAttributeStyleMap().getAfter("small-caps"));
}
else if (!bDecl && "it".equals(sShape) && config.getTextAttributeStyleMap().contains("italic")) {
ba.add(config.getTextAttributeStyleMap().getBefore("italic"),
config.getTextAttributeStyleMap().getAfter("italic"));
}
else {
if (style.isAutomatic()) { // optimize hard formatting
if (sShape.equals(nfssShape(context.getFontVariant(),context.getFontStyle()))) return;
if (context.getFontVariant()==null && context.getFontStyle()==null && sShape.equals("up")) return;
}
if (bDecl) ba.add("\\"+sShape+"shape","");
else ba.add("\\text"+sShape+"{","}");
}
}
}
}
/** <p>Apply font size.</p>
* @param style the OOo style to read attributesfrom
* @param bDecl true if declaration form is required
* @param bInherit true if inherited properties should be used
* @param ba the <code>BeforeAfter</code> to add LaTeX code to.
*/
private void applyNfssSize(StyleWithProperties style, boolean bDecl, boolean bInherit, BeforeAfter ba, Context context) {
// Note: if bDecl is true, nothing will be put in the "after" part of ba.
if (style==null|| bIgnoreFontsize || (bIgnoreHardFontsize && style.isAutomatic())) { return; }
if (style.getProperty(XMLString.FO_FONT_SIZE, bInherit)==null) { return; }
String sSize = nfssSize(style.getAbsoluteProperty(XMLString.FO_FONT_SIZE));
if (sSize==null) { return; }
if (sSize.equals(nfssSize(context.getFontSize()))) { return; }
if (bDecl) { ba.add(sSize,""); }
else { ba.add("{"+sSize+" ","}"); }
}
// Remaining methods are not context-sensitive
/** <p>Apply text position.</p>
* @param style the OOo style to read attributesfrom
* @param bInherit true if inherited properties should be used
* @param ba the <code>BeforeAfter</code> to add LaTeX code to.
*/
private void applyTextPosition(StyleWithProperties style, boolean bInherit, BeforeAfter ba) {
if (style!=null && !bIgnoreAll) {
String s = textPosition(style.getProperty(XMLString.STYLE_TEXT_POSITION, bInherit));
// Temporary: Support text-attribute style maps for this particular case
// TODO: Reimplement the CharStyleConverter to properly support this...
if (config.getTextAttributeStyleMap().contains("superscript") && "\\textsuperscript".equals(s)) {
ba.add(config.getTextAttributeStyleMap().getBefore("superscript"),
config.getTextAttributeStyleMap().getAfter("superscript"));
}
else if (config.getTextAttributeStyleMap().contains("subscript") && "\\textsubscript".equals(s)) {
ba.add(config.getTextAttributeStyleMap().getBefore("subscript"),
config.getTextAttributeStyleMap().getAfter("subscript"));
}
else if (s!=null) {
ba.add(s+"{","}");
}
}
}
/** <p>Apply text underline.</p>
* @param style the OOo style to read attributesfrom
* @param bInherit true if inherited properties should be used
* @param ba the <code>BeforeAfter</code> to add LaTeX code to.
*/
private void applyUnderline(StyleWithProperties style, boolean bInherit, BeforeAfter ba) {
if (style==null || !bUseUlem) { return; }
if (bIgnoreAll) { return; }
String sTag = ofr.isOpenDocument() ?
XMLString.STYLE_TEXT_UNDERLINE_STYLE :
XMLString.STYLE_TEXT_UNDERLINE;
String s = underline(style.getProperty(sTag, bInherit));
if (s!=null) { bNeedUlem = true; ba.add(s+"{","}"); }
}
/** <p>Apply text crossout.</p>
* @param style the OOo style to read attributesfrom
* @param bInherit true if inherited properties should be used
* @param ba the <code>BeforeAfter</code> to add LaTeX code to.
*/
private void applyCrossout(StyleWithProperties style, boolean bInherit, BeforeAfter ba) {
if (style==null || !bUseUlem) { return; }
if (bIgnoreAll) { return; }
String sTag = ofr.isOpenDocument() ?
XMLString.STYLE_TEXT_LINE_THROUGH_STYLE :
XMLString.STYLE_TEXT_CROSSING_OUT;
String s = crossout(style.getProperty(sTag, bInherit));
if (s!=null) { bNeedUlem = true; ba.add(s+"{","}"); }
}
/** <p>Apply change case.</p>
* @param style the OOo style to read attributesfrom
* @param bInherit true if inherited properties should be used
* @param ba the <code>BeforeAfter</code> to add LaTeX code to.
*/
private void applyChangeCase(StyleWithProperties style, boolean bInherit, BeforeAfter ba) {
if (style==null) { return; }
if (bIgnoreAll) { return; }
String s = changeCase(style.getProperty(XMLString.FO_TEXT_TRANSFORM));
if (s!=null) { ba.add(s+"{","}"); }
}
/** <p>Convert font declarations to LaTeX.</p>
* <p>It returns a generic LaTeX font family (rm, tt, sf).</p>
* <p>It returns null if the font declaration doesn't exist.</p>
* @param sName the name of the font declaration
* @return <code>String</code> with a LaTeX generic fontfamily
*/
private String convertFontDeclaration(String sName) {
FontDeclaration fd = ofr.getFontDeclaration(sName);
if (fd==null) { return null; }
if (!fontDecls.containsKey(sName)) {
String sFontFamily = fd.getFontFamily();
String sFontPitch = fd.getFontPitch();
String sFontFamilyGeneric = fd.getFontFamilyGeneric();
fontDecls.put(sName,nfssFamily(sFontFamily,sFontFamilyGeneric,sFontPitch));
}
return fontDecls.get(sName);
}
// The remaining methods are static helpers to convert single style properties
// Font change. These methods return the declaration form if the paramater
// bDecl is true, and otherwise the command form
private static final String nfssFamily(String sFontFamily, String sFontFamilyGeneric,
String sFontPitch){
// Note: Defaults to rm
// TODO: What about decorative, script, system?
if ("fixed".equals(sFontPitch)) return "tt";
else if ("modern".equals(sFontFamilyGeneric)) return "tt";
else if ("swiss".equals(sFontFamilyGeneric)) return "sf";
else return "rm";
}
private static final String nfssSeries(String sFontWeight){
if (sFontWeight==null) return null;
if ("bold".equals(sFontWeight)) return "bf";
else return "md";
}
private static final String nfssShape(String sFontVariant, String sFontStyle){
if (sFontVariant==null && sFontStyle==null) return null;
if ("small-caps".equals(sFontVariant)) return "sc";
else if ("italic".equals(sFontStyle)) return "it";
else if ("oblique".equals(sFontStyle)) return "sl";
else return "up";
}
private static final String nfssSize(String sFontSize){
if (sFontSize==null) return null;
return "\\fontsize{"+sFontSize+"}{"+Calc.multiply("120%",sFontSize)+"}\\selectfont";
}
// other character formatting
private final String textPosition(String sTextPosition){
if (sTextPosition==null) return null;
if (sTextPosition.startsWith("super")) return "\\textsuperscript";
if (sTextPosition.startsWith("sub") || sTextPosition.startsWith("-")) {
bNeedSubscript = true;
return "\\textsubscript";
}
if (sTextPosition.startsWith("0%")) return null;
return "\\textsuperscript";
}
private static final String underline(String sUnderline) {
if (sUnderline==null) { return null; }
if (sUnderline.equals("none")) { return null; }
if (sUnderline.indexOf("wave")>=0) { return "\\uwave"; }
return "\\uline";
}
private static final String crossout(String sCrossout) {
if (sCrossout==null) { return null; }
if (sCrossout.equals("X")) { return "\\xout"; }
if (sCrossout.equals("slash")) { return "\\xout"; }
return "\\sout";
}
private static final String changeCase(String sTextTransform){
if ("lowercase".equals(sTextTransform)) return "\\MakeLowercase";
if ("uppercase".equals(sTextTransform)) return "\\MakeUppercase";
return null;
}
}

View file

@ -0,0 +1,201 @@
/************************************************************************
*
* ColorConverter.java
*
* Copyright: 2002-2008 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.0 (2008-11-23)
*
*/
package writer2latex.latex;
import writer2latex.latex.util.BeforeAfter;
import writer2latex.latex.util.Context;
import writer2latex.office.OfficeReader;
import writer2latex.office.StyleWithProperties;
import writer2latex.office.XMLString;
import writer2latex.util.Misc;
/** This class converts color
*/
public class ColorConverter extends ConverterHelper {
private static final int RED = 0;
private static final int GREEN = 1;
private static final int BLUE = 2;
private boolean bUseColor;
/** <p>Constructs a new <code>CharStyleConverter</code>.</p>
*/
public ColorConverter(OfficeReader ofr, LaTeXConfig config, ConverterPalette palette) {
super(ofr,config,palette);
// We use color if requested in the configuration, however ignoring
// all formatting overrides this
bUseColor = config.useColor() && config.formatting()>LaTeXConfig.IGNORE_ALL;
}
public void appendDeclarations(LaTeXDocumentPortion pack, LaTeXDocumentPortion decl) {
if (bUseColor) {
pack.append("\\usepackage{color}").nl();
}
}
public void setNormalColor(String sColor, LaTeXDocumentPortion ldp) {
if (bUseColor && sColor!=null) {
ldp.append("\\renewcommand\\normalcolor{\\color")
.append(color(sColor)).append("}").nl();
}
}
public void applyNormalColor(BeforeAfter ba) {
if (bUseColor) { ba.add("\\normalcolor",""); }
}
/** <p>Apply foreground color.</p>
* @param style the OOo style to read attributesfrom
* @param bDecl true if declaration form is required
* @param bInherit true if inherited properties should be used
* @param ba the <code>BeforeAfter</code> to add LaTeX code to.
* @param context the current context
*/
public void applyColor(StyleWithProperties style, boolean bDecl, boolean bInherit, BeforeAfter ba, Context context) {
// Note: if bDecl is true, nothing will be put in the "after" part of ba.
if (bUseColor && style!=null) {
String sColor = style.getProperty(XMLString.FO_COLOR,bInherit);
if (sColor!=null) {
if (!sColor.equals(context.getFontColor())) {
// Convert color if it differs from the current font color
context.setFontColor(sColor);
applyColor(sColor, bDecl, ba, context);
}
}
else {
// No color; maybe automatic color?
String sAutomatic = style.getProperty(XMLString.STYLE_USE_WINDOW_FONT_COLOR,bInherit);
if (sAutomatic==null && bInherit) {
// We may need to inherit this property from the default style
StyleWithProperties defaultStyle = ofr.getDefaultParStyle();
if (defaultStyle!=null) {
sAutomatic = defaultStyle.getProperty(XMLString.STYLE_USE_WINDOW_FONT_COLOR,bInherit);
}
}
if ("true".equals(sAutomatic)) {
// Automatic color based on background
if (context.getBgColor()!=null) { applyAutomaticColor(ba,bDecl,context); }
}
}
}
}
/** <p>Apply a specific foreground color.</p>
* @param sColor the rgb color to use
* @param bDecl true if declaration form is required
* @param ba the <code>BeforeAfter</code> to add LaTeX code to.
*/
public void applyColor(String sColor, boolean bDecl, BeforeAfter ba, Context context) {
// Note: if bDecl is true, nothing will be put in the "after" part of ba.
if (bUseColor && sColor!=null) {
// If there's a background color, allow all colors
String s = context.getBgColor()!=null ? fullcolor(sColor) : color(sColor);
if (s!=null) {
if (bDecl) { ba.add("\\color"+s,""); }
else { ba.add("\\textcolor"+s+"{","}"); }
}
}
}
public void applyBgColor(String sCommand, String sColor, BeforeAfter ba, Context context) {
// Note: Will only fill "before" part of ba
if (sColor!=null && !"transparent".equals(sColor)) {
String s = fullcolor(sColor);
if (bUseColor && s!=null) {
context.setBgColor(sColor);
ba.add(sCommand+s,"");
}
}
}
public void applyAutomaticColor(BeforeAfter ba, boolean bDecl, Context context) {
String s = automaticcolor(context.getBgColor());
if (s!=null) {
if (bDecl) { ba.add("\\color"+s,""); }
else { ba.add("\\textcolor"+s+"{","}"); }
}
}
private static final String automaticcolor(String sBgColor) {
if (sBgColor!=null && sBgColor.length()==7) {
float[] rgb = getRgb(sBgColor);
if (rgb[RED]+rgb[GREEN]+rgb[BLUE]<0.6) {
// Dark background
return "{white}";
}
}
return "{black}";
}
private static final String color(String sColor){
if ("#000000".equalsIgnoreCase(sColor)) { return "{black}"; }
else if ("#ff0000".equalsIgnoreCase(sColor)) { return "{red}"; }
else if ("#00ff00".equalsIgnoreCase(sColor)) { return "{green}"; }
else if ("#0000ff".equalsIgnoreCase(sColor)) { return "{blue}"; }
else if ("#ffff00".equalsIgnoreCase(sColor)) { return "{yellow}"; }
else if ("#ff00ff".equalsIgnoreCase(sColor)) { return "{magenta}"; }
else if ("#00ffff".equalsIgnoreCase(sColor)) { return "{cyan}"; }
//no white, since we don't have background colors:
//else if ("#ffffff".equalsIgnoreCase(sColor)) { return "{white}"; }
else {
if (sColor==null || sColor.length()!=7) return null;
float[] rgb = getRgb(sColor);
// avoid very bright colors (since we don't have background colors):
if (rgb[RED]+rgb[GREEN]+rgb[BLUE]>2.7) { return "{black}"; }
else { return "[rgb]{"+rgb[RED]+","+rgb[GREEN]+","+rgb[BLUE]+"}"; }
}
}
private static final String fullcolor(String sColor){
if ("#000000".equalsIgnoreCase(sColor)) { return "{black}"; }
else if ("#ff0000".equalsIgnoreCase(sColor)) { return "{red}"; }
else if ("#00ff00".equalsIgnoreCase(sColor)) { return "{green}"; }
else if ("#0000ff".equalsIgnoreCase(sColor)) { return "{blue}"; }
else if ("#ffff00".equalsIgnoreCase(sColor)) { return "{yellow}"; }
else if ("#ff00ff".equalsIgnoreCase(sColor)) { return "{magenta}"; }
else if ("#00ffff".equalsIgnoreCase(sColor)) { return "{cyan}"; }
else if ("#ffffff".equalsIgnoreCase(sColor)) { return "{white}"; }
else {
// This could mean transparent:
if (sColor==null || sColor.length()!=7) return null;
float[] rgb = getRgb(sColor);
return "[rgb]{"+rgb[RED]+","+rgb[GREEN]+","+rgb[BLUE]+"}";
}
}
private static final float[] getRgb(String sColor) {
float[] rgb = new float[3];
rgb[RED] = (float)Misc.getIntegerFromHex(sColor.substring(1,3),0)/255;
rgb[GREEN] = (float)Misc.getIntegerFromHex(sColor.substring(3,5),0)/255;
rgb[BLUE] = (float)Misc.getIntegerFromHex(sColor.substring(5,7),0)/255;
return rgb;
}
}

View file

@ -0,0 +1,42 @@
/************************************************************************
*
* ContentHandlingOption.java
*
* Copyright: 2002-2008 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.0 (2008-09-08)
*
*/
package writer2latex.latex;
import writer2latex.base.IntegerOption;
public class ContentHandlingOption extends IntegerOption {
public ContentHandlingOption(String sName, String sDefaultValue) {
super(sName,sDefaultValue);
}
public void setString(String sValue) {
super.setString(sValue);
if ("accept".equals(sValue)) nValue = LaTeXConfig.ACCEPT;
else if ("ignore".equals(sValue)) nValue = LaTeXConfig.IGNORE;
else if ("warning".equals(sValue)) nValue = LaTeXConfig.WARNING;
else if ("error".equals(sValue)) nValue = LaTeXConfig.ERROR;
}
}

View file

@ -0,0 +1,47 @@
/************************************************************************
*
* ConverterHelper.java
*
* Copyright: 2002-2016 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.6 (2015-06-20)
*
*/
package writer2latex.latex;
import writer2latex.office.OfficeReader;
/**
* <p>This is an abstract superclass for converter helpers.</p>
*/
abstract class ConverterHelper {
OfficeReader ofr;
LaTeXConfig config;
ConverterPalette palette;
ConverterHelper(OfficeReader ofr, LaTeXConfig config, ConverterPalette palette) {
this.ofr = ofr;
this.config = config;
this.palette = palette;
}
abstract void appendDeclarations(LaTeXDocumentPortion pack, LaTeXDocumentPortion decl);
}

View file

@ -0,0 +1,298 @@
/************************************************************************
*
* ConverterPalette.java
*
* Copyright: 2002-2015 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.6 (2015-04-14)
*
*/
package writer2latex.latex;
import org.w3c.dom.Element;
import java.io.IOException;
import writer2latex.api.Config;
import writer2latex.api.ConverterFactory;
import writer2latex.base.ConverterBase;
import writer2latex.latex.i18n.ClassicI18n;
import writer2latex.latex.i18n.I18n;
import writer2latex.latex.i18n.XeTeXI18n;
import writer2latex.latex.util.Context;
import writer2latex.util.CSVList;
import writer2latex.util.ExportNameCollection;
import writer2latex.util.Misc;
import writer2latex.office.MIMETypes;
import writer2latex.office.StyleWithProperties;
import writer2latex.office.XMLString;
/** This class converts a Writer XML file to a LaTeX file
*/
public final class ConverterPalette extends ConverterBase {
// Configuration
private LaTeXConfig config;
public Config getConfig() { return config; }
// The main outfile
private LaTeXDocument texDoc;
// Various data used in conversion
private Context mainContext; // main context
private CSVList globalOptions; // global options
// The helpers (the "colors" of the palette)
private I18n i18n;
private ColorConverter colorCv;
private CharStyleConverter charSc;
private PageStyleConverter pageSc;
private BlockConverter blockCv;
private ParConverter parCv;
private HeadingConverter headingCv;
private IndexConverter indexCv;
private BibConverter bibCv;
private SectionConverter sectionCv;
private TableConverter tableCv;
private ListConverter listCv;
private NoteConverter noteCv;
private CaptionConverter captionCv;
private InlineConverter inlineCv;
private FieldConverter fieldCv;
private DrawConverter drawCv;
private MathConverter mathCv;
private Info info;
// Constructor
public ConverterPalette() {
super();
config = new LaTeXConfig();
}
// Accessor methods for data
public String getOutFileName() { return sTargetFileName; }
public Context getMainContext() { return mainContext; }
public void addGlobalOption(String sOption) {
globalOptions.addValue(sOption);
}
// Accessor methods for helpers
public I18n getI18n() { return i18n; }
public ColorConverter getColorCv() { return colorCv; }
public CharStyleConverter getCharSc() { return charSc; }
public PageStyleConverter getPageSc() { return pageSc; }
public BlockConverter getBlockCv() { return blockCv; }
public ParConverter getParCv() { return parCv; }
public HeadingConverter getHeadingCv() { return headingCv; }
public IndexConverter getIndexCv() { return indexCv; }
public BibConverter getBibCv() { return bibCv; }
public SectionConverter getSectionCv() { return sectionCv; }
public TableConverter getTableCv() { return tableCv; }
public ListConverter getListCv() { return listCv; }
public NoteConverter getNoteCv() { return noteCv; }
public CaptionConverter getCaptionCv() { return captionCv; }
public InlineConverter getInlineCv() { return inlineCv; }
public FieldConverter getFieldCv() { return fieldCv; }
public DrawConverter getDrawCv() { return drawCv; }
public MathConverter getMathCv() { return mathCv; }
public Info getInfo() { return info; }
// fill out inner converter method
public void convertInner() throws IOException {
sTargetFileName = Misc.trimDocumentName(sTargetFileName,".tex");
String sSafeTargetFileName = new ExportNameCollection(true).getExportName(sTargetFileName);
imageConverter.setBaseFileName(sSafeTargetFileName+"-img");
if (config.saveImagesInSubdir()) {
imageConverter.setUseSubdir(sSafeTargetFileName+"-img");
}
// Set graphics formats depending on backend
if (config.getBackend()==LaTeXConfig.PDFTEX || config.getBackend()==LaTeXConfig.XETEX) {
imageConverter.setDefaultFormat(MIMETypes.PNG);
imageConverter.setDefaultVectorFormat(MIMETypes.PDF);
imageConverter.addAcceptedFormat(MIMETypes.JPEG);
}
else if (config.getBackend()==LaTeXConfig.DVIPS) {
imageConverter.setDefaultFormat(MIMETypes.EPS);
}
// Other values: keep original format
// Inject user sequence names for tables and figures into OfficeReader
if (config.getTableSequenceName().length()>0) {
ofr.addTableSequenceName(config.getTableSequenceName());
}
if (config.getFigureSequenceName().length()>0) {
ofr.addFigureSequenceName(config.getFigureSequenceName());
}
// Create helpers
if (config.getBackend()!=LaTeXConfig.XETEX) {
i18n = new ClassicI18n(ofr,config,this);
}
else {
i18n = new XeTeXI18n(ofr,config,this);
}
colorCv = new ColorConverter(ofr,config,this);
charSc = new CharStyleConverter(ofr,config,this);
pageSc = new PageStyleConverter(ofr,config,this);
blockCv = new BlockConverter(ofr,config,this);
parCv = new ParConverter(ofr,config,this);
headingCv = new HeadingConverter(ofr,config,this);
indexCv = new IndexConverter(ofr,config,this);
bibCv = new BibConverter(ofr,config,this);
sectionCv = new SectionConverter(ofr,config,this);
tableCv = new TableConverter(ofr,config,this);
listCv = new ListConverter(ofr,config,this);
noteCv = new NoteConverter(ofr,config,this);
captionCv = new CaptionConverter(ofr,config,this);
inlineCv = new InlineConverter(ofr,config,this);
fieldCv = new FieldConverter(ofr,config,this);
drawCv = new DrawConverter(ofr,config,this);
mathCv = new MathConverter(ofr,config,this);
info = new Info(ofr,config,this);
// Create master document and add this
this.texDoc = new LaTeXDocument(sTargetFileName,config.getWrapLinesAfter(),true);
if (config.getBackend()!=LaTeXConfig.XETEX) {
texDoc.setEncoding(ClassicI18n.writeJavaEncoding(config.getInputencoding()));
}
else {
texDoc.setEncoding("UTF-8");
}
converterResult.addDocument(texDoc);
// Create other data
globalOptions = new CSVList(',');
// Setup context.
// The default language is specified in the default paragraph style:
mainContext = new Context();
mainContext.resetFormattingFromStyle(ofr.getDefaultParStyle());
mainContext.setInMulticols(pageSc.isTwocolumn());
// Create main LaTeXDocumentPortions
LaTeXDocumentPortion packages = new LaTeXDocumentPortion(false);
LaTeXDocumentPortion declarations = new LaTeXDocumentPortion(false);
LaTeXDocumentPortion body = new LaTeXDocumentPortion(true);
// Traverse the content
Element content = ofr.getContent();
blockCv.traverseBlockText(content,body,mainContext);
noteCv.insertEndnotes(body);
// Add declarations from our helpers
i18n.appendDeclarations(packages,declarations);
colorCv.appendDeclarations(packages,declarations);
noteCv.appendDeclarations(packages,declarations);
charSc.appendDeclarations(packages,declarations);
headingCv.appendDeclarations(packages,declarations);
parCv.appendDeclarations(packages,declarations);
pageSc.appendDeclarations(packages,declarations);
blockCv.appendDeclarations(packages,declarations);
indexCv.appendDeclarations(packages,declarations);
bibCv.appendDeclarations(packages,declarations);
sectionCv.appendDeclarations(packages,declarations);
tableCv.appendDeclarations(packages,declarations);
listCv.appendDeclarations(packages,declarations);
captionCv.appendDeclarations(packages,declarations);
inlineCv.appendDeclarations(packages,declarations);
fieldCv.appendDeclarations(packages,declarations);
drawCv.appendDeclarations(packages,declarations);
mathCv.appendDeclarations(packages,declarations);
// Add custom preamble
String sCustomPreamble = config.getCustomPreamble();
if (sCustomPreamble.length()>0) {
declarations.append(sCustomPreamble).nl();
}
// Set \title, \author and \date (for \maketitle)
createMeta("title",metaData.getTitle(),declarations);
if (config.metadata()) {
createMeta("author",metaData.getCreator(),declarations);
// According to the spec, the date has the format YYYY-MM-DDThh:mm:ss
String sDate = metaData.getDate();
if (sDate!=null) {
createMeta("date",Misc.dateOnly(sDate),declarations);
}
}
// Create options for documentclass
if (config.formatting()>=LaTeXConfig.CONVERT_MOST) {
StyleWithProperties dpStyle = ofr.getDefaultParStyle();
if (dpStyle!=null) {
String s = dpStyle.getProperty(XMLString.FO_FONT_SIZE);
if ("10pt".equals(s)) { globalOptions.addValue("10pt"); }
if ("11pt".equals(s)) { globalOptions.addValue("11pt"); }
if ("12pt".equals(s)) { globalOptions.addValue("12pt"); }
}
}
// Temp solution. TODO: Fix when new CSVList is implemented
if (config.getGlobalOptions().length()>0) {
globalOptions.addValue(config.getGlobalOptions());
}
// Assemble the document
LaTeXDocumentPortion result = texDoc.getContents();
if (!config.noPreamble()) {
// Create document class declaration
result.append("% This file was converted to LaTeX by Writer2LaTeX ver. "+ConverterFactory.getVersion()).nl()
.append("% see http://writer2latex.sourceforge.net for more info").nl();
result.append("\\documentclass");
if (!globalOptions.isEmpty()) {
result.append("[").append(globalOptions.toString()).append("]");
}
result.append("{").append(config.getDocumentclass()).append("}").nl();
result.append(packages)
.append(declarations)
.append("\\begin{document}").nl();
}
result.append(body);
if (!config.noPreamble()) {
result.append("\\end{document}").nl();
}
else {
result.append("\\endinput").nl();
}
// Add BibTeX document if there's any bibliographic data
if (bibCv.getBibTeXDocument()!=null) {
converterResult.addDocument(bibCv.getBibTeXDocument());
}
}
private void createMeta(String sName, String sValue,LaTeXDocumentPortion ldp) {
if (sValue==null) { return; }
// Meta data is assumed to be in the default language:
ldp.append("\\"+sName+"{"+i18n.convert(sValue,false,mainContext.getLang())+"}").nl();
}
}

View file

@ -0,0 +1,454 @@
/************************************************************************
*
* DrawConverter.java
*
* Copyright: 2002-2014 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.4 (2014-09-05)
*
*/
package writer2latex.latex;
import java.util.LinkedList;
import java.util.Stack;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import writer2latex.base.BinaryGraphicsDocument;
import writer2latex.latex.util.BeforeAfter;
import writer2latex.latex.util.Context;
import writer2latex.office.EmbeddedObject;
import writer2latex.office.EmbeddedXMLObject;
import writer2latex.office.MIMETypes;
import writer2latex.office.OfficeReader;
import writer2latex.office.XMLString;
import writer2latex.util.CSVList;
import writer2latex.util.Calc;
import writer2latex.util.Misc;
/**
* <p>This class handles draw elements.</p>
*/
public class DrawConverter extends ConverterHelper {
private boolean bNeedGraphicx = false;
// Keep track of floating frames (images, text boxes...)
private Stack<LinkedList<Element>> floatingFramesStack = new Stack<LinkedList<Element>>();
private Element getFrame(Element onode) {
if (ofr.isOpenDocument()) return (Element) onode.getParentNode();
else return onode;
}
public DrawConverter(OfficeReader ofr, LaTeXConfig config, ConverterPalette palette) {
super(ofr,config,palette);
floatingFramesStack.push(new LinkedList<Element>());
}
public void appendDeclarations(LaTeXDocumentPortion pack, LaTeXDocumentPortion decl) {
if (bNeedGraphicx) {
pack.append("\\usepackage");
if (config.getBackend()==LaTeXConfig.PDFTEX) pack.append("[pdftex]");
//else if (config.getBackend()==LaTeXConfig.XETEX) pack.append("[xetex]");
else if (config.getBackend()==LaTeXConfig.DVIPS) pack.append("[dvips]");
pack.append("{graphicx}").nl();
}
}
public void handleCaption(Element node, LaTeXDocumentPortion ldp, Context oc) {
// Floating frames should be positioned *above* the label, hence
// we use a separate ldp for the paragraphs and add this later
LaTeXDocumentPortion capLdp = new LaTeXDocumentPortion(true);
// Convert the caption
if (oc.isInFigureFloat()) { // float
capLdp.append("\\caption");
palette.getCaptionCv().handleCaptionBody(node,capLdp,oc,false);
}
else { // nonfloat
capLdp.append("\\captionof{figure}");
palette.getCaptionCv().handleCaptionBody(node,capLdp,oc,true);
}
flushFloatingFrames(ldp,oc);
ldp.append(capLdp);
}
// Process the first child of a draw:frame
public void handleDrawElement(Element node, LaTeXDocumentPortion ldp, Context oc) {
// node must be an element in the draw namespace
String sName = node.getTagName();
if (sName.equals(XMLString.DRAW_OBJECT)) {
handleDrawObject(node,ldp,oc);
}
else if (sName.equals(XMLString.DRAW_OBJECT_OLE)) {
handleDrawObject(node,ldp,oc);
}
else if ((!oc.isInHeaderFooter()) && sName.equals(XMLString.DRAW_IMAGE)) {
handleDrawImage(node,ldp,oc);
}
else if ((!oc.isInHeaderFooter()) && sName.equals(XMLString.DRAW_TEXT_BOX)) {
handleDrawTextBox(node,ldp,oc);
}
else if (sName.equals(XMLString.DRAW_A)) {
// we handle this like text:a
palette.getFieldCv().handleAnchor(node,ldp,oc);
}
else if (sName.equals(XMLString.DRAW_FRAME)) {
if (!palette.getMathCv().handleTexMathsEquation(node,ldp)) {
// OpenDocument: Get the actual draw element in the frame
handleDrawElement(Misc.getFirstChildElement(node),ldp,oc);
}
}
else if (sName.equals(XMLString.DRAW_G)) {
if (!palette.getMathCv().handleTexMathsEquation(node,ldp)) {
// Shapes are currently not supported
ldp.append("[Warning: Draw object ignored]");
}
}
else {
// Other drawing objects are currently not supported
ldp.append("[Warning: Draw object ignored]");
}
}
//-----------------------------------------------------------------
// handle draw:object elements (OOo objects such as Chart, Math,...)
private void handleDrawObject(Element node, LaTeXDocumentPortion ldp, Context oc) {
String sHref = Misc.getAttribute(node,XMLString.XLINK_HREF);
if (sHref!=null) { // Embedded object in package or linked object
if (ofr.isInPackage(sHref)) { // Embedded object in package
if (sHref.startsWith("#")) { sHref=sHref.substring(1); }
if (sHref.startsWith("./")) { sHref=sHref.substring(2); }
EmbeddedObject object = palette.getEmbeddedObject(sHref);
if (object!=null) {
if (MIMETypes.MATH.equals(object.getType()) || MIMETypes.ODF.equals(object.getType())) { // Formula!
try {
Document formuladoc = ((EmbeddedXMLObject) object).getContentDOM();
Element formula = Misc.getChildByTagName(formuladoc,XMLString.MATH); // Since OOo3.2
if (formula==null) {
formula = Misc.getChildByTagName(formuladoc,XMLString.MATH_MATH);
}
String sLaTeX = palette.getMathCv().convert(formula);
if (!" ".equals(sLaTeX)) { // ignore empty formulas
ldp.append(" $")
.append(sLaTeX)
.append("$");
if (Character.isLetterOrDigit(OfficeReader.getNextChar(node))) { ldp.append(" "); }
}
}
catch (org.xml.sax.SAXException e) {
e.printStackTrace();
}
catch (java.io.IOException e) {
e.printStackTrace();
}
}
else { // unsupported object
boolean bIgnore = true;
if (ofr.isOpenDocument()) { // look for replacement image
Element replacementImage = Misc.getChildByTagName(getFrame(node),XMLString.DRAW_IMAGE);
if (replacementImage!=null) {
handleDrawImage(replacementImage,ldp,oc);
bIgnore = false;
}
}
if (bIgnore) {
ldp.append("[Warning: object ignored]");
}
}
}
}
}
else { // flat xml, object is contained in node
Element formula = Misc.getChildByTagName(node,XMLString.MATH); // Since OOo 3.2
if (formula==null) {
formula = Misc.getChildByTagName(node,XMLString.MATH_MATH);
}
if (formula!=null) {
ldp.append(" $")
.append(palette.getMathCv().convert(formula))
.append("$");
if (Character.isLetterOrDigit(OfficeReader.getNextChar(node))) { ldp.append(" "); }
}
else { // unsupported object
boolean bIgnore = true;
if (ofr.isOpenDocument()) { // look for replacement image
Element replacementImage = Misc.getChildByTagName(getFrame(node),XMLString.DRAW_IMAGE);
if (replacementImage!=null) {
handleDrawImage(replacementImage,ldp,oc);
bIgnore = false;
}
}
if (bIgnore) {
ldp.append("[Warning: object ignored]");
}
}
}
}
//--------------------------------------------------------------------------
// Create float environment
private void applyFigureFloat(BeforeAfter ba, Context oc) {
// todo: check context...
if (config.floatFigures() && !oc.isInFrame() && !oc.isInTable()) {
if (oc.isInMulticols()) {
ba.add("\\begin{figure*}","\\end{figure*}\n");
}
else {
ba.add("\\begin{figure}","\\end{figure}\n");
}
if (config.getFloatOptions().length()>0) {
ba.add("["+config.getFloatOptions()+"]","");
}
ba.add("\n","");
oc.setInFigureFloat(true);
}
if (!oc.isInFrame() && config.alignFrames()) {
// Avoid nesting center environment
if (config.floatFigures()) {
// Inside floats we don't want the extra glue added by the center environment
ba.add("\\centering\n","\n");
}
else {
// Outside a float we certainly want it
ba.add("\\begin{center}\n","\n\\end{center}\n");
}
}
}
//--------------------------------------------------------------------------
// Handle draw:image elements
private void handleDrawImage(Element node, LaTeXDocumentPortion ldp, Context oc) {
// Include graphics if allowed by the configuration
switch (config.imageContent()) {
case LaTeXConfig.IGNORE:
// Ignore graphics silently
return;
case LaTeXConfig.WARNING:
System.err.println("Warning: Images are not allowed");
return;
case LaTeXConfig.ERROR:
ldp.append("% Error in document: An image was ignored");
return;
}
Element frame = getFrame(node);
String sName = frame.getAttribute(XMLString.DRAW_NAME);
palette.getFieldCv().addTarget(sName,"|graphic",ldp);
String sAnchor = frame.getAttribute(XMLString.TEXT_ANCHOR_TYPE);
//if (oc.isInFrame() || "as-char".equals(sAnchor)) {
if ("as-char".equals(sAnchor)) {
handleDrawImageAsChar(node,ldp,oc);
}
else {
floatingFramesStack.peek().add(node);
}
}
private void handleDrawImageAsChar(Element node, LaTeXDocumentPortion ldp, Context oc) {
ldp.append(" ");
includeGraphics(node,ldp,oc);
ldp.append(" ");
}
private void handleDrawImageFloat(Element node, LaTeXDocumentPortion ldp, Context oc) {
Context ic = (Context) oc.clone();
BeforeAfter ba = new BeforeAfter();
applyFigureFloat(ba,ic);
ldp.append(ba.getBefore());
includeGraphics(node,ldp,ic);
ldp.append(ba.getAfter());
}
private void includeGraphics(Element node, LaTeXDocumentPortion ldp, Context oc) {
String sFileName = null;
boolean bCommentOut = true;
BinaryGraphicsDocument bgd = palette.getImageCv().getImage(node);
if (bgd!=null) {
if (!bgd.isLinked()) { // embedded image
if (!bgd.isRecycled()) { palette.addDocument(bgd); }
sFileName = bgd.getFileName();
String sMIME = bgd.getMIMEType();
bCommentOut = !(
config.getBackend()==LaTeXConfig.UNSPECIFIED ||
(config.getBackend()==LaTeXConfig.PDFTEX && MIMETypes.JPEG.equals(sMIME)) ||
(config.getBackend()==LaTeXConfig.PDFTEX && MIMETypes.PNG.equals(sMIME)) ||
(config.getBackend()==LaTeXConfig.PDFTEX && MIMETypes.PDF.equals(sMIME)) ||
(config.getBackend()==LaTeXConfig.XETEX && MIMETypes.JPEG.equals(sMIME)) ||
(config.getBackend()==LaTeXConfig.XETEX && MIMETypes.PNG.equals(sMIME)) ||
(config.getBackend()==LaTeXConfig.XETEX && MIMETypes.PDF.equals(sMIME)) ||
(config.getBackend()==LaTeXConfig.DVIPS && MIMETypes.EPS.equals(sMIME)));
}
else { // linked image
sFileName = bgd.getFileName();
String sExt = Misc.getFileExtension(sFileName).toLowerCase();
// Accept only relative filenames and supported filetypes:
bCommentOut = sFileName.indexOf(":")>-1 || !(
config.getBackend()==LaTeXConfig.UNSPECIFIED ||
(config.getBackend()==LaTeXConfig.PDFTEX && MIMETypes.JPEG_EXT.equals(sExt)) ||
(config.getBackend()==LaTeXConfig.PDFTEX && MIMETypes.PNG_EXT.equals(sExt)) ||
(config.getBackend()==LaTeXConfig.PDFTEX && MIMETypes.PDF_EXT.equals(sExt)) ||
(config.getBackend()==LaTeXConfig.XETEX && MIMETypes.JPEG_EXT.equals(sExt)) ||
(config.getBackend()==LaTeXConfig.XETEX && MIMETypes.PNG_EXT.equals(sExt)) ||
(config.getBackend()==LaTeXConfig.XETEX && MIMETypes.PDF_EXT.equals(sExt)) ||
(config.getBackend()==LaTeXConfig.DVIPS && MIMETypes.EPS_EXT.equals(sExt)));
}
}
else {
ldp.append("[Warning: Image not found]");
return;
}
// Now for the actual inclusion:
bNeedGraphicx = true;
/* TODO: handle cropping and mirror:
style:mirror can be none, vertical (lodret), horizontal (vandret),
horizontal-on-odd, or
horizontal-on-even (horizontal on odd or even pages).
mirror is handled with scalebox, eg:
%\\scalebox{-1}[1]{...}
can check for even/odd page first!!
fo:clip="rect(t,r,b,l) svarer til trim
value can be auto - no clip!
cropping is handled with clip and trim:
\\includegraphics[clip,trim=l b r t]{...}
note the different order from xsl-fo!
*/
if (bCommentOut) {
ldp.append(" [Warning: Image ignored] ");
ldp.append("% Unhandled or unsupported graphics:").nl().append("%");
}
ldp.append("\\includegraphics");
CSVList options = new CSVList(',');
if (!config.originalImageSize()) {
Element frame = getFrame(node);
String sWidth = Calc.truncateLength(frame.getAttribute(XMLString.SVG_WIDTH));
String sHeight = Calc.truncateLength(frame.getAttribute(XMLString.SVG_HEIGHT));
if (sWidth!=null) { options.addValue("width="+sWidth); }
if (sHeight!=null) { options.addValue("height="+sHeight); }
}
if (config.getImageOptions().length()>0) {
options.addValue(config.getImageOptions()); // TODO: New CSVList...
}
if (!options.isEmpty()) {
ldp.append("[").append(options.toString()).append("]");
}
if (config.removeGraphicsExtension()) {
sFileName = Misc.removeExtension(sFileName);
}
ldp.append("{").append(sFileName).append("}");
if (bCommentOut) { ldp.nl(); }
}
//--------------------------------------------------------------------------
// handle draw:text-box element
private void handleDrawTextBox(Element node, LaTeXDocumentPortion ldp, Context oc) {
Element frame = getFrame(node);
String sName = frame.getAttribute(XMLString.DRAW_NAME);
palette.getFieldCv().addTarget(sName,"|frame",ldp);
String sAnchor = frame.getAttribute(XMLString.TEXT_ANCHOR_TYPE);
//if (oc.isInFrame() || "as-char".equals(sAnchor)) {
if ("as-char".equals(sAnchor)) {
makeDrawTextBox(node, ldp, oc);
}
else {
floatingFramesStack.peek().add(node);
}
}
private void handleDrawTextBoxFloat(Element node, LaTeXDocumentPortion ldp, Context oc) {
BeforeAfter ba = new BeforeAfter();
Context ic = (Context) oc.clone();
applyFigureFloat(ba,ic);
ldp.append(ba.getBefore());
makeDrawTextBox(node, ldp, ic);
ldp.append(ba.getAfter());
}
private void makeDrawTextBox(Element node, LaTeXDocumentPortion ldp, Context oc) {
Context ic = (Context) oc.clone();
ic.setInFrame(true);
ic.setNoFootnotes(true);
// Check to see, if this is really a container for a figure caption
boolean bIsCaption = false;
if (OfficeReader.isSingleParagraph(node)) {
Element par = Misc.getFirstChildElement(node);
String sSeqName = ofr.getSequenceName(par);
if (ofr.isFigureSequenceName(sSeqName)) { bIsCaption = true; }
}
String sWidth = Calc.truncateLength(getFrame(node).getAttribute(XMLString.SVG_WIDTH));
if (!bIsCaption) {
ldp.append("\\begin{minipage}{").append(sWidth).append("}").nl();
}
floatingFramesStack.push(new LinkedList<Element>());
palette.getBlockCv().traverseBlockText(node,ldp,ic);
flushFloatingFrames(ldp,ic);
floatingFramesStack.pop();
if (!bIsCaption) {
ldp.append("\\end{minipage}");
}
if (!oc.isNoFootnotes()) { palette.getNoteCv().flushFootnotes(ldp,oc); }
}
//-------------------------------------------------------------------------
//handle any pending floating frames
public void flushFloatingFrames(LaTeXDocumentPortion ldp, Context oc) {
// todo: fix language
LinkedList<Element> floatingFrames = floatingFramesStack.peek();
int n = floatingFrames.size();
if (n==0) { return; }
for (int i=0; i<n; i++) {
Element node = (Element) floatingFrames.get(i);
String sName = node.getNodeName();
if (sName.equals(XMLString.DRAW_IMAGE)) {
handleDrawImageFloat(node,ldp,oc);
}
else if (sName.equals(XMLString.DRAW_TEXT_BOX)) {
handleDrawTextBoxFloat(node,ldp,oc);
}
}
floatingFrames.clear();
}
}

View file

@ -0,0 +1,986 @@
/************************************************************************
*
* FieldConverter.java
*
* Copyright: 2002-2015 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.6 (2015-06-23)
*
*/
package writer2latex.latex;
//import java.io.UnsupportedEncodingException;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;
import java.util.regex.Pattern;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import writer2latex.latex.util.Context;
import writer2latex.latex.util.HeadingMap;
import writer2latex.office.ListStyle;
import writer2latex.office.OfficeReader;
import writer2latex.office.XMLString;
import writer2latex.util.ExportNameCollection;
import writer2latex.util.Misc;
import writer2latex.util.SimpleInputBuffer;
/**
* This class handles text fields and links in the document.
* Packages: lastpage, hyperref, titleref (all optional)
* TODO: Need proper treatment of "caption" and "text" for sequence
* references not to figures and tables (should be fairly rare, though)
*/
public class FieldConverter extends ConverterHelper {
// Identify Zotero items
private static final String ZOTERO_ITEM = "ZOTERO_ITEM";
// Identify JabRef items
private static final String JABREF_ITEM = "JR_cite";
// Links & references
private ExportNameCollection targets = new ExportNameCollection(true);
private ExportNameCollection refnames = new ExportNameCollection(true);
private ExportNameCollection bookmarknames = new ExportNameCollection(true);
private ExportNameCollection seqnames = new ExportNameCollection(true);
private ExportNameCollection seqrefnames = new ExportNameCollection(true);
// sequence declarations (maps name->text:sequence-decl element)
private Hashtable<String, Node> seqDecl = new Hashtable<String, Node>();
// first usage of sequence (maps name->text:sequence element)
private Hashtable<String, Element> seqFirst = new Hashtable<String, Element>();
private Vector<Element> postponedReferenceMarks = new Vector<Element>();
private Vector<Element> postponedBookmarks = new Vector<Element>();
private boolean bUseHyperref = false;
private boolean bUsesPageCount = false;
private boolean bUsesTitleref = false;
private boolean bConvertZotero = false;
private boolean bConvertJabRef = false;
private boolean bIncludeOriginalCitations = false;
private boolean bUseNatbib = false;
public FieldConverter(OfficeReader ofr, LaTeXConfig config, ConverterPalette palette) {
super(ofr,config,palette);
// hyperref.sty is not compatible with titleref.sty:
bUseHyperref = config.useHyperref() && !config.useTitleref();
bConvertZotero = config.useBibtex() && config.zoteroBibtexFiles().length()>0;
bConvertJabRef = config.useBibtex() && config.jabrefBibtexFiles().length()>0;
bIncludeOriginalCitations = config.includeOriginalCitations();
bUseNatbib = config.useBibtex() && config.useNatbib();
}
/** <p>Append declarations needed by the <code>FieldConverter</code> to
* the preamble.</p>
* @param pack the <code>LaTeXDocumentPortion</code> to which
* declarations of packages should be added (<code>\\usepackage</code>).
* @param decl the <code>LaTeXDocumentPortion</code> to which
* other declarations should be added.
*/
public void appendDeclarations(LaTeXDocumentPortion pack, LaTeXDocumentPortion decl) {
// Use natbib
if (config.useBibtex() && config.useNatbib()) {
pack.append("\\usepackage");
if (config.getNatbibOptions().length()>0) {
pack.append("[").append(config.getNatbibOptions()).append("]");
}
pack.append("{natbib}").nl();
}
// use lastpage.sty
if (bUsesPageCount) {
pack.append("\\usepackage{lastpage}").nl();
}
// use titleref.sty
if (bUsesTitleref) {
pack.append("\\usepackage{titleref}").nl();
}
// use hyperref.sty
if (bUseHyperref){
pack.append("\\usepackage{hyperref}").nl();
pack.append("\\hypersetup{");
if (config.getBackend()==LaTeXConfig.PDFTEX) pack.append("pdftex, ");
else if (config.getBackend()==LaTeXConfig.DVIPS) pack.append("dvips, ");
//else pack.append("hypertex");
pack.append("colorlinks=true, linkcolor=blue, citecolor=blue, filecolor=blue, urlcolor=blue");
if (config.getBackend()==LaTeXConfig.PDFTEX) {
pack.append(createPdfMeta("pdftitle",palette.getMetaData().getTitle()));
if (config.metadata()) {
pack.append(createPdfMeta("pdfauthor",palette.getMetaData().getCreator()))
.append(createPdfMeta("pdfsubject",palette.getMetaData().getSubject()))
.append(createPdfMeta("pdfkeywords",palette.getMetaData().getKeywords()));
}
}
pack.append("}").nl();
}
// Export sequence declarations
// The number format is fetched from the first occurence of the
// sequence in the text, while the outline level and the separation
// character are fetched from the declaration
Enumeration<String> names = seqFirst.keys();
while (names.hasMoreElements()) {
// Get first text:sequence element
String sName = names.nextElement();
Element first = seqFirst.get(sName);
// Collect data
String sNumFormat = Misc.getAttribute(first,XMLString.STYLE_NUM_FORMAT);
if (sNumFormat==null) { sNumFormat="1"; }
int nLevel = 0;
String sSepChar = ".";
if (seqDecl.containsKey(sName)) {
Element sdecl = (Element) seqDecl.get(sName);
nLevel = Misc.getPosInteger(sdecl.getAttribute(XMLString.TEXT_DISPLAY_OUTLINE_LEVEL),0);
if (sdecl.hasAttribute(XMLString.TEXT_SEPARATION_CHARACTER)) {
sSepChar = palette.getI18n().convert(
sdecl.getAttribute(XMLString.TEXT_SEPARATION_CHARACTER),
false,palette.getMainContext().getLang());
}
}
// Create counter
decl.append("\\newcounter{")
.append(seqnames.getExportName(sName))
.append("}");
String sPrefix = "";
if (nLevel>0) {
HeadingMap hm = config.getHeadingMap();
int nUsedLevel = nLevel<=hm.getMaxLevel() ? nLevel : hm.getMaxLevel();
if (nUsedLevel>0) {
decl.append("[").append(hm.getName(nUsedLevel)).append("]");
sPrefix = "\\the"+hm.getName(nUsedLevel)+sSepChar;
}
}
decl.nl()
.append("\\renewcommand\\the")
.append(seqnames.getExportName(sName))
.append("{").append(sPrefix)
.append(ListConverter.numFormat(sNumFormat))
.append("{").append(seqnames.getExportName(sName))
.append("}}").nl();
}
}
/** <p>Process sequence declarations</p>
* @param node the text:sequence-decls node
*/
public void handleSequenceDecls(Element node) {
Node child = node.getFirstChild();
while (child!=null) {
if (Misc.isElement(child,XMLString.TEXT_SEQUENCE_DECL)) {
// Don't process the declaration, but store a reference
seqDecl.put(((Element)child).getAttribute(XMLString.TEXT_NAME),child);
}
child = child.getNextSibling();
}
}
/** <p>Process a sequence field (text:sequence tag)</p>
* @param node The element containing the sequence field
* @param ldp the <code>LaTeXDocumentPortion</code> to which
* LaTeX code should be added
* @param oc the current context
*/
public void handleSequence(Element node, LaTeXDocumentPortion ldp, Context oc) {
String sName = Misc.getAttribute(node,XMLString.TEXT_NAME);
String sRefName = Misc.getAttribute(node,XMLString.TEXT_REF_NAME);
String sFormula = Misc.getAttribute(node,XMLString.TEXT_FORMULA);
if (sFormula==null) {
// If there's no formula, we must use the content as formula
// The parser below requires a namespace, so we add that..
sFormula = "ooow:"+Misc.getPCDATA(node);
}
if (sName!=null) {
if (ofr.isFigureSequenceName(sName) || ofr.isTableSequenceName(sName)) {
// Export \label only, assuming the number is generated by \caption
if (sRefName!=null && ofr.hasSequenceRefTo(sRefName)) {
ldp.append("\\label{seq:")
.append(seqrefnames.getExportName(sRefName))
.append("}");
}
}
else {
// General purpose sequence -> export as counter
if (!seqFirst.containsKey(sName)) {
// Save first occurence -> used to determine number format
seqFirst.put(sName,node);
}
if (sRefName!=null && ofr.hasSequenceRefTo(sRefName)) {
// Export as {\refstepcounter{name}\thename\label{refname}}
ldp.append("{").append(changeCounter(sName,sFormula,true))
.append("\\the").append(seqnames.getExportName(sName))
.append("\\label{seq:")
.append(seqrefnames.getExportName(sRefName))
.append("}}");
}
else {
// Export as \stepcounter{name}{\thename}
ldp.append(changeCounter(sName,sFormula,false))
.append("{\\the")
.append(seqnames.getExportName(sName))
.append("}");
}
}
}
}
/** <p>Create label for a sequence field (text:sequence tag)</p>
* @param node The element containing the sequence field
* @param ldp the <code>LaTeXDocumentPortion</code> to which
* LaTeX code should be added
*/
public void handleSequenceLabel(Element node, LaTeXDocumentPortion ldp) {
String sRefName = Misc.getAttribute(node,XMLString.TEXT_REF_NAME);
if (sRefName!=null && ofr.hasSequenceRefTo(sRefName)) {
ldp.append("\\label{seq:")
.append(seqrefnames.getExportName(sRefName))
.append("}");
}
}
// According to the spec for OpenDocument, the formula is application
// specific, prefixed with a namespace. OOo uses the namespace ooow, and
// we accept the formulas ooow:<number>, ooow:<name>, ooow:<name>+<number>
// and ooow:<name>-<number>
// Note: In OOo a counter is a 16 bit unsigned integer, whereas a (La)TeX
// counter can be negative - thus there will be a slight deviation in the
// (rare) case of a negative number
private String changeCounter(String sName, String sFormula, boolean bRef) {
if (sFormula!=null) {
sFormula = sFormula.trim();
if (sFormula.startsWith("ooow:")) {
SimpleInputBuffer input = new SimpleInputBuffer(sFormula.substring(5));
if (input.peekChar()>='0' && input.peekChar()<='9') {
// Value is <number>
String sNumber = input.getInteger();
if (input.atEnd()) {
return setCounter(sName, Misc.getPosInteger(sNumber,0), bRef);
}
}
else if (input.peekChar()=='-') {
// Value is a negative <number>
input.getChar();
if (input.peekChar()>='0' && input.peekChar()<='9') {
String sNumber = input.getInteger();
if (input.atEnd()) {
return setCounter(sName, -Misc.getPosInteger(sNumber,0), bRef);
}
}
}
else {
// Value starts with <name>
String sToken = input.getIdentifier();
if (sToken.equals(sName)) {
input.skipSpaces();
if (input.peekChar()=='+') {
// Value is <name>+<number>
input.getChar();
input.skipSpaces();
String sNumber = input.getInteger();
if (input.atEnd()) {
return addtoCounter(sName, Misc.getPosInteger(sNumber,0), bRef);
}
}
else if (input.peekChar()=='-') {
// Value is <name>-<number>
input.getChar();
input.skipSpaces();
String sNumber = input.getInteger();
if (input.atEnd()) {
return addtoCounter(sName, -Misc.getPosInteger(sNumber,0), bRef);
}
}
else if (input.atEnd()) {
// Value is <name>
return addtoCounter(sName, 0, bRef);
}
}
}
}
}
// No formula, or a formula we don't understand -> use default behavior
return stepCounter(sName, bRef);
}
private String stepCounter(String sName, boolean bRef) {
if (bRef) {
return "\\refstepcounter{" + seqnames.getExportName(sName) + "}";
}
else {
return "\\stepcounter{" + seqnames.getExportName(sName) + "}";
}
}
private String addtoCounter(String sName, int nValue, boolean bRef) {
if (nValue==1) {
return stepCounter(sName, bRef);
}
else if (bRef) {
return "\\addtocounter{" + seqnames.getExportName(sName) + "}"
+ "{" + Integer.toString(nValue-1) + "}"
+ "\\refstepcounter{" + seqnames.getExportName(sName) + "}";
}
else if (nValue!=0) {
return "\\addtocounter{" + seqnames.getExportName(sName) + "}"
+ "{" + Integer.toString(nValue) + "}";
}
else {
return "";
}
}
private String setCounter(String sName, int nValue, boolean bRef) {
if (bRef) {
return "\\setcounter{" + seqnames.getExportName(sName) + "}"
+ "{" + Integer.toString(nValue-1) + "}"
+ "\\refstepcounter{" + seqnames.getExportName(sName) + "}";
}
else {
return "\\setcounter{" + seqnames.getExportName(sName) + "}"
+ "{" + Integer.toString(nValue) + "}";
}
}
/** <p>Process a sequence reference (text:sequence-ref tag)</p>
* @param node The element containing the sequence reference
* @param ldp the <code>LaTeXDocumentPortion</code> to which
* LaTeX code should be added
* @param oc the current context
*/
public void handleSequenceRef(Element node, LaTeXDocumentPortion ldp, Context oc) {
String sRefName = Misc.getAttribute(node,XMLString.TEXT_REF_NAME);
String sFormat = Misc.getAttribute(node,XMLString.TEXT_REFERENCE_FORMAT);
String sName = ofr.getSequenceFromRef(sRefName);
if (sRefName!=null) {
if (sFormat==null || "page".equals(sFormat)) {
ldp.append("\\pageref{seq:")
.append(seqrefnames.getExportName(sRefName))
.append("}");
}
else if ("value".equals(sFormat)) {
ldp.append("\\ref{seq:")
.append(seqrefnames.getExportName(sRefName))
.append("}");
}
else if ("category-and-value".equals(sFormat)) {
// Export as Name~\\ref{refname}
if (sName!=null) {
if (ofr.isFigureSequenceName(sName)) {
ldp.append("\\figurename~");
}
else if (ofr.isTableSequenceName(sName)) {
ldp.append("\\tablename~");
}
else {
ldp.append(sName).append("~");
}
}
ldp.append("\\ref{seq:")
.append(seqrefnames.getExportName(sRefName))
.append("}");
}
else if ("caption".equals(sFormat) && config.useTitleref() &&
(ofr.isFigureSequenceName(sName) || ofr.isTableSequenceName(sName))) {
ldp.append("\\titleref{seq:")
.append(seqrefnames.getExportName(sRefName))
.append("}");
bUsesTitleref = true;
}
else if ("text".equals(sFormat) && config.useTitleref() &&
(ofr.isFigureSequenceName(sName) || ofr.isTableSequenceName(sName))) {
// This is a combination of "category-and-value" and "caption"
// Export as \\figurename~\ref{refname}:~\titleref{refname}
if (ofr.isFigureSequenceName(sName)) {
ldp.append("\\figurename");
}
else if (ofr.isTableSequenceName(sName)) {
ldp.append("\\tablename");
}
ldp.append("~\\ref{seq:")
.append(seqrefnames.getExportName(sRefName))
.append("}:~\\titleref{")
.append(seqrefnames.getExportName(sRefName))
.append("}");
bUsesTitleref = true;
}
else { // use current value
palette.getInlineCv().traversePCDATA(node,ldp,oc);
}
}
}
// Try to handle this reference name as a Zotero reference, return true on success
private boolean handleZoteroReferenceName(String sName, LaTeXDocumentPortion ldp, Context oc) {
// First parse the reference name:
// A Zotero reference name has the form ZOTERO_ITEM <json object> <identifier> with a single space separating the items
// The identifier is a unique identifier for the reference and is not used here
if (sName.startsWith(ZOTERO_ITEM)) {
int nObjectStart = sName.indexOf('{');
int nObjectEnd = sName.lastIndexOf('}');
if (nObjectStart>-1 && nObjectEnd>-1 && nObjectStart<nObjectEnd) {
String sJsonObject = sName.substring(nObjectStart, nObjectEnd+1);
JSONObject jo = null;
try {
jo = new JSONObject(sJsonObject);
} catch (JSONException e) {
return false;
}
// Successfully parsed the reference, now generate the code
// (we don't expect any errors and ignore them, if they happen anyway)
// Sort key (purpose? currently ignored)
/*boolean bSort = true;
try {
bSort = jo.getBoolean("sort");
}
catch (JSONException e) {
}*/
JSONArray citationItemsArray = null;
try { // The value is an array of objects, one for each source in this citation
citationItemsArray = jo.getJSONArray("citationItems");
}
catch (JSONException e) {
}
if (citationItemsArray!=null) {
int nCitationCount = citationItemsArray.length();
if (bUseNatbib) {
if (nCitationCount>1) {
// For multiple citations, use \citetext, otherwise we cannot add individual prefixes and suffixes
// TODO: If no prefixes or suffixes exist, it's safe to combine the citations
ldp.append("\\citetext{");
}
for (int nIndex=0; nIndex<nCitationCount; nIndex++) {
JSONObject citationItems = null;
try { // Each citation is represented as an object
citationItems = citationItemsArray.getJSONObject(nIndex);
}
catch (JSONException e) {
}
if (citationItems!=null) {
if (nIndex>0) {
ldp.append("; "); // Separate multiple citations in this reference
}
// Citation items
String sURI = "";
boolean bSuppressAuthor = false;
String sPrefix = "";
String sSuffix = "";
String sLocator = "";
String sLocatorType = "";
try { // The URI seems to be an array with a single string value(?)
sURI = citationItems.getJSONArray("uri").getString(0);
}
catch (JSONException e) {
}
try { // SuppressAuthor is a boolean value
bSuppressAuthor = citationItems.getBoolean("suppressAuthor");
}
catch (JSONException e) {
}
try { // Prefix is a string value
sPrefix = citationItems.getString("prefix");
}
catch (JSONException e) {
}
try { // Suffix is a string value
sSuffix = citationItems.getString("suffix");
}
catch (JSONException e) {
}
try { // Locator is a string value, e.g. a page number
sLocator = citationItems.getString("locator");
}
catch (JSONException e) {
}
try {
// LocatorType is a string value, e.g. book, verse, page (missing locatorType means page)
sLocatorType = citationItems.getString("locatorType");
}
catch (JSONException e) {
}
// Adjust locator type (empty locator type means "page")
// TODO: Handle other locator types (localize and abbreviate): Currently the internal name (e.g. book) is used.
if (sLocator.length()>0 && sLocatorType.length()==0) {
// A locator of the form <number><other characters><number> is interpreted as several pages
if (Pattern.compile("[0-9]+[^0-9]+[0-9]+").matcher(sLocator).find()) {
sLocatorType = "pp.";
}
else {
sLocatorType = "p.";
}
}
// Insert command. TODO: Evaluate this
if (nCitationCount>1) { // Use commands without parentheses
if (bSuppressAuthor) { ldp.append("\\citeyear"); }
else { ldp.append("\\citet"); }
}
else {
if (bSuppressAuthor) { ldp.append("\\citeyearpar"); }
else { ldp.append("\\citep"); }
}
if (sPrefix.length()>0) {
ldp.append("[").append(palette.getI18n().convert(sPrefix,true,oc.getLang())).append("]");
}
if (sPrefix.length()>0 || sSuffix.length()>0 || sLocatorType.length()>0 || sLocator.length()>0) {
// Note that we need to include an empty suffix if there's a prefix!
ldp.append("[")
.append(palette.getI18n().convert(sSuffix,true,oc.getLang()))
.append(palette.getI18n().convert(sLocatorType,true,oc.getLang()));
if (sLocatorType.length()>0 && sLocator.length()>0) {
ldp.append("~");
}
ldp.append(palette.getI18n().convert(sLocator,true,oc.getLang()))
.append("]");
}
ldp.append("{");
int nSlash = sURI.lastIndexOf('/');
if (nSlash>0) {
ldp.append(sURI.substring(nSlash+1));
}
else {
ldp.append(sURI);
}
ldp.append("}");
}
}
if (nCitationCount>1) { // End the \citetext command
ldp.append("}");
}
}
else { // natbib is not available, use simple \cite command
ldp.append("\\cite{");
for (int nIndex=0; nIndex<nCitationCount; nIndex++) {
JSONObject citationItems = null;
try { // Each citation is represented as an object
citationItems = citationItemsArray.getJSONObject(nIndex);
}
catch (JSONException e) {
}
if (citationItems!=null) {
if (nIndex>0) {
ldp.append(","); // Separate multiple citations in this reference
}
// Citation items
String sURI = "";
try { // The URI seems to be an array with a single string value(?)
sURI = citationItems.getJSONArray("uri").getString(0);
}
catch (JSONException e) {
}
int nSlash = sURI.lastIndexOf('/');
if (nSlash>0) {
ldp.append(sURI.substring(nSlash+1));
}
else {
ldp.append(sURI);
}
}
}
ldp.append("}");
}
oc.setInZoteroJabRefText(true);
return true;
}
}
}
return false;
}
// Try to handle this reference name as a JabRef reference, return true on success
private boolean handleJabRefReferenceName(String sName, LaTeXDocumentPortion ldp, Context oc) {
// First parse the reference name:
// A JabRef reference name has the form JR_cite<m>_<n>_<identifiers> where
// m is a sequence number to ensure unique citations (may be empty)
// n=1 for (Author date) and n=2 for Author (date) citations
// identifiers is a comma separated list of BibTeX keys
if (sName.startsWith(JABREF_ITEM)) {
String sRemains = sName.substring(JABREF_ITEM.length());
int nUnderscore = sRemains.indexOf('_');
if (nUnderscore>-1) {
sRemains = sRemains.substring(nUnderscore+1);
if (sRemains.length()>2) {
String sCommand;
if (bUseNatbib) {
if (sRemains.charAt(0)=='1') {
sCommand = "\\citep";
}
else {
sCommand = "\\citet";
}
}
else {
sCommand = "\\cite";
}
ldp.append(sCommand).append("{").append(sRemains.substring(2)).append("}");
}
}
oc.setInZoteroJabRefText(true);
return true;
}
return false;
}
private String shortenRefname(String s) {
// For Zotero items, use the trailing unique identifier
if (s.startsWith(ZOTERO_ITEM)) {
int nLast = s.lastIndexOf(' ');
if (nLast>0) {
return s.substring(nLast+1);
}
}
return s;
}
/** <p>Process a reference mark end (text:reference-mark-end tag)</p>
* @param node The element containing the reference mark
* @param ldp the <code>LaTeXDocumentPortion</code> to which
* LaTeX code should be added
* @param oc the current context
*/
public void handleReferenceMarkEnd(Element node, LaTeXDocumentPortion ldp, Context oc) {
// Nothing to do, except to mark that this ends any Zotero/JabRef citation
oc.setInZoteroJabRefText(false);
if (bIncludeOriginalCitations) { // Protect space after comment
ldp.append("{}");
}
}
/** <p>Process a reference mark (text:reference-mark or text:reference-mark-start tag)</p>
* @param node The element containing the reference mark
* @param ldp the <code>LaTeXDocumentPortion</code> to which
* LaTeX code should be added
* @param oc the current context
*/
public void handleReferenceMark(Element node, LaTeXDocumentPortion ldp, Context oc) {
if (!oc.isInSection() && !oc.isInCaption() && !oc.isVerbatim()) {
String sName = node.getAttribute(XMLString.TEXT_NAME);
// Zotero and JabRef (mis)uses reference marks to store citations, so check this first
if (sName!=null && (!bConvertZotero || !handleZoteroReferenceName(sName, ldp, oc))
&& (!bConvertJabRef || !handleJabRefReferenceName(sName, ldp, oc))) {
// Plain reference mark
// Note: Always include \label here, even when it's not used
ldp.append("\\label{ref:"+refnames.getExportName(shortenRefname(sName))+"}");
}
}
else {
// Reference marks should not appear within \section or \caption
postponedReferenceMarks.add(node);
}
}
/** <p>Process a reference (text:reference-ref tag)</p>
* @param node The element containing the reference
* @param ldp the <code>LaTeXDocumentPortion</code> to which
* LaTeX code should be added
* @param oc the current context
*/
public void handleReferenceRef(Element node, LaTeXDocumentPortion ldp, Context oc) {
String sFormat = node.getAttribute(XMLString.TEXT_REFERENCE_FORMAT);
String sName = node.getAttribute(XMLString.TEXT_REF_NAME);
if (("page".equals(sFormat) || "".equals(sFormat)) && sName!=null) {
ldp.append("\\pageref{ref:"+refnames.getExportName(shortenRefname(sName))+"}");
}
else if ("chapter".equals(sFormat) && ofr.referenceMarkInHeading(sName)) {
// This is safe if the reference mark is contained in a heading
ldp.append("\\ref{ref:"+refnames.getExportName(shortenRefname(sName))+"}");
}
else { // use current value
palette.getInlineCv().traversePCDATA(node,ldp,oc);
}
}
/** <p>Process a bookmark (text:bookmark tag)</p>
* <p>A bookmark may be the target for either a hyperlink or a reference,
* so this will generate a <code>\\hyperref</code> and/or a <code>\\label</code></p>
* @param node The element containing the bookmark
* @param ldp the <code>LaTeXDocumentPortion</code> to which
* LaTeX code should be added
* @param oc the current context
*/
public void handleBookmark(Element node, LaTeXDocumentPortion ldp, Context oc) {
if (!oc.isInSection() && !oc.isInCaption() && !oc.isVerbatim()) {
String sName = node.getAttribute(XMLString.TEXT_NAME);
if (sName!=null) {
// A bookmark may be used as a target for a hyperlink as well as
// for a reference. We export whatever is actually used:
addTarget(node,"",ldp);
if (ofr.hasBookmarkRefTo(sName)) {
ldp.append("\\label{bkm:"+bookmarknames.getExportName(sName)+"}");
}
}
}
else {
// Bookmarks should not appear within \section or \caption
postponedBookmarks.add(node);
}
}
/** <p>Process a bookmark reference (text:bookmark-ref tag).</p>
* @param node The element containing the bookmark reference
* @param ldp the <code>LaTeXDocumentPortion</code> to which
* LaTeX code should be added
* @param oc the current context
*/
public void handleBookmarkRef(Element node, LaTeXDocumentPortion ldp, Context oc) {
String sFormat = node.getAttribute(XMLString.TEXT_REFERENCE_FORMAT);
String sName = node.getAttribute(XMLString.TEXT_REF_NAME);
if (("page".equals(sFormat) || "".equals(sFormat)) && sName!=null) {
ldp.append("\\pageref{bkm:"+bookmarknames.getExportName(sName)+"}");
}
else if ("chapter".equals(sFormat) && ofr.bookmarkInHeading(sName)) {
// This is safe if the bookmark is contained in a heading
ldp.append("\\ref{bkm:"+bookmarknames.getExportName(sName)+"}");
}
else if (("number".equals(sFormat) || "number-no-superior".equals(sFormat) || "number-all-superior".equals(sFormat)) &&
(ofr.bookmarkInHeading(sName) || ofr.bookmarkInList(sName))) {
ListStyle style=null;
int nLevel = 0;
String sPrefix=null;
String sSuffix=null;
// Only convert the prefix and suffix if it is converted at the reference target
if (ofr.bookmarkInHeading(sName)) {
if (config.formatting()>=LaTeXConfig.CONVERT_MOST) {
style = ofr.getOutlineStyle();
}
nLevel = ofr.getBookmarkHeadingLevel(sName);
}
else {
if (config.formatting()>=LaTeXConfig.CONVERT_BASIC) {
style = ofr.getListStyle(ofr.getBookmarkListStyle(sName));
}
nLevel = ofr.getBookmarkListLevel(sName);
}
if (style!=null) {
sPrefix = style.getLevelProperty(nLevel, XMLString.STYLE_NUM_PREFIX);
sSuffix = style.getLevelProperty(nLevel, XMLString.STYLE_NUM_SUFFIX);
}
if (sPrefix!=null) ldp.append(palette.getI18n().convert(sPrefix,false,oc.getLang()));
ldp.append("\\ref{bkm:").append(bookmarknames.getExportName(sName)).append("}");
if (sSuffix!=null) ldp.append(palette.getI18n().convert(sSuffix,false,oc.getLang()));
}
else { // use current value
palette.getInlineCv().traversePCDATA(node,ldp,oc);
}
}
/** Do we have any pending reference marks or bookmarks, that may be inserted in this context?
*
* @param oc the context to verify against
* @return true if there are pending marks
*/
public boolean hasPendingReferenceMarks(Context oc) {
return !oc.isInSection() && !oc.isInCaption() && !oc.isVerbatim() &&
postponedReferenceMarks.size()+postponedBookmarks.size()>0;
}
/** <p>Process pending reference marks and bookmarks (which may have been
* postponed within sections, captions or verbatim text.</p>
* @param ldp the <code>LaTeXDocumentPortion</code> to which
* LaTeX code should be added
* @param oc the current context
*/
public void flushReferenceMarks(LaTeXDocumentPortion ldp, Context oc) {
// We may still be in a context with no reference marks
if (!oc.isInSection() && !oc.isInCaption() && !oc.isVerbatim()) {
// Type out all postponed reference marks
int n = postponedReferenceMarks.size();
for (int i=0; i<n; i++) {
handleReferenceMark(postponedReferenceMarks.get(i),ldp,oc);
}
postponedReferenceMarks.clear();
// Type out all postponed bookmarks
n = postponedBookmarks.size();
for (int i=0; i<n; i++) {
handleBookmark(postponedBookmarks.get(i),ldp,oc);
}
postponedBookmarks.clear();
}
}
/** <p>Process a hyperlink (text:a tag)</p>
* @param node The element containing the hyperlink
* @param ldp the <code>LaTeXDocumentPortion</code> to which
* LaTeX code should be added
* @param oc the current context
*/
public void handleAnchor(Element node, LaTeXDocumentPortion ldp, Context oc) {
String sHref = node.getAttribute(XMLString.XLINK_HREF);
if (sHref!=null) {
if (sHref.startsWith("#")) {
// TODO: hyperlinks to headings (?) and objects
if (bUseHyperref) {
ldp.append("\\hyperlink{")
.append(targets.getExportName(Misc.urlDecode(sHref.substring(1))))
.append("}{");
// ignore text style (let hyperref.sty handle the decoration):
palette.getInlineCv().traverseInlineText(node,ldp,oc);
ldp.append("}");
}
else { // user don't want to include hyperlinks
palette.getInlineCv().handleTextSpan(node,ldp,oc);
}
}
else {
if (bUseHyperref) {
if (OfficeReader.getTextContent(node).trim().equals(sHref)) {
// The link text equals the url
ldp.append("\\url{")
.append(escapeHref(sHref,oc.isInFootnote()))
.append("}");
}
else {
ldp.append("\\href{")
.append(escapeHref(sHref,oc.isInFootnote()))
.append("}{");
// ignore text style (let hyperref.sty handle the decoration):
palette.getInlineCv().traverseInlineText(node,ldp,oc);
ldp.append("}");
}
}
else { // user don't want to include hyperlinks
palette.getInlineCv().handleTextSpan(node,ldp,oc);
}
}
}
else {
palette.getInlineCv().handleTextSpan(node,ldp,oc);
}
}
/** <p>Add a <code>\\hypertarget</code></p>
* @param node The element containing the name of the target
* @param sSuffix A suffix to be added to the target,
* e.g. "|table" for a reference to a table.
* @param ldp the <code>LaTeXDocumentPortion</code> to which
* LaTeX code should be added
*/
public void addTarget(Element node, String sSuffix, LaTeXDocumentPortion ldp) {
// TODO: Remove this and use addTarget by name only
String sName = node.getAttribute(XMLString.TEXT_NAME);
if (sName == null) { sName = node.getAttribute(XMLString.TABLE_NAME); }
if (sName == null || !bUseHyperref) { return; }
if (!ofr.hasLinkTo(sName+sSuffix)) { return; }
ldp.append("\\hypertarget{")
.append(targets.getExportName(sName+sSuffix))
.append("}{}");
}
/** <p>Add a <code>\\hypertarget</code></p>
* @param sName The name of the target
* @param sSuffix A suffix to be added to the target,
* e.g. "|table" for a reference to a table.
* @param ldp the <code>LaTeXDocumentPortion</code> to which
* LaTeX code should be added
*/
public void addTarget(String sName, String sSuffix, LaTeXDocumentPortion ldp) {
if (sName!=null && bUseHyperref && ofr.hasLinkTo(sName+sSuffix)) {
ldp.append("\\hypertarget{")
.append(targets.getExportName(sName+sSuffix))
.append("}{}");
}
}
/** <p>Process a page number field (text:page-number tag)</p>
* @param node The element containing the page number field
* @param ldp the <code>LaTeXDocumentPortion</code> to which
* LaTeX code should be added
* @param oc the current context
*/
public void handlePageNumber(Element node, LaTeXDocumentPortion ldp, Context oc) {
// TODO: Obey attributes!
ldp.append("\\thepage{}");
}
/** <p>Process a page count field (text:page-count tag)</p>
* @param node The element containing the page count field
* @param ldp the <code>LaTeXDocumentPortion</code> to which
* LaTeX code should be added
* @param oc the current context
*/
public void handlePageCount(Element node, LaTeXDocumentPortion ldp, Context oc) {
// TODO: Obey attributes!
// Note: Actually LastPage refers to the page number of the last page, not the number of pages
if (config.useLastpage()) {
bUsesPageCount = true;
ldp.append("\\pageref{LastPage}");
}
else {
ldp.append("?");
}
}
// Helpers:
private String createPdfMeta(String sName, String sValue) {
if (sValue==null) { return ""; }
// Replace commas with semicolons (the keyval package doesn't like commas):
sValue = sValue.replace(',', ';');
// Meta data is assumed to be in the default language:
return ", "+sName+"="+palette.getI18n().convert(sValue,false,palette.getMainContext().getLang());
}
// For the argument to a href, we have to escape or encode certain characters
private String escapeHref(String s, boolean bInFootnote) {
StringBuilder buf = new StringBuilder();
for (int i=0; i<s.length(); i++) {
if (bInFootnote && s.charAt(i)=='#') { buf.append("\\#"); }
else if (bInFootnote && s.charAt(i)=='%') { buf.append("\\%"); }
// The following should not occur in an URL (see RFC1738), but just to be sure we encode them
else if (s.charAt(i)=='\\') { buf.append("\\%5C"); }
else if (s.charAt(i)=='{') { buf.append("\\%7B"); }
else if (s.charAt(i)=='}') { buf.append("\\%7D"); }
// hyperref.sty deals safely with other characters
else { buf.append(s.charAt(i)); }
}
return buf.toString();
}
}

View file

@ -0,0 +1,431 @@
/************************************************************************
*
* HeadingConverter.java
*
* Copyright: 2002-2015 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.6 (2015-04-14)
*
*/
package writer2latex.latex;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import writer2latex.latex.util.BeforeAfter;
import writer2latex.latex.util.Context;
import writer2latex.latex.util.HeadingMap;
import writer2latex.office.ListStyle;
import writer2latex.office.OfficeReader;
import writer2latex.office.StyleWithProperties;
import writer2latex.office.XMLString;
import writer2latex.util.Calc;
import writer2latex.util.Misc;
/* This class converts OpenDocument headings (<code>text:h</code>) and
* paragraph styles/formatting into LaTeX
* Export of formatting depends on the option "formatting":
* <ul>
* <li><code>ignore_all</code>
* <li><code>ignore_most</code>
* <li><code>convert_basic</code>
* <li><code>convert_most</code>
* <li><code>convert_all</code>
* </ul>
*/
public class HeadingConverter extends ConverterHelper {
private String[] sHeadingStyles = new String[11];
// Display hidden text?
private boolean bDisplayHiddenText = false;
/** Constructs a new <code>HeadingConverter</code>.
*/
public HeadingConverter(OfficeReader ofr, LaTeXConfig config, ConverterPalette palette) {
super(ofr,config,palette);
this.bDisplayHiddenText = config.displayHiddenText();
}
public void appendDeclarations(LaTeXDocumentPortion pack, LaTeXDocumentPortion decl) {
appendHeadingStyles(decl);
}
/** Process a heading
* @param node The text:h element node containing the heading
* @param ldp The LaTeXDocumentPortion to add LaTeX code to
* @param oc The current context
*/
public void handleHeading(Element node, LaTeXDocumentPortion ldp, Context oc) {
// Get the style
String sStyleName = node.getAttribute(XMLString.TEXT_STYLE_NAME);
StyleWithProperties style = ofr.getParStyle(sStyleName);
// Check for hidden text
if (!bDisplayHiddenText && style!=null && "none".equals(style.getProperty(XMLString.TEXT_DISPLAY))) {
return;
}
// Get the level
int nLevel = ofr.isOpenDocument() ?
Misc.getPosInteger(Misc.getAttribute(node, XMLString.TEXT_OUTLINE_LEVEL),1) :
Misc.getPosInteger(Misc.getAttribute(node, XMLString.TEXT_LEVEL),1);
boolean bUnNumbered = "true".equals(Misc.getAttribute(node,XMLString.TEXT_IS_LIST_HEADER));
// Get the heading map
HeadingMap hm = config.getHeadingMap();
if (nLevel<=hm.getMaxLevel()) {
// Always push the font used
palette.getI18n().pushSpecialTable(palette.getCharSc().getFontName(style));
Context ic = (Context) oc.clone();
ic.setInSection(true);
// Footnotes with more than one paragraph are not allowed within
// sections. To be safe, we disallow all footnotes
ic.setNoFootnotes(true);
// Apply style
BeforeAfter baHardPage = new BeforeAfter();
BeforeAfter baHardChar = new BeforeAfter();
applyHardHeadingStyle(nLevel, sStyleName,
baHardPage, baHardChar, ic);
// Export the heading
ldp.append(baHardPage.getBefore());
ldp.append("\\"+hm.getName(nLevel));
if (bUnNumbered) {
ldp.append("*");
}
else if (baHardChar.getBefore().length()>0 || containsElements(node)) {
// If this heading contains formatting, add optional argument:
ldp.append("[");
palette.getInlineCv().traversePlainInlineText(node,ldp,ic);
ldp.append("]");
}
ldp.append("{").append(baHardChar.getBefore());
palette.getInlineCv().traverseInlineText(node,ldp,ic);
ldp.append(baHardChar.getAfter()).append("}").nl();
ldp.append(baHardPage.getAfter());
// Include pending index marks, labels, footnotes & floating frames
palette.getFieldCv().flushReferenceMarks(ldp,oc);
palette.getIndexCv().flushIndexMarks(ldp,oc);
palette.getNoteCv().flushFootnotes(ldp,oc);
palette.getDrawCv().flushFloatingFrames(ldp,ic);
// Pop the font name
palette.getI18n().popSpecialTable();
}
else { // beyond supported headings - export as ordinary paragraph
palette.getParCv().handleParagraph(node,ldp,oc,false);
}
}
/** Use a paragraph style on a heading. If hard paragraph formatting
* is applied to a heading, page break and font is converted - other
* hard formatting is ignored.
* This method also collects name of heading style
* @param <code>nLevel</code> The level of this heading
* @param <code>sStyleName</code> the name of the paragraph style to use
* @param <code>baPage</code> a <code>BeforeAfter</code> to put page break code into
* @param <code>baText</code> a <code>BeforeAfter</code> to put character formatting code into
* @param <code>context</code> the current context. This method will use and update the formatting context
*/
private void applyHardHeadingStyle(int nLevel, String sStyleName,
BeforeAfter baPage, BeforeAfter baText, Context context) {
// Get the style
StyleWithProperties style = ofr.getParStyle(sStyleName);
if (style==null) { return; }
// Register heading style
if (sHeadingStyles[nLevel]==null) {
sHeadingStyles[nLevel] = style.isAutomatic() ? style.getParentName() : sStyleName;
}
// Do conversion
if (style.isAutomatic()) {
palette.getPageSc().applyPageBreak(style,false,baPage);
palette.getCharSc().applyHardCharFormatting(style,baText);
}
// Update context
context.updateFormattingFromStyle(style);
}
/** Convert heading styles and outline numbering to LaTeX.
* An array of stylenames to use is required: The OOo writer file format
* allows different paragraph styles to be applied to individual headings,
* so this is not included in the styles.
* LaTeX (and OOo Writer!) usually uses the same format for all headings.
* @param ldp the LaTeXDocumentPortion to add definitions to.
*/
// TODO: use method from ListStyleConverter to create labels
private void appendHeadingStyles(LaTeXDocumentPortion ldp) {
// The user may not want to convert the formatting of headings
if (config.formatting()<=LaTeXConfig.IGNORE_MOST) { return; }
HeadingMap hm = config.getHeadingMap();
// OK, we are going to convert. First find the max level for headings
int nMaxLevel = 0;
for (int i=1; i<=5; i++) { if (sHeadingStyles[i]!=null) { nMaxLevel=i; } }
if (nMaxLevel==0) { return; } // no headings, nothing to do!
if (nMaxLevel>hm.getMaxLevel()) { nMaxLevel = hm.getMaxLevel(); }
boolean bOnlyNum = config.formatting()==LaTeXConfig.CONVERT_BASIC;
if (bOnlyNum) {
ldp.append("% Outline numbering").nl();
}
else {
ldp.append("% Headings and outline numbering").nl()
.append("\\makeatletter").nl();
}
// Paragraph style for headings:
if (!bOnlyNum) {
for (int i=1; i<=nMaxLevel; i++) {
if (sHeadingStyles[i]!=null) {
StyleWithProperties style = ofr.getParStyle(sHeadingStyles[i]);
if (style!=null) {
BeforeAfter decl = new BeforeAfter();
BeforeAfter comm = new BeforeAfter();
palette.getPageSc().applyPageBreak(style,true,decl);
palette.getCharSc().applyNormalFont(decl);
palette.getCharSc().applyFont(style,true,true,decl,new Context());
palette.getParCv().applyAlignment(style,false,true,decl);
palette.getI18n().applyLanguage(style,false,true,comm);
palette.getCharSc().applyFontEffects(style,true,comm);
// Get margin parameters (using first line indent as left margin)
String sMarginTop = style.getAbsoluteLength(XMLString.FO_MARGIN_TOP);
String sMarginBottom = style.getAbsoluteLength(XMLString.FO_MARGIN_BOTTOM);
String sMarginLeft = style.getAbsoluteLength(XMLString.FO_MARGIN_LEFT);
String sTextIndent = style.getAbsoluteLength(XMLString.FO_TEXT_INDENT);
// Seems that we should *not* override the paragraph style
/*ListStyle outline = ofr.getOutlineStyle();
if (outline.isNewType(i)) {
String sNumFormat = ListStyleConverter.numFormat(outline.getLevelProperty(i,XMLString.STYLE_NUM_FORMAT));
if (sNumFormat!=null && !"".equals(sNumFormat)) {
// It there's a numbering, override left margins with the value from the outline
sMarginLeft = outline.getLevelStyleProperty(i, XMLString.FO_MARGIN_LEFT);
if (sMarginLeft==null) { sMarginLeft = "0cm"; }
sTextIndent = outline.getLevelStyleProperty(i, XMLString.FO_TEXT_INDENT);
if (sTextIndent==null) { sTextIndent = "0cm"; }
}
}*/
String sSecName = hm.getName(i);
if (!comm.isEmpty()) { // have to create a cs for this heading
ldp.append("\\newcommand\\cs").append(sSecName).append("[1]{")
.append(comm.getBefore()).append("#1").append(comm.getAfter())
.append("}").nl();
}
// Note: Use first line as left indent (cannot have separate first line indent)
ldp.append("\\renewcommand\\").append(sSecName)
.append("{\\@startsection{").append(sSecName).append("}{"+hm.getLevel(i))
.append("}{"+Calc.add(sMarginLeft,sTextIndent)+"}{");
// Suppress indentation after heading? currently not..
// ldp.append("-");
ldp.append(sMarginTop)
.append("}{")
.append(Calc.isZero(sMarginBottom) ? "0.1mm" : sMarginBottom)
.append("}{");
// Note: decl.getAfter() may include a page break after, which we ignore
ldp.append(decl.getBefore());
if (!comm.isEmpty()) {
ldp.append("\\cs").append(sSecName);
}
ldp.append("}}").nl();
}
}
}
}
// redefine formatting of section counters
// simplified if the user wants to ignore formatting
if (!bOnlyNum) {
ldp.append("\\renewcommand\\@seccntformat[1]{")
.append("\\csname @textstyle#1\\endcsname{\\csname the#1\\endcsname}")
.append("\\csname @distance#1\\endcsname}").nl();
}
// Collect numbering styles and set secnumdepth
int nSecnumdepth = nMaxLevel;
ListStyle outline = ofr.getOutlineStyle();
String[] sNumFormat = new String[6];
for (int i=nMaxLevel; i>=1; i--) {
sNumFormat[i] = ListConverter.numFormat(outline.getLevelProperty(i,
XMLString.STYLE_NUM_FORMAT));
if (sNumFormat[i]==null || "".equals(sNumFormat[i])) {
nSecnumdepth = i-1;
}
}
ldp.append("\\setcounter{secnumdepth}{"+nSecnumdepth+"}").nl();
for (int i=1; i<=nMaxLevel; i++) {
if (sNumFormat[i]==null || "".equals(sNumFormat[i])) {
// no numbering at this level
if (!bOnlyNum) {
ldp.append("\\newcommand\\@distance")
.append(hm.getName(i)).append("{}").nl()
.append("\\newcommand\\@textstyle")
.append(hm.getName(i)).append("[1]{#1}").nl();
}
}
else {
if (!bOnlyNum) {
// Distance between label and text:
String sSpaceChar="";
String sDistance=null;
if (outline.isNewType(i)) {
String sFormat = outline.getLevelStyleProperty(i, XMLString.TEXT_LABEL_FOLLOWED_BY);
if ("listtab".equals(sFormat)) {
String sMarginLeft="0cm";
String sTextIndent="0cm";
if (sHeadingStyles[i]!=null) {
StyleWithProperties style = ofr.getParStyle(sHeadingStyles[i]);
if (style!=null) {
sMarginLeft = style.getAbsoluteLength(XMLString.FO_MARGIN_LEFT);
sTextIndent = style.getAbsoluteLength(XMLString.FO_TEXT_INDENT);
}
}
// Seems that we should *not* override the paragraph style
/*String sMarginLeft = outline.getLevelStyleProperty(i, XMLString.FO_MARGIN_LEFT);
if (sMarginLeft==null) { sMarginLeft = "0cm"; }
String sTextIndent = outline.getLevelStyleProperty(i, XMLString.FO_TEXT_INDENT);
if (sTextIndent==null) { sTextIndent = "0cm"; }*/
String sTabPos = outline.getLevelStyleProperty(i, XMLString.TEXT_LIST_TAB_STOP_POSITION);
if (sTabPos==null) { sTabPos = "0cm"; }
sDistance = Calc.sub(sTabPos, Calc.add(sMarginLeft, sTextIndent));
}
else if ("space".equals(sFormat)) {
sSpaceChar="\\ ";
}
}
else {
sDistance = outline.getLevelStyleProperty(i,XMLString.TEXT_MIN_LABEL_DISTANCE);
}
ldp.append("\\newcommand\\@distance")
.append(hm.getName(i)).append("{");
if (sDistance!=null) {
ldp.append("\\hspace{").append(sDistance).append("}");
}
ldp.append("}").nl();
// Label width and alignment
String sTextAlign = outline.getLevelStyleProperty(i,XMLString.FO_TEXT_ALIGN);
String sAlignmentChar = "l"; // start (or left) is default
if (sTextAlign!=null) {
if ("end".equals(sTextAlign)) { sAlignmentChar="r"; }
else if ("right".equals(sTextAlign)) { sAlignmentChar="r"; }
else if ("center".equals(sTextAlign)) { sAlignmentChar="c"; }
}
String sLabelWidth = null;
if (outline.isNewType(i)) {
String sFormat = outline.getLevelStyleProperty(i, XMLString.TEXT_LABEL_FOLLOWED_BY);
if ("listtab".equals(sFormat) || sAlignmentChar=="r") {
sLabelWidth="0cm";
}
}
else {
sLabelWidth = outline.getLevelStyleProperty(i,XMLString.TEXT_MIN_LABEL_WIDTH);
}
// Textstyle to use for label:
String sStyleName = outline.getLevelProperty(i,XMLString.TEXT_STYLE_NAME);
// Prefix and suffix text to decorate the label
String sPrefix = outline.getLevelProperty(i,XMLString.STYLE_NUM_PREFIX);
String sSuffix = outline.getLevelProperty(i,XMLString.STYLE_NUM_SUFFIX);
// TODO is this correct?? todo: space-before??? start value???
BeforeAfter baText = new BeforeAfter();
if (!bOnlyNum) {palette.getCharSc().applyTextStyle(sStyleName,baText,new Context()); }
ldp.append("\\newcommand\\@textstyle")
.append(hm.getName(i)).append("[1]{");
if (!bOnlyNum && sLabelWidth!=null) {
ldp.append("\\protect\\makebox[").append(sLabelWidth).append("][").append(sAlignmentChar).append("]{");
}
ldp.append(baText.getBefore())
.append(sPrefix!=null ? palette.getI18n().convert(sPrefix,false,"en") : "")
.append("#1")
.append(sSuffix!=null ? palette.getI18n().convert(sSuffix,false,"en") : "")
.append(sSpaceChar)
.append(baText.getAfter());
if (!bOnlyNum && sLabelWidth!=null) {
ldp.append("}");
}
ldp.append("}").nl();
}
// The label:
int nLevels = Misc.getPosInteger(outline.getLevelProperty(i,
XMLString.TEXT_DISPLAY_LEVELS),1);
ldp.append("\\renewcommand\\the")
.append(hm.getName(i))
.append("{");
for (int j=i-nLevels+1; j<i; j++) {
ldp.append(sNumFormat[j])
.append("{").append(sectionName(j)).append("}")
.append(".");
}
ldp.append(sNumFormat[i])
.append("{").append(hm.getName(i)).append("}")
.append("}").nl();
}
}
if (!bOnlyNum) {
ldp.append("\\makeatother").nl();
}
}
/* Check to see if this node contains any element nodes, except reference marks */
public boolean containsElements(Node node) {
if (!node.hasChildNodes()) { return false; }
NodeList list = node.getChildNodes();
int nLen = list.getLength();
for (int i = 0; i < nLen; i++) {
Node child = list.item(i);
if (child.getNodeType()==Node.ELEMENT_NODE &&
!(child.getNodeName().startsWith(XMLString.TEXT_REFERENCE_MARK) ||
child.getNodeName().startsWith(XMLString.TEXT_BOOKMARK))) {
return true;
}
}
return false;
}
static final String sectionName(int nLevel){
switch (nLevel) {
case 1: return "section";
case 2: return "subsection";
case 3: return "subsubsection";
case 4: return "paragraph";
case 5: return "subparagraph";
default: return null;
}
}
}

View file

@ -0,0 +1,251 @@
/************************************************************************
*
* IndexConverter.java
*
* Copyright: 2002-2014 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.4 (2014-09-18)
*
*/
package writer2latex.latex;
import java.util.Vector;
import org.w3c.dom.Element;
//import org.w3c.dom.Node;
import writer2latex.util.Misc;
import writer2latex.office.IndexMark;
import writer2latex.office.OfficeReader;
import writer2latex.office.XMLString;
import writer2latex.latex.util.Context;
/**
* <p>This class handles indexes (table of contents, list of tables, list of
* illustrations, object index, user index, alphabetical index)
* as well as their associated index marks.</p>
*/
public class IndexConverter extends ConverterHelper {
private boolean bContainsAlphabeticalIndex = false;
private Vector<Element> postponedIndexMarks = new Vector<Element>();
/** <p>Construct a new <code>IndexConverter</code>.
* @param config the configuration to use
* @param palette the <code>ConverterPalette</code> to link to
* if such a document is created by the <code>IndexConverter</code>
*/
public IndexConverter(OfficeReader ofr,LaTeXConfig config, ConverterPalette palette) {
super(ofr,config,palette);
}
/** <p>Append declarations needed by the <code>IndexConverter</code> to
* the preamble.
* @param pack the <code>LaTeXDocumentPortion</code> to which
* declarations of packages should be added (<code>\\usepackage</code>).
* @param decl the <code>LaTeXDocumentPortion</code> to which
* other declarations should be added.
*/
public void appendDeclarations(LaTeXDocumentPortion pack, LaTeXDocumentPortion decl) {
if (bContainsAlphabeticalIndex) {
pack.append("\\usepackage{makeidx}").nl();
decl.append("\\makeindex").nl();
}
}
/** Process Table of Contents (text:table-of-content tag)
* @param node The element containing the Table of Contents
* @param ldp the <code>LaTeXDocumentPortion</code> to which
* LaTeX code should be added
* @param oc the current context
*/
public void handleTOC (Element node, LaTeXDocumentPortion ldp, Context oc) {
if (config.noIndex()) { return; }
/* TODO: Apply more formatting by modfification of \l@section etc.
Something like this:
\newcommand\l@section[2]{\@dottedtocline{1}{1.5em}{2.3em}{\textbf{#1}}{\textit{#2}}
Textformatting is trivial; see article.cls for examples of more complicated
formatting. Note: The section number can't be formatted indivdually.*/
Element source = Misc.getChildByTagName(node,XMLString.TEXT_TABLE_OF_CONTENT_SOURCE);
if (source!=null) {
if ("chapter".equals(source.getAttribute(XMLString.TEXT_INDEX_SOURCE))) {
ldp.append("[Warning: Table of content (for this chapter) ignored!]").nl().nl();
}
else {
int nLevel = Misc.getPosInteger(source.getAttribute(XMLString.TEXT_OUTLINE_LEVEL),1);
ldp.append("\\setcounter{tocdepth}{"+nLevel+"}").nl();
Element title = Misc.getChildByTagName(source,XMLString.TEXT_INDEX_TITLE_TEMPLATE);
if (title!=null) {
ldp.append("\\renewcommand\\contentsname{");
palette.getInlineCv().traversePCDATA(title,ldp,oc);
ldp.append("}").nl();
}
}
}
ldp.append("\\tableofcontents").nl();
}
/** Process List of Illustrations (text:list-of-illustrations tag)
* @param node The element containing the List of Illustrations
* @param ldp the <code>LaTeXDocumentPortion</code> to which
* LaTeX code should be added
* @param oc the current context
*/
public void handleLOF (Element node, LaTeXDocumentPortion ldp, Context oc) {
if (config.noIndex()) { return; }
ldp.append("\\listoffigures").nl();
}
/** Process List of Tables (text:list-of-tables tag)
* @param node The element containing the List of Tables
* @param ldp the <code>LaTeXDocumentPortion</code> to which
* LaTeX code should be added
* @param oc the current context
*/
public void handleLOT (Element node, LaTeXDocumentPortion ldp, Context oc) {
if (config.noIndex()) { return; }
ldp.append("\\listoftables").nl();
}
/** Process Object Index (text:object index tag)
* @param node The element containing the Object Index
* @param ldp the <code>LaTeXDocumentPortion</code> to which
* LaTeX code should be added
* @param oc the current context
*/
public void handleObjectIndex (Element node, LaTeXDocumentPortion ldp, Context oc) {
if (config.noIndex()) { return; }
ldp.append("[Warning: Object index ignored]").nl().nl();
}
/** Process User Index (text:user-index tag)
* @param node The element containing the User Index
* @param ldp the <code>LaTeXDocumentPortion</code> to which
* LaTeX code should be added
* @param oc the current context
*/
public void handleUserIndex (Element node, LaTeXDocumentPortion ldp, Context oc) {
if (config.noIndex()) { return; }
ldp.append("[Warning: User index ignored]").nl().nl();
}
/** Process Alphabetical Index (text:alphabetical-index tag)
* @param node The element containing the Alphabetical Index
* @param ldp the <code>LaTeXDocumentPortion</code> to which
* LaTeX code should be added
* @param oc the current context
*/
public void handleAlphabeticalIndex (Element node, LaTeXDocumentPortion ldp, Context oc) {
if (config.noIndex()) { return; }
ldp.append("\\printindex").nl();
bContainsAlphabeticalIndex = true;
}
/** Process an Alphabetical Index Mark (text:alphabetical-index-mark{-start} tag)
* @param node The element containing the Mark
* @param ldp the <code>LaTeXDocumentPortion</code> to which
* LaTeX code should be added
* @param oc the current context
*/
public void handleAlphabeticalIndexMark(Element node, LaTeXDocumentPortion ldp, Context oc) {
if (!oc.isInSection() && !oc.isInCaption() && !oc.isVerbatim()) {
String sValue = IndexMark.getIndexValue(node);
if (sValue!=null) {
ldp.append("\\index{");
String sKey1 = IndexMark.getKey1(node);
if (sKey1!=null) {
writeIndexText(sKey1.trim(),ldp,oc);
ldp.append("!");
}
String sKey2 = IndexMark.getKey2(node);
if (sKey2!=null) {
writeIndexText(sKey2.trim(),ldp,oc);
ldp.append("!");
}
writeIndexText(sValue.trim(),ldp,oc);
ldp.append("}");
}
}
else {
// Index marks should not appear within \section or \caption
postponedIndexMarks.add(node);
}
}
/** Do we have any pending index marks, that may be inserted in this context?
*
* @param oc the context to verify against
* @return true if there are pending index marks
*/
public boolean hasPendingIndexMarks(Context oc) {
return !oc.isInSection() && !oc.isInCaption() && !oc.isVerbatim() &&
postponedIndexMarks.size()>0;
}
public void flushIndexMarks(LaTeXDocumentPortion ldp, Context oc) {
// We may still be in a context with no index marks
if (!oc.isInSection() && !oc.isInCaption() && !oc.isVerbatim()) {
// Type out all postponed index marks
int n = postponedIndexMarks.size();
for (int i=0; i<n; i++) {
handleAlphabeticalIndexMark(postponedIndexMarks.get(i),ldp,oc);
}
postponedIndexMarks.clear();
}
}
// Helper: Write the text of an index mark, escaping special characters
private void writeIndexText(String sText, LaTeXDocumentPortion ldp, Context oc) {
String sTextOut = palette.getI18n().convert(sText,false,oc.getLang());
// need to escape !, @, | and ":
int nLen = sTextOut.length();
boolean bBackslash = false;
for (int i=0; i<nLen; i++) {
if (bBackslash) {
ldp.append(sTextOut.substring(i,i+1));
bBackslash = false;
}
else {
switch (sTextOut.charAt(i)) {
case '\\' : bBackslash = true;
ldp.append("\\");
break;
case '~' :
case '\u00A0' :
// Non-breaking space causes trouble in index:
ldp.append(" ");
break;
case '!' :
case '@' :
case '|' :
case '"' : ldp.append("\"");
default : ldp.append(sTextOut.substring(i,i+1));
}
}
}
}
}

View file

@ -0,0 +1,73 @@
/************************************************************************
*
* Info.java
*
* Copyright: 2002-2015 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.6 (2015-06-21)
*
*/
package writer2latex.latex;
import org.w3c.dom.Element;
import writer2latex.util.Misc;
import writer2latex.office.OfficeReader;
import writer2latex.office.StyleWithProperties;
import writer2latex.office.XMLString;
/** This class creates various information to the user about the conversion.
*/
class Info extends ConverterHelper {
@Override public void appendDeclarations(LaTeXDocumentPortion pack, LaTeXDocumentPortion decl) {
// Currently nothing
}
Info(OfficeReader ofr, LaTeXConfig config, ConverterPalette palette) {
super(ofr,config,palette);
}
void addDebugInfo(Element node, LaTeXDocumentPortion ldp) {
if (config.debug()) {
ldp.append("% ").append(node.getNodeName());
addDebugInfo(node,ldp,XMLString.TEXT_ID);
addDebugInfo(node,ldp,XMLString.TEXT_NAME);
addDebugInfo(node,ldp,XMLString.TABLE_NAME);
addDebugInfo(node,ldp,XMLString.TEXT_STYLE_NAME);
if (node.getNodeName().equals(XMLString.TEXT_P) || node.getNodeName().equals(XMLString.TEXT_H)) {
StyleWithProperties style = ofr.getParStyle(node.getAttribute(XMLString.TEXT_STYLE_NAME));
if (style!=null && style.isAutomatic()) {
ldp.append(" ("+style.getParentName()+")");
}
ldp.append(" ("+ofr.getParStyles().getDisplayName(node.getAttribute(XMLString.TEXT_STYLE_NAME))+")");
}
ldp.nl();
}
}
void addDebugInfo(Element node, LaTeXDocumentPortion ldp, String sAttribute) {
String sValue = Misc.getAttribute(node,sAttribute);
if (sValue!=null) {
ldp.append(" ").append(sAttribute).append("=\"").append(sValue).append("\"");
}
}
}

View file

@ -0,0 +1,740 @@
/************************************************************************
*
* InlineConverter.java
*
* Copyright: 2002-2014 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.4 (2014-09-19)
*
*/
package writer2latex.latex;
import java.util.Vector;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import writer2latex.util.Misc;
import writer2latex.office.OfficeReader;
import writer2latex.office.StyleWithProperties;
import writer2latex.office.XMLString;
import writer2latex.latex.util.BeforeAfter;
import writer2latex.latex.util.Context;
import writer2latex.latex.util.HeadingMap;
/**
* <p>This class handles basic inline text.</p>
*/
public class InlineConverter extends ConverterHelper {
// Display hidden text?
private boolean bDisplayHiddenText = false;
private boolean bIncludeOriginalCitations = false;
private String sTabstop = "\\ \\ ";
private boolean bHasPdfannotation = false;
public InlineConverter(OfficeReader ofr, LaTeXConfig config, ConverterPalette palette) {
super(ofr,config,palette);
bIncludeOriginalCitations = config.includeOriginalCitations();
// Get custom code for tab stops
if (config.getTabstop().length()>0) {
sTabstop = config.getTabstop();
}
this.bDisplayHiddenText = config.displayHiddenText();
}
public void appendDeclarations(LaTeXDocumentPortion pack, LaTeXDocumentPortion decl) {
if (bHasPdfannotation) {
decl.append("\\newcommand\\pdfannotation[1]")
.append("{\\ifx\\pdfoutput\\undefined\\marginpar{#1}\\else")
.append("\\pdfstringdef\\tempstring{#1}\\marginpar{")
.append("\\pdfannot width 5cm height 12pt depth 4cm ")
.append("{ /Subtype /Text /Open false /Contents(\\tempstring) /Color [1 0 0]}")
.append("}\\fi}").nl();
}
}
/** Handle several text:span elements
*
*/
private void handleTextSpans(Element[] nodes, LaTeXDocumentPortion ldp, Context oc) {
if (oc.isMathMode()) {
for (Element node : nodes) {
handleTextSpanMath(node, ldp, oc);
}
}
else {
handleTextSpanText(ldp, oc, nodes);
}
}
/** Handle a text:span element
*/
public void handleTextSpan(Element node, LaTeXDocumentPortion ldp, Context oc) {
if (oc.isMathMode()) { handleTextSpanMath(node, ldp, oc); }
else { handleTextSpanText(ldp, oc, node); }
}
private void handleTextSpanMath(Element node, LaTeXDocumentPortion ldp, Context oc) {
// TODO: Handle a selection of formatting attributes: color, superscript...
String sStyleName = node.getAttribute(XMLString.TEXT_STYLE_NAME);
StyleWithProperties style = ofr.getTextStyle(sStyleName);
// Check for hidden text
if (!bDisplayHiddenText && style!=null && "none".equals(style.getProperty(XMLString.TEXT_DISPLAY))) {
return;
}
// Always push the font used
palette.getI18n().pushSpecialTable(palette.getCharSc().getFontName(style));
// Convert formatting
BeforeAfter ba = new BeforeAfter();
if (style!=null) {
String sPos = style.getProperty(XMLString.STYLE_TEXT_POSITION, true);
if (sPos!=null) {
if (sPos.startsWith("sub") || sPos.startsWith("-")) {
ba.add("_{", "}");
}
else if (sPos.startsWith("super") || !sPos.startsWith("0%")) {
ba.add("^{", "}");
}
}
}
ldp.append(ba.getBefore());
traverseInlineMath(node, ldp, oc);
ldp.append(ba.getAfter());
// finally pop the font table
palette.getI18n().popSpecialTable();
}
// Handle several spans.
// If the converted formatting happens to be identical (e.g. \textbf{...}), the spans will be merged.
private void handleTextSpanText(LaTeXDocumentPortion ldp, Context oc, Element... nodes) {
// The current formatting
BeforeAfter baCurrent = new BeforeAfter();
for (Element node : nodes) {
String sStyleName = node.getAttribute(XMLString.TEXT_STYLE_NAME);
StyleWithProperties style = ofr.getTextStyle(sStyleName);
// First check for hidden text
if (bDisplayHiddenText || style==null || !"none".equals(style.getProperty(XMLString.TEXT_DISPLAY))) {
// Then check for strict handling of styles
String sDisplayName = ofr.getTextStyles().getDisplayName(sStyleName);
if (config.otherStyles()!=LaTeXConfig.ACCEPT && !config.getTextStyleMap().contains(sDisplayName)) {
if (config.otherStyles()==LaTeXConfig.WARNING) {
System.err.println("Warning: Text with style "+sDisplayName+" was ignored");
}
else if (config.otherStyles()==LaTeXConfig.ERROR) {
ldp.append("% Error in source document: Text with style ")
.append(palette.getI18n().convert(sDisplayName,false,oc.getLang()))
.append(" was ignored").nl();
}
}
else {
// We do want to convert this span :-)
// Always push the font used
palette.getI18n().pushSpecialTable(palette.getCharSc().getFontName(ofr.getTextStyle(sStyleName)));
// Apply the style
BeforeAfter ba = new BeforeAfter();
Context ic = (Context) oc.clone();
// Don't style it if
// - we're already within a verbatim environment
// - a {foot|end}note is the only content
// - there is no content
// - this is an automatic style in header/footer (name clash problem, only in package format)
if (!oc.isVerbatim() && !onlyNote(node) && OfficeReader.getCharacterCount(node)>0
&& !(ofr.isPackageFormat() && (style!=null && style.isAutomatic()) && oc.isInHeaderFooter())) {
palette.getCharSc().applyTextStyle(sStyleName,ba,ic);
}
// Footnote problems:
// No footnotes in sub/superscript (will disappear)
// No multiparagraph footnotes embedded in text command (eg. \textbf{..})
// Simple solution: styled text element is forbidden area for footnotes
if ((ba.getBefore().length()>0 || ba.getAfter().length()>0) && !ic.isInFootnote()) {
ic.setNoFootnotes(true);
}
// Merge spans? If the formatting of this span differs from the previous span, we will close the
// previous span and start a new one
if (!ba.getBefore().equals(baCurrent.getBefore()) || !ba.getAfter().equals(baCurrent.getAfter())) {
ldp.append(baCurrent.getAfter());
ldp.append(ba.getBefore());
baCurrent = ba;
}
traverseInlineText(node,ldp,ic);
// In the special case of pending footnotes, index marks and reference marks, we will close the span now.
// Otherwise we will wait and see
if (palette.getNoteCv().hasPendingFootnotes(oc)
|| palette.getIndexCv().hasPendingIndexMarks(oc)
|| palette.getFieldCv().hasPendingReferenceMarks(oc)) {
ldp.append(baCurrent.getAfter());
baCurrent = new BeforeAfter();
}
// Flush any pending footnotes, index marks and reference marks
if (!ic.isInFootnote()) { palette.getNoteCv().flushFootnotes(ldp,oc); }
palette.getFieldCv().flushReferenceMarks(ldp,oc);
palette.getIndexCv().flushIndexMarks(ldp,oc);
// finally pop the special table
palette.getI18n().popSpecialTable();
}
}
}
ldp.append(baCurrent.getAfter());
}
public void traverseInlineText(Element node, LaTeXDocumentPortion ldp, Context oc) {
if (oc.isVerbatim()) {
traverseVerbatimInlineText(node,ldp,oc);
}
else if (oc.isMathMode()) {
traverseInlineMath(node,ldp,oc);
}
else {
traverseOrdinaryInlineText(node,ldp,oc);
}
}
// Traverse ordinary inline text in text mode (temporarily changing to math
// mode for a sequence of text:span with style "OOoLaTeX")
private void traverseOrdinaryInlineText(Element node,LaTeXDocumentPortion ldp, Context oc) {
Node childNode = node.getFirstChild();
while (childNode!=null) {
short nodeType = childNode.getNodeType();
switch (nodeType) {
case Node.TEXT_NODE:
String s = childNode.getNodeValue();
if (s.length() > 0) {
if (oc.isInZoteroJabRefText()) {
if (bIncludeOriginalCitations) { // Include original citation as a comment
ldp.append("%")
.append(palette.getI18n().convert(s, false, oc.getLang()))
.nl();
}
}
else { // Normal text
ldp.append(palette.getI18n().convert(s, false, oc.getLang()));
}
}
break;
case Node.ELEMENT_NODE:
Element child = (Element)childNode;
String sName = child.getTagName();
if (sName.equals(XMLString.TEXT_SPAN)) {
String sStyleName = child.getAttribute(XMLString.TEXT_STYLE_NAME);
boolean bIsMathSpan = "OOoLaTeX".equals(ofr.getTextStyles().getDisplayName(sStyleName));
if (bIsMathSpan) {
// Temporarily change to math mode
Context ic = (Context) oc.clone();
ic.setMathMode(true);
ldp.append("$");
Node remember;
boolean bContinue = false;
do {
handleTextSpanMath((Element)childNode, ldp, ic);
remember = childNode;
childNode = childNode.getNextSibling();
bContinue = false;
if (childNode!=null && childNode.getNodeType()==Node.ELEMENT_NODE &&
childNode.getNodeName().equals(XMLString.TEXT_SPAN)) {
sStyleName = Misc.getAttribute(childNode,XMLString.TEXT_STYLE_NAME);
if ("OOoLaTeX".equals(ofr.getTextStyles().getDisplayName(sStyleName)))
//child = (Element) childNode;
bContinue = true;
}
} while(bContinue);
childNode = remember;
ldp.append("$");
}
else {
// Collect further spans
Vector<Element> spans = new Vector<Element>();
Node remember;
boolean bContinue = false;
do {
spans.add((Element)childNode);
remember = childNode;
childNode = childNode.getNextSibling();
bContinue = false;
if (childNode!=null && childNode.getNodeType()==Node.ELEMENT_NODE &&
childNode.getNodeName().equals(XMLString.TEXT_SPAN)) {
sStyleName = Misc.getAttribute(childNode,XMLString.TEXT_STYLE_NAME);
if (!"OOoLaTeX".equals(ofr.getTextStyles().getDisplayName(sStyleName)))
bContinue = true;
}
} while(bContinue);
childNode = remember;
handleTextSpans(spans.toArray(new Element[spans.size()]),ldp,oc);
}
}
else if (child.getNodeName().startsWith("draw:")) {
palette.getDrawCv().handleDrawElement(child,ldp,oc);
}
else if (sName.equals(XMLString.TEXT_S)) {
if (config.ignoreDoubleSpaces()) {
ldp.append(" ");
}
else {
int count= Misc.getPosInteger(child.getAttribute(XMLString.TEXT_C),1);
//String sSpace = config.ignoreDoubleSpaces() ? " " : "\\ ";
for ( ; count > 0; count--) { ldp.append("\\ "); }
}
}
else if (sName.equals(XMLString.TEXT_TAB_STOP) || sName.equals(XMLString.TEXT_TAB)) { // text:tab in oasis
// tab stops are not supported by the converter, but the special usage
// of tab stops in header and footer can be emulated with \hfill
// TODO: Sometimes extra \hfill should be added at end of line
if (oc.isInHeaderFooter()) { ldp.append("\\hfill "); }
else { ldp.append(sTabstop); }
}
else if (sName.equals(XMLString.TEXT_LINE_BREAK)) {
if (!oc.isInHeaderFooter() && !config.ignoreHardLineBreaks()) {
ldp.append("\\newline").nl();
}
else { ldp.append(" "); }
}
else if (sName.equals(XMLString.TEXT_A)) {
palette.getFieldCv().handleAnchor(child,ldp,oc);
}
else if (sName.equals(XMLString.OFFICE_ANNOTATION)) {
handleOfficeAnnotation(child,ldp,oc);
}
else if (sName.equals(XMLString.TEXT_PAGE_NUMBER)) {
palette.getFieldCv().handlePageNumber(child,ldp,oc);
}
else if (sName.equals(XMLString.TEXT_PAGE_COUNT)) {
palette.getFieldCv().handlePageCount(child,ldp,oc);
}
else if (oc.isInHeaderFooter()) {
if (sName.equals(XMLString.TEXT_CHAPTER)) {
handleChapterField(child,ldp,oc);
}
else if (sName.startsWith("text:")) {
traverseInlineText(child,ldp,oc);
}
}
else {
// These tags are ignored in header and footer
if (sName.equals(XMLString.TEXT_FOOTNOTE)) {
palette.getNoteCv().handleFootnote(child,ldp,oc);
}
else if (sName.equals(XMLString.TEXT_ENDNOTE)) {
palette.getNoteCv().handleEndnote(child,ldp,oc);
}
else if (sName.equals(XMLString.TEXT_NOTE)) {
if ("endnote".equals(child.getAttribute(XMLString.TEXT_NOTE_CLASS))) {
palette.getNoteCv().handleEndnote(child,ldp,oc);
}
else {
palette.getNoteCv().handleFootnote(child,ldp,oc);
}
}
else if (sName.equals(XMLString.TEXT_SEQUENCE)) {
palette.getFieldCv().handleSequence(child,ldp,oc);
}
else if (sName.equals(XMLString.TEXT_SEQUENCE_REF)) {
palette.getFieldCv().handleSequenceRef(child,ldp,oc);
}
else if (sName.equals(XMLString.TEXT_FOOTNOTE_REF)) {
palette.getNoteCv().handleFootnoteRef(child,ldp,oc);
}
else if (sName.equals(XMLString.TEXT_ENDNOTE_REF)) {
palette.getNoteCv().handleEndnoteRef(child,ldp,oc);
}
else if (sName.equals(XMLString.TEXT_NOTE_REF)) { // oasis
palette.getNoteCv().handleNoteRef(child,ldp,oc);
}
else if (sName.equals(XMLString.TEXT_REFERENCE_MARK)) {
palette.getFieldCv().handleReferenceMark(child,ldp,oc);
}
else if (sName.equals(XMLString.TEXT_REFERENCE_MARK_START)) {
palette.getFieldCv().handleReferenceMark(child,ldp,oc);
}
else if (sName.equals(XMLString.TEXT_REFERENCE_MARK_END)) {
palette.getFieldCv().handleReferenceMarkEnd(child,ldp,oc);
}
else if (sName.equals(XMLString.TEXT_REFERENCE_REF)) {
palette.getFieldCv().handleReferenceRef(child,ldp,oc);
}
else if (sName.equals(XMLString.TEXT_BOOKMARK)) {
palette.getFieldCv().handleBookmark(child,ldp,oc);
}
else if (sName.equals(XMLString.TEXT_BOOKMARK_START)) {
palette.getFieldCv().handleBookmark(child,ldp,oc);
}
else if (sName.equals(XMLString.TEXT_BOOKMARK_REF)) {
palette.getFieldCv().handleBookmarkRef(child,ldp,oc);
}
else if (sName.equals(XMLString.TEXT_BIBLIOGRAPHY_MARK)) {
palette.getBibCv().handleBibliographyMark(child,ldp,oc);
}
else if (sName.equals(XMLString.TEXT_ALPHABETICAL_INDEX_MARK)) {
palette.getIndexCv().handleAlphabeticalIndexMark(child,ldp,oc);
}
else if (sName.equals(XMLString.TEXT_ALPHABETICAL_INDEX_MARK_START)) {
palette.getIndexCv().handleAlphabeticalIndexMark(child,ldp,oc);
}
else if (sName.startsWith("text:")) {
traverseInlineText(child,ldp,oc);
}
}
break;
default:
// Do nothing
}
childNode = childNode.getNextSibling();
}
}
/* traverse inline text, ignoring any draw objects, footnotes, formatting and hyperlinks */
public void traversePlainInlineText(Element node,LaTeXDocumentPortion ldp, Context oc) {
String styleName = node.getAttribute(XMLString.TEXT_STYLE_NAME);
// Always push the font used
palette.getI18n().pushSpecialTable(palette.getCharSc().getFontName(ofr.getTextStyle(styleName)));
Node childNode = node.getFirstChild();
while (childNode!=null) {
short nodeType = childNode.getNodeType();
switch (nodeType) {
case Node.TEXT_NODE:
String s = childNode.getNodeValue();
if (s.length() > 0) {
// Need to protect ]
for (int j=0; j<s.length(); j++) {
if (s.charAt(j)!=']') {
ldp.append(palette.getI18n().convert(Character.toString(s.charAt(j)),false,oc.getLang()));
}
else {
ldp.append("{]}");
}
}
}
break;
case Node.ELEMENT_NODE:
Element child = (Element)childNode;
String sName = child.getTagName();
if (sName.equals(XMLString.TEXT_SPAN)) {
traversePlainInlineText(child,ldp,oc);
}
else if (sName.equals(XMLString.TEXT_S)) {
int count= Misc.getPosInteger(child.getAttribute(XMLString.TEXT_C),1);
for ( ; count > 0; count--) {
ldp.append("\\ ");
}
}
else if (sName.equals(XMLString.TEXT_TAB_STOP) || sName.equals(XMLString.TEXT_TAB)) { // text:tab in oasis
// tab stops are not supported by the converter
ldp.append(sTabstop);
}
else if (OfficeReader.isNoteElement(child)) {
// ignore
}
else if (OfficeReader.isTextElement(child)) {
traversePlainInlineText(child,ldp,oc);
}
break;
default:
// Do nothing
}
childNode = childNode.getNextSibling();
}
// finally pop the special table
palette.getI18n().popSpecialTable();
}
/* traverse inline math, ignoring any draw objects, footnotes, formatting and hyperlinks */
public void traverseInlineMath(Element node,LaTeXDocumentPortion ldp, Context oc) {
String styleName = node.getAttribute(XMLString.TEXT_STYLE_NAME);
// Always push the font used
palette.getI18n().pushSpecialTable(palette.getCharSc().getFontName(ofr.getTextStyle(styleName)));
Node childNode = node.getFirstChild();
while (childNode!=null) {
short nodeType = childNode.getNodeType();
switch (nodeType) {
case Node.TEXT_NODE:
String s = childNode.getNodeValue();
ldp.append(palette.getI18n().convert(s,true,oc.getLang()));
break;
case Node.ELEMENT_NODE:
Element child = (Element)childNode;
String sName = child.getTagName();
if (sName.equals(XMLString.TEXT_S)) {
int count= Misc.getPosInteger(child.getAttribute(XMLString.TEXT_C),1);
for ( ; count > 0; count--) {
ldp.append("\\ ");
}
}
else if (sName.equals(XMLString.TEXT_TAB_STOP) || sName.equals(XMLString.TEXT_TAB)) { // text:tab in oasis
// tab stops are not supported by the converter
ldp.append(" ");
}
else if (OfficeReader.isNoteElement(child)) {
// ignore
}
else if (OfficeReader.isTextElement(child)) {
traversePlainInlineText(child,ldp,oc);
}
break;
default:
// Do nothing
}
childNode = childNode.getNextSibling();
}
// finally pop the special table
palette.getI18n().popSpecialTable();
}
/* traverse verbatim inline text, ignoring any draw objects, footnotes, formatting and hyperlinks */
private void traverseVerbatimInlineText(Element node,LaTeXDocumentPortion ldp, Context oc) {
if (node.hasChildNodes()) {
NodeList nList = node.getChildNodes();
int len = nList.getLength();
for (int i = 0; i < len; i++) {
Node childNode = nList.item(i);
short nodeType = childNode.getNodeType();
switch (nodeType) {
case Node.TEXT_NODE:
String s = childNode.getNodeValue();
if (s.length() > 0) {
// text is copied verbatim! (Will be replaced by
// question marks if outside inputenc)
ldp.append(s);
}
break;
case Node.ELEMENT_NODE:
Element child = (Element)childNode;
String sName = child.getTagName();
if (sName.equals(XMLString.TEXT_S)) {
int count= Misc.getPosInteger(child.getAttribute(XMLString.TEXT_C),1);
for ( ; count > 0; count--) {
ldp.append(" ");
}
}
else if (sName.equals(XMLString.TEXT_TAB_STOP) || sName.equals(XMLString.TEXT_TAB)) { // text:tab in oasis
// tab stops are not supported by the onverter
ldp.append(sTabstop);
}
else if (sName.equals(XMLString.TEXT_LINE_BREAK)) {
if (!oc.isNoLineBreaks()) { ldp.nl(); }
}
else if (sName.equals(XMLString.TEXT_NOTE)) {
// oasis; ignore
}
else if (sName.equals(XMLString.TEXT_FOOTNOTE)) {
// ignore
}
else if (sName.equals(XMLString.TEXT_ENDNOTE)) {
// ignore
}
// The respective handlers know how to postpone these marks in verbatim context:
else if (sName.equals(XMLString.TEXT_ALPHABETICAL_INDEX_MARK)) {
palette.getIndexCv().handleAlphabeticalIndexMark(child,ldp,oc);
}
else if (sName.equals(XMLString.TEXT_ALPHABETICAL_INDEX_MARK_START)) {
palette.getIndexCv().handleAlphabeticalIndexMark(child,ldp,oc);
}
else if (sName.equals(XMLString.TEXT_REFERENCE_MARK)) {
palette.getFieldCv().handleReferenceMark(child,ldp,oc);
}
else if (sName.equals(XMLString.TEXT_REFERENCE_MARK_START)) {
palette.getFieldCv().handleReferenceMark(child,ldp,oc);
}
else if (sName.equals(XMLString.TEXT_BOOKMARK)) {
palette.getFieldCv().handleBookmark(child,ldp,oc);
}
else if (sName.equals(XMLString.TEXT_BOOKMARK_START)) {
palette.getFieldCv().handleBookmark(child,ldp,oc);
}
else if (sName.startsWith("text:")) {
traverseVerbatimInlineText(child,ldp,oc);
}
break;
default:
// Do nothing
}
}
}
}
public void traversePCDATA(Element node, LaTeXDocumentPortion ldp, Context oc) {
if (node.hasChildNodes()) {
NodeList nl = node.getChildNodes();
int nLen = nl.getLength();
for (int i=0; i<nLen; i++) {
if (nl.item(i).getNodeType()==Node.TEXT_NODE) {
ldp.append(palette.getI18n().convert(nl.item(i).getNodeValue(),false,oc.getLang()));
}
}
}
}
private void handleChapterField(Element node, LaTeXDocumentPortion ldp, Context oc) {
HeadingMap hm = config.getHeadingMap();
int nLevel = Misc.getPosInteger(node.getAttribute(XMLString.TEXT_OUTLINE_LEVEL),1);
if (nLevel<=hm.getMaxLevel()) {
int nLaTeXLevel = hm.getLevel(nLevel);
if (nLaTeXLevel==1) {
palette.getPageSc().setChapterField1(node.getAttribute(XMLString.TEXT_DISPLAY));
ldp.append("{\\leftmark}");
}
else if (nLaTeXLevel==2) {
palette.getPageSc().setChapterField2(node.getAttribute(XMLString.TEXT_DISPLAY));
ldp.append("{\\rightmark}");
}
}
}
////////////////////////////////////////////////////////////////////
// Annotations
private void handleOfficeAnnotation(Element node, LaTeXDocumentPortion ldp, Context oc) {
String sCommand = null;
switch (config.notes()) {
case LaTeXConfig.IGNORE: return;
case LaTeXConfig.COMMENT:
// Get the unformatted text of all paragraphs and insert each paragraph as a single comment
Element creator = null;
Element date = null;
ldp.append("%").nl();
Node child = node.getFirstChild();
while (child!=null) {
if (Misc.isElement(child, XMLString.TEXT_P)) {
ldp.append("%");
traversePlainInlineText((Element)child, ldp, oc);
ldp.nl();
}
else if (Misc.isElement(child, XMLString.DC_CREATOR)) {
creator = (Element) child;
}
else if (Misc.isElement(child, XMLString.DC_DATE)) {
date = (Element) child;
}
child = child.getNextSibling();
}
if (creator!=null) {
ldp.append("%");
traversePlainInlineText(creator, ldp, oc);
ldp.nl();
}
if (date!=null) {
ldp.append("%")
.append(Misc.formatDate(OfficeReader.getTextContent(date), palette.getI18n().getDefaultLanguage(), null))
.nl();
}
return;
case LaTeXConfig.PDFANNOTATION:
bHasPdfannotation = true;
sCommand = "\\pdfannotation";
break;
case LaTeXConfig.MARGINPAR:
sCommand = "\\marginpar";
break;
case LaTeXConfig.CUSTOM:
sCommand = config.getNotesCommand();
break;
}
// Get the unformatted text of all paragraphs, separated by spaces
ldp.append(sCommand).append("{");
Element creator = null;
Element date = null;
boolean bFirst = true;
Node child = node.getFirstChild();
while (child!=null) {
if (Misc.isElement(child, XMLString.TEXT_P)) {
if (!bFirst) ldp.append(" ");
traversePlainInlineText((Element)child, ldp, oc);
bFirst = false;
}
else if (Misc.isElement(child, XMLString.DC_CREATOR)) {
creator = (Element) child;
}
else if (Misc.isElement(child, XMLString.DC_DATE)) {
date = (Element) child;
}
child = child.getNextSibling();
}
if (creator!=null) {
if (!bFirst) ldp.append(" - ");
traversePlainInlineText(creator, ldp, oc);
}
if (date!=null) {
if (creator!=null) ldp.append(", ");
else if (!bFirst) ldp.append(" ");
ldp.append(Misc.formatDate(OfficeReader.getTextContent(date), palette.getI18n().getDefaultLanguage(), null));
}
ldp.append("}");
}
/* Check to see if this node has a footnote or endnote as the only subnode */
private boolean onlyNote(Node node) {
if (!node.hasChildNodes()) { return false; }
NodeList nList = node.getChildNodes();
int nLen = nList.getLength();
for (int i = 0; i < nLen; i++) {
Node child = nList.item(i);
short nType = child.getNodeType();
switch (nType) {
case Node.TEXT_NODE: return false;
case Node.ELEMENT_NODE:
if (!OfficeReader.isNoteElement(child)) { return false; }
}
}
return true;
}
}

View file

@ -0,0 +1,750 @@
/************************************************************************
*
* LaTeXConfig.java
*
* Copyright: 2002-2015 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.6 (2015-07-23)
*
*/
package writer2latex.latex;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.Element;
import writer2latex.api.ComplexOption;
import writer2latex.base.BooleanOption;
import writer2latex.base.IntegerOption;
import writer2latex.base.Option;
import writer2latex.latex.util.HeadingMap;
import writer2latex.latex.i18n.ClassicI18n;
import writer2latex.latex.i18n.ReplacementTrie;
import writer2latex.latex.util.StyleMap;
import writer2latex.latex.util.StyleMapItem;
import writer2latex.util.Misc;
public class LaTeXConfig extends writer2latex.base.ConfigBase {
/////////////////////////////////////////////////////////////////////////
// I. Define items needed by ConfigBase
protected int getOptionCount() { return 73; }
protected String getDefaultConfigPath() { return "/writer2latex/latex/config/"; }
/////////////////////////////////////////////////////////////////////////
// II. Override getter and setter methods for simple options in order to:
// - Treat the custom preamble like a regular option, even though the xml representation is different
// - Be backwards compatible (renamed the option keep_image_size)
@Override public void setOption(String sName,String sValue) {
if (sName.equals("custom-preamble")) {
sCustomPreamble = sValue;
}
else {
// this option has been renamed:
if (sName.equals("keep_image_size")) { sName = "original_image_size"; }
super.setOption(sName, sValue);
}
}
@Override public String getOption(String sName) {
if (sName.equals("custom-preamble")) {
return sCustomPreamble;
}
else {
return super.getOption(sName);
}
}
/////////////////////////////////////////////////////////////////////////
// III. Declare all constants
// Backend
public static final int GENERIC = 0;
public static final int DVIPS = 1;
public static final int PDFTEX = 2;
public static final int UNSPECIFIED = 3;
public static final int XETEX = 4;
// Formatting (must be ordered)
public static final int IGNORE_ALL = 0;
public static final int IGNORE_MOST = 1;
public static final int CONVERT_BASIC = 2;
public static final int CONVERT_MOST = 3;
public static final int CONVERT_ALL = 4;
// Page formatting
public static final int CONVERT_HEADER_FOOTER = 5;
public static final int CONVERT_GEOMETRY = 6;
// Handling of other formatting
public static final int IGNORE = 0;
public static final int ACCEPT = 1;
public static final int WARNING = 2;
public static final int ERROR = 3;
// Notes
//public static final int IGNORE = 0;
public static final int COMMENT = 1;
public static final int PDFANNOTATION = 2;
public static final int MARGINPAR = 3;
public static final int CUSTOM = 4;
// Options
private static final int BACKEND = 0;
private static final int NO_PREAMBLE = 1;
private static final int NO_INDEX = 2;
private static final int DOCUMENTCLASS = 3;
private static final int GLOBAL_OPTIONS = 4;
private static final int INPUTENCODING = 5;
private static final int MULTILINGUAL = 6;
private static final int GREEK_MATH = 7;
private static final int USE_OOOMATH = 8;
private static final int USE_PIFONT = 9;
private static final int USE_IFSYM = 10;
private static final int USE_WASYSYM = 11;
private static final int USE_BBDING = 12;
private static final int USE_EUROSYM = 13;
private static final int USE_TIPA = 14;
private static final int USE_COLOR = 15;
private static final int USE_COLORTBL = 16;
private static final int USE_GEOMETRY = 17;
private static final int USE_FANCYHDR = 18;
private static final int USE_TITLESEC = 19;
private static final int USE_TITLETOC = 20;
private static final int USE_HYPERREF = 21;
private static final int USE_CAPTION = 22;
private static final int USE_LONGTABLE = 23;
private static final int USE_SUPERTABULAR = 24;
private static final int USE_TABULARY = 25;
private static final int USE_ENDNOTES = 26;
private static final int USE_ULEM = 27;
private static final int USE_LASTPAGE = 28;
private static final int USE_TITLEREF = 29;
private static final int USE_BIBTEX = 30;
private static final int BIBTEX_STYLE = 31;
private static final int EXTERNAL_BIBTEX_FILES = 32;
private static final int BIBTEX_ENCODING = 33;
private static final int ZOTERO_BIBTEX_FILES = 34;
private static final int JABREF_BIBTEX_FILES = 35;
private static final int INCLUDE_ORIGINAL_CITATIONS = 36;
private static final int USE_NATBIB = 37;
private static final int NATBIB_OPTIONS = 38;
private static final int FONT = 39;
private static final int FORMATTING = 40;
private static final int PAGE_FORMATTING = 41;
private static final int OTHER_STYLES = 42;
private static final int IMAGE_CONTENT = 43;
private static final int TABLE_CONTENT = 44;
private static final int TABLE_FIRST_HEAD_STYLE = 45;
private static final int TABLE_HEAD_STYLE = 46;
private static final int TABLE_FOOT_STYLE = 47;
private static final int TABLE_LAST_FOOT_STYLE = 48;
private static final int IGNORE_HARD_PAGE_BREAKS = 49;
private static final int IGNORE_HARD_LINE_BREAKS = 50;
private static final int IGNORE_EMPTY_PARAGRAPHS =51;
private static final int IGNORE_DOUBLE_SPACES = 52;
private static final int DISPLAY_HIDDEN_TEXT = 53;
private static final int ALIGN_FRAMES = 54;
private static final int FLOAT_FIGURES = 55;
private static final int FLOAT_TABLES = 56;
private static final int FLOAT_OPTIONS = 57;
private static final int FIGURE_SEQUENCE_NAME = 58;
private static final int TABLE_SEQUENCE_NAME = 59;
private static final int IMAGE_OPTIONS = 60;
private static final int REMOVE_GRAPHICS_EXTENSION = 61;
private static final int ORIGINAL_IMAGE_SIZE = 62;
private static final int SIMPLE_TABLE_LIMIT = 63;
private static final int NOTES = 64;
private static final int METADATA = 65;
private static final int TABSTOP = 66;
private static final int WRAP_LINES_AFTER = 67;
private static final int SPLIT_LINKED_SECTIONS = 68;
private static final int SPLIT_TOPLEVEL_SECTIONS = 69;
private static final int SAVE_IMAGES_IN_SUBDIR = 70;
private static final int OLD_MATH_COLORS = 71;
private static final int DEBUG = 72;
/////////////////////////////////////////////////////////////////////////
// IV. Our options data
private ComplexOption headingMap;
private ComplexOption parMap;
private ComplexOption parBlockMap;
private ComplexOption listMap;
private ComplexOption listItemMap;
private ComplexOption textMap;
private ComplexOption textAttrMap;
private ComplexOption stringReplace;
private ComplexOption mathSymbols;
private String sCustomPreamble = "";
/////////////////////////////////////////////////////////////////////////
// V. The rather long constructor setting all defaults
/** Construct a new <code>LaTeXConfig</code> with default values for all options
*/
public LaTeXConfig() {
super();
// create options with default values
options[NO_PREAMBLE] = new BooleanOption("no_preamble","false");
options[NO_INDEX] = new BooleanOption("no_index","false");
options[DOCUMENTCLASS] = new Option("documentclass","article");
options[GLOBAL_OPTIONS] = new Option("global_options","");
options[BACKEND] = new IntegerOption("backend","pdftex") {
public void setString(String sValue) {
super.setString(sValue);
if ("generic".equals(sValue)) nValue = GENERIC;
else if ("dvips".equals(sValue)) nValue = DVIPS;
else if ("pdftex".equals(sValue)) nValue = PDFTEX;
else if ("unspecified".equals(sValue)) nValue = UNSPECIFIED;
else if ("xetex".equals(sValue)) nValue = XETEX;
}
};
options[INPUTENCODING] = new IntegerOption("inputencoding",ClassicI18n.writeInputenc(ClassicI18n.ASCII)) {
public void setString(String sValue) {
super.setString(sValue);
nValue = ClassicI18n.readInputenc(sValue);
}
};
options[MULTILINGUAL] = new BooleanOption("multilingual","true");
options[GREEK_MATH] = new BooleanOption("greek_math","true");
options[USE_OOOMATH] = new BooleanOption("use_ooomath","false");
options[USE_PIFONT] = new BooleanOption("use_pifont","false");
options[USE_IFSYM] = new BooleanOption("use_ifsym","false");
options[USE_WASYSYM] = new BooleanOption("use_wasysym","false");
options[USE_BBDING] = new BooleanOption("use_bbding","false");
options[USE_EUROSYM] = new BooleanOption("use_eurosym","false");
options[USE_TIPA] = new BooleanOption("use_tipa","false");
options[USE_COLOR] = new BooleanOption("use_color","true");
options[USE_COLORTBL] = new BooleanOption("use_colortbl","false");
options[USE_GEOMETRY] = new BooleanOption("use_geometry","false");
options[USE_FANCYHDR] = new BooleanOption("use_fancyhdr","false");
options[USE_TITLESEC] = new BooleanOption("use_titlesec","false");
options[USE_TITLETOC] = new BooleanOption("use_titletoc","false");
options[USE_HYPERREF] = new BooleanOption("use_hyperref","true");
options[USE_CAPTION] = new BooleanOption("use_caption","false");
options[USE_LONGTABLE] = new BooleanOption("use_longtable","false");
options[USE_SUPERTABULAR] = new BooleanOption("use_supertabular","true");
options[USE_TABULARY] = new BooleanOption("use_tabulary","false");
options[USE_ENDNOTES] = new BooleanOption("use_endnotes","false");
options[USE_ULEM] = new BooleanOption("use_ulem","false");
options[USE_LASTPAGE] = new BooleanOption("use_lastpage","false");
options[USE_TITLEREF] = new BooleanOption("use_titleref","false");
options[USE_BIBTEX] = new BooleanOption("use_bibtex","false");
options[BIBTEX_STYLE] = new Option("bibtex_style","plain");
options[EXTERNAL_BIBTEX_FILES] = new Option("external_bibtex_files","");
options[BIBTEX_ENCODING] = new IntegerOption("bibtex_encoding","document") {
public void setString(String sValue) {
super.setString(sValue);
if ("document".equals(sValue)) { nValue = -1; }
else { nValue = ClassicI18n.readInputenc(sValue); }
}
};
options[ZOTERO_BIBTEX_FILES] = new Option("zotero_bibtex_files","");
options[JABREF_BIBTEX_FILES] = new Option("jabref_bibtex_files","");
options[INCLUDE_ORIGINAL_CITATIONS] = new BooleanOption("include_original_citations","false");
options[USE_NATBIB] = new BooleanOption("use_natbib","false");
options[NATBIB_OPTIONS] = new Option("natbib_options","");
options[FONT] = new Option("font","default");
options[FORMATTING] = new IntegerOption("formatting","convert_basic") {
public void setString(String sValue) {
super.setString(sValue);
if ("convert_all".equals(sValue)) nValue = CONVERT_ALL;
else if ("convert_most".equals(sValue)) nValue = CONVERT_MOST;
else if ("convert_basic".equals(sValue)) nValue = CONVERT_BASIC;
else if ("ignore_most".equals(sValue)) nValue = IGNORE_MOST;
else if ("ignore_all".equals(sValue)) nValue = IGNORE_ALL;
}
};
options[PAGE_FORMATTING] = new IntegerOption("page_formatting","convert_all") {
public void setString(String sValue) {
super.setString(sValue);
if ("convert_all".equals(sValue)) nValue = CONVERT_ALL;
else if ("convert_header_footer".equals(sValue)) nValue = CONVERT_HEADER_FOOTER;
else if ("convert_geometry".equals(sValue)) nValue = CONVERT_GEOMETRY;
else if ("ignore_all".equals(sValue)) nValue = IGNORE_ALL;
}
};
options[OTHER_STYLES] = new ContentHandlingOption("other_styles","accept");
options[IMAGE_CONTENT] = new ContentHandlingOption("image_content","accept");
options[TABLE_CONTENT] = new ContentHandlingOption("table_content","accept");
options[TABLE_FIRST_HEAD_STYLE] = new Option("table_first_head_style","");
options[TABLE_HEAD_STYLE] = new Option("table_head_style","");
options[TABLE_FOOT_STYLE] = new Option("table_foot_style","");
options[TABLE_LAST_FOOT_STYLE] = new Option("table_last_foot_style","");
options[IGNORE_HARD_PAGE_BREAKS] = new BooleanOption("ignore_hard_page_breaks","false");
options[IGNORE_HARD_LINE_BREAKS] = new BooleanOption("ignore_hard_line_breaks","false");
options[IGNORE_EMPTY_PARAGRAPHS] = new BooleanOption("ignore_empty_paragraphs","false");
options[IGNORE_DOUBLE_SPACES] = new BooleanOption("ignore_double_spaces","false");
options[DISPLAY_HIDDEN_TEXT] = new BooleanOption("display_hidden_text","false");
options[ALIGN_FRAMES] = new BooleanOption("align_frames","true");
options[FLOAT_FIGURES] = new BooleanOption("float_figures","false");
options[FLOAT_TABLES] = new BooleanOption("float_tables","false");
options[FLOAT_OPTIONS] = new Option("float_options","h");
options[FIGURE_SEQUENCE_NAME] = new BooleanOption("figure_sequence_name","");
options[TABLE_SEQUENCE_NAME] = new BooleanOption("table_sequence_name","");
options[IMAGE_OPTIONS] = new Option("image_options","");
options[REMOVE_GRAPHICS_EXTENSION] = new BooleanOption("remove_graphics_extension","false");
options[ORIGINAL_IMAGE_SIZE] = new BooleanOption("original_image_size","false");
options[SIMPLE_TABLE_LIMIT] = new IntegerOption("simple_table_limit","0") {
public void setString(String sValue) {
super.setString(sValue);
nValue = Misc.getPosInteger(sValue,0);
}
};
options[NOTES] = new IntegerOption("notes","comment") {
public void setString(String sValue) {
super.setString(sValue);
if ("ignore".equals(sValue)) nValue = IGNORE;
else if ("comment".equals(sValue)) nValue = COMMENT;
else if ("pdfannotation".equals(sValue)) nValue = PDFANNOTATION;
else if ("marginpar".equals(sValue)) nValue = MARGINPAR;
else nValue = CUSTOM;
}
};
options[METADATA] = new BooleanOption("metadata","true");
options[TABSTOP] = new Option("tabstop","");
options[WRAP_LINES_AFTER] = new IntegerOption("wrap_lines_after","120") {
public void setString(String sValue) {
super.setString(sValue);
nValue = Misc.getPosInteger(sValue,0);
}
};
options[SPLIT_LINKED_SECTIONS] = new BooleanOption("split_linked_sections","false");
options[SPLIT_TOPLEVEL_SECTIONS] = new BooleanOption("split_toplevel_sections","false");
options[SAVE_IMAGES_IN_SUBDIR] = new BooleanOption("save_images_in_subdir","false");
options[OLD_MATH_COLORS] = new BooleanOption("old_math_colors","false");
options[DEBUG] = new BooleanOption("debug","false");
// Complex options - heading map
headingMap = addComplexOption("heading-map");
Map<String,String> attr = new HashMap<String,String>();
attr.put("name", "section");
attr.put("level", "1");
headingMap.put("1", attr);
attr = new HashMap<String,String>();
attr.put("name", "subsection");
attr.put("level", "2");
headingMap.put("2", attr);
attr = new HashMap<String,String>();
attr.put("name", "subsubsection");
attr.put("level", "3");
headingMap.put("3", attr);
attr = new HashMap<String,String>();
attr.put("name", "paragraph");
attr.put("level", "4");
headingMap.put("4", attr);
attr = new HashMap<String,String>();
attr.put("name", "subparagraph");
attr.put("level", "5");
headingMap.put("5", attr);
// Complex options - style maps
parMap = addComplexOption("paragraph-map");
parBlockMap = addComplexOption("paragraph-block-map");
listMap = addComplexOption("list-map");
listItemMap = addComplexOption("listitem-map");
textMap = addComplexOption("text-map");
textAttrMap = addComplexOption("text-attribute-map");
// Complex options - string replace
stringReplace=addComplexOption("string-replace");
// Standard string replace:
// Fix french spacing; replace nonbreaking space
// right before em-dash, !, ?, : and ; (babel handles this)
attr = new HashMap<String,String>();
attr.put("fontenc", "any");
attr.put("latex-code", " \u2014");
stringReplace.put("\u00A0\u2014",attr);
attr = new HashMap<String,String>();
attr.put("fontenc", "any");
attr.put("latex-code", " !");
stringReplace.put("\u00A0!",attr);
attr = new HashMap<String,String>();
attr.put("fontenc", "any");
attr.put("latex-code", " ?");
stringReplace.put("\u00A0?",attr);
attr = new HashMap<String,String>();
attr.put("fontenc", "any");
attr.put("latex-code", " :");
stringReplace.put("\u00A0:",attr);
attr = new HashMap<String,String>();
attr.put("fontenc", "any");
attr.put("latex-code", " ;");
stringReplace.put("\u00A0;",attr);
// Right after opening guillemet and right before closing guillemet:
// Here we must *keep* the non-breaking space
// TODO: Use \og and \fg if the document contains french...
//stringReplace.put("\u00AB\u00A0","\u00AB ",I18n.readFontencs("any"));
//stringReplace.put("\u00A0\u00BB"," \u00BB",I18n.readFontencs("any"));
// Complex options - math user defined symbols
mathSymbols = addComplexOption("math-symbol-map");
}
////////////////////////////////////////////////////////////////////////////
// VI. Provide methods to fill in the gaps in the supers read and write methods
protected void readInner(Element elm) {
if (elm.getTagName().equals("heading-map")) {
// Unlike other complex options, a heading map is completely replaced
headingMap.clear();
Node child = elm.getFirstChild();
while (child!=null) {
if (child.getNodeType()==Node.ELEMENT_NODE) {
Element childElm = (Element) child;
if (childElm.getTagName().equals("heading-level-map")) {
if (childElm.hasAttribute("writer-level")) {
Map<String,String> attr = new HashMap<String,String>();
attr.put("name",childElm.getAttribute("name"));
attr.put("level",childElm.getAttribute("level"));
headingMap.put(childElm.getAttribute("writer-level"), attr);
}
}
}
child = child.getNextSibling();
}
}
else if (elm.getTagName().equals("style-map")) {
String sName = elm.getAttribute("name");
String sFamily = elm.getAttribute("family");
if (sFamily.length()==0) { // try old name
sFamily = elm.getAttribute("class");
}
Map<String,String> attr = new HashMap<String,String>();
attr.put("before", elm.getAttribute("before"));
attr.put("after", elm.getAttribute("after"));
if ("paragraph".equals(sFamily)) {
if (elm.hasAttribute("line-break")) { attr.put("line-break", elm.getAttribute("line-break")); }
if (elm.hasAttribute("break-after")) { attr.put("break-after", elm.getAttribute("break-after")); }
if (elm.hasAttribute("verbatim")) { attr.put("verbatim", elm.getAttribute("verbatim")); }
parMap.put(sName, attr);
}
if ("paragraph-block".equals(sFamily)) {
attr.put("next", elm.getAttribute("next"));
if (elm.hasAttribute("verbatim")) { attr.put("verbatim", elm.getAttribute("verbatim")); }
parBlockMap.put(sName, attr);
}
else if ("list".equals(sFamily)) {
listMap.put(sName, attr);
}
else if ("listitem".equals(sFamily)) {
listItemMap.put(sName, attr);
}
else if ("text".equals(sFamily)) {
if (elm.hasAttribute("verbatim")) { attr.put("verbatim", elm.getAttribute("verbatim")); }
textMap.put(sName, attr);
}
else if ("text-attribute".equals(sFamily)) {
textAttrMap.put(sName, attr);
}
}
else if (elm.getTagName().equals("string-replace")) {
String sInput = elm.getAttribute("input");
Map<String,String> attributes = new HashMap<String,String>();
attributes.put("latex-code", elm.getAttribute("latex-code"));
if (elm.hasAttribute("fontenc") && elm.getAttribute("fontenc").length()>0) {
// The fontenc attribute is optional
attributes.put("fontenc", elm.getAttribute("fontenc"));
}
else {
attributes.put("fontenc", "any");
}
stringReplace.put(sInput,attributes);
}
else if (elm.getTagName().equals("math-symbol-map")) {
String sName = elm.getAttribute("name");
Map<String,String> attr = new HashMap<String,String>();
attr.put("latex", elm.getAttribute("latex"));
mathSymbols.put(sName, attr);
}
else if (elm.getTagName().equals("custom-preamble")) {
StringBuilder buf = new StringBuilder();
Node child = elm.getFirstChild();
while (child!=null) {
if (child.getNodeType()==Node.TEXT_NODE) {
buf.append(child.getNodeValue());
}
child = child.getNextSibling();
}
sCustomPreamble = buf.toString();
}
}
protected void writeInner(Document dom) {
// Write heading map
int nMaxLevel = 0;
while (nMaxLevel<10 && headingMap.get(Integer.toString(nMaxLevel+1))!=null) { nMaxLevel++; }
Element hmNode = dom.createElement("heading-map");
// This attribute is not used anymore, but we keep it for backwards compatibility
hmNode.setAttribute("max-level",Integer.toString(nMaxLevel));
dom.getDocumentElement().appendChild(hmNode);
for (int i=1; i<=nMaxLevel; i++) {
Element hlmNode = dom.createElement("heading-level-map");
String sWriterLevel = Integer.toString(i);
hlmNode.setAttribute("writer-level",sWriterLevel);
Map<String,String> attr = headingMap.get(sWriterLevel);
hlmNode.setAttribute("name",attr.get("name"));
hlmNode.setAttribute("level",attr.get("level"));
hmNode.appendChild(hlmNode);
}
// Write style maps
writeStyleMap(dom,parMap,"paragraph");
writeStyleMap(dom,parBlockMap,"paragraph-block");
writeStyleMap(dom,listMap,"list");
writeStyleMap(dom,listItemMap,"listitem");
writeStyleMap(dom,textMap,"text");
writeStyleMap(dom,textAttrMap,"text-attribute");
// Write string replace
Set<String> inputStrings = stringReplace.keySet();
for (String sInput : inputStrings) {
Map<String,String> attributes = stringReplace.get(sInput);
Element srNode = dom.createElement("string-replace");
srNode.setAttribute("input",sInput);
srNode.setAttribute("latex-code",attributes.get("latex-code"));
srNode.setAttribute("fontenc",attributes.get("fontenc"));
dom.getDocumentElement().appendChild(srNode);
}
// Write math symbol map
for (String sName : mathSymbols.keySet()) {
String sLatex = mathSymbols.get(sName).get("latex");
Element msNode = dom.createElement("math-symbol-map");
msNode.setAttribute("name",sName);
msNode.setAttribute("latex",sLatex);
dom.getDocumentElement().appendChild(msNode);
}
// Write custom preamble
Element cp = dom.createElement("custom-preamble");
cp.appendChild(dom.createTextNode( sCustomPreamble));
dom.getDocumentElement().appendChild(cp);
}
private void writeStyleMap(Document dom, ComplexOption co, String sFamily) {
for (String sName : co.keySet()) {
Map<String,String> attr = co.get(sName);
Element smNode = dom.createElement("style-map");
smNode.setAttribute("name",sName);
smNode.setAttribute("family",sFamily);
smNode.setAttribute("before",attr.containsKey("before") ? attr.get("before") : "");
smNode.setAttribute("after",attr.containsKey("after") ? attr.get("after") : "");
if (attr.containsKey("next")) {
smNode.setAttribute("next",attr.get("next"));
}
if (attr.containsKey("line-break")) {
smNode.setAttribute("line-break",attr.get("line-break"));
}
if (attr.containsKey("break-after")) {
smNode.setAttribute("break-after", attr.get("break-after"));
}
if (attr.containsKey("verbatim")) {
smNode.setAttribute("verbatim",attr.get("verbatim"));
}
dom.getDocumentElement().appendChild(smNode);
}
}
/////////////////////////////////////////////////////////////////////////
// VII. Convenience accessor methods
public HeadingMap getHeadingMap() {
int nMaxLevel = 0;
while (nMaxLevel<10 && headingMap.get(Integer.toString(nMaxLevel+1))!=null) { nMaxLevel++; }
HeadingMap map = new HeadingMap(nMaxLevel);
for (int i=1; i<=nMaxLevel; i++) {
String sWriterLevel = Integer.toString(i);
Map<String,String> attr = headingMap.get(sWriterLevel);
String sName = attr.get("name");
int nLevel = Misc.getPosInteger(attr.get("level"),0);
map.setLevelData(i, sName, nLevel);
}
return map;
}
// Get style maps
public StyleMap getParStyleMap() { return getStyleMap(parMap); }
public StyleMap getParBlockStyleMap() { return getStyleMap(parBlockMap); }
public StyleMap getListStyleMap() { return getStyleMap(listMap); }
public StyleMap getListItemStyleMap() { return getStyleMap(listItemMap); }
public StyleMap getTextAttributeStyleMap() { return getStyleMap(textAttrMap); }
public StyleMap getTextStyleMap() { return getStyleMap(textMap); }
private StyleMap getStyleMap(ComplexOption co) {
StyleMap map = new StyleMap();
for (String sName : co.keySet()) {
Map<String,String> attr = co.get(sName);
String sBefore = attr.containsKey("before") ? attr.get("before") : "";
String sAfter = attr.containsKey("after") ? attr.get("after") : "";
String sNext = attr.containsKey("next") ? attr.get("next") : "";
boolean bLineBreak = !"false".equals(attr.get("line-break"));
int nBreakAfter = StyleMapItem.PAR;
String sBreakAfter = attr.get("break-after");
if ("none".equals(sBreakAfter)) { nBreakAfter = StyleMapItem.NONE; }
else if ("line".equals(sBreakAfter)) { nBreakAfter = StyleMapItem.LINE; }
boolean bVerbatim = "true".equals(attr.get("verbatim"));
map.put(sName, sBefore, sAfter, sNext, bLineBreak, nBreakAfter, bVerbatim);
}
return map;
}
// Return current string replace as a trie
public ReplacementTrie getStringReplace() {
ReplacementTrie trie = new ReplacementTrie();
for (String sInput : stringReplace.keySet()) {
Map<String,String> attributes = stringReplace.get(sInput);
String sLaTeXCode = attributes.get("latex-code");
String sFontenc = attributes.get("fontenc");
trie.put(sInput,sLaTeXCode!=null ? sLaTeXCode : "",
ClassicI18n.readFontencs(sFontenc!=null ? sFontenc : "any"));
}
return trie;
}
// Get the math symbols as a simple Map
public Map<String, String> getMathSymbols() {
Map<String,String> map = new HashMap<String,String>();
for (String sName : mathSymbols.keySet()) {
String sLatex = mathSymbols.get(sName).get("latex");
map.put(sName, sLatex);
}
return map;
}
// Get the custom preamble
public String getCustomPreamble() { return sCustomPreamble; }
// Common options
public boolean debug() { return ((BooleanOption) options[DEBUG]).getValue(); }
// General options
public String getDocumentclass() { return options[DOCUMENTCLASS].getString(); }
public String getGlobalOptions() { return options[GLOBAL_OPTIONS].getString(); }
public int getBackend() { return ((IntegerOption) options[BACKEND]).getValue(); }
public int getInputencoding() { return ((IntegerOption) options[INPUTENCODING]).getValue(); }
public boolean multilingual() { return ((BooleanOption) options[MULTILINGUAL]).getValue(); }
public boolean greekMath() { return ((BooleanOption) options[GREEK_MATH]).getValue(); }
public boolean noPreamble() { return ((BooleanOption) options[NO_PREAMBLE]).getValue(); }
public boolean noIndex() { return ((BooleanOption) options[NO_INDEX]).getValue(); }
// Package options
public boolean useOoomath() { return ((BooleanOption) options[USE_OOOMATH]).getValue(); }
public boolean usePifont() { return ((BooleanOption) options[USE_PIFONT]).getValue(); }
public boolean useIfsym() { return ((BooleanOption) options[USE_IFSYM]).getValue(); }
public boolean useWasysym() { return ((BooleanOption) options[USE_WASYSYM]).getValue(); }
public boolean useBbding() { return ((BooleanOption) options[USE_BBDING]).getValue(); }
public boolean useEurosym() { return ((BooleanOption) options[USE_EUROSYM]).getValue(); }
public boolean useTipa() { return ((BooleanOption) options[USE_TIPA]).getValue(); }
public boolean useColor() { return ((BooleanOption) options[USE_COLOR]).getValue(); }
public boolean useColortbl() { return ((BooleanOption) options[USE_COLORTBL]).getValue(); }
public boolean useGeometry() { return ((BooleanOption) options[USE_GEOMETRY]).getValue(); }
public boolean useFancyhdr() { return ((BooleanOption) options[USE_FANCYHDR]).getValue(); }
public boolean useTitlesec() { return ((BooleanOption) options[USE_TITLESEC]).getValue(); }
public boolean useTitletoc() { return ((BooleanOption) options[USE_TITLETOC]).getValue(); }
public boolean useHyperref() { return ((BooleanOption) options[USE_HYPERREF]).getValue(); }
public boolean useCaption() { return ((BooleanOption) options[USE_CAPTION]).getValue(); }
public boolean useLongtable() { return ((BooleanOption) options[USE_LONGTABLE]).getValue(); }
public boolean useSupertabular() { return ((BooleanOption) options[USE_SUPERTABULAR]).getValue(); }
public boolean useTabulary() { return ((BooleanOption) options[USE_TABULARY]).getValue(); }
public boolean useEndnotes() { return ((BooleanOption) options[USE_ENDNOTES]).getValue(); }
public boolean useUlem() { return ((BooleanOption) options[USE_ULEM]).getValue(); }
public boolean useLastpage() { return ((BooleanOption) options[USE_LASTPAGE]).getValue(); }
public boolean useTitleref() { return ((BooleanOption) options[USE_TITLEREF]).getValue(); }
public boolean useBibtex() { return ((BooleanOption) options[USE_BIBTEX]).getValue(); }
public String bibtexStyle() { return options[BIBTEX_STYLE].getString(); }
public String externalBibtexFiles() { return options[EXTERNAL_BIBTEX_FILES].getString(); }
public int getBibtexEncoding() { return ((IntegerOption) options[BIBTEX_ENCODING]).getValue(); }
public String zoteroBibtexFiles() { return options[ZOTERO_BIBTEX_FILES].getString(); }
public String jabrefBibtexFiles() { return options[JABREF_BIBTEX_FILES].getString(); }
public boolean includeOriginalCitations() { return ((BooleanOption) options[INCLUDE_ORIGINAL_CITATIONS]).getValue(); }
public boolean useNatbib() { return ((BooleanOption) options[USE_NATBIB]).getValue(); }
public String getNatbibOptions() { return options[NATBIB_OPTIONS].getString(); }
// Formatting options
public String getFont() { return options[FONT].getString(); }
public int formatting() { return ((IntegerOption) options[FORMATTING]).getValue(); }
public int pageFormatting() { return ((IntegerOption) options[PAGE_FORMATTING]).getValue(); }
public int otherStyles() { return ((IntegerOption) options[OTHER_STYLES]).getValue(); }
public int imageContent() { return ((IntegerOption) options[IMAGE_CONTENT]).getValue(); }
public int tableContent() { return ((IntegerOption) options[TABLE_CONTENT]).getValue(); }
public String getTableFirstHeadStyle() { return options[TABLE_FIRST_HEAD_STYLE].getString(); }
public String getTableHeadStyle() { return options[TABLE_HEAD_STYLE].getString(); }
public String getTableFootStyle() { return options[TABLE_FOOT_STYLE].getString(); }
public String getTableLastFootStyle() { return options[TABLE_LAST_FOOT_STYLE].getString(); }
public boolean ignoreHardPageBreaks() { return ((BooleanOption) options[IGNORE_HARD_PAGE_BREAKS]).getValue(); }
public boolean ignoreHardLineBreaks() { return ((BooleanOption) options[IGNORE_HARD_LINE_BREAKS]).getValue(); }
public boolean ignoreEmptyParagraphs() { return ((BooleanOption) options[IGNORE_EMPTY_PARAGRAPHS]).getValue(); }
public boolean ignoreDoubleSpaces() { return ((BooleanOption) options[IGNORE_DOUBLE_SPACES]).getValue(); }
public boolean displayHiddenText() { return ((BooleanOption) options[DISPLAY_HIDDEN_TEXT]).getValue(); }
// Graphics options
public boolean alignFrames() { return ((BooleanOption) options[ALIGN_FRAMES]).getValue(); }
public boolean floatFigures() { return ((BooleanOption) options[FLOAT_FIGURES]).getValue(); }
public boolean floatTables() { return ((BooleanOption) options[FLOAT_TABLES]).getValue(); }
public String getFloatOptions() { return options[FLOAT_OPTIONS].getString(); }
public String getFigureSequenceName() { return options[FIGURE_SEQUENCE_NAME].getString(); }
public String getTableSequenceName() { return options[TABLE_SEQUENCE_NAME].getString(); }
public String getImageOptions() { return options[IMAGE_OPTIONS].getString(); }
public boolean removeGraphicsExtension() { return ((BooleanOption) options[REMOVE_GRAPHICS_EXTENSION]).getValue(); }
public boolean originalImageSize() { return ((BooleanOption) options[ORIGINAL_IMAGE_SIZE]).getValue(); }
// Tables
public int getSimpleTableLimit() { return ((IntegerOption) options[SIMPLE_TABLE_LIMIT]).getValue(); }
// Notes
public int notes() { return ((IntegerOption) options[NOTES]).getValue(); }
public String getNotesCommand() { return options[NOTES].getString(); }
// Metadata
public boolean metadata() { return ((BooleanOption) options[METADATA]).getValue(); }
// Tab stops
public String getTabstop() { return options[TABSTOP].getString(); }
// Files
public int getWrapLinesAfter() { return ((IntegerOption) options[WRAP_LINES_AFTER]).getValue(); }
public boolean splitLinkedSections() { return ((BooleanOption) options[SPLIT_LINKED_SECTIONS]).getValue(); }
public boolean splitToplevelSections() { return ((BooleanOption) options[SPLIT_TOPLEVEL_SECTIONS]).getValue(); }
public boolean saveImagesInSubdir() { return ((BooleanOption) options[SAVE_IMAGES_IN_SUBDIR]).getValue(); }
// Compatibility options
public boolean oldMathColors() { return ((BooleanOption) options[OLD_MATH_COLORS]).getValue(); }
}

View file

@ -0,0 +1,154 @@
/************************************************************************
*
* LaTeXDocument.java
*
* Copyright: 2002-2015 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.6 (2015-05-05)
*
*/
package writer2latex.latex;
import writer2latex.api.MIMETypes;
import writer2latex.api.OutputFile;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
/**
* <p>Class representing a LaTeX document.</p>
*
*/
public class LaTeXDocument implements OutputFile {
private static final String FILE_EXTENSION = ".tex";
private String sName;
private boolean bIsMaster;
private String sEncoding = "ASCII";
private int nWrap;
private LaTeXDocumentPortion contents;
/**
* <p>Constructs a new LaTeX Document.</p>
*
* <p>This new document is empty. Document data must added to the preamble and
* the body using appropriate methods.</p>
*
* @param sName The name of the <code>LaTeXDocument</code>.
* @param nWrap Lines should be wrapped after this position
* @param bIsMaster true if this is a master document
*/
public LaTeXDocument(String sName,int nWrap,boolean bIsMaster) {
this.nWrap = nWrap;
this.sName = trimDocumentName(sName);
this.bIsMaster = bIsMaster;
contents = new LaTeXDocumentPortion(true);
}
/**
* <p>Returns the <code>Document</code> name with no file extension.</p>
*
* @return The <code>Document</code> name with no file extension.
*/
public String getName() {
return sName;
}
/**
* <p>Returns the <code>Document</code> name with file extension.</p>
*
* @return The <code>Document</code> name with file extension.
*/
public String getFileName() {
return new String(sName + FILE_EXTENSION);
}
public String getMIMEType() {
return MIMETypes.LATEX;
}
public boolean isMasterDocument() {
return bIsMaster;
}
public boolean containsMath() {
// We don't use this information currently
return true;
}
/**
* <p>Writes out the <code>Document</code> content to the specified
* <code>OutputStream</code>.</p>
*
* <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</code> to write out the
* <code>Document</code> content.
*
* @throws IOException If any I/O error occurs.
*/
public void write(OutputStream os) throws IOException {
OutputStreamWriter osw = new OutputStreamWriter(os,sEncoding);
contents.write(osw,nWrap,"\n");
osw.flush();
osw.close();
}
/**
* <p> Set the output encoding to use when writing the document.</p>
*/
public void setEncoding(String sEncoding) { this.sEncoding = sEncoding; }
/**
* <p>Returns the <code>LaTeXDocumentPortion</code>, that contains the
* contents of the document.</p>
*
* @return The content <code>LaTeXDocumentPortion</code>.
*/
public LaTeXDocumentPortion getContents(){
return contents;
}
/*
* Utility method to make sure the document name is stripped of any file
* extensions before use.
*/
private String trimDocumentName(String name) {
String temp = name.toLowerCase();
if (temp.endsWith(FILE_EXTENSION)) {
// strip the extension
int nlen = name.length();
int endIndex = nlen - FILE_EXTENSION.length();
name = name.substring(0,endIndex);
}
return name;
}
}

View file

@ -0,0 +1,242 @@
/************************************************************************
*
* LaTeXDocumentPortion.java
*
* Copyright: 2002-2014 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.4 (2014-09-19)
*
*/
package writer2latex.latex;
import java.io.OutputStreamWriter;
import java.io.IOException;
import java.util.Vector;
import writer2latex.util.Misc;
/** This class represents a portion of a LaTeX document. A portion is any
number of lines, and may include subportions. */
public class LaTeXDocumentPortion {
private Vector<Object> nodes; // The collection of all nodes in this portion
private StringBuilder curText; // The currently active node (always the last node)
private boolean bEmpty; // Is the active node empty?
private boolean bWrap; // Do we allow line wrap in this portion?
/** Construct a new empty <code>LaTeXDocumentPortion</code>
*
* @param bWrap set to true if lines may be wrapped on writing
*/
public LaTeXDocumentPortion(boolean bWrap){
this.bWrap = bWrap;
nodes = new Vector<Object>();
curText = new StringBuilder();
bEmpty = true;
}
/** Add another portion to the end of this portion
*
* @param ldp The <code>LaTeXDocuemtPortion</code> to add
* @return a reference to this <code>LaTeXDocumentPortion</code> (not the appended one)
*/
public LaTeXDocumentPortion append(LaTeXDocumentPortion ldp) {
if (!bEmpty) {
// add the current node to the node list and create new current node
nodes.add(curText);
curText = new StringBuilder();
bEmpty = true;
}
nodes.add(ldp);
return this;
}
/** Add a string to the end of this portion
*
* @param s the string to add
* @return a reference to this <code>LaTeXDocumentPortion</code>
*/
public LaTeXDocumentPortion append(String s){
curText.append(s);
bEmpty = false; // even if this is the empty string!
return this;
}
/** Add an integer to the end of this portion
*
* @param n the integer to add
* @return a reference to this <code>LaTeXDocumentPortion</code>
*/
public LaTeXDocumentPortion append(int n){
curText.append(n);
bEmpty = false;
return this;
}
/** Add a newline to the end of this portion
*
* @return a reference to this <code>LaTeXDocumentPortion</code>
*/
public LaTeXDocumentPortion nl(){
curText.append("\n");
bEmpty = false;
return this;
}
/** write a segment of text (eg. a word) to the output */
private void writeSegment(String s, int nStart, int nEnd, OutputStreamWriter osw) throws IOException {
for (int i=nStart; i<nEnd; i++) { osw.write(s.charAt(i)); }
}
/** write the contents of a StringBuilder to the output */
private void writeBuffer(StringBuilder text, OutputStreamWriter osw, int nLineLen, String sNewline) throws IOException {
String s = text.toString();
int nLen = s.length();
int[] nBreakPoints = new int[100];
int nLastBPIndex = 99;
int nStart = 0;
while (nStart<nLen) {
// identify line and breakpoints
int nBPIndex = 0;
boolean bEscape = false;
boolean bComment = false;
int nNewline = nStart;
char c;
while (nNewline<nLen) {
if (nBPIndex==nLastBPIndex) {
nBreakPoints = Misc.doubleIntArray(nBreakPoints);
nLastBPIndex = nBreakPoints.length-1;
}
c = s.charAt(nNewline);
if (c=='\n') {
nBreakPoints[nBPIndex++] = nNewline;
break;
}
if (bEscape) { bEscape = false; }
else if (c=='\\') { bEscape = true; }
else if (c=='%') { bComment = true; }
else if (!bComment && c==' ') { nBreakPoints[nBPIndex++] = nNewline; }
nNewline++;
}
if (nBPIndex==nLastBPIndex) {
nBreakPoints = Misc.doubleIntArray(nBreakPoints);
nLastBPIndex = nBreakPoints.length-1;
}
if (nNewline==nLen) { nBreakPoints[nBPIndex++] = nNewline; }
// write out line
int nCurLineLen = nBreakPoints[0]-nStart;
writeSegment(s,nStart,nBreakPoints[0],osw);
for (int i=0; i<nBPIndex-1; i++) {
int nSegmentLen = nBreakPoints[i+1]-nBreakPoints[i];
if (nSegmentLen+nCurLineLen>nLineLen) {
// break line before this segment
osw.write(sNewline);
nCurLineLen = nSegmentLen;
}
else {
// segment fits in current line
osw.write(" ");
nCurLineLen += nSegmentLen;
}
writeSegment(s,nBreakPoints[i]+1,nBreakPoints[i+1],osw);
}
osw.write(sNewline);
nStart = nNewline+1;
}
}
/** write the contents of a StringBuilder to the output without wrap */
private void writeBuffer(StringBuilder text, OutputStreamWriter osw, String sNewline) throws IOException {
String s = text.toString();
int nLen = s.length();
int nStart = 0;
while (nStart<nLen) {
// identify line
int nNewline = nStart;
while (nNewline<nLen) {
if (s.charAt(nNewline)=='\n') { break; }
nNewline++;
}
// write out line
writeSegment(s,nStart,nNewline,osw);
osw.write(sNewline);
nStart = nNewline+1;
}
}
/** Write this portion to the output
*
* @param osw an <code>OutputStreamWriter</code> to write to
* @param nLineLen the line length after which automatic line breaks should occur if allowed (nLineLen=0 means no wrap)
* @param sNewline the newline character(s) to use
* @throws IOException if an exception occurs writing to to osw
*/
public void write(OutputStreamWriter osw, int nLineLen, String sNewline) throws IOException {
int n = nodes.size();
for (int i=0; i<n; i++) {
if (nodes.get(i) instanceof LaTeXDocumentPortion) {
((LaTeXDocumentPortion) nodes.get(i)).write(osw,nLineLen,sNewline);
}
else if (bWrap && nLineLen>0) {
writeBuffer((StringBuilder) nodes.get(i),osw,nLineLen,sNewline);
}
else {
writeBuffer((StringBuilder) nodes.get(i),osw,sNewline);
}
}
if (!bEmpty) { // write current node as well
if (bWrap && nLineLen>0) {
writeBuffer(curText,osw,nLineLen,sNewline);
}
else {
writeBuffer(curText,osw,sNewline);
}
}
}
/** Return the content of this LaTeXDocumentPortion as a string
*
* @return a string representation of the <code>LaTeXDocumentPortion</code>
*/
public String toString() {
StringBuilder buf = new StringBuilder();
int n = nodes.size();
for (int i=0; i<n; i++) {
if (nodes.get(i) instanceof LaTeXDocumentPortion) {
buf.append(((LaTeXDocumentPortion) nodes.get(i)).toString());
}
else {
buf.append((StringBuilder) nodes.get(i));
}
}
if (!bEmpty) { // write current node as well
buf.append(curText.toString());
}
return buf.toString();
}
}

View file

@ -0,0 +1,588 @@
/************************************************************************
*
* ListConverter.java
*
* Copyright: 2002-2015 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.6 (2015-04-14)
*
*/
package writer2latex.latex;
import java.util.Hashtable;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import writer2latex.latex.util.BeforeAfter;
import writer2latex.latex.util.Context;
import writer2latex.office.ListStyle;
import writer2latex.office.OfficeReader;
import writer2latex.office.XMLString;
import writer2latex.util.Calc;
import writer2latex.util.Misc;
public class ListConverter extends StyleConverter {
boolean bNeedSaveEnumCounter = false;
private Hashtable<String, String[]> listStyleLevelNames = new Hashtable<String, String[]>();
/** Construct a new <code>ListConverter</code>
*/
public ListConverter(OfficeReader ofr, LaTeXConfig config, ConverterPalette palette) {
super(ofr,config,palette);
}
@Override public void appendDeclarations(LaTeXDocumentPortion pack, LaTeXDocumentPortion decl) {
if (config.formatting()>=LaTeXConfig.CONVERT_MOST || !styleNames.isEmpty()) {
decl.append("% List styles").nl();
// May need an extra counter to handle continued numbering in lists
if (bNeedSaveEnumCounter) {
decl.append("\\newcounter{saveenum}").nl();
}
// If we export formatting, we need some hooks from lists to paragraphs:
if (config.formatting()>=LaTeXConfig.CONVERT_MOST) {
decl.append("\\newcommand\\writerlistleftskip{}").nl()
.append("\\newcommand\\writerlistparindent{}").nl()
.append("\\newcommand\\writerlistlabel{}").nl()
.append("\\newcommand\\writerlistremovelabel{")
.append("\\aftergroup\\let\\aftergroup\\writerlistparindent\\aftergroup\\relax")
.append("\\aftergroup\\let\\aftergroup\\writerlistlabel\\aftergroup\\relax}").nl();
}
super.appendDeclarations(pack,decl);
}
}
/** <p> Process a list (text:ordered-lst or text:unordered-list tag)</p>
* @param node The element containing the list
* @param ldp the <code>LaTeXDocumentPortion</code> to which
* LaTeX code should be added
* @param oc the current context
*/
public void handleList(Element node, LaTeXDocumentPortion ldp, Context oc) {
// Set up new context
Context ic = (Context) oc.clone();
ic.incListLevel();
if ("true".equals(node.getAttribute(XMLString.TEXT_CONTINUE_NUMBERING))) { ic.setInContinuedList(true); }
// Get the style name, if we don't know it already
if (ic.getListStyleName()==null) {
ic.setListStyleName(node.getAttribute(XMLString.TEXT_STYLE_NAME));
}
// Use the style to determine the type of list
ListStyle style = ofr.getListStyle(ic.getListStyleName());
boolean bOrdered = style!=null && style.isNumber(ic.getListLevel());
// If the list contains headings, ignore it!
if (ic.isIgnoreLists() || listContainsHeadings(node)) {
ic.setIgnoreLists(true);
traverseList(node,ldp,ic);
return;
}
// Apply the style
BeforeAfter ba = new BeforeAfter();
applyListStyle(bOrdered,ba,ic);
// Export the list
if (ba.getBefore().length()>0) { ldp.append(ba.getBefore()).nl(); }
traverseList(node,ldp,ic);
if (ba.getAfter().length()>0) { ldp.append(ba.getAfter()).nl(); }
}
/*
* Process the contents of a list
*/
private void traverseList (Element node, LaTeXDocumentPortion ldp, Context oc) {
if (node.hasChildNodes()) {
NodeList list = node.getChildNodes();
int nLen = list.getLength();
for (int i = 0; i < nLen; i++) {
Node child = list.item(i);
if (child.getNodeType() == Node.ELEMENT_NODE) {
String nodeName = child.getNodeName();
palette.getInfo().addDebugInfo((Element)child,ldp);
if (nodeName.equals(XMLString.TEXT_LIST_ITEM)) {
handleListItem((Element)child,ldp,oc);
}
if (nodeName.equals(XMLString.TEXT_LIST_HEADER)) {
handleListItem((Element)child,ldp,oc);
}
}
}
}
}
private void handleListItem(Element node, LaTeXDocumentPortion ldp, Context oc) {
// Are we ignoring this list?
if (oc.isIgnoreLists()) {
palette.getBlockCv().traverseBlockText(node,ldp,oc);
return;
}
// Apply the style
BeforeAfter ba = new BeforeAfter();
applyListItemStyle(
oc.getListStyleName(), oc.getListLevel(),
node.getNodeName().equals(XMLString.TEXT_LIST_HEADER),
"true".equals(node.getAttribute(XMLString.TEXT_RESTART_NUMBERING)),
Misc.getPosInteger(node.getAttribute(XMLString.TEXT_START_VALUE),1)-1,
ba,oc);
// export the list item (note the special treatment of lists in tables)
if (ba.getBefore().length()>0) {
ldp.append(ba.getBefore());
if (config.formatting()>=LaTeXConfig.CONVERT_MOST && !oc.isInTable()) { ldp.nl(); }
}
palette.getBlockCv().traverseBlockText(node,ldp,oc);
if (ba.getAfter().length()>0 || oc.isInTable()) { ldp.append(ba.getAfter()).nl(); }
}
/*
* Helper: Check to see, if this list contains headings
* (in that case we will ignore the list!)
*/
private boolean listContainsHeadings (Node node) {
if (node.hasChildNodes()) {
NodeList nList = node.getChildNodes();
int len = nList.getLength();
for (int i = 0; i < len; i++) {
Node child = nList.item(i);
if (child.getNodeType() == Node.ELEMENT_NODE) {
String nodeName = child.getNodeName();
if (nodeName.equals(XMLString.TEXT_LIST_ITEM)) {
if (listItemContainsHeadings(child)) return true;
}
if (nodeName.equals(XMLString.TEXT_LIST_HEADER)) {
if (listItemContainsHeadings(child)) return true;
}
}
}
}
return false;
}
private boolean listItemContainsHeadings(Node node) {
if (node.hasChildNodes()) {
NodeList nList = node.getChildNodes();
int len = nList.getLength();
for (int i = 0; i < len; i++) {
Node child = nList.item(i);
if (child.getNodeType() == Node.ELEMENT_NODE) {
String nodeName = child.getNodeName();
if(nodeName.equals(XMLString.TEXT_H)) {
return true;
}
if (nodeName.equals(XMLString.TEXT_LIST)) {
if (listContainsHeadings(child)) return true;
}
if (nodeName.equals(XMLString.TEXT_ORDERED_LIST)) {
if (listContainsHeadings(child)) return true;
}
if (nodeName.equals(XMLString.TEXT_UNORDERED_LIST)) {
if (listContainsHeadings(child)) return true;
}
}
}
}
return false;
}
// Convert style information
/** <p>Apply a list style to an ordered or unordered list.</p> */
private void applyListStyle(boolean bOrdered, BeforeAfter ba, Context oc) {
// Step 1. We may have a style map, this always takes precedence
String sDisplayName = ofr.getListStyles().getDisplayName(oc.getListStyleName());
if (config.getListStyleMap().contains(sDisplayName)) {
ba.add(config.getListStyleMap().getBefore(sDisplayName),
config.getListStyleMap().getAfter(sDisplayName));
return;
}
// Step 2: The list style may not exist, or the user wants to ignore it.
// In this case we create default lists
ListStyle style = ofr.getListStyle(oc.getListStyleName());
if (style==null || config.formatting()<=LaTeXConfig.IGNORE_MOST) {
if (oc.getListLevel()<=4) {
if (bOrdered) {
ba.add("\\begin{enumerate}","\\end{enumerate}");
}
else {
ba.add("\\begin{itemize}","\\end{itemize}");
}
}
return;
}
// Step 3: Export as default lists, but redefine labels
// (for list in tables this is the maximum formatting we export)
if (config.formatting()==LaTeXConfig.CONVERT_BASIC ||
(config.formatting()>=LaTeXConfig.CONVERT_MOST && oc.isInTable())) {
if (oc.getListLevel()==1) {
if (!listStyleLevelNames.containsKey(oc.getListStyleName())) {
createListStyleLabels(oc.getListStyleName());
}
ba.add("\\liststyle"+styleNames.getExportName(getDisplayName(oc.getListStyleName()))+"\n","");
}
if (oc.getListLevel()<=4) {
String sCounterName = listStyleLevelNames.get(oc.getListStyleName())[oc.getListLevel()];
if (oc.isInContinuedList() && style.isNumber(oc.getListLevel())) {
bNeedSaveEnumCounter = true;
ba.add("\\setcounter{saveenum}{\\value{"+sCounterName+"}}\n","");
}
if (bOrdered) {
ba.add("\\begin{enumerate}","\\end{enumerate}");
}
else {
ba.add("\\begin{itemize}","\\end{itemize}");
}
if (oc.isInContinuedList() && style.isNumber(oc.getListLevel())) {
ba.add("\n\\setcounter{"+sCounterName+"}{\\value{saveenum}}","");
}
}
return;
}
// Step 4: Export with formatting, as "Writer style" custom lists
if (oc.getListLevel()<=4) { // TODO: Max level should not be fixed
if (!styleNames.containsName(getDisplayName(oc.getListStyleName()))) {
createListStyle(oc.getListStyleName());
}
String sTeXName="list"+styleNames.getExportName(getDisplayName(oc.getListStyleName()))
+"level"+Misc.int2roman(oc.getListLevel());
if (!oc.isInContinuedList() && style.isNumber(oc.getListLevel())) {
int nStartValue = Misc.getPosInteger(style.getLevelProperty(oc.getListLevel(),XMLString.TEXT_START_VALUE),1)-1;
// Note that we need a blank line after certain constructions to get proper indentation
ba.add("\n\\setcounter{"+sTeXName+"}{"+Integer.toString(nStartValue)+"}\n","");
}
ba.add("\\begin{"+sTeXName+"}","\\end{"+sTeXName+"}");
}
}
/** <p>Apply a list style to a list item.</p> */
private void applyListItemStyle(String sStyleName, int nLevel, boolean bHeader,
boolean bRestart, int nStartValue, BeforeAfter ba, Context oc) {
// Step 1. We may have a style map, this always takes precedence
String sDisplayName = ofr.getListStyles().getDisplayName(sStyleName);
if (config.getListItemStyleMap().contains(sDisplayName)) {
ba.add(config.getListItemStyleMap().getBefore(sDisplayName),
config.getListItemStyleMap().getAfter(sDisplayName));
return;
}
// Step 2: The list style may not exist, or the user wants to ignore it.
// In this case we create default lists
ListStyle style = ofr.getListStyle(sStyleName);
if (style==null || config.formatting()<=LaTeXConfig.IGNORE_MOST) {
if (nLevel<=4) {
if (bHeader) { ba.add("\\item[] ",""); }
else { ba.add("\\item ",""); }
}
return;
}
// Step 3: Export as default lists (with redefined labels)
// (for list in tables this is the maximum formatting we export)
if (config.formatting()==LaTeXConfig.CONVERT_BASIC ||
(config.formatting()>=LaTeXConfig.CONVERT_MOST && oc.isInTable())) {
if (nLevel<=4) {
if (bHeader) {
ba.add("\\item[] ","");
}
else if (bRestart && style.isNumber(nLevel)) {
ba.add("\n\\setcounter{enum"+Misc.int2roman(nLevel)
+"}{"+(nStartValue-1)+"}\n\\item ","");
}
else {
ba.add("\\item ","");
}
}
return;
}
// Step 4: Export with formatting, as "Writer style" custom lists
if (nLevel<=4 && !bHeader) { // TODO: Max level should not be fixed
String sTeXName="list"+styleNames.getExportName(getDisplayName(sStyleName))
+"level"+Misc.int2roman(nLevel);
if (bRestart && style.isNumber(nLevel)) {
ba.add("\\setcounter{"+sTeXName+"}{"+(nStartValue-1)+"}\n","");
}
ba.add("\\item ","");
}
}
/** <p>Create labels for default lists (enumerate/itemize) based on
* a List Style
*/
private void createListStyleLabels(String sStyleName) {
String sTeXName = styleNames.getExportName(getDisplayName(sStyleName));
declarations.append("\\newcommand\\liststyle")
.append(sTeXName).append("{%").nl();
ListStyle style = ofr.getListStyle(sStyleName);
int nEnum = 0;
int nItem = 0;
String sName[] = new String[5];
for (int i=1; i<=4; i++) {
if (style.isNumber(i)) { sName[i]="enum"+Misc.int2roman(++nEnum); }
else { sName[i]="item"+Misc.int2roman(++nItem); }
}
listStyleLevelNames.put(sStyleName, sName);
createLabels(style, sName, 4, false, true, false, declarations);
declarations.append("}").nl();
}
/** <p>Create "Writer style" lists based on a List Style.
<p>A list in writer is really a sequence of numbered paragraphs, so
this is also how we implement it in LaTeX.
The enivronment + redefined \item defines three hooks:
\writerlistleftskip, \writerlistparindent, \writerlistlabel
which are used by exported paragraph styles to apply numbering.
*/
private void createListStyle(String sStyleName) {
ListStyle style = ofr.getListStyle(sStyleName);
// Create labels
String sTeXName = styleNames.getExportName(getDisplayName(sStyleName));
String[] sLevelName = new String[5];
for (int i=1; i<=4; i++) {
sLevelName[i]="list"+sTeXName+"level"+Misc.int2roman(i);
}
createLabels(style,sLevelName,4,true,false,true,declarations);
// Create environments
for (int i=1; i<=4; i++) {
// The alignment of the label works the same for old and new format
String sTextAlign = style.getLevelStyleProperty(i,XMLString.FO_TEXT_ALIGN);
String sAlignmentChar = "l"; // start (or left) is default
if (sTextAlign!=null) {
if ("end".equals(sTextAlign)) { sAlignmentChar="r"; }
else if ("right".equals(sTextAlign)) { sAlignmentChar="r"; }
else if ("center".equals(sTextAlign)) { sAlignmentChar="c"; }
}
if (style.isNewType(i)) {
// The new type from ODT 1.2 is somewhat weird; we take it step by step
// Fist the list style defines a left margin (leftskip) and a first line indent (parindent)
// to *replace* the values from the paragraph style
String sMarginLeft = style.getLevelStyleProperty(i, XMLString.FO_MARGIN_LEFT);
if (sMarginLeft==null) { sMarginLeft = "0cm"; }
String sTextIndent = style.getLevelStyleProperty(i, XMLString.FO_TEXT_INDENT);
if (sTextIndent==null) { sTextIndent = "0cm"; }
// Generate the LaTeX code to replace these values
String sDefWriterlistleftskip = "\\def\\writerlistleftskip{\\setlength\\leftskip{"+sMarginLeft+"}}";
String sDefWriterlistparindent = "\\def\\writerlistparindent{\\setlength\\parindent{"+sTextIndent+"}}";
// Next we have three types of label format: listtab, space, nothing
String sFormat = style.getLevelStyleProperty(i, XMLString.TEXT_LABEL_FOLLOWED_BY);
// Generate LaTeX code to typeset the label, followed by a space character if required
String sTheLabel = "\\label"+sLevelName[i]+("space".equals(sFormat) ? "\\ " : "");
if ("listtab".equals(sFormat) || sAlignmentChar=="r") {
// In these cases we typeset the label aligned at a zero width box (rather than as an integrated part of the text)
sTheLabel = "\\makebox[0cm][" + sAlignmentChar + "]{"+sTheLabel+"}";
if ("listtab".equals(sFormat)) {
// In the tab case we must the calculate the hspace to put *after* the zero width box
// This defines the position of an additional tab stop, which really means the start position of the text *after* the label
String sTabPos = style.getLevelStyleProperty(i, XMLString.TEXT_LIST_TAB_STOP_POSITION);
if (sTabPos==null) { sTabPos = "0cm"; }
sTheLabel += "\\hspace{"+Calc.sub(sTabPos, Calc.add(sMarginLeft, sTextIndent))+"}";
}
}
// We are now ready to declare the list style
declarations.append("\\newenvironment{").append(sLevelName[i]).append("}{")
// Initialize hooks
.append(sDefWriterlistleftskip)
.append("\\def\\writerlistparindent{}")
.append("\\def\\writerlistlabel{}")
// Redefine \item
.append("\\def\\item{")
// The new parindent is the position of the label
.append(sDefWriterlistparindent)
.append("\\def\\writerlistlabel{");
if (style.isNumber(i)) {
declarations.append("\\stepcounter{").append(sLevelName[i]).append("}");
}
declarations.append(sTheLabel).append("\\writerlistremovelabel}}}{}").nl();
}
else {
String sSpaceBefore = getLength(style,i,XMLString.TEXT_SPACE_BEFORE);
String sLabelWidth = getLength(style,i,XMLString.TEXT_MIN_LABEL_WIDTH);
String sLabelDistance = getLength(style,i,XMLString.TEXT_MIN_LABEL_DISTANCE);
declarations
.append("\\newenvironment{")
.append(sLevelName[i]).append("}{")
.append("\\def\\writerlistleftskip{\\addtolength\\leftskip{")
.append(Calc.add(sSpaceBefore,sLabelWidth)).append("}}")
.append("\\def\\writerlistparindent{}")
.append("\\def\\writerlistlabel{}");
// Redefine \item
declarations
.append("\\def\\item{")
.append("\\def\\writerlistparindent{\\setlength\\parindent{")
.append("-").append(sLabelWidth).append("}}")
.append("\\def\\writerlistlabel{");
if (style.isNumber(i)) {
declarations.append("\\stepcounter{")
.append(sLevelName[i]).append("}");
}
declarations
.append("\\makebox[").append(sLabelWidth).append("][")
.append(sAlignmentChar).append("]{")
.append("\\label").append(sLevelName[i]).append("}")
.append("\\hspace{").append(sLabelDistance).append("}")
.append("\\writerlistremovelabel}}}{}").nl();
}
}
}
/** <p>Create LaTeX list labels from an OOo list style. Examples:</p>
* <p>Bullets:</p>
* <pre>\newcommand\labelliststylei{\textbullet}
* \newcommand\labelliststyleii{*}
* \newcommand\labelliststyleiii{\textstylebullet{>}}</pre>
* <p>Numbering:</p>
* <pre>\newcounter{liststylei}
* \newcounter{liststyleii}[liststylei]
* \newcounter{liststyleiii}[liststyleii]
* \renewcommand\theliststylei{\Roman{liststylei}}
* \renewcommand\theliststyleii{\Roman{liststylei}.\arabic{liststyleii}}
* \renewcommand\theliststyleiii{\alph{liststyleiii}}
* \newcommand\labelliststylei{\textstylelabel{\theliststylei .}}
* \newcommand\labelliststyleii{\textstylelabel{\theliststyleii .}}
* \newcommand\labelliststyleiii{\textstylelabel{\theliststyleiii )}}</pre>
*
* @param <code>style</code> the OOo list style to use
* @param <code>sName</code> an array of label basenames to use
* @param <code>nMaxLevel</code> the highest level in this numbering
* @param <code>bDeclareCounters</code> true if counters should be declared (they may
* exist already, eg. "section", "subsection"... or "enumi", "enumii"...
* @param <code>bRenewLabels</code> true if labels should be defined with \renewcommand
* @param <code>bUseTextStyle</code> true if labels should be formatted with the associated text style
* (rather than \newcommand).
* @param <code>ldp</code> the <code>LaTeXDocumentPortion</code> to add LaTeX code to.
*/
private void createLabels(ListStyle style, String[] sName, int nMaxLevel,
boolean bDeclareCounters, boolean bRenewLabels,
boolean bUseTextStyle, LaTeXDocumentPortion ldp) {
// Declare counters if required (eg. "\newcounter{countername1}[countername2]")
if (bDeclareCounters) {
int j = 0;
for (int i=1; i<=nMaxLevel; i++) {
if (style.isNumber(i)) {
ldp.append("\\newcounter{").append(sName[i]).append("}");
if (j>0) { ldp.append("[").append(sName[j]).append("]"); }
ldp.nl();
j = i;
}
}
}
// Create numbering for each level (eg. "\arabic{countername}")
String[] sNumFormat = new String[nMaxLevel+1];
for (int i=1; i<=nMaxLevel; i++) {
String s = numFormat(style.getLevelProperty(i,XMLString.STYLE_NUM_FORMAT));
if (s==null) { sNumFormat[i]=""; }
else { sNumFormat[i] = s + "{" + sName[i] + "}"; }
}
// Create numberings (ie. define "\thecountername"):
for (int i=1; i<=nMaxLevel; i++) {
if (style.isNumber(i)) {
ldp.append("\\renewcommand\\the").append(sName[i]).append("{");
int nLevels = Misc.getPosInteger(style.getLevelProperty(i,XMLString.TEXT_DISPLAY_LEVELS),1);
for (int j=i-nLevels+1; j<i; j++) {
if (style.isNumber(j)) {
ldp.append(sNumFormat[j]).append(".");
}
}
ldp.append(sNumFormat[i]);
ldp.append("}").nl();
}
}
// Create labels (ie. define "\labelcountername"):
for (int i=1; i<=nMaxLevel; i++) {
ldp.append(bRenewLabels ? "\\renewcommand" : "\\newcommand")
.append("\\label").append(sName[i]).append("{");
// Apply text style if required
BeforeAfter baText = new BeforeAfter();
if (bUseTextStyle) {
String sStyleName = style.getLevelProperty(i,XMLString.TEXT_STYLE_NAME);
palette.getCharSc().applyTextStyle(sStyleName,baText,new Context());
}
// Create label content
if (style.isNumber(i)) {
String sPrefix = style.getLevelProperty(i,XMLString.STYLE_NUM_PREFIX);
String sSuffix = style.getLevelProperty(i,XMLString.STYLE_NUM_SUFFIX);
// Apply style
ldp.append(baText.getBefore());
if (sPrefix!=null) { ldp.append(palette.getI18n().convert(sPrefix,false,"en")); }
ldp.append("\\the").append(sName[i]);
if (sSuffix!=null) { ldp.append(palette.getI18n().convert(sSuffix,false,"en")); }
ldp.append(baText.getAfter());
}
else if (style.isBullet(i)) {
String sBullet = style.getLevelProperty(i,XMLString.TEXT_BULLET_CHAR);
// Apply style
ldp.append(baText.getBefore());
if (sBullet!=null) {
String sFontName = palette.getCharSc().getFontName(style.getLevelProperty(i,XMLString.TEXT_STYLE_NAME));
palette.getI18n().pushSpecialTable(sFontName);
// Bullets are usually symbols, so this should be OK:
ldp.append(palette.getI18n().convert(sBullet,false,"en"));
palette.getI18n().popSpecialTable();
}
ldp.append(baText.getAfter());
}
else {
// TODO: Support images!
ldp.append("\\textbullet");
}
ldp.append("}").nl();
}
}
/* Helper: Get a length property that defaults to 0cm. */
private String getLength(ListStyle style,int nLevel,String sProperty) {
String s = style.getLevelStyleProperty(nLevel,sProperty);
if (s==null) { return "0cm"; }
else { return s; }
}
/* Helper: Get display name, or original name if it doesn't exist */
private String getDisplayName(String sName) {
String sDisplayName = ofr.getListStyles().getDisplayName(sName);
return sDisplayName!=null ? sDisplayName : sName;
}
/* Helper: Convert OOo number format to LaTeX number format */
public static final String numFormat(String sFormat){
if ("1".equals(sFormat)) { return "\\arabic"; }
else if ("i".equals(sFormat)) { return "\\roman"; }
else if ("I".equals(sFormat)) { return "\\Roman"; }
else if ("a".equals(sFormat)) { return "\\alph"; }
else if ("A".equals(sFormat)) { return "\\Alph"; }
else { return null; }
}
}

View file

@ -0,0 +1,401 @@
/************************************************************************
*
* MathConverter.java
*
* Copyright: 2002-2014 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.4 (2014-09-03)
*
*/
package writer2latex.latex;
import java.util.Map;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import writer2latex.base.ConverterBase.TexMathsStyle;
import writer2latex.office.EmbeddedObject;
import writer2latex.office.EmbeddedXMLObject;
import writer2latex.office.MIMETypes;
import writer2latex.office.OfficeReader;
import writer2latex.office.StyleWithProperties;
import writer2latex.office.TableReader;
import writer2latex.office.XMLString;
import writer2latex.util.Misc;
/**
* This ConverterHelper converts mathematical content to LaTeX.
* It works slightly different than the other helpers: A number of elements may or may not
* have content that should be converted to math. Thus the methods offered first examines
* the content. If it turns out to be a mathematical formula, it is converted. Otherwise
* nothing is done, and the method returns false.
* Mathematical content may be MathML (with StarMath annotation), TexMaths or (the now obsolete) OOoLaTeX
*/
public final class MathConverter extends ConverterHelper {
private StarMathConverter smc;
private boolean bContainsFormulas = false;
private boolean bAddParAfterDisplay = false;
private boolean bNeedTexMathsPreamble = false;
private boolean bNeedOOoLaTeXPreamble = false;
private Element theEquation = null;
private Element theSequence = null;
public MathConverter(OfficeReader ofr,LaTeXConfig config, ConverterPalette palette) {
super(ofr,config,palette);
smc = new StarMathConverter(palette.getI18n(),config);
bAddParAfterDisplay = config.formatting()>=LaTeXConfig.CONVERT_MOST;
}
public void appendDeclarations(LaTeXDocumentPortion pack, LaTeXDocumentPortion decl) {
if (bContainsFormulas) {
smc.appendDeclarations(pack,decl);
}
if (bNeedTexMathsPreamble) {
// The preamble may be stored as a user defined property (newline is represented as paragraph sign)
Map<String,String> props = palette.getMetaData().getUserDefinedMetaData();
if (props.containsKey("TexMathsPreamble")) {
decl.append("% TexMaths preamble\n")
.append(props.get("TexMathsPreamble").replace('\u00a7', '\n'));
}
}
if (bNeedOOoLaTeXPreamble) {
// The preamble may be stored in the description
String sDescription = palette.getMetaData().getDescription();
int nStart = sDescription.indexOf("%%% OOoLatex Preamble %%%%%%%%%%%%%%");
int nEnd = sDescription.indexOf("%%% End OOoLatex Preamble %%%%%%%%%%%%");
if (nStart>-1 && nEnd>nStart) {
decl.append("% OOoLaTeX preamble").nl()
.append(sDescription.substring(nStart+37,nEnd));
}
}
}
// TODO: Replace with a method "handleEquation"
public String convert(Element formula) {
// TODO: Use settings to determine display mode/text mode
// formula must be a math:math node
// First try to find a StarMath annotation
Node semantics = Misc.getChildByTagName(formula,XMLString.SEMANTICS); // Since OOo 3.2
if (semantics==null) {
semantics = Misc.getChildByTagName(formula,XMLString.MATH_SEMANTICS);
}
if (semantics!=null) {
Node annotation = Misc.getChildByTagName(semantics,XMLString.ANNOTATION); // Since OOo 3.2
if (annotation==null) {
annotation = Misc.getChildByTagName(semantics,XMLString.MATH_ANNOTATION);
}
if (annotation!=null) {
String sStarMath = "";
if (annotation.hasChildNodes()) {
NodeList anl = annotation.getChildNodes();
int nLen = anl.getLength();
for (int i=0; i<nLen; i++) {
if (anl.item(i).getNodeType() == Node.TEXT_NODE) {
sStarMath+=anl.item(i).getNodeValue();
}
}
bContainsFormulas = true;
return smc.convert(sStarMath);
}
}
}
// No annotation was found. In this case we should convert the mathml,
// but currently we ignore the problem.
// TODO: Investigate if Vasil I. Yaroshevich's MathML->LaTeX
// XSL transformation could be used here. (Potential problem:
// OOo uses MathML 1.01, not MathML 2)
if (formula.hasChildNodes()) {
return "\\text{Warning: No StarMath annotation}";
}
else { // empty formula
return " ";
}
}
/** Try to convert a draw:frame or draw:g element as an (inline) TexMaths or OOoLaTeX equation
*
* @param node the element containing the equation (draw:frame or draw:g)
* @param ldp the LaTeXDocumentPortion to contain the converted equation
*
* @return true if this elements happen to be a TexMaths equation, otherwise false
*/
public boolean handleTexMathsEquation(Element node, LaTeXDocumentPortion ldp) {
String sLaTeX = null;
Element equation = palette.getTexMathsEquation(node);
if (equation!=null) {
sLaTeX = Misc.getPCDATA(equation);
if (sLaTeX!=null) { bNeedTexMathsPreamble = true; }
}
else { // Try OOoLaTeX
// The LaTeX code is embedded in a custom style attribute:
StyleWithProperties style = ofr.getFrameStyle(Misc.getAttribute(node, XMLString.DRAW_STYLE_NAME));
if (style!=null) {
sLaTeX = style.getProperty("OOoLatexArgs");
if (sLaTeX!=null) { bNeedOOoLaTeXPreamble = true; }
}
}
if (sLaTeX!=null) {
// Format is <point size>X<mode>X<TeX code>X<format>X<resolution>X<transparency>
// where X is a paragraph sign
switch (palette.getTexMathsStyle(sLaTeX)) {
case inline:
ldp.append("$").append(palette.getTexMathsEquation(sLaTeX)).append("$");
break;
case display:
ldp.append("$\\displaystyle ").append(palette.getTexMathsEquation(sLaTeX)).append("$");
break;
case latex:
ldp.append(palette.getTexMathsEquation(sLaTeX));
}
return true;
}
return false;
}
/** Try to convert a table as a display equation:
* A 1 row by 2 columns table in which each cell contains exactly one paragraph,
* the left cell contains exactly one formula and the right cell contains exactly
* one sequence number is treated as a (numbered) display equation.
* This happens to coincide with the AutoText provided with OOo Writer :-)
* @param table the table reader
* @param ldp the LaTeXDocumentPortion to contain the converted equation
* @return true if the conversion was successful, false if the table
* did not represent a display equation
*/
public boolean handleDisplayEquation(TableReader table, LaTeXDocumentPortion ldp) {
if (table.getRowCount()==1 && table.getColCount()==2 &&
OfficeReader.isSingleParagraph(table.getCell(0, 0)) && OfficeReader.isSingleParagraph(table.getCell(0, 1)) ) {
// Table of the desired form
if (parseDisplayEquation(Misc.getFirstChildElement(table.getCell(0, 0))) && theEquation!=null && theSequence==null) {
// Found equation in first cell
Element myEquation = theEquation;
if (parseDisplayEquation(Misc.getFirstChildElement(table.getCell(0, 1))) && theEquation==null && theSequence!=null) {
// Found sequence in second cell
handleDisplayEquation(myEquation, theSequence, ldp);
return true;
}
}
}
return false;
}
/**Try to convert a paragraph as a display equation:
* A paragraph which contains exactly one formula + at most one sequence
* number is treated as a display equation. Other content must be brackets
* or whitespace (possibly with formatting).
* @param node the paragraph
* @param ldp the LaTeXDocumentPortion to contain the converted equation
* @return true if the conversion was successful, false if the paragraph
* did not contain a display equation
*/
public boolean handleDisplayEquation(Element node, LaTeXDocumentPortion ldp) {
if (parseDisplayEquation(node) && theEquation!=null) {
handleDisplayEquation(theEquation, theSequence, ldp);
return true;
}
else {
return false;
}
}
private void handleDisplayEquation(Element equation, Element sequence, LaTeXDocumentPortion ldp) {
boolean bTexMaths = equation.getTagName().equals(XMLString.SVG_DESC);
TexMathsStyle style = TexMathsStyle.inline;
String sLaTeX;
if (bTexMaths) {
// TeXMaths equation
sLaTeX = palette.getTexMathsEquation(Misc.getPCDATA(equation));
style = palette.getTexMathsStyle(Misc.getPCDATA(equation));
if (sLaTeX!=null) { bNeedTexMathsPreamble = true; }
}
else {
// MathML equation
sLaTeX = convert(equation);
}
if (sLaTeX!=null && !" ".equals(sLaTeX)) { // ignore empty formulas
if (!bTexMaths || style!=TexMathsStyle.latex) {
// Unfortunately we can't do numbered equations for TexMaths equations with latex style
if (sequence!=null) {
// Numbered equation
ldp.append("\\begin{equation}");
palette.getFieldCv().handleSequenceLabel(sequence,ldp);
if (bTexMaths && style==TexMathsStyle.inline) {
ldp.append("\\textstyle ");
}
ldp.nl()
.append(sLaTeX).nl()
.append("\\end{equation}").nl();
}
else {
// Unnumbered equation
ldp.append("\\begin{equation*}");
if (bTexMaths && style==TexMathsStyle.inline) {
ldp.append("\\textstyle ");
}
ldp.nl()
.append(sLaTeX).nl()
.append("\\end{equation*}").nl();
}
}
else {
ldp.append(sLaTeX).nl();
}
if (bAddParAfterDisplay) { ldp.nl(); }
}
}
/** Determine whether or not a paragraph contains a display equation.
* A paragraph is a display equation if it contains a single formula and no text content except whitespace
* and an optional sequence number which may be in brackets.
* As a side effect, this method keeps a reference to the equation and the sequence number
*
* @param node the paragraph
* @return true if this is a display equation
*/
private boolean parseDisplayEquation(Node node) {
theEquation = null;
theSequence = null;
return doParseDisplayEquation(node);
}
private boolean doParseDisplayEquation(Node node) {
Node child = node.getFirstChild();
while (child!=null) {
if (Misc.isElement(child)) {
Element elm = (Element) child;
String sName = elm.getTagName();
// First check for MathML or TexMaths equation
Element equation = getMathmlEquation(elm);
if (equation==null) {
equation = palette.getTexMathsEquation(elm);
}
if (equation!=null) {
if (theEquation==null) {
theEquation = equation;
}
else { // two or more equations -> not a display
return false;
}
}
else if (XMLString.TEXT_SEQUENCE.equals(sName)) {
if (theSequence==null) {
theSequence = elm;
}
else { // two sequence numbers -> not a display
return false;
}
}
else if (XMLString.TEXT_SPAN.equals(sName)) {
if (!doParseDisplayEquation(child)) {
return false;
}
}
else if (XMLString.TEXT_S.equals(sName)) {
// Spaces are allowed
}
else if (XMLString.TEXT_TAB.equals(sName)) {
// Tab stops are allowed
}
else if (XMLString.TEXT_TAB_STOP.equals(sName)) { // old
// Tab stops are allowed
}
else if (XMLString.TEXT_SOFT_PAGE_BREAK.equals(sName)) { // since ODF 1.1
// Soft page breaks are allowed
}
else {
// Other elements -> not a display
return false;
}
}
else if (Misc.isText(child)) {
String s = child.getNodeValue();
int nLen = s.length();
for (int i=0; i<nLen; i++) {
char c = s.charAt(i);
if (c!='(' && c!=')' && c!='[' && c!=']' && c!='{' && c!='}' && c!=' ' && c!='\u00A0') {
// Characters except brackets and whitespace -> not a display
return false;
}
}
}
child = child.getNextSibling();
}
return true;
}
/** Get a MathML formula from a draw:frame
*
* @param node the draw:frame
* @return the MathML element, or null if this is not a MathML formula
*/
private Element getMathmlEquation(Element node) {
if (node.getTagName().equals(XMLString.DRAW_FRAME)) {
node=Misc.getFirstChildElement(node);
}
String sHref = Misc.getAttribute(node,XMLString.XLINK_HREF);
if (sHref!=null) { // Embedded object in package or linked object
if (ofr.isInPackage(sHref)) { // Embedded object in package
if (sHref.startsWith("#")) { sHref=sHref.substring(1); }
if (sHref.startsWith("./")) { sHref=sHref.substring(2); }
EmbeddedObject object = palette.getEmbeddedObject(sHref);
if (object!=null) {
if (MIMETypes.MATH.equals(object.getType()) || MIMETypes.ODF.equals(object.getType())) { // Formula!
try {
Document formuladoc = ((EmbeddedXMLObject) object).getContentDOM();
Element formula = Misc.getChildByTagName(formuladoc,XMLString.MATH); // Since OOo 3.2
if (formula==null) {
formula = Misc.getChildByTagName(formuladoc,XMLString.MATH_MATH);
}
return formula;
}
catch (org.xml.sax.SAXException e) {
e.printStackTrace();
}
catch (java.io.IOException e) {
e.printStackTrace();
}
}
}
}
}
else { // flat XML, object is contained in node
Element formula = Misc.getChildByTagName(node,XMLString.MATH); // Since OOo 3.2
if (formula==null) {
formula = Misc.getChildByTagName(node,XMLString.MATH_MATH);
}
return formula;
}
return null;
}
}

View file

@ -0,0 +1,400 @@
/************************************************************************
*
* NoteConverter.java
*
* Copyright: 2002-2015 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.6 (2015-04-15)
*
*/
// TODO: Get the styles for footnotes and endnotes and use Context.resetFormattingFromStyle...
package writer2latex.latex;
import java.util.LinkedList;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import writer2latex.util.Misc;
import writer2latex.util.ExportNameCollection;
import writer2latex.office.OfficeReader;
import writer2latex.office.PropertySet;
import writer2latex.office.StyleWithProperties;
import writer2latex.office.XMLString;
import writer2latex.latex.util.BeforeAfter;
import writer2latex.latex.util.Context;
/**
* <p>This class handles conversion of footnotes and endnotes, including
* references. It takes advantage of the packages <code>endnotes.sty</code>
* and <code>perpage.sty</code> if allowed in the configuration.</p>
*/
public class NoteConverter extends ConverterHelper {
private ExportNameCollection footnotenames = new ExportNameCollection(true);
private ExportNameCollection endnotenames = new ExportNameCollection(true);
private boolean bContainsEndnotes = false;
private boolean bContainsFootnotes = false;
// Keep track of footnotes (inside minipage etc.), that should be typeset later
private LinkedList<Element> postponedFootnotes = new LinkedList<Element>();
public NoteConverter(OfficeReader ofr, LaTeXConfig config, ConverterPalette palette) {
super(ofr,config,palette);
}
/** <p>Append declarations needed by the <code>NoteConverter</code> to
* the preamble.
* @param pack the <code>LaTeXDocumentPortion</code> to which
* declarations of packages should be added (<code>\\usepackage</code>).
* @param decl the <code>LaTeXDocumentPortion</code> to which
* other declarations should be added.
*/
public void appendDeclarations(LaTeXDocumentPortion pack, LaTeXDocumentPortion decl) {
if (bContainsEndnotes) { pack.append("\\usepackage{endnotes}").nl(); }
if (bContainsFootnotes) convertFootnotesConfiguration(decl);
if (bContainsEndnotes) convertEndnotesConfiguration(decl);
}
/** <p>Process a footnote (text:footnote tag)
* @param node The element containing the footnote
* @param ldp the <code>LaTeXDocumentPortion</code> to which
* LaTeX code should be added
* @param oc the current context
*/
public void handleFootnote(Element node, LaTeXDocumentPortion ldp, Context oc) {
Context ic = (Context) oc.clone();
ic.setInFootnote(true);
String sId = node.getAttribute(XMLString.TEXT_ID);
Element fntbody = Misc.getChildByTagName(node,XMLString.TEXT_FOOTNOTE_BODY);
if (fntbody==null) { // try oasis
fntbody = Misc.getChildByTagName(node,XMLString.TEXT_NOTE_BODY);
}
if (fntbody != null) {
bContainsFootnotes = true;
if (ic.isNoFootnotes()) {
ldp.append("\\footnotemark{}");
postponedFootnotes.add(fntbody);
}
else {
ldp.append("\\footnote");
ldp.append("{");
if (sId != null && ofr.hasFootnoteRefTo(sId)) {
ldp.append("\\label{fnt:"+footnotenames.getExportName(sId)+"}");
}
traverseNoteBody(fntbody,ldp,ic);
ldp.append("}");
}
}
}
/** Do we have any pending footnotes, that may be inserted in this context?
*
* @param oc the context to verify against
* @return true if there are pending footnotes
*/
public boolean hasPendingFootnotes(Context oc) {
return !oc.isNoFootnotes() && postponedFootnotes.size()>0;
}
/** Flush the queue of postponed footnotes */
public void flushFootnotes(LaTeXDocumentPortion ldp, Context oc) {
// We may still be in a context with no footnotes
if (oc.isNoFootnotes()) { return; }
// Type out all postponed footnotes:
Context ic = (Context) oc.clone();
ic.setInFootnote(true);
int n = postponedFootnotes.size();
if (n==1) {
ldp.append("\\footnotetext{");
traverseNoteBody(postponedFootnotes.get(0),ldp,ic);
ldp.append("}").nl();
postponedFootnotes.clear();
}
else if (n>1) {
// Several footnotes; have to adjust the footnote counter
ldp.append("\\addtocounter{footnote}{-"+n+"}").nl();
for (int i=0; i<n; i++) {
ldp.append("\\stepcounter{footnote}\\footnotetext{");
traverseNoteBody(postponedFootnotes.get(i),ldp,ic);
ldp.append("}").nl();
}
postponedFootnotes.clear();
}
}
/** <p>Process an endnote (text:endnote tag)
* @param node The element containing the endnote
* @param ldp the <code>LaTeXDocumentPortion</code> to which
* LaTeX code should be added
* @param oc the current context
*/
public void handleEndnote(Element node, LaTeXDocumentPortion ldp, Context oc) {
Context ic = (Context) oc.clone();
ic.setInFootnote(true);
String sId = node.getAttribute(XMLString.TEXT_ID);
Element entbody = Misc.getChildByTagName(node,XMLString.TEXT_ENDNOTE_BODY);
if (entbody==null) { // try oasis
entbody = Misc.getChildByTagName(node,XMLString.TEXT_NOTE_BODY);
}
if (entbody != null) {
if (ic.isNoFootnotes() && !config.useEndnotes()) {
ldp.append("\\footnotemark()");
postponedFootnotes.add(entbody);
}
else {
if (config.useEndnotes()) {
ldp.append("\\endnote");
bContainsEndnotes = true;
}
else {
ldp.append("\\footnote");
bContainsFootnotes = true;
}
ldp.append("{");
if (sId != null && ofr.hasEndnoteRefTo(sId)) {
ldp.append("\\label{ent:"+endnotenames.getExportName(sId)+"}");
}
traverseNoteBody(entbody,ldp,ic);
ldp.append("}");
}
}
}
/** <p>Insert the endnotes into the documents.
* @param ldp the <code>LaTeXDocumentPortion</code> to which
* the endnotes should be added.
*/
public void insertEndnotes(LaTeXDocumentPortion ldp) {
if (bContainsEndnotes) {
ldp.append("\\clearpage").nl()
.append("\\theendnotes").nl();
}
}
/** <p>Process a note reference (text:note-ref tag, oasis)
* @param node The element containing the note reference
* @param ldp the <code>LaTeXDocumentPortion</code> to which
* LaTeX code should be added
* @param oc the current context
*/
public void handleNoteRef(Element node, LaTeXDocumentPortion ldp, Context oc) {
String sClass=node.getAttribute(XMLString.TEXT_NOTE_CLASS);
if (sClass.equals("footnote")) { handleFootnoteRef(node,ldp,oc); }
else if (sClass.equals("endnote")) { handleEndnoteRef(node,ldp,oc); }
}
/** <p>Process a footnote reference (text:footnote-ref tag)
* @param node The element containing the footnote reference
* @param ldp the <code>LaTeXDocumentPortion</code> to which
* LaTeX code should be added
* @param oc the current context
*/
public void handleFootnoteRef(Element node, LaTeXDocumentPortion ldp, Context oc) {
String sFormat = node.getAttribute(XMLString.TEXT_REFERENCE_FORMAT);
String sName = node.getAttribute(XMLString.TEXT_REF_NAME);
if (("page".equals(sFormat) || "".equals(sFormat)) && sName!=null) {
ldp.append("\\pageref{fnt:"+footnotenames.getExportName(sName)+"}");
}
else if ("text".equals(sFormat) && sName!=null) {
ldp.append("\\ref{fnt:"+footnotenames.getExportName(sName)+"}");
}
else { // use current value
palette.getInlineCv().traversePCDATA(node,ldp,oc);
}
}
/** <p>Process an endnote reference (text:endnote-ref tag)
* @param node The element containing the endnote reference
* @param ldp the <code>LaTeXDocumentPortion</code> to which
* LaTeX code should be added
* @param oc the current context
*/
public void handleEndnoteRef(Element node, LaTeXDocumentPortion ldp, Context oc) {
String sFormat = node.getAttribute(XMLString.TEXT_REFERENCE_FORMAT);
String sName = node.getAttribute(XMLString.TEXT_REF_NAME);
if (("page".equals(sFormat) || "".equals(sFormat)) && sName!=null) {
ldp.append("\\pageref{ent:"+endnotenames.getExportName(sName)+"}");
}
else if ("text".equals(sFormat) && sName!=null) {
ldp.append("\\ref{ent:"+endnotenames.getExportName(sName)+"}");
}
else { // use current value
palette.getInlineCv().traversePCDATA(node,ldp,oc);
}
}
/** <p>Add a footnote name. The method <code>handleFootnote</code> includes
* a <code>\label</code> only if the footnote name is already known to the
* <code>NoteConverter</code>. Hence this method is invoked by the prepass
* for each footnote reference. The end result is, that only necessary
* labels will be included.
* @param sName the name (id) of the footnote
*/
public void addFootnoteName(String sName) { footnotenames.addName(sName); }
/** <p>Add an endnote name. The method <code>handleEndnote</code> includes
* a <code>\label</code> only if the endnote name is already known to the
* <code>NoteConverter</code>. Hence this method is invoked by the prepass
* for each endnote reference. The end result is, that only necessary
* labels will be included.
* @param sName the name (id) of the endnote
*/
public void addEndnoteName(String sName) { endnotenames.addName(sName); }
/*
* Process the contents of a footnote or endnote
* TODO: Merge with BlockConverter.traverseBlockText?
*/
private void traverseNoteBody (Element node, LaTeXDocumentPortion ldp, Context oc) {
if (node.hasChildNodes()) {
NodeList nList = node.getChildNodes();
int len = nList.getLength();
for (int i = 0; i < len; i++) {
Node childNode = nList.item(i);
if (childNode.getNodeType() == Node.ELEMENT_NODE) {
Element child = (Element)childNode;
String nodeName = child.getTagName();
palette.getInfo().addDebugInfo(child,ldp);
// Headings inside footnotes are considere a mistake and exported as ordinary paragraphs
if (nodeName.equals(XMLString.TEXT_H) || nodeName.equals(XMLString.TEXT_P)) {
StyleWithProperties style = ofr.getParStyle(node.getAttribute(XMLString.TEXT_STYLE_NAME));
oc.resetFormattingFromStyle(style);
palette.getInlineCv().traverseInlineText(child,ldp,oc);
if (i<len-1) {
if (nList.item(i+1).getNodeName().startsWith(XMLString.TEXT_)) {
ldp.append("\\par ");
}
else {
ldp.nl();
}
}
}
else if (nodeName.equals(XMLString.TEXT_LIST)) { // oasis
palette.getListCv().handleList(child,ldp,oc);
}
if (nodeName.equals(XMLString.TEXT_ORDERED_LIST)) {
palette.getListCv().handleList(child,ldp,oc);
}
if (nodeName.equals(XMLString.TEXT_UNORDERED_LIST)) {
palette.getListCv().handleList(child,ldp,oc);
}
}
}
}
}
// Convert footnotes configuration.
private void convertFootnotesConfiguration(LaTeXDocumentPortion ldp) {
// Note: Continuation notices are not supported in LaTeX
// TODO: Support text:footnotes-postion="document" (footnotes as endnotes)
// TODO: Support text:start-numbering-at="page" (footnpag.sty/footmisc.sty)
convertFootEndnotesConfiguration(ofr.getFootnotesConfiguration(),"foot",ldp);
}
// Convert endnotes configuration.
private void convertEndnotesConfiguration(LaTeXDocumentPortion ldp) {
// Note: Continuation notices are not supported in LaTeX
convertFootEndnotesConfiguration(ofr.getEndnotesConfiguration(),"end",ldp);
}
/* Convert {foot|end}notes configuration.
* Note: All {foot|end}notes are formatted with the default style for {foot|end}footnotes.
* (This doesn't conform with the file format specification, but in LaTeX
* all {foot|end}notes are usually formatted in a fixed style.)
*/
private void convertFootEndnotesConfiguration(PropertySet notes, String sType, LaTeXDocumentPortion ldp) {
if (config.formatting()<LaTeXConfig.CONVERT_BASIC) { return; }
String sTypeShort = sType.equals("foot") ? "fn" : "en";
if (notes==null) { return; }
ldp.append("% ").append(sType).append("notes configuration").nl()
.append("\\makeatletter").nl();
// The numbering style is controlled by \the{foot|end}note
String sFormat = notes.getProperty(XMLString.STYLE_NUM_FORMAT);
if (sFormat!=null) {
ldp.append("\\renewcommand\\the").append(sType).append("note{")
.append(ListConverter.numFormat(sFormat))
.append("{").append(sType).append("note}}").nl();
}
// Number {foot|end}notes by sections
if ("chapter".equals(notes.getProperty(XMLString.TEXT_START_NUMBERING_AT))) {
ldp.append("\\@addtoreset{").append(sType).append("note}{section}").nl();
}
// Set start value offset (default 0)
int nStartValue = Misc.getPosInteger(notes.getProperty(XMLString.TEXT_START_VALUE),0);
if (nStartValue!=0) {
ldp.append("\\setcounter{").append(sType).append("note}{"+nStartValue+"}").nl();
}
if (config.formatting()>=LaTeXConfig.CONVERT_MOST) {
// The formatting of the {foot|end}note citation is controlled by \@make{fn|en}mark
String sCitBodyStyle = notes.getProperty(XMLString.TEXT_CITATION_BODY_STYLE_NAME);
if (sCitBodyStyle!=null && ofr.getTextStyle(sCitBodyStyle)!=null) {
BeforeAfter baText = new BeforeAfter();
palette.getCharSc().applyTextStyle(sCitBodyStyle,baText,new Context());
ldp.append("\\renewcommand\\@make").append(sTypeShort).append("mark{\\mbox{")
.append(baText.getBefore())
.append("\\@the").append(sTypeShort).append("mark")
.append(baText.getAfter())
.append("}}").nl();
}
// The layout and formatting of the {foot|end}note is controlled by \@make{fn|en}text
String sCitStyle = notes.getProperty(XMLString.TEXT_CITATION_STYLE_NAME);
String sStyleName = notes.getProperty(XMLString.TEXT_DEFAULT_STYLE_NAME);
if (sStyleName!=null) {
BeforeAfter baText = new BeforeAfter();
palette.getCharSc().applyTextStyle(sCitStyle,baText,new Context());
StyleWithProperties style = ofr.getParStyle(sStyleName);
if (style!=null) {
BeforeAfter baPar = new BeforeAfter();
palette.getCharSc().applyHardCharFormatting(style,baPar);
ldp.append("\\renewcommand\\@make").append(sTypeShort)
.append("text[1]{\\noindent")
.append(baText.getBefore())
.append("\\@the").append(sTypeShort).append("mark\\ ")
.append(baText.getAfter())
.append(baPar.getBefore())
.append("#1")
.append(baPar.getAfter());
ldp.append("}").nl();
}
}
}
ldp.append("\\makeatother").nl();
}
}

View file

@ -0,0 +1,16 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title>The package writer2latex.latex</title>
</head>
<body>
<p>This package contains LaTeX specific code.</p>
<p>It contains a <code>writerlatex.api.Converter</code> implementation for
conversion into LaTeX.</p>
<p>Since version 1.0 you can build Writer2LaTeX without this package if you
don't need LaTeX support (in this case you can exclude writer2latex.bibtex as
well).</p>
</body>
</html>

View file

@ -0,0 +1,596 @@
/************************************************************************
*
* PageStyleConverter.java
*
* Copyright: 2002-2015 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.6 (2015-04-15)
*
*/
package writer2latex.latex;
import java.util.Enumeration;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import writer2latex.util.CSVList;
import writer2latex.util.Calc;
import writer2latex.util.Misc;
import writer2latex.office.*;
import writer2latex.latex.util.BeforeAfter;
import writer2latex.latex.util.Context;
// TODO: chngpage.sty??
/* This class creates LaTeX code from OOo page layouts/master pages
*/
public class PageStyleConverter extends StyleConverter {
// Value of attribute text:display of most recent text:chapter field
// This is used to handle chaptermarks in headings
private String sChapterField1 = null;
private String sChapterField2 = null;
// The page layout used for the page geometry
// (LaTeX only supports one page geometry per page)
private PageLayout mainPageLayout;
/** <p>Constructs a new <code>PageStyleConverter</code>.</p>
*/
public PageStyleConverter(OfficeReader ofr, LaTeXConfig config,
ConverterPalette palette) {
super(ofr,config,palette);
// Determine the main page master
MasterPage firstMasterPage = ofr.getFirstMasterPage();
String sPageLayoutName = null;
if (firstMasterPage!=null) {
MasterPage nextMasterPage = ofr.getMasterPage(
firstMasterPage.getProperty(XMLString.STYLE_NEXT_STYLE_NAME));
if (nextMasterPage!=null) {
sPageLayoutName = nextMasterPage.getPageLayoutName();
}
else {
sPageLayoutName = firstMasterPage.getPageLayoutName();
}
}
mainPageLayout = ofr.getPageLayout(sPageLayoutName);
}
public void appendDeclarations(LaTeXDocumentPortion pack, LaTeXDocumentPortion decl) {
if (config.useFancyhdr()) { pack.append("\\usepackage{fancyhdr}").nl(); }
// The first master page must be known
MasterPage firstMasterPage = ofr.getFirstMasterPage();
if (firstMasterPage!=null) {
styleNames.addName(getDisplayName(firstMasterPage.getName()));
}
// Convert page geometry
convertPageMasterGeometry(pack,decl);
// Convert master pages
convertMasterPages(decl);
if (firstMasterPage!=null) {
BeforeAfter ba = new BeforeAfter();
applyMasterPage(firstMasterPage.getName(),ba);
decl.append(ba.getBefore());
}
}
public void setChapterField1(String s) { sChapterField1 = s; }
public void setChapterField2(String s) { sChapterField2 = s; }
public boolean isTwocolumn() {
return mainPageLayout!=null && mainPageLayout.getColCount()>1;
}
/** <p>Apply page break properties from a style.</p>
* @param style the style to use
* @param bInherit true if inheritance from parent style should be used
* @param ba a <code>BeforeAfter</code> to put code into
*/
public void applyPageBreak(StyleWithProperties style, boolean bInherit, BeforeAfter ba) {
if (style==null) { return; }
if (style.isAutomatic() && config.ignoreHardPageBreaks()) { return; }
// A page break can be a simple page break before or after...
String s = style.getProperty(XMLString.FO_BREAK_BEFORE,bInherit);
if ("page".equals(s)) { ba.add("\\clearpage",""); }
s = style.getProperty(XMLString.FO_BREAK_AFTER,bInherit);
if ("page".equals(s)) { ba.add("","\\clearpage"); }
// ...or it can be a new master page
String sMasterPage = style.getMasterPageName();
if (sMasterPage==null || sMasterPage.length()==0) { return; }
ba.add("\\clearpage","");
String sPageNumber=style.getProperty(XMLString.STYLE_PAGE_NUMBER);
if (sPageNumber!=null) {
int nPageNumber = Misc.getPosInteger(sPageNumber,1);
ba.add("\\setcounter{page}{"+nPageNumber+"}","");
}
applyMasterPage(sMasterPage,ba);
}
/** <p>Use a Master Page (pagestyle in LaTeX)</p>
* @param sName name of the master page to use
* @param ba the <code>BeforeAfter</code> to add code to.
*/
private void applyMasterPage(String sName, BeforeAfter ba) {
if (config.pageFormatting()==LaTeXConfig.IGNORE_ALL || config.pageFormatting()==LaTeXConfig.CONVERT_GEOMETRY) return;
MasterPage style = ofr.getMasterPage(sName);
if (style==null) { return; }
String sNextName = style.getProperty(XMLString.STYLE_NEXT_STYLE_NAME);
MasterPage nextStyle = ofr.getMasterPage(sNextName);
if (style==nextStyle || nextStyle==null) {
ba.add("\\pagestyle{"+styleNames.getExportName(getDisplayName(sName))+"}\n", "");
}
else {
ba.add("\\pagestyle{"+styleNames.getExportName(getDisplayName(sNextName))+"}\n"+
"\\thispagestyle{"+styleNames.getExportName(getDisplayName(sName))+"}\n","");
}
// todo: should warn the user if next master also contains a next-style-name;
// LaTeX's page style mechanism cannot handle that
}
/*
* Process header or footer contents
*/
private void convertMasterPages(LaTeXDocumentPortion ldp) {
if (config.pageFormatting()==LaTeXConfig.IGNORE_ALL || config.pageFormatting()==LaTeXConfig.CONVERT_GEOMETRY) { return; }
Context context = new Context();
context.resetFormattingFromStyle(ofr.getDefaultParStyle());
context.setInHeaderFooter(true);
Enumeration<OfficeStyle> styles = ofr.getMasterPages().getStylesEnumeration();
ldp.append("% Pages styles").nl();
if (!config.useFancyhdr()) {
ldp.append("\\makeatletter").nl();
}
while (styles.hasMoreElements()) {
MasterPage style = (MasterPage) styles.nextElement();
String sName = style.getName();
if (styleNames.containsName(getDisplayName(sName))) {
sChapterField1 = null;
sChapterField2 = null;
String sPageLayout = style.getPageLayoutName();
PageLayout pageLayout = ofr.getPageLayout(sPageLayout);
if (config.useFancyhdr()) {
ldp.append("\\fancypagestyle{")
.append(styleNames.getExportName(getDisplayName(sName)))
.append("}{\\fancyhf{}").nl();
// Header - odd or both
ldp.append(" \\fancyhead[")
.append(getParAlignment(style.getHeader()))
.append(style.getHeaderLeft()!=null ? "O" : "")
.append("]{");
traverseHeaderFooter((Element)style.getHeader(),ldp,context);
ldp.append("}").nl();
// Header - even
if (style.getHeaderLeft()!=null) {
ldp.append(" \\fancyhead[")
.append(getParAlignment(style.getHeaderLeft()))
.append("E]{");
traverseHeaderFooter((Element)style.getHeaderLeft(),ldp,context);
ldp.append("}").nl();
}
// Footer - odd or both
ldp.append(" \\fancyfoot[")
.append(getParAlignment(style.getFooter()))
.append(style.getFooterLeft()!=null ? "O" : "")
.append("]{");
traverseHeaderFooter((Element)style.getFooter(),ldp,context);
ldp.append("}").nl();
// Footer - even
if (style.getFooterLeft()!=null) {
ldp.append(" \\fancyfoot[")
.append(getParAlignment(style.getFooterLeft()))
.append("E]{");
traverseHeaderFooter((Element)style.getFooterLeft(),ldp,context);
ldp.append("}").nl();
}
// Rules
ldp.append(" \\renewcommand\\headrulewidth{")
.append(getBorderWidth(pageLayout,true))
.append("}").nl()
.append(" \\renewcommand\\footrulewidth{")
.append(getBorderWidth(pageLayout,false))
.append("}").nl();
}
else { // use low-level page styles
ldp.append("\\newcommand\\ps@")
.append(styleNames.getExportName(getDisplayName(sName)))
.append("{").nl();
// Header
ldp.append(" \\renewcommand\\@oddhead{");
traverseHeaderFooter((Element)style.getHeader(),ldp,context);
ldp.append("}").nl();
ldp.append(" \\renewcommand\\@evenhead{");
if (style.getHeaderLeft()!=null) {
traverseHeaderFooter((Element)style.getHeaderLeft(),ldp,context);
}
else if (style.getHeader()!=null) {
ldp.append("\\@oddhead");
}
ldp.append("}").nl();
// Footer
ldp.append(" \\renewcommand\\@oddfoot{");
traverseHeaderFooter((Element)style.getFooter(),ldp,context);
ldp.append("}").nl();
ldp.append(" \\renewcommand\\@evenfoot{");
if (style.getFooterLeft()!=null) {
traverseHeaderFooter((Element)style.getFooterLeft(),ldp,context);
}
else if (style.getFooter()!=null) {
ldp.append("\\@oddfoot");
}
ldp.append("}").nl();
}
// Sectionmark and subsectionmark
if (sChapterField1!=null) {
ldp.append(" \\def\\sectionmark##1{\\markboth{");
if ("name".equals(sChapterField1)) { ldp.append("##1"); }
else if ("number".equals(sChapterField1) || "plain-number".equals(sChapterField1)) {
ldp.append("\\thesection");
}
else { ldp.append("\\thesection\\ ##1"); }
ldp.append("}{}}").nl();
}
if (sChapterField2!=null) {
if (sChapterField1==null) {
ldp.append(" \\def\\sectionmark##1{\\markboth{}{}}").nl();
}
ldp.append(" \\def\\subsectionmark##1{\\markright{");
if ("name".equals(sChapterField2)) { ldp.append("##1"); }
else if ("number".equals(sChapterField2) || "plain-number".equals(sChapterField1)) {
ldp.append("\\thesubsection");
}
else { ldp.append("\\thesubsection\\ ##1"); }
ldp.append("}{}}").nl();
}
// Page number (this is the only part of the page master used in each page style)
if (pageLayout!=null) {
String sNumFormat = pageLayout.getProperty(XMLString.STYLE_NUM_FORMAT);
if (sNumFormat!=null) {
ldp.append(" \\renewcommand\\thepage{")
.append(ListConverter.numFormat(sNumFormat))
.append("{page}}").nl();
}
String sPageNumber = pageLayout.getProperty(XMLString.STYLE_FIRST_PAGE_NUMBER);
if (sPageNumber!=null && !sPageNumber.equals("continue")) {
ldp.append(" \\setcounter{page}{")
.append(Misc.getPosInteger(sPageNumber,0))
.append("}").nl();
}
}
ldp.append("}").nl();
}
}
if (!config.useFancyhdr()) {
ldp.append("\\makeatother").nl();
}
}
// Get alignment of first paragraph in node
private String getParAlignment(Node node) {
String sAlign = "L";
if (node!=null) {
Element par = Misc.getChildByTagName(node,XMLString.TEXT_P);
if (par!=null) {
String sStyleName = Misc.getAttribute(par,XMLString.TEXT_STYLE_NAME);
StyleWithProperties style = ofr.getParStyle(sStyleName);
if (style!=null) {
String s = style.getProperty(XMLString.FO_TEXT_ALIGN);
if ("center".equals(s)) { sAlign = "C"; }
else if ("end".equals(s)) { sAlign = "R"; }
}
}
}
return sAlign;
}
// Get border width from header/footer style
private String getBorderWidth(PageLayout style, boolean bHeader) {
if (style==null) { return "0pt"; }
String sBorder;
if (bHeader) {
sBorder = style.getHeaderProperty(XMLString.FO_BORDER_BOTTOM);
if (sBorder==null) {
sBorder = style.getHeaderProperty(XMLString.FO_BORDER);
}
}
else {
sBorder = style.getFooterProperty(XMLString.FO_BORDER_TOP);
if (sBorder==null) {
sBorder = style.getFooterProperty(XMLString.FO_BORDER);
}
}
if (sBorder!=null && !sBorder.equals("none")) {
return sBorder.substring(0,sBorder.indexOf(' '));
}
else {
return "0pt";
}
}
private void traverseHeaderFooter(Element node, LaTeXDocumentPortion ldp, Context context) {
if (node==null) { return; }
// get first paragraph; all other content is ignored
Element par = Misc.getChildByTagName(node,XMLString.TEXT_P);
if (par==null) { return; }
String sStyleName = par.getAttribute(XMLString.TEXT_STYLE_NAME);
BeforeAfter ba = new BeforeAfter();
// Temp solution: Ignore hard formatting in header/footer (name clash problem)
// only in package format. TODO: Find a better solution!
StyleWithProperties style = ofr.getParStyle(sStyleName);
if (style!=null && (!ofr.isPackageFormat() || !style.isAutomatic())) {
palette.getCharSc().applyHardCharFormatting(style,ba);
}
if (par.hasChildNodes()) {
ldp.append(ba.getBefore());
palette.getInlineCv().traverseInlineText(par,ldp,context);
ldp.append(ba.getAfter());
}
}
// TODO: Reenable several geometries per document??
private void convertPageMasterGeometry(LaTeXDocumentPortion pack, LaTeXDocumentPortion ldp) {
if (config.pageFormatting()!=LaTeXConfig.CONVERT_ALL && config.pageFormatting()!=LaTeXConfig.CONVERT_GEOMETRY) { return; }
if (mainPageLayout==null) { return; }
// Set global document options
if ("mirrored".equals(mainPageLayout.getPageUsage())) {
palette.addGlobalOption("twoside");
}
if (isTwocolumn()) {
palette.addGlobalOption("twocolumn");
}
// Collect all page geometry
// 1. Page size
String sPaperHeight = mainPageLayout.getAbsoluteProperty(XMLString.FO_PAGE_HEIGHT);
String sPaperWidth = mainPageLayout.getAbsoluteProperty(XMLString.FO_PAGE_WIDTH);
// 2. Margins
String sMarginTop = mainPageLayout.getAbsoluteProperty(XMLString.FO_MARGIN_TOP);
String sMarginBottom = mainPageLayout.getAbsoluteProperty(XMLString.FO_MARGIN_BOTTOM);
String sMarginLeft = mainPageLayout.getAbsoluteProperty(XMLString.FO_MARGIN_LEFT);
String sMarginRight = mainPageLayout.getAbsoluteProperty(XMLString.FO_MARGIN_RIGHT);
// 3. Header+footer dimensions
String sHeadHeight = "0cm";
String sHeadSep = "0cm";
String sFootHeight = "0cm";
String sFootSep = "0cm";
boolean bIncludeHead = false;
boolean bIncludeFoot = false;
// Look through all applied page layouts and use largest heights
Enumeration<OfficeStyle> masters = ofr.getMasterPages().getStylesEnumeration();
while (masters.hasMoreElements()) {
MasterPage master = (MasterPage) masters.nextElement();
if (styleNames.containsName(getDisplayName(master.getName()))) {
PageLayout layout = ofr.getPageLayout(master.getPageLayoutName());
if (layout!=null) {
if (layout.hasHeaderStyle()) {
String sThisHeadHeight = layout.getHeaderProperty(XMLString.FO_MIN_HEIGHT);
if (sThisHeadHeight!=null && Calc.isLessThan(sHeadHeight,sThisHeadHeight)) {
sHeadHeight = sThisHeadHeight;
}
String sThisHeadSep = layout.getHeaderProperty(XMLString.FO_MARGIN_BOTTOM);
if (sThisHeadSep!=null && Calc.isLessThan(sHeadSep,sThisHeadSep)) {
sHeadSep = sThisHeadSep;
}
bIncludeHead = true;
}
if (layout.hasFooterStyle()) {
String sThisFootHeight = layout.getFooterProperty(XMLString.FO_MIN_HEIGHT);
if (sThisFootHeight!=null && Calc.isLessThan(sFootHeight,sThisFootHeight)) {
sFootHeight = sThisFootHeight;
}
String sThisFootSep = layout.getFooterProperty(XMLString.FO_MARGIN_TOP);
if (sThisFootSep!=null && Calc.isLessThan(sFootSep,sThisFootSep)) {
sFootSep = sThisFootSep;
}
bIncludeFoot = true;
}
}
}
}
// Define 12pt as minimum height (the source may specify 0pt..)
if (bIncludeHead && Calc.isLessThan(sHeadHeight,"12pt")) {
sHeadHeight = "12pt";
}
if (bIncludeFoot && Calc.isLessThan(sFootHeight,"12pt")) {
sFootHeight = "12pt";
}
String sFootSkip = Calc.add(sFootHeight,sFootSep);
if (config.useGeometry()) {
// Set up options for geometry.sty
CSVList props = new CSVList(",");
if (!standardPaperSize(sPaperWidth,sPaperHeight)) {
props.addValue("paperwidth="+sPaperWidth);
props.addValue("paperheight="+sPaperHeight);
}
props.addValue("top="+sMarginTop);
props.addValue("bottom="+sMarginBottom);
props.addValue("left="+sMarginLeft);
props.addValue("right="+sMarginRight);
if (bIncludeHead) {
props.addValue("includehead");
props.addValue("head="+sHeadHeight);
props.addValue("headsep="+sHeadSep);
}
else {
props.addValue("nohead");
}
if (bIncludeFoot) {
props.addValue("includefoot");
props.addValue("foot="+sFootHeight);
props.addValue("footskip="+sFootSkip);
}
else {
props.addValue("nofoot");
}
// Use the package
pack.append("\\usepackage[").append(props.toString()).append("]{geometry}").nl();
}
else {
// Calculate text height and text width
String sTextHeight = Calc.sub(sPaperHeight,sMarginTop);
sTextHeight = Calc.sub(sTextHeight,sHeadHeight);
sTextHeight = Calc.sub(sTextHeight,sHeadSep);
sTextHeight = Calc.sub(sTextHeight,sFootSkip);
sTextHeight = Calc.sub(sTextHeight,sMarginBottom);
String sTextWidth = Calc.sub(sPaperWidth,sMarginLeft);
sTextWidth = Calc.sub(sTextWidth,sMarginRight);
ldp.append("% Page layout (geometry)").nl();
// Page dimensions
if (!standardPaperSize(sPaperWidth,sPaperHeight)) {
ldp.append("\\setlength\\paperwidth{").append(sPaperWidth).append("}").nl()
.append("\\setlength\\paperheight{").append(sPaperHeight).append("}").nl();
}
// PDF page dimensions, only if hyperref.sty is not loaded
if (config.getBackend()==LaTeXConfig.PDFTEX && !config.useHyperref()) {
ldp.append("\\setlength\\pdfpagewidth{").append(sPaperWidth).append("}").nl()
.append("\\setlength\\pdfpageheight{").append(sPaperHeight).append("}").nl();
}
// Page starts in upper left corner of paper!!
ldp.append("\\setlength\\voffset{-1in}").nl()
.append("\\setlength\\hoffset{-1in}").nl();
// Margins
ldp.append("\\setlength\\topmargin{").append(sMarginTop).append("}").nl()
.append("\\setlength\\oddsidemargin{").append(sMarginLeft).append("}").nl();
// Left margin for even (left) pages; only for mirrored page master
if ("mirrored".equals(mainPageLayout.getPageUsage())) {
ldp.append("\\setlength\\evensidemargin{").append(sMarginRight).append("}").nl();
}
// Text size (sets bottom and right margins indirectly)
ldp.append("\\setlength\\textheight{").append(sTextHeight).append("}").nl();
ldp.append("\\setlength\\textwidth{").append(sTextWidth).append("}").nl();
// Header and footer
ldp.append("\\setlength\\footskip{").append(sFootSkip).append("}").nl();
ldp.append("\\setlength\\headheight{").append(sHeadHeight).append("}").nl();
ldp.append("\\setlength\\headsep{").append(sHeadSep).append("}").nl();
}
// Footnote rule
// TODO: Support alignment.
String sAdjustment = mainPageLayout.getFootnoteProperty(XMLString.STYLE_ADJUSTMENT);
String sBefore = mainPageLayout.getFootnoteProperty(XMLString.STYLE_DISTANCE_BEFORE_SEP);
if (sBefore==null) { sBefore = "1mm"; }
String sAfter = mainPageLayout.getFootnoteProperty(XMLString.STYLE_DISTANCE_AFTER_SEP);
if (sAfter==null) { sAfter = "1mm"; }
String sHeight = mainPageLayout.getFootnoteProperty(XMLString.STYLE_WIDTH);
if (sHeight==null) { sHeight = "0.2mm"; }
String sWidth = mainPageLayout.getFootnoteProperty(XMLString.STYLE_REL_WIDTH);
if (sWidth==null) { sWidth = "25%"; }
sWidth=Float.toString(Calc.getFloat(sWidth.substring(0,sWidth.length()-1),1)/100);
BeforeAfter baColor = new BeforeAfter();
String sColor = mainPageLayout.getFootnoteProperty(XMLString.STYLE_COLOR);
palette.getColorCv().applyColor(sColor,false,baColor,new Context());
String sSkipFootins = Calc.add(sBefore,sHeight);
ldp.append("% Footnote rule").nl()
.append("\\setlength{\\skip\\footins}{").append(sSkipFootins).append("}").nl()
.append("\\renewcommand\\footnoterule{\\vspace*{-").append(sHeight)
.append("}");
if ("right".equals(sAdjustment)) {
ldp.append("\\setlength\\leftskip{0pt plus 1fil}\\setlength\\rightskip{0pt}");
}
else if ("center".equals(sAdjustment)) {
ldp.append("\\setlength\\leftskip{0pt plus 1fil}\\setlength\\rightskip{0pt plus 1fil}");
}
else { // default left
ldp.append("\\setlength\\leftskip{0pt}\\setlength\\rightskip{0pt plus 1fil}");
}
ldp.append("\\noindent")
.append(baColor.getBefore()).append("\\rule{").append(sWidth)
.append("\\columnwidth}{").append(sHeight).append("}")
.append(baColor.getAfter())
.append("\\vspace*{").append(sAfter).append("}}").nl();
}
private boolean standardPaperSize(String sWidth, String sHeight) {
if (standardPaperSize1(sWidth,sHeight)) {
return true;
}
else if (standardPaperSize1(sHeight,sWidth)) {
palette.addGlobalOption("landscape");
return true;
}
return false;
}
private boolean standardPaperSize1(String sWidth, String sHeight) {
// The list of known paper sizes in LaTeX's standard classes is rather short
if (compare(sWidth, "210mm", "0.5mm") && compare(sHeight, "297mm", "0.5mm")) {
palette.addGlobalOption("a4paper");
return true;
}
else if (compare(sWidth, "148mm", "0.5mm") && compare(sHeight, "210mm", "0.5mm")) {
palette.addGlobalOption("a5paper");
return true;
}
else if (compare(sWidth, "176mm", "0.5mm") && compare(sHeight, "250mm", "0.5mm")) {
palette.addGlobalOption("b5paper");
return true;
}
else if (compare(sWidth, "8.5in", "0.02in") && compare(sHeight, "11in", "0.02in")) {
palette.addGlobalOption("letterpaper");
return true;
}
else if (compare(sWidth, "8.5in", "0.02in") && compare(sHeight, "14in", "0.02in")) {
palette.addGlobalOption("legalpaper");
return true;
}
else if (compare(sWidth, "7.25in", "0.02in") && compare(sHeight, "10.5in", "0.02in")) {
palette.addGlobalOption("executivepaper");
return true;
}
return false;
}
private boolean compare(String sLength1, String sLength2, String sTolerance) {
return Calc.isLessThan(Calc.abs(Calc.sub(sLength1,sLength2)),sTolerance);
}
/* Helper: Get display name, or original name if it doesn't exist */
private String getDisplayName(String sName) {
String sDisplayName = ofr.getMasterPages().getDisplayName(sName);
return sDisplayName!=null ? sDisplayName : sName;
}
}

View file

@ -0,0 +1,586 @@
/************************************************************************
*
* ParConverter.java
*
* Copyright: 2002-2014 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.4 (2014-09-08)
*
*/
package writer2latex.latex;
import org.w3c.dom.Element;
import writer2latex.latex.util.BeforeAfter;
import writer2latex.latex.util.Context;
import writer2latex.latex.util.StyleMapItem;
import writer2latex.latex.util.StyleMap;
import writer2latex.office.OfficeReader;
import writer2latex.office.StyleWithProperties;
import writer2latex.office.XMLString;
import writer2latex.util.Calc;
/* <p>This class converts OpenDocument paragraphs (<code>text:p</code>) and
* paragraph styles/formatting into LaTeX</p>
* <p>Export of formatting depends on the option "formatting":</p>
* <ul>
* <li><code>ignore_all</code>
* <li><code>ignore_most</code>
* <li><code>convert_basic</code>
* <li><code>convert_most</code>
* <li><code>convert_all</code>
* </ul>
* <p>TODO: Captions and {foot|end}notes should also use this class
*/
public class ParConverter extends StyleConverter {
private boolean bNeedArrayBslash = false;
// Display hidden text?
private boolean bDisplayHiddenText = false;
/** <p>Constructs a new <code>ParConverter</code>.</p>
*/
public ParConverter(OfficeReader ofr, LaTeXConfig config, ConverterPalette palette) {
super(ofr,config,palette);
this.bDisplayHiddenText = config.displayHiddenText();
}
public void appendDeclarations(LaTeXDocumentPortion pack, LaTeXDocumentPortion decl) {
if (bNeedArrayBslash) {
// centering and raggedright redefines \\, fix this
// Note: aviods nameclash with tabularx (arraybackslash)
// TODO: Should perhaps choose to load tabularx instead?
decl.append("\\makeatletter").nl()
.append("\\newcommand\\arraybslash{\\let\\\\\\@arraycr}").nl()
.append("\\makeatother").nl();
}
if (config.formatting()>=LaTeXConfig.CONVERT_MOST) {
// We typeset with \raggedbottom since OOo doesn't use rubber lengths
// TODO: Maybe turn vertical spacing from OOo into rubber lengths?
decl.append("\\raggedbottom").nl();
}
if (config.formatting()>=LaTeXConfig.CONVERT_MOST) {
decl.append("% Paragraph styles").nl();
// First default paragraph style
palette.getCharSc().applyDefaultFont(ofr.getDefaultParStyle(),decl);
super.appendDeclarations(pack,decl);
}
}
/**
* <p> Process a text:p tag</p>
* @param node The text:p element node containing the paragraph
* @param ldp The <code>LaTeXDocumentPortion</code> to add LaTeX code to
* @param oc The current context
* @param bLastInBlock If this is true, the paragraph is the
* last one in a block, and we need no trailing blank line (eg. right before
* \end{enumerate}).
*/
public void handleParagraph(Element node, LaTeXDocumentPortion ldp, Context oc, boolean bLastInBlock) {
// Check for display equation (except in table cells)
if ((!oc.isInTable()) && palette.getMathCv().handleDisplayEquation(node,ldp)) { return; }
// Get the style for this paragraph
String sStyleName = node.getAttribute(XMLString.TEXT_STYLE_NAME);
StyleWithProperties style = ofr.getParStyle(sStyleName);
String sDisplayName = ofr.getParStyles().getDisplayName(sStyleName);
// Check for hidden text
if (!bDisplayHiddenText && style!=null && "none".equals(style.getProperty(XMLString.TEXT_DISPLAY))) {
return;
}
// Check for strict handling of styles
if (config.otherStyles()!=LaTeXConfig.ACCEPT && !config.getParStyleMap().contains(sDisplayName)) {
if (config.otherStyles()==LaTeXConfig.WARNING) {
System.err.println("Warning: A paragraph with style "+sDisplayName+" was ignored");
}
else if (config.otherStyles()==LaTeXConfig.ERROR) {
ldp.append("% Error in source document: A paragraph with style ")
.append(palette.getI18n().convert(sDisplayName,false,oc.getLang()))
.append(" was ignored").nl();
}
// Ignore this paragraph:
return;
}
// Empty paragraphs are often (mis)used to achieve vertical spacing in WYSIWYG
// word processors. Hence we translate an empty paragraph to \bigskip.
// This also solves the problem that LaTeX ignores empty paragraphs, Writer doesn't.
// In a well-structured document, an empty paragraph is probably a mistake,
// hence the configuration can specify that it should be ignored.
// Note: Don't use \bigskip in tables (this can lead to strange results)
if (OfficeReader.isWhitespaceContent(node)) {
// Always add page break; other formatting is ignored
BeforeAfter baPage = new BeforeAfter();
palette.getPageSc().applyPageBreak(style,true,baPage);
if (!oc.isInTable()) { ldp.append(baPage.getBefore()); }
if (!config.ignoreEmptyParagraphs()) {
if (!oc.isInTable()) {
ldp.nl().append("\\bigskip").nl();
}
else {
ldp.append("~").nl();
}
if (!bLastInBlock) { ldp.nl(); }
}
if (!oc.isInTable()) { ldp.append(baPage.getAfter()); }
return;
}
Context ic = (Context) oc.clone();
// Always push the font used
palette.getI18n().pushSpecialTable(palette.getCharSc().getFontName(ofr.getParStyle(sStyleName)));
// Apply the style
int nBreakAfter;
BeforeAfter ba = new BeforeAfter();
if (oc.isInTable()) {
nBreakAfter = applyCellParStyle(sStyleName,ba,ic,OfficeReader.getCharacterCount(node)==0,bLastInBlock);
}
else {
nBreakAfter = applyParStyle(sStyleName,ba,ic,OfficeReader.getCharacterCount(node)==0);
}
// Do conversion
ldp.append(ba.getBefore());
palette.getInlineCv().traverseInlineText(node,ldp,ic);
ldp.append(ba.getAfter());
// Add line break if desired
if (nBreakAfter!=StyleMapItem.NONE) { ldp.nl(); }
// Add a blank line except within verbatim and last in a block, and if desired by style map
if (!bLastInBlock && !ic.isVerbatim() && !ic.isInSimpleTable() && nBreakAfter==StyleMapItem.PAR) { ldp.nl(); }
// Flush any pending index marks, reference marks and floating frames
palette.getFieldCv().flushReferenceMarks(ldp,oc);
palette.getIndexCv().flushIndexMarks(ldp,oc);
palette.getDrawCv().flushFloatingFrames(ldp,oc);
// pop the font name
palette.getI18n().popSpecialTable();
}
private int applyCellParStyle(String sName, BeforeAfter ba, Context context, boolean bNoTextPar, boolean bLastInBlock) {
// Paragraph formatting for paragraphs within table cells
// We always use simple par styles here
context.setVerbatim(false);
int nBreakAfter = bLastInBlock ? StyleMapItem.NONE : StyleMapItem.PAR;
if (context.isInSimpleTable()) {
if (config.formatting()!=LaTeXConfig.IGNORE_ALL) {
// only character formatting!
StyleWithProperties style = ofr.getParStyle(sName);
if (style!=null) {
palette.getI18n().applyLanguage(style,true,true,ba);
palette.getCharSc().applyFont(style,true,true,ba,context);
if (ba.getBefore().length()>0) { ba.add(" ",""); }
}
}
nBreakAfter = StyleMapItem.NONE;
}
else if (config.getParStyleMap().contains(ofr.getParStyles().getDisplayName(sName))) {
// We have a style map in the configuration
StyleMap sm = config.getParStyleMap();
String sDisplayName = ofr.getParStyles().getDisplayName(sName);
String sBefore = sm.getBefore(sDisplayName);
String sAfter = sm.getAfter(sDisplayName);
ba.add(sBefore, sAfter);
// Add line breaks inside?
if (sm.getLineBreak(sDisplayName)) {
if (sBefore.length()>0) { ba.add("\n",""); }
if (sAfter.length()>0 && !"}".equals(sAfter)) { ba.add("","\n"); }
}
nBreakAfter = sm.getBreakAfter(sDisplayName);
if (sm.getVerbatim(sDisplayName)) { context.setVerbatim(true); }
}
else if (bNoTextPar && (config.formatting()==LaTeXConfig.CONVERT_BASIC || config.formatting()==LaTeXConfig.IGNORE_MOST) ) {
// only alignment!
StyleWithProperties style = ofr.getParStyle(sName);
if (style!=null) {
// Apply hard formatting attributes
// Note: Left justified text is exported as full justified text!
palette.getPageSc().applyPageBreak(style,false,ba);
String sTextAlign = style.getProperty(XMLString.FO_TEXT_ALIGN,true);
if (bLastInBlock && context.isInLastTableColumn()) { // no grouping needed, but need to fix problem with \\
if ("center".equals(sTextAlign)) { ba.add("\\centering\\arraybslash ",""); }
else if ("end".equals(sTextAlign)) { ba.add("\\raggedleft\\arraybslash ",""); }
bNeedArrayBslash = true;
}
else if (bLastInBlock) { // no grouping needed
if ("center".equals(sTextAlign)) { ba.add("\\centering ",""); }
else if ("end".equals(sTextAlign)) { ba.add("\\raggedleft ",""); }
}
else {
if ("center".equals(sTextAlign)) { ba.add("{\\centering ","\\par}"); }
else if ("end".equals(sTextAlign)) { ba.add("{\\raggedleft ","\\par}"); }
nBreakAfter = StyleMapItem.LINE;
}
}
}
else {
// Export character formatting + alignment only
BeforeAfter baPar = new BeforeAfter();
BeforeAfter baText = new BeforeAfter();
// Apply hard formatting attributes
// Note: Left justified text is exported as full justified text!
StyleWithProperties style = ofr.getParStyle(sName);
if (style!=null) {
String sTextAlign = style.getProperty(XMLString.FO_TEXT_ALIGN,true);
if (bLastInBlock && context.isInLastTableColumn()) { // no grouping needed, but need to fix problem with \\
if ("center".equals(sTextAlign)) { baPar.add("\\centering\\arraybslash",""); }
else if ("end".equals(sTextAlign)) { baPar.add("\\raggedleft\\arraybslash",""); }
bNeedArrayBslash = true;
}
else if (bLastInBlock) { // no \par needed
if ("center".equals(sTextAlign)) { baPar.add("\\centering",""); }
else if ("end".equals(sTextAlign)) { baPar.add("\\raggedleft",""); }
}
else {
if ("center".equals(sTextAlign)) { baPar.add("\\centering","\\par"); }
else if ("end".equals(sTextAlign)) { baPar.add("\\raggedleft","\\par"); }
}
palette.getI18n().applyLanguage(style,true,true,baText);
palette.getCharSc().applyFont(style,true,true,baText,context);
}
// Group the contents if this is not the last paragraph in the cell
boolean bIsGrouped = false;
if ((!baPar.isEmpty() || !baText.isEmpty()) && !bLastInBlock) {
ba.add("{","}");
bIsGrouped = true;
}
ba.add(baPar);
// Group the text formatting in any case (supertabular needs this)
if (!baText.isEmpty() && !bIsGrouped) {
ba.add("{", "}");
}
ba.add(baText);
if (ba.getBefore().length()>0) { ba.add(" ",""); }
}
// Update context
StyleWithProperties style = ofr.getParStyle(sName);
if (style!=null) {
context.updateFormattingFromStyle(style);
}
return nBreakAfter;
}
/** <p>Use a paragraph style in LaTeX.</p>
* @param <code>sName</code> the name of the text style
* @param <code>ba</code> a <code>BeforeAfter</code> to put code into
* @param <code>context</code> the current context. This method will use and update the formatting context
* @param <code>bNoTextPar</code> true if this paragraph has no text content (hence character formatting is not needed)
*/
private int applyParStyle(String sName, BeforeAfter ba, Context context, boolean bNoTextPar) {
return applyParStyle(sName,ba,context,bNoTextPar,true);
}
private int applyParStyle(String sName, BeforeAfter ba, Context context, boolean bNoTextPar, boolean bBreakInside) {
// No style specified?
if (sName==null) { return StyleMapItem.PAR; }
/*if (context.isInSimpleTable()) {
if (config.formatting()!=LaTeXConfig.IGNORE_ALL) {
// only character formatting!
StyleWithProperties style = ofr.getParStyle(sName);
if (style!=null) {
palette.getI18n().applyLanguage(style,true,true,ba);
palette.getCharSc().applyFont(style,true,true,ba,context);
if (ba.getBefore().length()>0) { ba.add(" ",""); }
}
}
}
else*/
int nBreakAfter = StyleMapItem.PAR;
if (bNoTextPar && (config.formatting()==LaTeXConfig.CONVERT_BASIC || config.formatting()==LaTeXConfig.IGNORE_MOST) ) {
//TODO: If there is a style map, we should respect that despite the fact that the paragraph is empty
// only alignment!
StyleWithProperties style = ofr.getParStyle(sName);
if (style!=null) {
// Apply hard formatting attributes
// Note: Left justified text is exported as full justified text!
palette.getPageSc().applyPageBreak(style,false,ba);
String sTextAlign = style.getProperty(XMLString.FO_TEXT_ALIGN,true);
if ("center".equals(sTextAlign)) { ba.add("{\\centering ","\\par}"); nBreakAfter = StyleMapItem.LINE; }
else if ("end".equals(sTextAlign)) { ba.add("{\\raggedleft ","\\par}"); nBreakAfter = StyleMapItem.LINE; }
}
}
else {
// Apply the style
if (!styleMap.contains(sName)) { createParStyle(sName); }
String sBefore = styleMap.getBefore(sName);
String sAfter = styleMap.getAfter(sName);
ba.add(sBefore,sAfter);
// Add line breaks inside?
if (bBreakInside && styleMap.getLineBreak(sName)) {
if (sBefore.length()>0) { ba.add("\n",""); }
if (sAfter.length()>0 && !"}".equals(sAfter)) { ba.add("","\n"); }
}
nBreakAfter = styleMap.getBreakAfter(sName);
}
// Update context
StyleWithProperties style = ofr.getParStyle(sName);
if (style!=null) {
context.updateFormattingFromStyle(style);
}
context.setVerbatim(styleMap.getVerbatim(sName));
return nBreakAfter;
}
/** <p>Convert a paragraph style to LaTeX. </p>
* <p>A soft style is declared in <code>styleDeclarations</code> as
* <code>\newenvironment...</code></p>
* <p>A hard style is used by applying LaTeX code directly</p>
* @param <code>sName</code> the OOo name of the style
*/
private void createParStyle(String sName) {
// A paragraph style should always be created relative to main context
Context context = (Context) palette.getMainContext().clone();
// The style may already be declared in the configuration:
String sDisplayName = ofr.getParStyles().getDisplayName(sName);
StyleMap sm = config.getParStyleMap();
if (sm.contains(sDisplayName)) {
styleMap.put(sName,sm.getBefore(sDisplayName),sm.getAfter(sDisplayName),
sm.getLineBreak(sDisplayName),sm.getBreakAfter(sDisplayName),sm.getVerbatim(sDisplayName));
return;
}
// Does the style exist?
StyleWithProperties style = ofr.getParStyle(sName);
if (style==null) {
styleMap.put(sName,"","");
return;
}
// Convert the style!
switch (config.formatting()) {
case LaTeXConfig.CONVERT_MOST:
if (style.isAutomatic()) {
createAutomaticParStyle(style,context);
return;
}
case LaTeXConfig.CONVERT_ALL:
createSoftParStyle(style,context);
return;
case LaTeXConfig.CONVERT_BASIC:
case LaTeXConfig.IGNORE_MOST:
createSimpleParStyle(style,context);
return;
case LaTeXConfig.IGNORE_ALL:
default:
styleMap.put(sName,"","");
}
}
private void createAutomaticParStyle(StyleWithProperties style, Context context) {
// Hard paragraph formatting from this style should be ignored
// (because the user wants to ignore hard paragraph formatting
// or there is a style map for the parent.)
BeforeAfter ba = new BeforeAfter();
BeforeAfter baPar = new BeforeAfter();
BeforeAfter baText = new BeforeAfter();
// Apply paragraph formatting from parent
// If parent is verbatim, this is all
String sParentName = style.getParentName();
if (styleMap.getVerbatim(sParentName)) {
styleMap.put(style.getName(),styleMap.getBefore(sParentName),styleMap.getAfter(sParentName),
styleMap.getLineBreak(sParentName),styleMap.getBreakAfter(sParentName),styleMap.getVerbatim(sParentName));
return;
}
applyParStyle(sParentName,baPar,context,false,false);
// Apply hard formatting properties:
palette.getPageSc().applyPageBreak(style,false,ba);
palette.getI18n().applyLanguage(style,true,false,baText);
palette.getCharSc().applyFont(style,true,false,baText,context);
// Assemble the bits. If there is any hard character formatting
// we must group the contents.
if (baPar.isEmpty() && !baText.isEmpty()) { ba.add("{","}"); }
else { ba.add(baPar.getBefore(),baPar.getAfter()); }
ba.add(baText.getBefore(),baText.getAfter());
boolean bLineBreak = styleMap.getLineBreak(sParentName);
if (!bLineBreak && !baText.isEmpty()) { ba.add(" ",""); }
styleMap.put(style.getName(),ba.getBefore(),ba.getAfter(),bLineBreak,styleMap.getBreakAfter(sParentName), false);
}
private void createSimpleParStyle(StyleWithProperties style, Context context) {
// Export character formatting + alignment only
if (style.isAutomatic() && config.getParStyleMap().contains(ofr.getParStyles().getDisplayName(style.getParentName()))) {
createAutomaticParStyle(style,context);
return;
}
BeforeAfter ba = new BeforeAfter();
BeforeAfter baText = new BeforeAfter();
// Apply hard formatting attributes
// Note: Left justified text is exported as full justified text!
palette.getPageSc().applyPageBreak(style,false,ba);
String sTextAlign = style.getProperty(XMLString.FO_TEXT_ALIGN,true);
if ("center".equals(sTextAlign)) { baText.add("\\centering","\\par"); }
else if ("end".equals(sTextAlign)) { baText.add("\\raggedleft","\\par"); }
palette.getI18n().applyLanguage(style,true,true,baText);
palette.getCharSc().applyFont(style,true,true,baText,context);
// Assemble the bits. If there is any hard character formatting
// or alignment we must group the contents.
if (!baText.isEmpty()) { ba.add("{","}"); }
ba.add(baText.getBefore(),baText.getAfter());
styleMap.put(style.getName(),ba.getBefore(),ba.getAfter());
}
private void createSoftParStyle(StyleWithProperties style, Context context) {
// This style should be converted to an enviroment, except if
// it's automatic and there is a config style map for the parent
if (style.isAutomatic() && config.getParStyleMap().contains(ofr.getParStyles().getDisplayName(style.getParentName()))) {
createAutomaticParStyle(style,context);
}
BeforeAfter ba = new BeforeAfter();
applyParProperties(style,ba);
ba.add("\\writerlistparindent\\writerlistleftskip","");
palette.getI18n().applyLanguage(style,true,true,ba);
ba.add("\\leavevmode","");
palette.getCharSc().applyNormalFont(ba);
palette.getCharSc().applyFont(style,true,true,ba,context);
ba.add("\\writerlistlabel","");
ba.add("\\ignorespaces","");
// Declare the paragraph style (\newenvironment)
String sTeXName = "style" + styleNames.getExportName(style.getDisplayName());
styleMap.put(style.getName(),"\\begin{"+sTeXName+"}","\\end{"+sTeXName+"}");
declarations.append("\\newenvironment{").append(sTeXName)
.append("}{").append(ba.getBefore()).append("}{")
.append(ba.getAfter()).append("}").nl();
}
// Remaining methods are private helpers
/** <p>Apply line spacing from a style.</p>
* @param <code>style</code> the paragraph style to use
* @param <code>ba</code> a <code>BeforeAfter</code> to put code into
*/
private void applyLineSpacing(StyleWithProperties style, BeforeAfter ba) {
if (style==null) { return; }
String sLineHeight = style.getProperty(XMLString.FO_LINE_HEIGHT);
if (sLineHeight==null || !sLineHeight.endsWith("%")) { return; }
float fPercent=Calc.getFloat(sLineHeight.substring(0,sLineHeight.length()-1),100);
// Do not allow less that 120% (LaTeX default)
if (fPercent<120) { fPercent = 120; }
ba.add("\\renewcommand\\baselinestretch{"+fPercent/120+"}","");
}
/** <p>Helper: Create a horizontal border. Currently unused</p>
*/
/*private String createBorder(String sLeft, String sRight, String sTop,
String sHeight, String sColor) {
BeforeAfter baColor = new BeforeAfter();
palette.getColorCv().applyColor(sColor,false,baColor, new Context());
return "{\\setlength\\parindent{0pt}\\setlength\\leftskip{" + sLeft + "}"
+ "\\setlength\\baselineskip{0pt}\\setlength\\parskip{" + sHeight + "}"
+ baColor.getBefore()
+ "\\rule{\\textwidth-" + sLeft + "-" + sRight + "}{" + sHeight + "}"
+ baColor.getAfter()
+ "\\par}";
}*/
/** <p>Apply margin+alignment properties from a style.</p>
* @param <code>style</code> the paragraph style to use
* @param <code>ba</code> a <code>BeforeAfter</code> to put code into
*/
private void applyMargins(StyleWithProperties style, BeforeAfter ba) {
// Read padding/margin/indentation properties:
//String sPaddingTop = style.getAbsoluteLength(XMLString.FO_PADDING_TOP);
//String sPaddingBottom = style.getAbsoluteLength(XMLString.FO_PADDING_BOTTOM);
//String sPaddingLeft = style.getAbsoluteLength(XMLString.FO_PADDING_LEFT);
//String sPaddingRight = style.getAbsoluteLength(XMLString.FO_PADDING_RIGHT);
String sMarginTop = style.getAbsoluteLength(XMLString.FO_MARGIN_TOP);
String sMarginBottom = style.getAbsoluteLength(XMLString.FO_MARGIN_BOTTOM);
String sMarginLeft = style.getAbsoluteLength(XMLString.FO_MARGIN_LEFT);
String sMarginRight = style.getAbsoluteLength(XMLString.FO_MARGIN_RIGHT);
String sTextIndent;
if ("true".equals(style.getProperty(XMLString.STYLE_AUTO_TEXT_INDENT))) {
sTextIndent = "2em";
}
else {
sTextIndent = style.getAbsoluteLength(XMLString.FO_TEXT_INDENT);
}
// Read alignment properties:
boolean bRaggedLeft = false; // add 1fil to \leftskip
boolean bRaggedRight = false; // add 1fil to \rightskip
boolean bParFill = false; // add 1fil to \parfillskip
String sTextAlign = style.getProperty(XMLString.FO_TEXT_ALIGN);
if ("center".equals(sTextAlign)) {
bRaggedLeft = true; bRaggedRight = true; // centered paragraph
}
else if ("start".equals(sTextAlign)) {
bRaggedRight = true; bParFill = true; // left aligned paragraph
}
else if ("end".equals(sTextAlign)) {
bRaggedLeft = true; // right aligned paragraph
}
else if (!"justify".equals(style.getProperty(XMLString.FO_TEXT_ALIGN_LAST))) {
bParFill = true; // justified paragraph with ragged last line
}
// Create formatting:
String sRubberMarginTop = Calc.multiply("10%",sMarginTop);
if (Calc.length2px(sRubberMarginTop).equals("0")) { sRubberMarginTop="1pt"; }
String sRubberMarginBottom = Calc.multiply("10%",sMarginBottom);
if (Calc.length2px(sRubberMarginBottom).equals("0")) { sRubberMarginBottom="1pt"; }
ba.add("\\setlength\\leftskip{"+sMarginLeft+(bRaggedLeft?" plus 1fil":"")+"}","");
ba.add("\\setlength\\rightskip{"+sMarginRight+(bRaggedRight?" plus 1fil":"")+"}","");
ba.add("\\setlength\\parindent{"+sTextIndent+"}","");
ba.add("\\setlength\\parfillskip{"+(bParFill?"0pt plus 1fil":"0pt")+"}","");
ba.add("\\setlength\\parskip{"+sMarginTop+" plus "+sRubberMarginTop+"}",
"\\unskip\\vspace{"+sMarginBottom+" plus "+sRubberMarginBottom+"}");
}
public void applyAlignment(StyleWithProperties style, boolean bIsSimple, boolean bInherit, BeforeAfter ba) {
if (bIsSimple || style==null) { return; }
String sTextAlign = style.getProperty(XMLString.FO_TEXT_ALIGN,bInherit);
if ("center".equals(sTextAlign)) { ba.add("\\centering",""); }
else if ("start".equals(sTextAlign)) { ba.add("\\raggedright",""); }
else if ("end".equals(sTextAlign)) { ba.add("\\raggedleft",""); }
}
/** <p>Apply all paragraph properties.</p>
* @param <code>style</code> the paragraph style to use
* @param <code>ba</code> a <code>BeforeAfter</code> to put code into
*/
private void applyParProperties(StyleWithProperties style, BeforeAfter ba) {
palette.getPageSc().applyPageBreak(style,true,ba);
ba.add("","\\par");
applyLineSpacing(style,ba);
applyMargins(style,ba);
}
}

View file

@ -0,0 +1,224 @@
/************************************************************************
*
* SectionConverter.java
*
* Copyright: 2002-2014 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.4 (2014-09-08)
*
*/
package writer2latex.latex;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import writer2latex.util.*;
import writer2latex.office.*;
import writer2latex.latex.i18n.ClassicI18n;
import writer2latex.latex.util.BeforeAfter;
import writer2latex.latex.util.Context;
/** <p>This class creates LaTeX code from OOo sections.
* <p>Sections are converted to multicols environments using <code>multicol.sty</code>
*/
public class SectionConverter extends ConverterHelper {
// Do we need multicols.sty?
private boolean bNeedMulticol = false;
// Display hidden text?
private boolean bDisplayHiddenText = false;
// Filenames for external sections
private ExportNameCollection fileNames = new ExportNameCollection(true);
/** <p>Constructs a new <code>SectionStyleConverter</code>.</p>
*/
public SectionConverter(OfficeReader ofr, LaTeXConfig config, ConverterPalette palette) {
super(ofr,config,palette);
this.bDisplayHiddenText = config.displayHiddenText();
}
public void appendDeclarations(LaTeXDocumentPortion pack, LaTeXDocumentPortion decl) {
if (bNeedMulticol) { pack.append("\\usepackage{multicol}").nl(); }
}
// Handle a section as a Zotero bibliography
private boolean handleZoteroBibliography(Element node, LaTeXDocumentPortion ldp, Context oc) {
String sName = node.getAttribute(XMLString.TEXT_NAME);
if (config.useBibtex() && config.zoteroBibtexFiles().length()>0 && sName.startsWith("ZOTERO_BIBL")) {
// This section is a Zotero bibliography, and the user wishes to handle it as such
// A Zotero bibliography name has the form ZOTERO_BIBL <json object> <identifier> with a single space separating the items
// The identifier is a unique identifier for the bibliography and is not used here
if (!config.noIndex()) {
// Parse the name (errors are ignored) and add \nocite commands as appropriate
int nObjectStart = sName.indexOf('{');
int nObjectEnd = sName.lastIndexOf('}');
if (nObjectStart>-1 && nObjectEnd>-1 && nObjectStart<nObjectEnd) {
String sJsonObject = sName.substring(nObjectStart, nObjectEnd+1);
JSONObject jo = null;
try {
jo = new JSONObject(sJsonObject);
} catch (JSONException e) {
}
if (jo!=null) {
JSONArray uncited = null;
try {
uncited = jo.getJSONArray("uncited");
}
catch (JSONException e) {
}
if (uncited!=null) {
int nCount = uncited.length();
if (nCount>0) {
ldp.append("\\nocite{");
for (int nIndex=0; nIndex<nCount; nIndex++) {
if (nIndex>0) {
ldp.append(",");
}
String sURI = null;
try { // Each item is an array containing a single string
sURI = uncited.getJSONArray(nIndex).getString(0);
}
catch (JSONException e) {
}
if (sURI!=null) {
int nSlash = sURI.lastIndexOf('/');
if (nSlash>0) { ldp.append(sURI.substring(nSlash+1)); }
else { ldp.append(sURI); }
}
}
ldp.append("}").nl();
}
}
}
}
// Use the BibTeX style and files given in the configuration
ldp.append("\\bibliographystyle{").append(config.bibtexStyle()).append("}").nl()
.append("\\bibliography{").append(config.zoteroBibtexFiles()).append("}").nl();
}
return true;
}
else {
return false;
}
}
// Handle a section as a JabRef bibliography
private boolean handleJabRefBibliography(Element node, LaTeXDocumentPortion ldp, Context oc) {
String sName = node.getAttribute(XMLString.TEXT_NAME);
if (config.useBibtex() && config.jabrefBibtexFiles().length()>0 && sName.equals("JR_bib")) {
// This section is a JabRef bibliography, and the user wishes to handle it as such
// A JabRef bibliography is identified by the name JR_bib
// Use the BibTeX style and files given in the configuration
ldp.append("\\bibliographystyle{").append(config.bibtexStyle()).append("}").nl()
.append("\\bibliography{").append(config.jabrefBibtexFiles()).append("}").nl();
return true;
}
return false;
}
/** <p> Process a section (text:section tag)</p>
* @param node The element containing the section
* @param ldp the <code>LaTeXDocumentPortion</code> to which
* LaTeX code should be added
* @param oc the current context
*/
public void handleSection(Element node, LaTeXDocumentPortion ldp, Context oc) {
// Unlike headings, paragraphs and spans, text:display is not attached to the style:
if (!bDisplayHiddenText && "none".equals(Misc.getAttribute(node,XMLString.TEXT_DISPLAY))) {
return;
}
// We may need a hyperlink target, add this first
palette.getFieldCv().addTarget(node,"|region",ldp);
// Create new document, if desired
String sFileName = null;
Element source = Misc.getChildByTagName(node,XMLString.TEXT_SECTION_SOURCE);
if (config.splitLinkedSections() && source!=null) {
sFileName = fileNames.getExportName(Misc.removeExtension(Misc.urlDecode(source.getAttribute(XMLString.XLINK_HREF))));
}
else if (config.splitToplevelSections() && isToplevel(node)) {
//sFileName = fileNames.getExportName(palette.getOutFileName()+node.getAttribute(XMLString.TEXT_NAME));
sFileName = fileNames.getExportName(node.getAttribute(XMLString.TEXT_NAME));
}
LaTeXDocumentPortion sectionLdp = ldp;
if (sFileName!=null) {
LaTeXDocument newDoc = new LaTeXDocument(sFileName,config.getWrapLinesAfter(),false);
if (config.getBackend()!=LaTeXConfig.XETEX) {
newDoc.setEncoding(ClassicI18n.writeJavaEncoding(config.getInputencoding()));
}
else {
newDoc.setEncoding("UTF-8");
}
palette.addDocument(newDoc);
sectionLdp = newDoc.getContents();
}
// Apply the style
String sStyleName = node.getAttribute(XMLString.TEXT_STYLE_NAME);
BeforeAfter ba = new BeforeAfter();
Context ic = (Context) oc.clone();
applySectionStyle(sStyleName,ba,ic);
// Do conversion
ldp.append(ba.getBefore());
if (sFileName!=null) {
ldp.append("\\input{").append(sFileName).append("}").nl();
}
// Zotero or JabRef might have generated this section as a bibliograhy:
if (!handleZoteroBibliography(node,sectionLdp,ic) && !handleJabRefBibliography(node,sectionLdp,ic)) {
palette.getBlockCv().traverseBlockText(node,sectionLdp,ic);
}
if (sectionLdp!=ldp) { sectionLdp.append("\\endinput").nl(); }
ldp.append(ba.getAfter());
}
// Create multicols environment as needed
private void applySectionStyle(String sStyleName, BeforeAfter ba, Context context) {
StyleWithProperties style = ofr.getSectionStyle(sStyleName);
// Don't nest multicols and require at least 2 columns
if (context.isInMulticols() || style==null || style.getColCount()<2) { return; }
int nCols = style.getColCount();
bNeedMulticol = true;
context.setInMulticols(true);
ba.add("\\begin{multicols}{"+(nCols>10 ? 10 : nCols)+"}\n", "\\end{multicols}\n");
}
// return true if this node is *not* contained in a text:section element
private boolean isToplevel(Node node) {
Node parent = node.getParentNode();
if (XMLString.TEXT_SECTION.equals(parent.getNodeName())) {
return false;
}
else if (XMLString.OFFICE_BODY.equals(parent.getNodeName())) {
return true;
}
return isToplevel(parent);
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,50 @@
/************************************************************************
*
* StyleConverter.java
*
* Copyright: 2002-2008 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.0 (2008-09-08)
*
*/
package writer2latex.latex;
import writer2latex.latex.util.StyleMap;
import writer2latex.util.ExportNameCollection;
import writer2latex.office.OfficeReader;
/**
* <p>This is an abstract superclass for style converters.</p>
*/
public abstract class StyleConverter extends ConverterHelper {
// Names and maps + necessary declarations for these styles
protected ExportNameCollection styleNames = new ExportNameCollection(false);
protected StyleMap styleMap = new StyleMap();
protected LaTeXDocumentPortion declarations = new LaTeXDocumentPortion(false);
protected StyleConverter(OfficeReader ofr, LaTeXConfig config, ConverterPalette palette) {
super(ofr,config,palette);
}
public void appendDeclarations(LaTeXDocumentPortion pack, LaTeXDocumentPortion decl) {
decl.append(declarations);
}
}

View file

@ -0,0 +1,491 @@
/************************************************************************
*
* TableConverter.java
*
* Copyright: 2002-2014 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.4 (2014-09-03)
*
*/
package writer2latex.latex;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import writer2latex.util.*;
import writer2latex.office.*;
import writer2latex.latex.util.BeforeAfter;
import writer2latex.latex.util.Context;
enum RowType {
FIRST_HEAD, HEAD, BODY, FOOT, LAST_FOOT;
}
/** <p>This class converts OpenDocument tables to LaTeX.</p>
* <p>The following LaTeX packages are used; some of them are optional</p>
* <p>array.sty, longtable.sty, supertabular.sty, tabulary.sty, hhline.sty,
* colortbl.sty.</p>
* <p>Options:</p>
* <ul>
* <li>use_longtable = true|false</li>
* <li>use_supertabular = true|false</li>
* <li>use_tabulary = true|false</li>
* <li>use_colortbl = true|false</li>
* <li>float_tables = true|false</li>
* <li>float_options = &lt;string&gt;</li>
* <li>table_content = accept|ignore|warning|error</li>
* </ul>
*
*/
public class TableConverter extends ConverterHelper {
private boolean bNeedLongtable = false;
private boolean bNeedSupertabular = false;
private boolean bNeedTabulary = false;
private boolean bNeedColortbl = false;
private boolean bContainsTables = false;
/** <p>Constructs a new <code>TableConverter</code>.</p>
*/
public TableConverter(OfficeReader ofr, LaTeXConfig config,
ConverterPalette palette) {
super(ofr,config,palette);
}
public void appendDeclarations(LaTeXDocumentPortion pack, LaTeXDocumentPortion decl) {
pack.append("\\usepackage{array}").nl(); // TODO: Make this optional
if (bNeedLongtable) { pack.append("\\usepackage{longtable}").nl(); }
if (bNeedSupertabular) { pack.append("\\usepackage{supertabular}").nl(); }
if (bNeedTabulary) { pack.append("\\usepackage{tabulary}").nl(); }
pack.append("\\usepackage{hhline}").nl(); // TODO: Make this optional
if (bNeedColortbl) { pack.append("\\usepackage{colortbl}").nl(); }
// Set padding for table cells (1mm is default in OOo!)
// For vertical padding we can only specify a relative size
if (bContainsTables) {
decl.append("\\setlength\\tabcolsep{1mm}").nl();
decl.append("\\renewcommand\\arraystretch{1.3}").nl();
}
}
// Export a lonely table caption
public void handleCaption(Element node, LaTeXDocumentPortion ldp, Context oc) {
ldp.append("\\captionof{table}");
palette.getCaptionCv().handleCaptionBody(node,ldp,oc,true);
}
/** <p> Process a table (table:table or table:sub-table tag)</p>
* @param node The element containing the table
* @param ldp the <code>LaTeXDocumentPortion</code> to which
* LaTeX code should be added
* @param oc the current context
*/
public void handleTable(Element node, Element caption, boolean bCaptionAbove,
LaTeXDocumentPortion ldp, Context oc) {
// Export table, if allowed by the configuration
switch (config.tableContent()) {
case LaTeXConfig.ACCEPT:
new SingleTableConverter().handleTable(node,caption,bCaptionAbove,ldp,oc);
bContainsTables = true;
break;
case LaTeXConfig.IGNORE:
// Ignore table silently
break;
case LaTeXConfig.WARNING:
System.err.println("Warning: Tables are not allowed");
break;
case LaTeXConfig.ERROR:
ldp.append("% Error in document: A table was ignored");
}
}
// Inner class to convert a single table
private class SingleTableConverter {
private TableReader table;
private TableFormatter formatter;
private Element caption;
private boolean bCaptionAbove;
private BeforeAfter baTable;
private BeforeAfter baTableAlign;
private RowType[] rowTypes;
// Return the paragraph style of the first paragraph/heading within this block text
private String getFirstParStyle(Element node) {
Node child = node.getFirstChild();
while (child!=null) {
if (Misc.isElement(child, XMLString.TEXT_P) || Misc.isElement(child, XMLString.TEXT_H)) {
String sStyleName = Misc.getAttribute(child, XMLString.TEXT_STYLE_NAME);
if (sStyleName!=null) {
StyleWithProperties style = ofr.getParStyle(sStyleName);
if (style!=null) {
if (style.isAutomatic()) {
sStyleName = style.getParentName();
}
return ofr.getParStyles().getDisplayName(sStyleName);
}
}
return null;
}
else if (OfficeReader.isTextElement(child)) {
return getFirstParStyle((Element)child);
}
child = child.getNextSibling();
}
return null;
}
private boolean hasRowType(RowType test) {
for (RowType type : rowTypes) {
if (type==test) { return true; }
}
return false;
}
private void handleTable(Element node, Element caption, boolean bCaptionAbove,
LaTeXDocumentPortion ldp, Context oc) {
// Store the caption
this.caption = caption;
this.bCaptionAbove = bCaptionAbove;
// Read the table
table = ofr.getTableReader(node);
if (palette.getMathCv().handleDisplayEquation(table,ldp)) { return; }
// Get formatter and update flags according to formatter
formatter = new TableFormatter(ofr,config,palette,table,!oc.isInMulticols(),oc.isInTable());
bContainsTables = true;
bNeedLongtable |= formatter.isLongtable();
bNeedSupertabular |= formatter.isSupertabular();
bNeedTabulary |= formatter.isTabulary();
bNeedColortbl |= formatter.isColortbl();
// Update the context
Context ic = (Context) oc.clone();
ic.setInTable(true);
ic.setInSimpleTable(formatter.isSimple());
// Never allow footnotes in tables
// (longtable.sty *does* allow footnotes in body, but not in head -
// only consistent solution is to disallow all footnotes)
ic.setNoFootnotes(true);
// Get table declarations
baTable = new BeforeAfter();
baTableAlign = new BeforeAfter();
formatter.applyTableStyle(baTable,baTableAlign,config.floatTables() && !ic.isInFrame() && !table.isSubTable());
// Identify the row types
rowTypes = new RowType[table.getRowCount()];
for (int nRow=0; nRow<table.getRowCount(); nRow++) {
// First collect the row type as defined in the document
if (nRow<table.getFirstBodyRow()) {
rowTypes[nRow] = RowType.HEAD;
}
else {
rowTypes[nRow] = RowType.BODY;
}
if (formatter.isLongtable() || formatter.isSupertabular()) {
// Then override with user defined row types where applicable
// (but only for multipage tables)
// The row type is determined from the first paragraph in the first cell
String sStyleName = getFirstParStyle(table.getCell(nRow, 0));
if (sStyleName!=null) {
if (sStyleName.equals(config.getTableFirstHeadStyle())) {
rowTypes[nRow] = RowType.FIRST_HEAD;
}
else if (sStyleName.equals(config.getTableHeadStyle())) {
rowTypes[nRow] = RowType.HEAD;
}
else if (sStyleName.equals(config.getTableFootStyle())) {
rowTypes[nRow] = RowType.FOOT;
}
else if (sStyleName.equals(config.getTableLastFootStyle())) {
rowTypes[nRow] = RowType.LAST_FOOT;
}
}
}
}
// Convert table
if (formatter.isSupertabular()) {
handleSupertabular(ldp,ic);
}
else if (formatter.isLongtable()) {
handleLongtable(ldp,ic);
}
else if (config.floatTables() && !ic.isInFrame() && !table.isSubTable()) {
handleTableFloat(ldp,ic);
}
else {
handleTabular(ldp,ic);
}
// Insert any pending footnotes
palette.getNoteCv().flushFootnotes(ldp,oc);
}
private void handleSupertabular(LaTeXDocumentPortion ldp, Context oc) {
ldp.append(baTableAlign.getBefore());
// Caption
if (caption!=null) {
handleCaption(bCaptionAbove ? "\\topcaption" : "\\bottomcaption", ldp, oc);
}
// Table head
ldp.append("\\tablefirsthead{");
if (hasRowType(RowType.FIRST_HEAD)) {
handleRows(ldp,oc,RowType.FIRST_HEAD,true,false);
}
else {
handleRows(ldp,oc,RowType.HEAD,true,false);
}
ldp.append("}\n");
ldp.append("\\tablehead{");
handleRows(ldp,oc,RowType.HEAD,true,false);
ldp.append("}\n");
// Table foot
ldp.append("\\tabletail{");
handleRows(ldp,oc,RowType.FOOT,true,true);
ldp.append("}\n");
ldp.append("\\tablelasttail{");
if (hasRowType(RowType.LAST_FOOT)) {
handleRows(ldp,oc,RowType.LAST_FOOT,true,true);
}
else {
handleRows(ldp,oc,RowType.FOOT,true,true);
}
ldp.append("}\n");
// The table body
handleHyperTarget(ldp);
ldp.append(baTable.getBefore()).nl();
handleRows(ldp,oc,RowType.BODY,true,!hasRowType(RowType.FOOT) && !hasRowType(RowType.LAST_FOOT));
ldp.nl().append(baTable.getAfter()).nl();
ldp.append(baTableAlign.getAfter());
}
private void handleLongtable(LaTeXDocumentPortion ldp, Context oc) {
handleHyperTarget(ldp);
ldp.append(baTable.getBefore()).nl();
// First head
if (caption!=null && bCaptionAbove) {
// If there's a caption above, we must use \endfirsthead
// and have to repeat the head if there's no first head
handleCaption("\\caption",ldp,oc);
ldp.append("\\\\").nl();
if (hasRowType(RowType.FIRST_HEAD)) {
handleRows(ldp,oc,RowType.FIRST_HEAD,true,true);
}
else {
handleRows(ldp,oc,RowType.HEAD,true,true);
}
ldp.nl().append("\\endfirsthead").nl();
}
else if (hasRowType(RowType.FIRST_HEAD)) {
// Otherwise we only need it if the table contains a first head
handleRows(ldp,oc,RowType.FIRST_HEAD,true,true);
ldp.nl().append("\\endfirsthead").nl();
}
// Head
handleRows(ldp,oc,RowType.HEAD,true,true);
ldp.nl().append("\\endhead").nl();
// Foot
handleRows(ldp,oc,RowType.FOOT,false,true);
ldp.nl().append("\\endfoot").nl();
// Last foot
if (caption!=null && !bCaptionAbove) {
// If there's a caption below, we must use \endlastfoot
// and have to repeat the foot if there's no last foot
if (hasRowType(RowType.LAST_FOOT)) {
handleRows(ldp,oc,RowType.LAST_FOOT,false,true);
ldp.nl();
}
else if (hasRowType(RowType.FOOT)){
handleRows(ldp,oc,RowType.FOOT,false,true);
ldp.nl();
}
handleCaption("\\caption",ldp,oc);
ldp.append("\\endlastfoot").nl();
}
else if (hasRowType(RowType.LAST_FOOT)) {
// Otherwise we only need it if the table contains a last foot
handleRows(ldp,oc,RowType.LAST_FOOT,false,true);
ldp.nl().append("\\endlastfoot").nl();
}
// Body
handleRows(ldp,oc,RowType.BODY,!hasRowType(RowType.HEAD) && !hasRowType(RowType.FIRST_HEAD),true);
ldp.nl().append(baTable.getAfter()).nl();
}
private void handleTableFloat(LaTeXDocumentPortion ldp, Context oc) {
ldp.append("\\begin{table}");
if (config.getFloatOptions().length()>0) {
ldp.append("[").append(config.getFloatOptions()).append("]");
}
ldp.nl();
ldp.append(baTableAlign.getBefore());
// Caption above
if (caption!=null && bCaptionAbove) {
handleCaption("\\caption",ldp,oc);
}
// The table
handleHyperTarget(ldp);
ldp.append(baTable.getBefore()).nl();
handleRows(ldp,oc,RowType.HEAD,true,true);
ldp.nl();
handleRows(ldp,oc,RowType.BODY,!hasRowType(RowType.HEAD),true);
ldp.append(baTable.getAfter()).nl();
// Caption below
if (caption!=null && !bCaptionAbove) {
handleCaption("\\caption",ldp,oc);
}
ldp.nl().append(baTableAlign.getAfter());
ldp.append("\\end{table}").nl();
}
private void handleTabular(LaTeXDocumentPortion ldp, Context oc) {
ldp.append(baTableAlign.getBefore());
// Caption above
if (caption!=null && bCaptionAbove) {
TableConverter.this.handleCaption(caption,ldp,oc);
}
// The table
handleHyperTarget(ldp);
ldp.append(baTable.getBefore()).nl();
handleRows(ldp,oc,RowType.HEAD,true,true);
ldp.nl();
handleRows(ldp,oc,RowType.BODY,!hasRowType(RowType.HEAD),true);
ldp.nl().append(baTable.getAfter()).nl();
// Caption below
if (caption!=null && !bCaptionAbove) {
TableConverter.this.handleCaption(caption,ldp,oc);
}
ldp.append(baTableAlign.getAfter());
}
private void handleCaption(String sCommand, LaTeXDocumentPortion ldp, Context oc) {
ldp.append(sCommand);
palette.getCaptionCv().handleCaptionBody(caption,ldp,oc,false);
}
private void handleHyperTarget(LaTeXDocumentPortion ldp) {
// We may need a hyperlink target
if (!table.isSubTable()) {
palette.getFieldCv().addTarget(table.getTableName(),"|table",ldp);
}
}
private void handleRows(LaTeXDocumentPortion ldp, Context oc, RowType rowType, boolean bLineBefore, boolean bLineAfter) {
int nRowCount = table.getRowCount();
int nColCount = table.getColCount();
boolean bFirst = true;
boolean bProtect = false; // Do we need to protect '['?
int nPreviousRow = -1;
for (int nRow=0; nRow<nRowCount; nRow++) {
if (rowTypes[nRow]==rowType) {
// Add interrow material from previous row, if any
if (nPreviousRow>-1) {
String sInterRowMaterial = formatter.getInterrowMaterial(nPreviousRow+1);
ldp.append(sInterRowMaterial).nl();
if (sInterRowMaterial.length()>0) { bProtect=false; }
}
nPreviousRow = nRow;
// If it's the first row, add top interrow material
if (bFirst && bLineBefore) {
String sInter = formatter.getInterrowMaterial(nRow);
if (sInter.length()>0) { ldp.append(sInter).nl(); }
bFirst=false;
}
// Export columns in this row
LaTeXDocumentPortion rowLdp = new LaTeXDocumentPortion(true);
Context icRow = (Context) oc.clone();
BeforeAfter baRow = new BeforeAfter();
formatter.applyRowStyle(nRow,baRow,icRow);
if (!baRow.isEmpty()) {
rowLdp.append(baRow.getBefore());
if (!formatter.isSimple()) { rowLdp.nl(); }
}
int nCol = 0;
while (nCol<nColCount) {
Element cell = (Element) table.getCell(nRow,nCol);
if (cell!=null) {
if (XMLString.TABLE_TABLE_CELL.equals(cell.getNodeName())) {
Context icCell = (Context) icRow.clone();
BeforeAfter baCell = new BeforeAfter();
formatter.applyCellStyle(nRow,nCol,baCell,icCell);
rowLdp.append(baCell.getBefore());
if (nCol==nColCount-1) { icCell.setInLastTableColumn(true); }
palette.getBlockCv().traverseBlockText(cell,rowLdp,icCell);
rowLdp.append(baCell.getAfter());
}
// Otherwise ignore; the cell is covered by a \multicolumn entry.
// (table:covered-table-cell)
int nColSpan = Misc.getPosInteger(cell.getAttribute(
XMLString.TABLE_NUMBER_COLUMNS_SPANNED),1);
if (nCol+nColSpan<nColCount) {
if (formatter.isSimple()) { rowLdp.append(" & "); }
else { rowLdp.append(" &").nl(); }
}
nCol+=nColSpan;
}
else { // Non-existing cell, ignore (assuming it is a trailing cell)
nCol++;
}
}
rowLdp.append("\\\\");
// We have to translate the row to a string to avoid extra newlines and to see the first characters
String sRowLdp = rowLdp.toString();
// Protect leading [
if (bProtect && ((sRowLdp.length()>0 && sRowLdp.charAt(0)=='[') || sRowLdp.startsWith("\n["))) {
ldp.append("{}");
}
ldp.append(sRowLdp);
bProtect = true;
}
}
// Add interrow material from last row, if required
if (nPreviousRow>-1 && bLineAfter) {
ldp.append(formatter.getInterrowMaterial(nPreviousRow+1));
}
}
}
}

View file

@ -0,0 +1,473 @@
/************************************************************************
*
* TableFormatter.java
*
* Copyright: 2002-2015 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.6 (2015-04-15)
*
*/
package writer2latex.latex;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import writer2latex.util.*;
import writer2latex.office.*;
import writer2latex.latex.util.BeforeAfter;
import writer2latex.latex.util.Context;
/**
* <p>This class converts OOo table styles to LaTeX.</p>
* <p> In OOo the table style is distributed on table, column and cell styles.
* <p> In LaTeX we have to rearrange this information slightly, so this class
* takes care of that.</p>
*/
public class TableFormatter extends ConverterHelper {
//private boolean bApplyCellFormat;
private TableReader table;
private char[][] cAlign;
private char[] cGlobalAlign;
private boolean[][] bHBorder;
private boolean[][] bVBorder;
private boolean[] bGlobalVBorder;
private String[] sRowColor;
private String[][] sCellColor;
private String[] sColumnWidth;
private boolean bIsLongtable;
private boolean bIsSupertabular;
private boolean bIsTabulary;
private boolean bIsColortbl;
private boolean bIsSimple;
/** <p>Constructor: Create from a TableReader.</p>
*/
public TableFormatter(OfficeReader ofr, LaTeXConfig config, ConverterPalette palette,
TableReader table, boolean bAllowPageBreak, boolean bIsInTable) {
super(ofr,config,palette);
this.table = table;
//bApplyCellFormat = config.formatting()>=LaTeXConfig.CONVERT_MOST;
int nRowCount = table.getRowCount();
int nColCount = table.getColCount();
int nSimpleTableLimit = config.getSimpleTableLimit();
// Step 1: Collect alignment and identify simple tables
bIsSimple = true;
cAlign = new char[nRowCount][nColCount];
cGlobalAlign = new char[nColCount];
// Keep track of characters to be counted
int[] nPendingChars = new int[nRowCount];
int[] nPendingColSpan = new int[nRowCount];
int nTableWidth = 0;
for (int nCol=0; nCol<nColCount; nCol++) {
// Collect chars to be counted in this column
for (int nRow=0; nRow<nRowCount; nRow++) {
Element cell = table.getCell(nRow, nCol);
if (cell!=null && Misc.isElement(cell, XMLString.TABLE_TABLE_CELL)) {
// Now we're here: Collect alignment
if (OfficeReader.isSingleParagraph(cell)) {
Node par = Misc.getChildByTagName(cell,XMLString.TEXT_P);
StyleWithProperties style = ofr.getParStyle(Misc.getAttribute(par,XMLString.TEXT_STYLE_NAME));
cAlign[nRow][nCol] = 'l';
if (style!=null) {
String sAlign = style.getProperty(XMLString.FO_TEXT_ALIGN,true);
if ("center".equals(sAlign)) { cAlign[nRow][nCol] = 'c'; }
else if ("end".equals(sAlign)) { cAlign[nRow][nCol] = 'r'; }
}
}
else {
// Found cell with more than one paragraph
bIsSimple = false;
}
// Collect characters (the cell contains this many characters that should be distributed over that many columns)
nPendingChars[nRow] = OfficeReader.getCharacterCount(cell);
nPendingColSpan[nRow] = Misc.getPosInteger(cell.getAttribute(XMLString.TABLE_NUMBER_COLUMNS_SPANNED), 1);
}
}
// Determine the number of characters to count *now* (because they cannot be postponed to next column)
int nColChars = 0;
for (int nRow=0; nRow<nRowCount; nRow++) {
if (nPendingColSpan[nRow]==1) {
nColChars = Math.max(nColChars, nPendingChars[nRow]);
}
}
// Reduce pending chars and increase table width
nTableWidth += nColChars;
for (int nRow=0; nRow<nRowCount; nRow++) {
if (nPendingColSpan[nRow]>=1) {
nPendingChars[nRow] = Math.max(0, nPendingChars[nRow]-nColChars);
nPendingColSpan[nRow]--;
}
}
}
if (nTableWidth>nSimpleTableLimit) bIsSimple = false;
// Step 2: Create global alignment
for (int nCol=0; nCol<nColCount; nCol++) {
int nCenter = 0;
int nRight = 0;
for (int nRow=0; nRow<nRowCount; nRow++) {
if (cAlign[nRow][nCol]=='c') { nCenter++; }
else if (cAlign[nRow][nCol]=='r') { nRight++; }
}
cGlobalAlign[nCol] = 'l';
int nLeft = nColCount-nCenter-nRight;
if (nCenter>nLeft) {
if (nRight>nLeft) { cGlobalAlign[nCol] = 'r'; }
else { cGlobalAlign[nCol] = 'c'; }
}
else if (nRight>nLeft) {
cGlobalAlign[nCol] = 'r';
}
}
// Step 3: Initialize borders:
bHBorder = new boolean[nRowCount+1][nColCount];
for (int nRow=0; nRow<=nRowCount; nRow++) {
for (int nCol=0; nCol<nColCount; nCol++) {
bHBorder[nRow][nCol] = false;
}
}
bVBorder = new boolean[nRowCount][nColCount+1];
for (int nRow=0; nRow<nRowCount; nRow++) {
for (int nCol=0; nCol<=nColCount; nCol++) {
bVBorder[nRow][nCol] = false;
}
}
// Step 4: Collect borders from cell styles:
for (int nRow=0; nRow<nRowCount; nRow++) {
int nCol = 0;
while (nCol<nColCount) {
Node cell = table.getCell(nRow,nCol);
if (cell!=null) {
String sStyleName = Misc.getAttribute(cell,XMLString.TABLE_STYLE_NAME);
StyleWithProperties style = ofr.getCellStyle(sStyleName);
int nColSpan = Misc.getPosInteger(Misc.getAttribute(cell,
XMLString.TABLE_NUMBER_COLUMNS_SPANNED),1);
boolean bLeft = false;
boolean bRight = false;
boolean bTop = false;
boolean bBottom = false;
if (style!=null) {
String sBorder = style.getProperty(XMLString.FO_BORDER);
if (sBorder!=null && !"none".equals(sBorder)) {
bLeft = true; bRight = true; bTop = true; bBottom = true;
}
sBorder = style.getProperty(XMLString.FO_BORDER_LEFT);
if (sBorder!=null && !"none".equals(sBorder)) {
bLeft = true;
}
sBorder = style.getProperty(XMLString.FO_BORDER_RIGHT);
if (sBorder!=null && !"none".equals(sBorder)) {
bRight = true;
}
sBorder = style.getProperty(XMLString.FO_BORDER_TOP);
if (sBorder!=null && !"none".equals(sBorder)) {
bTop = true;
}
sBorder = style.getProperty(XMLString.FO_BORDER_BOTTOM);
if (sBorder!=null && !"none".equals(sBorder)) {
bBottom = true;
}
}
bVBorder[nRow][nCol] |= bLeft;
bVBorder[nRow][nCol+nColSpan] |= bRight;
do {
bHBorder[nRow][nCol] |= bTop;
bHBorder[nRow+1][nCol] |= bBottom;
nCol++;
} while (--nColSpan>0);
}
else { // Non-existing cell, treat as empty cell with no borders
nCol++;
}
}
}
// Step 5: Create global vertical borders based on simple majority
// (in order to minimize the number of \multicolum{1} entries)
bGlobalVBorder = new boolean[nColCount+1];
for (int nCol=0; nCol<=nColCount; nCol++) {
int nBalance = 0;
for (int nRow=0; nRow<nRowCount; nRow++) {
nBalance += bVBorder[nRow][nCol] ? 1 : -1;
}
bGlobalVBorder[nCol] = nBalance>0;
}
// Step 6: Get background colors
sRowColor = new String[nRowCount];
sCellColor = new String[nRowCount][nColCount];
if (config.useColortbl()) {
// Table background
String sTableColor = null;
StyleWithProperties tableStyle = ofr.getTableStyle(table.getTableStyleName());
if (tableStyle!=null) {
sTableColor = tableStyle.getProperty(XMLString.FO_BACKGROUND_COLOR);
}
// Row background
for (int nRow=0; nRow<nRowCount; nRow++) {
StyleWithProperties rowStyle = ofr.getRowStyle(table.getRow(nRow).getStyleName());
if (rowStyle!=null) {
sRowColor[nRow] = rowStyle.getProperty(XMLString.FO_BACKGROUND_COLOR);
}
if (sRowColor[nRow]==null) {
sRowColor[nRow] = sTableColor;
}
if (sRowColor[nRow]!=null) {
bIsColortbl = true;
}
}
// Cell background
for (int nRow=0; nRow<nRowCount; nRow++) {
for (int nCol=0; nCol<nColCount; nCol++) {
StyleWithProperties cellStyle = ofr.getCellStyle(Misc.getAttribute(table.getCell(nRow,nCol),XMLString.TABLE_STYLE_NAME));
if (cellStyle!=null) {
sCellColor[nRow][nCol] = cellStyle.getProperty(XMLString.FO_BACKGROUND_COLOR);
if (sCellColor[nRow][nCol]!=null) {
bIsColortbl = true;
if (sCellColor[nRow][nCol].equals(sRowColor[nRow])) {
// Avoid redundant cell background
sCellColor[nRow][nCol] = null;
}
}
}
}
}
}
// Step 7: Read column style information
sColumnWidth = new String[nColCount];
for (int nCol=0; nCol<nColCount; nCol++) {
StyleWithProperties colStyle
= ofr.getColumnStyle(table.getCol(nCol).getStyleName());
if (colStyle!=null) {
sColumnWidth[nCol]
= colStyle.getProperty(XMLString.STYLE_COLUMN_WIDTH);
}
if (sColumnWidth[nCol]==null) { // Emergency! should never happen!
sColumnWidth[nCol]="2cm";
}
}
// Step 8: Identify longtable, supertabular or tabulary
bIsLongtable = false; bIsSupertabular = false; bIsTabulary = false;
if (!table.isSubTable() && !bIsInTable) {
String sStyleName = table.getTableStyleName();
StyleWithProperties style = ofr.getTableStyle(sStyleName);
boolean bMayBreak = style==null ||
!"false".equals(style.getProperty(XMLString.STYLE_MAY_BREAK_BETWEEN_ROWS));
if (config.useLongtable() && bMayBreak && bAllowPageBreak) {
bIsLongtable = true;
}
else if (config.useSupertabular() && bMayBreak && bAllowPageBreak) {
bIsSupertabular = true;
}
else if (!bIsSimple && config.useTabulary()) {
bIsTabulary = true;
}
}
}
@Override public void appendDeclarations(LaTeXDocumentPortion pack, LaTeXDocumentPortion decl) {
}
/** is this a longtable? */
public boolean isLongtable() { return bIsLongtable; }
/** is this a supertabular? */
public boolean isSupertabular() { return bIsSupertabular; }
/** is this a tabulary? */
public boolean isTabulary() { return bIsTabulary; }
/** is this a colortbl? */
public boolean isColortbl() { return bIsColortbl; }
/** is this a simple table (lcr columns rather than p{})? */
public boolean isSimple() { return bIsSimple; }
/**
* <p>Create table environment based on table style.</p>
* <p>Returns eg. "\begin{longtable}{m{2cm}|m{4cm}}", "\end{longtable}".</p>
* @param ba the <code>BeforeAfter</code> to contain the table code
* @param baAlign the <code>BeforeAfter</code> to contain the alignment code, if it's separate
* @param bInFloat true if the table should be floating
*/
public void applyTableStyle(BeforeAfter ba, BeforeAfter baAlign, boolean bInFloat) {
// Read formatting info from table style
// Only supported properties are alignment and may-break-between-rows.
String sStyleName = table.getTableStyleName();
StyleWithProperties style = ofr.getTableStyle(sStyleName);
char cAlign = 'c';
if (style!=null && !table.isSubTable()) {
String s = style.getProperty(XMLString.TABLE_ALIGN);
if ("left".equals(s)) { cAlign='l'; }
else if ("right".equals(s)) { cAlign='r'; }
}
String sAlign="center";
switch (cAlign) {
case 'c': sAlign="center"; break;
case 'r': sAlign="flushright"; break;
case 'l': sAlign="flushleft";
}
// Create table alignment (for supertabular, tabular and tabulary)
if (!bIsLongtable && !table.isSubTable()) {
if (bInFloat & !bIsSupertabular) {
// Inside a float we don't want the extra glue added by the flushleft/center/flushright environment
switch (cAlign) {
case 'c' : baAlign.add("\\centering\n", ""); break;
case 'r' : baAlign.add("\\raggedleft\n", ""); break;
case 'l' : baAlign.add("\\raggedright\n", "");
}
}
else {
// But outside floats we do want it
baAlign.add("\\begin{"+sAlign+"}\n","\\end{"+sAlign+"}\n");
}
}
// Create table declaration
if (bIsLongtable) {
ba.add("\\begin{longtable}["+cAlign+"]", "\\end{longtable}");
}
else if (bIsSupertabular) {
ba.add("\\begin{supertabular}","\\end{supertabular}");
}
else if (bIsTabulary) {
ba.add("\\begin{tabulary}{"+table.getTableWidth()+"}","\\end{tabulary}");
}
else if (!table.isSubTable()) {
ba.add("\\begin{tabular}","\\end{tabular}");
}
else { // subtables should occupy the entire width, including padding!
ba.add("\\hspace*{-\\tabcolsep}\\begin{tabular}",
"\\end{tabular}\\hspace*{-\\tabcolsep}");
}
// columns
ba.add("{","");
if (bGlobalVBorder[0]) { ba.add("|",""); }
int nColCount = table.getColCount();
for (int nCol=0; nCol<nColCount; nCol++){
if (bIsSimple) {
ba.add(Character.toString(cGlobalAlign[nCol]),"");
}
else if (!bIsTabulary) {
// note: The column width in OOo includes padding, which we subtract
ba.add("m{"+Calc.add(sColumnWidth[nCol],"-0.2cm")+"}","");
}
else {
ba.add("J","");
}
if (bGlobalVBorder[nCol+1]) { ba.add("|",""); }
}
ba.add("}","");
}
/** <p>Create interrow material</p> */
public String getInterrowMaterial(int nRow) {
int nColCount = table.getColCount();
int nCount = 0;
for (int nCol=0; nCol<nColCount; nCol++) {
if (bHBorder[nRow][nCol]) { nCount++; }
}
if (nCount==0) { // no borders at this row
return "";
}
else if (nCount==nColCount) { // complete set of borders
return "\\hline";
}
else { // individual borders for each column
StringBuilder buf = new StringBuilder();
buf.append("\\hhline{");
for (int nCol=0; nCol<nColCount; nCol++) {
if (bHBorder[nRow][nCol]) { buf.append("-"); }
else { buf.append("~"); }
}
buf.append("}");
/* TODO: hhline.sty should be optional, and i not used, do as before:
boolean bInCline = false;
for (int nCol=0; nCol<nColCount; nCol++) {
if (bInCline && !bHBorder[nRow][nCol]) { // close \cline
buf.append(nCol).append("}");
bInCline = false;
}
else if (!bInCline && bHBorder[nRow][nCol]) { // open \cline
buf.append("\\cline{").append(nCol+1).append("-");
bInCline = true;
}
}
if (bInCline) { buf.append(nColCount).append("}"); }
*/
return buf.toString();
}
}
/** <p>Get material to put before a table row (background color)
*/
public void applyRowStyle(int nRow, BeforeAfter ba, Context context) {
palette.getColorCv().applyBgColor("\\rowcolor",sRowColor[nRow],ba,context);
}
/** Get material to put before and after a table cell.
* In case of columnspan or different borders this will contain a \multicolumn command.
*/
public void applyCellStyle(int nRow, int nCol, BeforeAfter ba, Context context) {
Node cell = table.getCell(nRow,nCol);
int nColSpan = Misc.getPosInteger(Misc.getAttribute(cell,
XMLString.TABLE_NUMBER_COLUMNS_SPANNED),1);
// Construct column declaration as needed
boolean bNeedLeft = (nCol==0) && (bVBorder[nRow][0]!=bGlobalVBorder[0]);
boolean bNeedRight = bVBorder[nRow][nCol+1]!=bGlobalVBorder[nCol+1];
boolean bNeedAlign = bIsSimple && cGlobalAlign[nCol]!=cAlign[nRow][nCol];
// calculate column width
String sTotalColumnWidth = sColumnWidth[nCol];
for (int i=nCol+1; i<nCol+nColSpan; i++) {
sTotalColumnWidth = Calc.add(sTotalColumnWidth,sColumnWidth[i]);
}
sTotalColumnWidth = Calc.add(sTotalColumnWidth,"-0.2cm");
if (bNeedAlign || bNeedLeft || bNeedRight || nColSpan>1) {
ba.add("\\multicolumn{"+nColSpan+"}{","");
if (nCol==0 && bVBorder[nRow][0]) { ba.add("|",""); }
if (bIsSimple) {
ba.add(Character.toString(cAlign[nRow][nCol]),"");
}
else {
ba.add("m{"+sTotalColumnWidth+"}","");
}
if (bVBorder[nRow][nCol+nColSpan]) { ba.add("|",""); }
ba.add("}{","}");
}
palette.getColorCv().applyBgColor("\\cellcolor",sCellColor[nRow][nCol],ba,context);
}
}

View file

@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- clean.xml
This is a sample configuration file for Writer2LaTeX.
The options are set to produce a clean
LaTeX file from an arbitrary Writer document
- at the expense of loss of formatting.
An even cleaner LaTeX file is produced with ultraclean.xml.
-->
<config>
<option name="documentclass" value="article" />
<option name="backend" value="generic" />
<option name="inputencoding" value="ascii" />
<option name="multilingual" value="false" />
<option name="use_ooomath" value="false" />
<option name="use_color" value="true" />
<option name="use_colortbl" value="false" />
<option name="use_geometry" value="true" />
<option name="use_fancyhdr" value="false" />
<option name="use_hyperref" value="true" />
<option name="use_caption" value="true" />
<option name="use_endnotes" value="false" />
<option name="use_bibtex" value="true" />
<option name="bibtex_style" value="plain" />
<option name="formatting" value="ignore_most" />
<option name="page_formatting" value="convert_geometry" />
<option name="ignore_empty_paragraphs" value="true" />
<option name="ignore_hard_page_breaks" value="false" />
<option name="ignore_hard_line_breaks" value="false" />
<option name="ignore_double_spaces" value="true" />
<option name="display_hidden_text" value="false" />
<option name="debug" value="false" />
<heading-map max-level="5">
<heading-level-map writer-level="1" name="section" level="1" />
<heading-level-map writer-level="2" name="subsection" level="2" />
<heading-level-map writer-level="3" name="subsubsection" level="3" />
<heading-level-map writer-level="4" name="paragraph" level="4" />
<heading-level-map writer-level="5" name="subparagraph" level="5" />
</heading-map>
<custom-preamble />
<!-- Style maps: These rules defines how styles in OOo are mapped to LaTeX code.
A number of predefined Writer styles are converted -->
<!-- "Title" is mapped to \maketitle. If the user chooses to export meta data,
the author and date will be inserted automatically -->
<style-map name="Title" class="paragraph" before="\title{" after="}&#10;\maketitle" line-break="false" />
<!-- "Quotations" is mapped to a quotation environment -->
<style-map name="Quotations" family="paragraph-block" next="Quotations" before="\begin{quotation}" after="\end{quotation}" />
<style-map name="Quotations" family="paragraph" before="" after="" />
<!-- Preformatted Text is mapped to a verbatim environment
Note the attribute verbatim, which instructs OOo to output the content
verbatim (characters not available in the inputencoding will be replaced
by question marks; other content will be lost). -->
<style-map name="Preformatted Text" family="paragraph-block" next="Preformatted Text" before="\begin{verbatim}" after="\end{verbatim}" />
<style-map name="Preformatted Text" family="paragraph" before="" after="" verbatim="true" />
<!-- "Horizontal line" is mapped to a \hrule -->
<style-map name="Horizontal Line" family="paragraph" before="" after="&#10;\begin{center}\hrule\end{center}" />
<!-- "Emphasis" text style is mapped to \emph -->
<style-map name="Emphasis" family="text" before="\emph{" after="}" />
<!-- "Strong Emphasis" text style is mapped to \textbf -->
<style-map name="Strong Emphasis" family="text" before="\textbf{" after="}" />
<!-- "Teletype" text style is mapped to \texttt -->
<style-map name="Teletype" family="text" before="\texttt{" after="}" />
<!-- "List Heading" and "List Contents" are mapped to a description environment -->
<style-map name="List Heading" family="paragraph-block" next="List Heading;List Contents" before="\begin{description}" after="\end{description}"/>
<style-map name="List Heading" family="paragraph" before="\item[" after="]" line-break="false" />
<style-map name="List Contents" family="paragraph" before="" after="" />
</config>

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- default.xml
This is a sample configuration file for Writer2LaTeX.
It sets no options, thus using defaults everywhere
-->
<config/>

View file

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- pdfprint.xml
This is a sample configuration file for Writer2LaTeX.
The output will be for pdfTeX, using all the packages pifont, ifsym,
wasysym, eurosym, hyperref, endnotes and ulem. -->
<config>
<option name="documentclass" value="article" />
<option name="backend" value="pdftex" />
<option name="inputencoding" value="ascii" />
<option name="use_geometry" value="true" />
<option name="use_fancyhdr" value="true" />
<option name="use_ooomath" value="false" />
<option name="use_pifont" value="true" />
<option name="use_ifsym" value="true" />
<option name="use_wasysym" value="true" />
<option name="use_eurosym" value="true" />
<option name="use_color" value="true" />
<option name="use_colortbl" value="true" />
<option name="use_hyperref" value="true" />
<option name="use_endnotes" value="true" />
<option name="use_ulem" value="true" />
<option name="use_lastpage" value="true" />
<option name="formatting" value="convert_all" />
<option name="page_formatting" value="convert_all" />
<option name="ignore_empty_paragraphs" value="false" />
<option name="ignore_hard_page_breaks" value="false" />
<option name="ignore_hard_line_breaks" value="false" />
<option name="ignore_double_spaces" value="false" />
<option name="display_hidden_text" value="false" />
<option name="debug" value="false" />
<heading-map max-level="5">
<heading-level-map writer-level="1" name="section" level="1" />
<heading-level-map writer-level="2" name="subsection" level="2" />
<heading-level-map writer-level="3" name="subsubsection" level="3" />
<heading-level-map writer-level="4" name="paragraph" level="4" />
<heading-level-map writer-level="5" name="subparagraph" level="5" />
</heading-map>
<!-- We add \sloppy to avoid overful hboxes. To get better results,
this should be removed and overful hboxes fixed by hand. -->
<custom-preamble>\sloppy</custom-preamble>
</config>

View file

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- pdfscreen.xml
This is a sample configuration file for Writer2LaTeX.
The output will be for pdfTeX, using all the packages pifont, ifsym,
wasysym, eurosym, hyperref, endnotes and ulem.
The package pdfscreen.sty will be loaded to create a pdf file
suitable for viewing on screen. -->
<config>
<option name="documentclass" value="article" />
<option name="backend" value="pdftex" />
<option name="inputencoding" value="ascii" />
<option name="use_ooomath" value="false" />
<option name="use_pifont" value="true" />
<option name="use_ifsym" value="true" />
<option name="use_wasysym" value="true" />
<option name="use_bbding" value="false" />
<option name="use_eurosym" value="true" />
<option name="use_color" value="true" />
<option name="use_colortbl" value="true" />
<option name="use_hyperref" value="true" />
<option name="use_endnotes" value="true" />
<option name="use_ulem" value="true" />
<option name="use_lastpage" value="true" />
<option name="formatting" value="convert_all" />
<option name="page_formatting" value="ignore_all" />
<option name="ignore_empty_paragraphs" value="false" />
<option name="ignore_hard_page_breaks" value="false" />
<option name="ignore_hard_line_breaks" value="false" />
<option name="ignore_double_spaces" value="true" />
<option name="display_hidden_text" value="false" />
<option name="debug" value="false" />
<heading-map max-level="5">
<heading-level-map writer-level="1" name="section" level="1" />
<heading-level-map writer-level="2" name="subsection" level="2" />
<heading-level-map writer-level="3" name="subsubsection" level="3" />
<heading-level-map writer-level="4" name="paragraph" level="4" />
<heading-level-map writer-level="5" name="subparagraph" level="5" />
</heading-map>
<!-- load pdfscreen.sty with suitable options. (Note that export of page
formatting is disabled above; pdfscreen.sty takes care of page setup.)
The lines are relatively short, so we add \sloppy to avoid overful hboxes.
-->
<custom-preamble>\usepackage{palatino}
\usepackage[bluelace,screen,nopanel,sectionbreak]{pdfscreen}
%\hypersetup{pdfpagemode={FullScreen}}
\margins{0.5in}{0.5in}{0.5in}{0.5in}
\screensize{6in}{8in}
\sloppy</custom-preamble>
</config>

View file

@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- ultraclean.xml
This is a sample configuration file for Writer2LaTeX.
The options are set to produce the cleanest possible
LaTeX file from an arbitrary Writer document
- by removing practically all formatting.
-->
<config>
<option name="documentclass" value="article" />
<option name="backend" value="generic" />
<option name="inputencoding" value="ascii" />
<option name="multilingual" value="false" />
<option name="use_ooomath" value="false" />
<option name="use_color" value="false" />
<option name="use_colortbl" value="false" />
<option name="use_geometry" value="false" />
<option name="use_fancyhdr" value="false" />
<option name="use_hyperref" value="false" />
<option name="use_caption" value="true" />
<option name="use_endnotes" value="false" />
<option name="use_bibtex" value="true" />
<option name="bibtex_style" value="plain" />
<option name="formatting" value="ignore_all" />
<option name="page_formatting" value="ignore_all" />
<option name="ignore_empty_paragraphs" value="true" />
<option name="ignore_hard_page_breaks" value="true" />
<option name="ignore_hard_line_breaks" value="true" />
<option name="ignore_double_spaces" value="true" />
<option name="display_hidden_text" value="false" />
<option name="debug" value="false" />
<heading-map max-level="5">
<heading-level-map writer-level="1" name="section" level="1" />
<heading-level-map writer-level="2" name="subsection" level="2" />
<heading-level-map writer-level="3" name="subsubsection" level="3" />
<heading-level-map writer-level="4" name="paragraph" level="4" />
<heading-level-map writer-level="5" name="subparagraph" level="5" />
</heading-map>
<custom-preamble />
<!-- Style maps: These rules defines how styles in OOo are mapped to LaTeX code.
A number of predefined Writer styles are converted -->
<!-- "Title" is mapped to \maketitle. If the user chooses to export meta data,
the author and date will be inserted automatically -->
<style-map name="Title" class="paragraph" before="\title{" after="}&#10;\maketitle" line-break="false" />
<!-- "Quotations" is mapped to a quotation environment -->
<style-map name="Quotations" family="paragraph-block" next="Quotations" before="\begin{quotation}" after="\end{quotation}" />
<style-map name="Quotations" family="paragraph" before="" after="" />
<!-- Preformatted Text is mapped to a verbatim environment
Note the attribute verbatim, which instructs OOo to output the content
verbatim (characters not available in the inputencoding will be replaced
by question marks; other content will be lost). -->
<style-map name="Preformatted Text" family="paragraph-block" next="Preformatted Text" before="\begin{verbatim}" after="\end{verbatim}" />
<style-map name="Preformatted Text" family="paragraph" before="" after="" verbatim="true" />
<!-- "Horizontal line" is mapped to a \hrule -->
<style-map name="Horizontal Line" family="paragraph" before="" after="&#10;\begin{center}\hrule\end{center}" />
<!-- "Emphasis" text style is mapped to \emph -->
<style-map name="Emphasis" family="text" before="\emph{" after="}" />
<!-- "Strong Emphasis" text style is mapped to \textbf -->
<style-map name="Strong Emphasis" family="text" before="\textbf{" after="}" />
<!-- "Teletype" text style is mapped to \texttt -->
<style-map name="Teletype" family="text" before="\texttt{" after="}" />
<!-- "List Heading" and "List Contents" are mapped to a description environment -->
<style-map name="List Heading" family="paragraph-block" next="List Heading;List Contents" before="\begin{description}" after="\end{description}"/>
<style-map name="List Heading" family="paragraph" before="\item[" after="]" line-break="false" />
<style-map name="List Contents" family="paragraph" before="" after="" />
</config>

View file

@ -0,0 +1,773 @@
/************************************************************************
*
* ClassicI18n.java
*
* Copyright: 2002-2015 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.6 (2015-05-12)
*
*/
package writer2latex.latex.i18n;
import java.io.InputStream;
import java.util.Hashtable;
import java.util.Stack;
import java.util.Iterator;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.parsers.SAXParser;
import writer2latex.util.CSVList;
import writer2latex.latex.LaTeXConfig;
import writer2latex.latex.LaTeXDocumentPortion;
import writer2latex.latex.ConverterPalette;
import writer2latex.latex.util.BeforeAfter;
import writer2latex.office.OfficeReader;
import writer2latex.office.StyleWithProperties;
import writer2latex.office.XMLString;
/** This class (and the helpers in the same package) takes care of i18n in
* Writer2LaTeX. In classic LaTeX, i18n is a mixture of inputencodings, fontencodings
* and babel languages. The class ClassicI18n thus manages these, and in particular
* implements a Unicode->LaTeX translation that can handle different
* inputencodings and fontencodings.
* The translation is table driven, using symbols.xml (embedded in the jar)
* Various sections of symbols.xml handles different cases:
* <ul>
* <li>common symbols in various font encodings such as T1, T2A, LGR etc.</li>
* <li>input encodings such as ISO-8859-1 (latin-1), ISO-8859-7 (latin/greek) etc.</li>
* <li>additional symbol fonts such as wasysym, dingbats etc.</li>
* <li>font-specific symbols, eg. for 8-bit fonts/private use area</li>
* </ul>
* The class uses the packages inputenc, fontenc, babel, tipa, bbding,
* ifsym, pifont, eurosym, amsmath, wasysym, amssymb, amsfonts and textcomp
* in various combinations depending on the configuration.
*/
public class ClassicI18n extends I18n {
// **** Static data and methods: Inputencodings ****
public static final int ASCII = 0;
public static final int LATIN1 = 1; // ISO Latin 1 (ISO-8859-1)
public static final int LATIN2 = 2; // ISO Latin 1 (ISO-8859-1)
public static final int ISO_8859_7 = 3; // ISO latin/greek
public static final int CP1250 = 4; // Microsoft Windows Eastern European
public static final int CP1251 = 5; // Microsoft Windows Cyrillic
public static final int KOI8_R = 6; // Latin/russian
public static final int UTF8 = 7; // UTF-8
// Read an inputencoding from a string
public static final int readInputenc(String sInputenc) {
if ("ascii".equals(sInputenc)) return ASCII;
else if ("latin1".equals(sInputenc)) return LATIN1;
else if ("latin2".equals(sInputenc)) return LATIN2;
else if ("iso-8859-7".equals(sInputenc)) return ISO_8859_7;
else if ("cp1250".equals(sInputenc)) return CP1250;
else if ("cp1251".equals(sInputenc)) return CP1251;
else if ("koi8-r".equals(sInputenc)) return KOI8_R;
else if ("utf8".equals(sInputenc)) return UTF8;
else return ASCII; // unknown = ascii
}
// Return the LaTeX name of an inputencoding
public static final String writeInputenc(int nInputenc) {
switch (nInputenc) {
case ASCII : return "ascii";
case LATIN1 : return "latin1";
case LATIN2 : return "latin2";
case ISO_8859_7 : return "iso-8859-7";
case CP1250 : return "cp1250";
case CP1251 : return "cp1251";
case KOI8_R : return "koi8-r";
case UTF8 : return "utf8";
default : return "???";
}
}
// Return the java i18n name of an inputencoding
public static final String writeJavaEncoding(int nInputenc) {
switch (nInputenc) {
case ASCII : return "ASCII";
case LATIN1 : return "ISO8859_1";
case LATIN2 : return "ISO8859_2";
case ISO_8859_7 : return "ISO8859_7";
case CP1250 : return "Cp1250";
case CP1251 : return "Cp1251";
case KOI8_R : return "KOI8_R";
case UTF8 : return "UTF-8";
default : return "???";
}
}
// **** Static data and methods: Fontencodings ****
private static final int T1_ENC = 1;
private static final int T2A_ENC = 2;
private static final int T3_ENC = 4;
private static final int LGR_ENC = 8;
private static final int ANY_ENC = 15;
// read set of font encodings from a string
public static final int readFontencs(String sFontencs) {
sFontencs = sFontencs.toUpperCase();
if ("ANY".equals(sFontencs)) return ANY_ENC;
int nFontencs = 0;
if (sFontencs.indexOf("T1")>=0) nFontencs+=T1_ENC;
if (sFontencs.indexOf("T2A")>=0) nFontencs+=T2A_ENC;
if (sFontencs.indexOf("T3")>=0) nFontencs+=T3_ENC;
if (sFontencs.indexOf("LGR")>=0) nFontencs+=LGR_ENC;
return nFontencs;
}
// return string representation of a single font encoding
/*private static final String writeFontenc(int nFontenc) {
switch (nFontenc) {
case T1_ENC: return "T1";
case T2A_ENC: return "T2A";
case T3_ENC: return "T3";
case LGR_ENC: return "LGR";
}
return null;
}*/
// check that a given set of font encodings contains a specific font encoding
private static final boolean supportsFontenc(int nFontencs, int nFontenc) {
return (nFontencs & nFontenc) != 0;
}
// get one fontencoding from a set of fontencodings
private static final int getFontenc(int nFontencs) {
if (supportsFontenc(nFontencs,T1_ENC)) return T1_ENC;
if (supportsFontenc(nFontencs,T2A_ENC)) return T2A_ENC;
if (supportsFontenc(nFontencs,T3_ENC)) return T3_ENC;
if (supportsFontenc(nFontencs,LGR_ENC)) return LGR_ENC;
return 0;
}
// get the font encoding for a specific iso language
private static final int getFontenc(String sLang) {
// Greek uses "local greek" encoding
if ("el".equals(sLang)) return LGR_ENC;
// Russian, ukrainian, bulgarian and serbian uses T2A encoding
else if ("ru".equals(sLang)) return T2A_ENC;
else if ("uk".equals(sLang)) return T2A_ENC;
else if ("bg".equals(sLang)) return T2A_ENC;
else if ("sr".equals(sLang)) return T2A_ENC;
// Other languages uses T1 encoding
else return T1_ENC;
}
// return cs for a fontencoding
private static final String getFontencCs(int nFontenc) {
switch (nFontenc) {
case T1_ENC: return "\\textlatin"; // requires babel
case T2A_ENC: return "\\textcyrillic"; // requires babel with russian, bulgarian or ukrainian option
case T3_ENC: return "\\textipa"; // requires tipa.sty
case LGR_ENC: return "\\textgreek"; // requires babel with greek option
default: return null;
}
}
private static Hashtable<String,String> babelLanguages; // mappings iso->babel language
static {
babelLanguages = new Hashtable<String,String>();
babelLanguages.put("en", "english"); // latin1
babelLanguages.put("bg", "bulgarian"); // cp1251?
babelLanguages.put("cs", "czech"); // latin2
babelLanguages.put("da", "danish"); // latin1
babelLanguages.put("de", "ngerman"); // latin1
babelLanguages.put("el", "greek"); // iso-8859-7
babelLanguages.put("es", "spanish"); // latin1
babelLanguages.put("fi", "finnish"); // latin1 (latin9?)
babelLanguages.put("fr", "french"); // latin1 (latin9?)
babelLanguages.put("ga", "irish"); // latin1
babelLanguages.put("hr", "croatian"); // latin2
babelLanguages.put("hu", "magyar"); // latin2
babelLanguages.put("la", "latin"); // ascii
babelLanguages.put("is", "icelandic"); // latin1
babelLanguages.put("it", "italian"); // latin1
babelLanguages.put("nl", "dutch"); // latin1
babelLanguages.put("nb", "norsk"); // latin1
babelLanguages.put("nn", "nynorsk"); // latin1
babelLanguages.put("pl", "polish"); // latin2
babelLanguages.put("pt", "portuges"); // latin1
babelLanguages.put("ro", "romanian"); // latin2
babelLanguages.put("ru", "russian"); // cp1251?
babelLanguages.put("sk", "slovak"); // latin2
babelLanguages.put("sl", "slovene"); // latin2
babelLanguages.put("sr", "serbian"); // cp1251?
babelLanguages.put("sv", "swedish"); // latin1
babelLanguages.put("tr", "turkish");
babelLanguages.put("uk", "ukrainian"); // cp1251?
}
// End of static part of I18n!
// **** Global variables ****
// Unicode translation
private Hashtable<String,UnicodeTable> tableSet; // all tables
private UnicodeTable table; // currently active table (top of stack)
private Stack<UnicodeTable> tableStack; // stack of active tables
private UnicodeStringParser ucparser; // Unicode string parser
// Collected data
private int nDefaultFontenc; // Fontenc for the default language
private boolean bT2A = false; // Do we use cyrillic letters?
private boolean bGreek = false; // Do we use greek letters?
private boolean bPolytonicGreek = false; // Do we use polytonic greek letters?
// **** Constructors ****
/** Construct a new ClassicI18n as ConverterHelper
* @param ofr the OfficeReader to get language information from
* @param config the configuration which determines the symbols to use
* @param palette the ConverterPalette (unused)
*/
public ClassicI18n(OfficeReader ofr, LaTeXConfig config, ConverterPalette palette) {
super(ofr,config,palette);
// We don't need the palette and the office reader is only used to
// identify the default language
nDefaultFontenc = getFontenc(sDefaultLanguage);
// Unicode stuff
ucparser = new UnicodeStringParser();
String sSymbols="ascii"; // always load common symbols
if (config.getInputencoding()!=ASCII) {
sSymbols+="|"+writeInputenc(config.getInputencoding());
}
if (config.useWasysym()) sSymbols+="|wasysym";
if (config.useBbding()) sSymbols+="|bbding";
if (config.useIfsym()) sSymbols+="|ifsym";
if (config.usePifont()) sSymbols+="|dingbats";
if (config.useEurosym()) sSymbols+="|eurosym";
if (config.useTipa()) sSymbols+="|tipa";
tableSet = new Hashtable<String,UnicodeTable>();
UnicodeTableHandler handler=new UnicodeTableHandler(tableSet, sSymbols);
SAXParserFactory factory=SAXParserFactory.newInstance();
InputStream is = this.getClass().getResourceAsStream("symbols.xml");
try {
SAXParser saxParser=factory.newSAXParser();
saxParser.parse(is,handler);
}
catch (Throwable t){
System.err.println("Oops - Unable to read symbols.xml");
t.printStackTrace();
}
// put root table at top of stack
tableStack = new Stack<UnicodeTable>();
tableStack.push(tableSet.get("root"));
table = tableSet.get("root");
}
/** Construct a new I18n for general use
* @param config the configuration which determines the symbols to use
*/
public ClassicI18n(LaTeXConfig config) {
this (null, config, null);
}
/** Add declarations to the preamble to load the required packages
* @param pack usepackage declarations
* @param decl other declarations
*/
public void appendDeclarations(LaTeXDocumentPortion pack, LaTeXDocumentPortion decl) {
useInputenc(pack);
useSymbolFonts(pack);
useTextFonts(pack);
useBabel(pack);
}
private void useInputenc(LaTeXDocumentPortion ldp) {
ldp.append("\\usepackage[")
.append(writeInputenc(config.getInputencoding()))
.append("]{inputenc}").nl();
}
private void useBabel(LaTeXDocumentPortion ldp) {
// If the document contains "anonymous" greek letters we need greek in any case
// If the document contains "anonymous cyrillic letters we need one of the
// languages russian, ukrainian or bulgarian
if (greek() && !languages.contains("el")) languages.add("el");
if (cyrillic() && !(languages.contains("ru") || languages.contains("uk") || languages.contains("bg"))) {
languages.add("ru");
}
// Load babel with the used languages
CSVList babelopt = new CSVList(",");
Iterator<String> langiter = languages.iterator();
while (langiter.hasNext()) {
String sLang = langiter.next();
if (!sLang.equals(sDefaultLanguage)) {
if ("el".equals(sLang) && this.polytonicGreek()) {
babelopt.addValue("polutonikogreek");
}
else {
String sBabelLang = getBabelLanguage(sLang);
if (sBabelLang!=null) {
babelopt.addValue(sBabelLang);
}
}
}
}
// The default language must be the last one
if (sDefaultLanguage!=null) {
if ("el".equals(sDefaultLanguage) && this.polytonicGreek()) {
babelopt.addValue("polutonikogreek");
}
else {
String sBabelLang = getBabelLanguage(sDefaultLanguage);
if (sBabelLang!=null) {
babelopt.addValue(sBabelLang);
}
}
}
if (!babelopt.isEmpty()) {
ldp.append("\\usepackage[")
.append(babelopt.toString())
.append("]{babel}").nl();
// For Polish we must undefine \lll which is later defined by ams
if (languages.contains("pl")) {
ldp.append("\\let\\lll\\undefined").nl();
}
}
}
private void useSymbolFonts(LaTeXDocumentPortion ldp) {
if (config.useTipa()) {
ldp.append("\\usepackage[noenc]{tipa}").nl()
.append("\\usepackage{tipx}").nl();
}
// Has to avoid some nameclashes
if (config.useBbding()) {
ldp.append("\\usepackage{bbding}").nl()
.append("\\let\\bbCross\\Cross\\let\\Cross\\undefined").nl()
.append("\\let\\bbSquare\\Square\\let\\Square\\undefined").nl()
.append("\\let\\bbTrianbleUp\\TriangleUp\\let\\TriangleUp\\undefined").nl()
.append("\\let\\bbTrianlgeDown\\TriangleDown\\let\\TriangleDown\\undefined").nl();
}
if (config.useIfsym()) {
ldp.append("\\usepackage[geometry,weather,misc,clock]{ifsym}").nl();
}
if (config.usePifont()) { ldp.append("\\usepackage{pifont}").nl(); }
if (config.useEurosym()) { ldp.append("\\usepackage{eurosym}").nl(); }
// Always use amsmath
ldp.append("\\usepackage{amsmath}").nl();
// wasysym *must* be loaded between amsmath and amsfonts!
if (config.useWasysym()) {
ldp.append("\\usepackage{wasysym}").nl();
}
// Always use amssymb, amsfonts, textcomp (always!)
ldp.append("\\usepackage{amssymb,amsfonts,textcomp}").nl();
}
private void useTextFonts(LaTeXDocumentPortion ldp) {
// usepackage fontenc
CSVList fontencs = new CSVList(',');
if (bT2A) { fontencs.addValue("T2A"); }
if (bGreek) { fontencs.addValue("LGR"); }
if (config.useTipa()) { fontencs.addValue("T3"); }
fontencs.addValue("T1");
ldp.append("\\usepackage[").append(fontencs.toString())
.append("]{fontenc}").nl();
// use font package(s)
useFontPackages(ldp);
}
private void useFontPackages(LaTeXDocumentPortion ldp) {
String sFont = config.getFont();
// Sources:
// A Survey of Free Math Fonts for TeX and LaTeX, Stephen G. Hartke 2006
if ("cmbright".equals(sFont)) { // Computer Modern Bright designed by Walter A. Schmidt
ldp.append("\\usepackage{cmbright}").nl();
}
else if ("ccfonts".equals(sFont)) { // Concrete designed by Donald E. Knuth
ldp.append("\\usepackage{ccfonts}").nl();
}
else if ("ccfonts-euler".equals(sFont)) { // Concrete with Euler math fonts
ldp.append("\\usepackage{ccfonts,eulervm}").nl();
}
else if ("iwona".equals(sFont)) { // Iwona
ldp.append("\\usepackage[math]{iwona}").nl();
}
else if ("kurier".equals(sFont)) { // Kurier
ldp.append("\\usepackage[math]{kurier}").nl();
}
else if ("anttor".equals(sFont)) { // Antykwa Torunska
ldp.append("\\usepackage[math]{anttor}").nl();
}
else if ("kmath-kerkis".equals(sFont)) { // Kerkis
ldp.append("\\usepackage{kmath,kerkis}");
}
else if ("fouriernc".equals(sFont)) { // New Century Schoolbook + Fourier
ldp.append("\\usepackage{fouriernc}");
}
else if ("pxfonts".equals(sFont)) { // Palatino + pxfonts math
ldp.append("\\usepackage{pxfonts}");
}
else if ("mathpazo".equals(sFont)) { // Palatino + Pazo math
ldp.append("\\usepackage{mathpazo}");
}
else if ("mathpple".equals(sFont)) { // Palatino + Euler
ldp.append("\\usepackage{mathpple}");
}
else if ("txfonts".equals(sFont)) { // Times + txfonts math
ldp.append("\\usepackage[varg]{txfonts}");
}
else if ("mathptmx".equals(sFont)) { // Times + Symbol
ldp.append("\\usepackage{mathptmx}");
}
else if ("arev".equals(sFont)) { // Arev Sans + Arev math
ldp.append("\\usepackage{arev}");
}
else if ("charter-mathdesign".equals(sFont)) { // Bitstream Charter + Math Design
ldp.append("\\usepackage[charter]{mathdesign}");
}
else if ("utopia-mathdesign".equals(sFont)) { // Utopia + Math Design
ldp.append("\\usepackage[utopia]{mathdesign}");
}
else if ("fourier".equals(sFont)) { // Utopia + Fourier
ldp.append("\\usepackage{fourier}");
}
}
/** Apply a language language
* @param style the OOo style to read attributes from
* @param bDecl true if declaration form is required
* @param bInherit true if inherited properties should be used
* @param ba the <code>BeforeAfter</code> to add LaTeX code to.
*/
public void applyLanguage(StyleWithProperties style, boolean bDecl, boolean bInherit, BeforeAfter ba) {
if (!bAlwaysUseDefaultLang && style!=null) {
String sISOLang = style.getProperty(XMLString.FO_LANGUAGE,bInherit);
if (sISOLang!=null) {
languages.add(sISOLang);
String sLang = getBabelLanguage(sISOLang);
if (sLang!=null) {
if (bDecl) {
ba.add("\\selectlanguage{"+sLang+"}","");
//ba.add("\\begin{otherlanguage}{"+sLang+"}","\\end{otherlanguage}");
}
else {
ba.add("\\foreignlanguage{"+sLang+"}{","}");
}
}
}
}
}
/** Push a font to the font stack
* @param sName the name of the font
*/
public void pushSpecialTable(String sName) {
// If no name is specified we should keep the current table
// Otherwise try to find the table, and use root if it's not available
if (sName!=null) {
table = tableSet.get(sName);
if (table==null) { table = tableSet.get("root"); }
}
tableStack.push(table);
}
/** Pop a font from the font stack
*/
public void popSpecialTable() {
tableStack.pop();
table = tableStack.peek();
}
/** Get the number of characters defined in the current table
* (for informational purposes only)
* @return the number of characters
*/
public int getCharCount() { return table.getCharCount(); }
/** Convert a string of characters into LaTeX
* @param s the source string
* @param bMathMode true if the string should be rendered in math mode
* @param sLang the iso language of the string
* @return the LaTeX string
*/
public String convert(String s, boolean bMathMode, String sLang){
if (!bAlwaysUseDefaultLang && sLang!=null) { languages.add(sLang); }
StringBuilder buf=new StringBuilder();
int nFontenc = bAlwaysUseDefaultLang ? nDefaultFontenc : getFontenc(sLang);
int nLen = s.length();
int i = 0;
int nStart = i;
while (i<nLen) {
ReplacementTrieNode node = stringReplace.get(s,i,nLen);
if (node!=null) {
if (i>nStart) {
convert(s,nStart,i,bMathMode,sLang,buf,nFontenc);
}
boolean bOtherFontenc = !supportsFontenc(node.getFontencs(),nFontenc);
if (bOtherFontenc) {
buf.append(getFontencCs(getFontenc(node.getFontencs()))).append("{");
}
buf.append(node.getLaTeXCode());
if (bOtherFontenc) {
buf.append("}");
}
i += node.getInputLength();
nStart = i;
}
else {
i++;
}
}
if (nStart<nLen) {
convert(s,nStart,nLen,bMathMode,sLang,buf,nFontenc);
}
return buf.toString();
}
private void convert(String s, int nStart, int nEnd, boolean bMathMode, String sLang, StringBuilder buf, int nFontenc) {
int nCurFontenc = nFontenc;
ucparser.reset(table,s,nStart,nEnd);
boolean bIsFirst = true; // Protect all dangerous characters at the start
char cProtect = '\u0000'; // Current character to protect
boolean bTempMathMode = false;
while (ucparser.next()) {
char c = ucparser.getChar();
if (bMathMode) {
buf.append(convertMathChar(c,nFontenc));
}
else if (greekMath(c,nFontenc) || (table.hasMathChar(c) && !table.hasTextChar(c))) {
if (!bTempMathMode) { // switch to math mode
buf.append("$");
bTempMathMode = true;
}
buf.append(convertMathChar(c,nFontenc));
cProtect = '\u0000';
}
else if (table.hasTextChar(c)) {
if (bTempMathMode) { // switch to text mode
buf.append("$");
bTempMathMode = false;
}
int nFontencs = table.getFontencs(c);
if (supportsFontenc(nFontencs,nCurFontenc)) {
// The text character is valid in the current font encoding
// Note: Change of font encoding is greedy - change?
// Prevent unwanted ligatures (-, ', `)
char cProtectThis = table.getProtectChar(c);
if (cProtectThis!='\u0000' && (cProtectThis==cProtect || bIsFirst)) {
buf.append("{}");
}
cProtect = cProtectThis;
setFlags(c,nCurFontenc);
if (ucparser.hasCombiningChar()) {
char cc = ucparser.getCombiningChar();
if (supportsFontenc(table.getFontencs(cc),nCurFontenc)) {
buf.append(table.getTextChar(cc)).append("{")
.append(table.getTextChar(c)).append("}");
}
else { // ignore combining char if not valid in this font encoding
buf.append(table.getTextChar(c));
}
}
else {
buf.append(table.getTextChar(c));
}
}
else {
// The text character is valid in another font encoding
cProtect = table.getProtectChar(c);
int nFontenc1 = getFontenc(nFontencs);
setFlags(c,nFontenc1);
if (nCurFontenc!=nFontenc) { // end "other font encoding"
buf.append("}");
}
if (nFontenc1!=nFontenc) { // start "other font encoding"
buf.append(getFontencCs(nFontenc1)).append("{");
}
if (ucparser.hasCombiningChar()) {
char cc = ucparser.getCombiningChar();
if (supportsFontenc(table.getFontencs(cc),nCurFontenc)) {
buf.append(table.getTextChar(cc)).append("{")
.append(table.getTextChar(c)).append("}");
}
else { // ignore combining char if not valid in this font encoding
buf.append(table.getTextChar(c));
}
}
else {
buf.append(table.getTextChar(c));
}
nCurFontenc = nFontenc1;
}
}
else {
buf.append(notFound(c,nCurFontenc));
}
bIsFirst = false;
}
if (bTempMathMode) { // turn of math mode
buf.append("$");
}
if (nCurFontenc!=nFontenc) { // end unfinished "other font encoding"
buf.append("}");
}
}
// convert a single math character
private String convertMathChar(char c, int nFontenc) {
if (table.hasMathChar(c)) {
return table.getMathChar(c);
}
else if (table.hasTextChar(c)) { // use text mode as a fallback
int nFontencs = table.getFontencs(c);
if (supportsFontenc(nFontencs,nFontenc)) {
// The text character is valid in the current font encoding
setFlags(c,nFontenc);
if (table.getCharType(c)==UnicodeCharacter.COMBINING) {
return "\\text{" + table.getTextChar(c) +"{}}";
}
else {
return "\\text{" + table.getTextChar(c) +"}";
}
}
else {
// The text character is valid in another font encoding
int nFontenc1 = getFontenc(nFontencs);
setFlags(c,nFontenc1);
if (table.getCharType(c)==UnicodeCharacter.COMBINING) {
return "\\text{" + getFontencCs(nFontenc1) + "{" + table.getTextChar(c) +"{}}}";
}
else {
return "\\text{" + getFontencCs(nFontenc1) + "{" + table.getTextChar(c) +"}}";
}
}
}
else {
return "\\text{" + notFound(c,nFontenc) + "}";
}
}
// Missing symbol
private String notFound(char c,int nFontenc) {
//String sErrorMsg = "[Warning: Missing symbol " + Integer.toHexString(c).toUpperCase() +"]";
String sErrorMsg = "["+Integer.toHexString(c).toUpperCase() +"?]";
if (nFontenc==T1_ENC) return sErrorMsg;
else return "\\textlatin{"+sErrorMsg+"}";
}
// Convert a single character
/*private String convert(char c, boolean bMathMode, String sLang){
int nFontenc = bAlwaysUseDefaultLang ? nDefaultFontenc : getFontenc(sLang);
if (bMathMode) {
return convertMathChar(c,nFontenc);
}
else if (greekMath(c,nFontenc) || (table.hasMathChar(c) && !table.hasTextChar(c))) {
return "$" + convertMathChar(c,nFontenc) + "$";
}
else if (table.hasTextChar(c)) {
int nFontencs = table.getFontencs(c);
if (supportsFontenc(nFontencs,nFontenc)) {
// The text character is valid in the current font encoding
setFlags(c,nFontenc);
if (table.getCharType(c)==UnicodeCharacter.COMBINING) {
return table.getTextChar(c)+"{}";
}
else {
return table.getTextChar(c);
}
}
else {
// The text character is valid in another font encoding
int nFontenc1 = getFontenc(nFontencs);
setFlags(c,nFontenc1);
if (table.getCharType(c)==UnicodeCharacter.COMBINING) {
return getFontencCs(nFontenc1) + "{" + table.getTextChar(c) +"{}}";
}
else {
return getFontencCs(nFontenc1) + "{" + table.getTextChar(c) +"}";
}
}
}
else {
return notFound(c,nFontenc);
}
}*/
// **** Languages ****
// Convert iso language to babel language
// todo: include iso country
// todo: support automatic choice of inputenc (see comments)?
private String getBabelLanguage(String sLang) {
if (babelLanguages.containsKey(sLang)) {
return babelLanguages.get(sLang);
}
else {
return null; // Unknown language
}
}
// **** Helpers to collect various information ****
// Did we use cyrillic?
private boolean cyrillic() { return bT2A; }
// Did we use greek?
private boolean greek() { return bGreek; }
// Did we use polytonic greek?
private boolean polytonicGreek() { return bPolytonicGreek; }
// Outside greek text, greek letters may be rendered in math mode,
// if the user requires that in the configuration.
private boolean greekMath(char c, int nFontenc) {
return bGreekMath && nFontenc!=LGR_ENC && table.getFontencs(c)==LGR_ENC;
}
// Set cyrillic and greek flags
private void setFlags(char c, int nFontenc) {
if ((c>='\u1F00') && (c<='\u1FFF')) bPolytonicGreek = true;
if (nFontenc==LGR_ENC) bGreek = true;
if (nFontenc==T2A_ENC) bT2A = true;
}
}

View file

@ -0,0 +1,142 @@
/************************************************************************
*
* I18n.java
*
* Copyright: 2002-2011 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.2 (2011-05-09)
*
*/
package writer2latex.latex.i18n;
import java.util.HashSet;
import writer2latex.office.*;
import writer2latex.latex.LaTeXConfig;
import writer2latex.latex.LaTeXDocumentPortion;
import writer2latex.latex.ConverterPalette;
import writer2latex.latex.util.BeforeAfter;
/** This abstract class takes care of i18n in the LaTeX export.
* Since i18n is handled quite differently in LaTeX "Classic"
* and XeTeX, we use two different classes
*/
public abstract class I18n {
// **** Global variables ****
// The office reader
protected OfficeReader ofr;
// Configuration items
protected LaTeXConfig config;
protected ReplacementTrie stringReplace;
protected boolean bGreekMath; // Use math mode for Greek letters
protected boolean bAlwaysUseDefaultLang; // Ignore sLang parameter to convert()
// Collected data
protected String sDefaultCTLLanguage; // The default CTL ISO language to use
protected String sDefaultCTLCountry; // The default CTL ISO country to use
protected String sDefaultLanguage; // The default LCG ISO language to use
protected String sDefaultCountry; // The default LCG ISO country to use
protected HashSet<String> languages = new HashSet<String>(); // All LCG languages used
// **** Constructors ****
/** Construct a new I18n as ConverterHelper
* @param ofr the OfficeReader to get language information from
* @param config the configuration which determines the symbols to use
* @param palette the ConverterPalette (unused)
*/
public I18n(OfficeReader ofr, LaTeXConfig config, ConverterPalette palette) {
// We don't need the palette
this.ofr = ofr;
// Set up config items
this.config = config;
stringReplace = config.getStringReplace();
bGreekMath = config.greekMath();
bAlwaysUseDefaultLang = !config.multilingual();
// Default language
if (ofr!=null) {
if (config.multilingual()) {
// Read the default language from the default paragraph style
StyleWithProperties style = ofr.getDefaultParStyle();
if (style!=null) {
sDefaultLanguage = style.getProperty(XMLString.FO_LANGUAGE);
sDefaultCountry = style.getProperty(XMLString.FO_COUNTRY);
sDefaultCTLLanguage = style.getProperty(XMLString.STYLE_LANGUAGE_COMPLEX);
sDefaultCTLCountry = style.getProperty(XMLString.STYLE_COUNTRY_COMPLEX);
}
}
else {
// the most common language is the only language
sDefaultLanguage = ofr.getMajorityLanguage();
}
}
if (sDefaultLanguage==null) { sDefaultLanguage="en"; }
}
/** Add declarations to the preamble to load the required packages
* @param pack usepackage declarations
* @param decl other declarations
*/
public abstract void appendDeclarations(LaTeXDocumentPortion pack, LaTeXDocumentPortion decl);
/** Apply a language language
* @param style the OOo style to read attributes from
* @param bDecl true if declaration form is required
* @param bInherit true if inherited properties should be used
* @param ba the <code>BeforeAfter</code> to add LaTeX code to.
*/
public abstract void applyLanguage(StyleWithProperties style, boolean bDecl, boolean bInherit, BeforeAfter ba);
/** Push a font to the font stack
* @param sName the name of the font
*/
public abstract void pushSpecialTable(String sName);
/** Pop a font from the font stack
*/
public abstract void popSpecialTable();
/** Convert a string of characters into LaTeX
* @param s the source string
* @param bMathMode true if the string should be rendered in math mode
* @param sLang the ISO language of the string
* @return the LaTeX string
*/
public abstract String convert(String s, boolean bMathMode, String sLang);
/** Get the default language (either the document language or the most used language)
*
* @return the default language
*/
public String getDefaultLanguage() {
return sDefaultLanguage;
}
/** Get the default country
*
* @return the default country
*/
public String getDefaultCountry() {
return sDefaultCountry;
}
}

View file

@ -0,0 +1,16 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title>The package writer2latex.xhtml.i18n</title>
</head>
<body>
<p>This package takes care of i18n for LaTeX.</p>
<p>In LaTeX, i18n is a mixture of inputencodings, fontencodings
and babel languages. In particualar, the package provides a Unicode->LaTeX
translation that can handle different inputencodings and fontencodings.</p>
<p>The pacakge could (with modification) in theory be used in other programs
that convert unicode to LaTeX.</p>
</body>
</html>

View file

@ -0,0 +1,216 @@
/************************************************************************
*
* XeTeXI18n.java
*
* Copyright: 2002-2010 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.2 (2010-12-15)
*
*/
package writer2latex.latex.i18n;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class Polyglossia {
private static Map<String,String> languageMap;
private static Map<String,String> variantMap;
static {
languageMap = new HashMap<String,String>();
languageMap.put("am", "amharic");
languageMap.put("ar", "arabic");
languageMap.put("ast", "asturian");
languageMap.put("bg", "bulgarian");
languageMap.put("bn", "bengali");
languageMap.put("br", "breton");
languageMap.put("ca", "catalan");
languageMap.put("cop", "coptic");
languageMap.put("cs", "czech");
languageMap.put("cy", "welsh");
languageMap.put("da", "danish");
languageMap.put("de", "german");
languageMap.put("dsb", "lsorbian");
languageMap.put("dv", "divehi");
languageMap.put("el", "greek");
languageMap.put("en", "english");
languageMap.put("eo", "esperanto");
languageMap.put("es", "spanish");
languageMap.put("et", "estonian");
languageMap.put("eu", "basque");
languageMap.put("fa", "farsi");
languageMap.put("fi", "finnish");
languageMap.put("fr", "french");
languageMap.put("ga", "irish");
languageMap.put("gd", "scottish");
languageMap.put("gl", "galician");
languageMap.put("grc", "greek");
languageMap.put("he", "hebrew");
languageMap.put("hi", "hindi");
languageMap.put("hr", "croatian");
languageMap.put("hsb", "usorbian");
languageMap.put("hu", "magyar");
languageMap.put("hy", "armenian");
languageMap.put("id", "bahasai"); // Bahasa Indonesia
languageMap.put("ie", "interlingua");
languageMap.put("is", "icelandic");
languageMap.put("it", "italian");
languageMap.put("la", "latin");
languageMap.put("lo", "lao");
languageMap.put("lt", "lithuanian");
languageMap.put("lv", "latvian");
languageMap.put("ml", "malayalam");
languageMap.put("mr", "marathi");
languageMap.put("ms", "bahasam"); // Bahasa Melayu
languageMap.put("nb", "norsk");
languageMap.put("nl", "dutch");
languageMap.put("nn", "nynorsk");
languageMap.put("oc", "occitan");
languageMap.put("pl", "polish");
languageMap.put("pt", "portuges");
languageMap.put("pt-BR", "brazilian");
languageMap.put("ro", "romanian");
languageMap.put("ru", "russian");
languageMap.put("sa", "sanskrit");
languageMap.put("sk", "slovak");
languageMap.put("sl", "slovenian");
languageMap.put("sq", "albanian");
languageMap.put("sr", "serbian");
languageMap.put("sv", "swedish");
languageMap.put("syr", "syriac");
languageMap.put("ta", "tamil");
languageMap.put("te", "telugu");
languageMap.put("th", "thai");
languageMap.put("tk", "turkmen");
languageMap.put("tr", "turkish");
languageMap.put("uk", "ukrainian");
languageMap.put("ur", "urdu");
languageMap.put("vi", "vietnamese");
// TODO: Which language is samin?? One guess could be sami with the n for north?
//languageMap.put("??", "samin");
variantMap = new HashMap<String,String>();
// English variants
variantMap.put("en-US", "american");
variantMap.put("en-GB", "british");
variantMap.put("en-AU", "australian");
variantMap.put("en-NZ", "newzealand");
// Greek variants
variantMap.put("el", "monotonic");
variantMap.put("grc", "ancient"); // Supported in OOo since 3.2
}
private static String getEntry(Map<String,String> map, String sLocale, String sLang) {
if (map.containsKey(sLocale)) {
return map.get(sLocale);
}
else if (map.containsKey(sLang)) {
return map.get(sLang);
}
return null;
}
// This ended the static part of Polyglossia
private Set<String> languages = new HashSet<String>();
private List<String> declarations = new ArrayList<String>();
private Map<String,String[]> commands = new HashMap<String,String[]>();
/** <p>Get the declarations for the applied languages, in the form</p>
* <p><code>\\usepackage{polyglossia}</code></p>
* <p><code>\\setdefaultlanguage{language1}</code></p>
* <p><code>\\setotherlanguage{language2}</code></p>
* <p><code>\\setotherlanguage{language3}</code></p>
* <p><code>...</code></p>
*
* @return the declarations as a string array
*/
public String[] getDeclarations() {
return declarations.toArray(new String[declarations.size()]);
}
/** <p>Add the given locale to the list of applied locales and return definitions for applying the
* language to a text portion:</p>
* <ul>
* <li>A command of the forn <code>\textlanguage[variant=languagevariant]</code></li>
* <li>An environment in the form
* <code>\begin{language}[variant=languagevariant]</code>...<code>\end{language}</code></li>
* </ul>
* <p>The first applied language is the default language</p>
*
* @param sLang The language
* @param sCountry The country (may be null)
* @return a string array containing definitions to apply the language: Entry 0 contains a command
* and Entry 1 and 2 contains an environment
*/
public String[] applyLanguage(String sLang, String sCountry) {
String sLocale = sCountry!=null ? sLang+"-"+sCountry : sLang;
if (commands.containsKey(sLocale)) {
return commands.get(sLocale);
}
else {
// Get the Polyglossia language and variant
String sPolyLang = getEntry(languageMap,sLocale,sLang);
if (sPolyLang!=null) {
String sVariant = getEntry(variantMap,sLocale,sLang);
if (sVariant!=null) {
sVariant = "[variant="+sVariant+"]";
}
else {
sVariant = "";
}
if (languages.size()==0) {
// First language, load Polyglossia and make the language default
declarations.add("\\usepackage{polyglossia}");
declarations.add("\\setdefaultlanguage"+sVariant+"{"+sPolyLang+"}");
languages.add(sPolyLang);
sVariant = ""; // Do not apply variant directly
}
else if (!languages.contains(sPolyLang)) {
// New language, add to declarations
declarations.add("\\setotherlanguage"+sVariant+"{"+sPolyLang+"}");
languages.add(sPolyLang);
sVariant = ""; // Do not apply variant directly
}
String[] sCommand = new String[3];
sCommand[0] = "\\text"+sPolyLang+sVariant;
if ("arabic".equals(sPolyLang)) { sPolyLang="Arabic"; }
sCommand[1] = "\\begin{"+sPolyLang+"}"+sVariant;
sCommand[2] = "\\end{"+sPolyLang+"}";
commands.put(sLocale, sCommand);
return sCommand;
}
else {
// Unknown language
String[] sCommand = new String[3];
sCommand[0] = "";
sCommand[1] = "";
sCommand[2] = "";
commands.put(sLocale, sCommand);
return sCommand;
}
}
}
}

View file

@ -0,0 +1,60 @@
/************************************************************************
*
* ReplacementTrie.java
*
* Copyright: 2002-2009 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.2 (2009-09-20)
*
*/
package writer2latex.latex.i18n;
import java.util.HashSet;
import java.util.Set;
/** This class contains a trie of string -> LaTeX code replacements
*/
public class ReplacementTrie extends ReplacementTrieNode {
public ReplacementTrie() {
super('*',0);
}
public ReplacementTrieNode get(String sInput) {
return get(sInput,0,sInput.length());
}
public ReplacementTrieNode get(String sInput, int nStart, int nEnd) {
if (sInput.length()==0) { return null; }
else { return super.get(sInput,nStart,nEnd); }
}
public void put(String sInput, String sLaTeXCode, int nFontencs) {
if (sInput.length()==0) { return; }
else { super.put(sInput,sLaTeXCode,nFontencs); }
}
public Set<String> getInputStrings() {
HashSet<String> strings = new HashSet<String>();
collectStrings(strings,"");
return strings;
}
}

View file

@ -0,0 +1,139 @@
/************************************************************************
*
* ReplacementTrieNode.java
*
* Copyright: 2002-2014 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.4 (2014-09-24)
*
*/
package writer2latex.latex.i18n;
import java.util.Set;
/** This class contains a node in a trie of string -> LaTeX code replacements
*/
public class ReplacementTrieNode {
private char cLetter;
private int nInputLength;
private String sLaTeXCode = null;
private int nFontencs = 0;
private ReplacementTrieNode son = null;
private ReplacementTrieNode brother = null;
public ReplacementTrieNode(char cLetter, int nInputLength) {
this.cLetter = cLetter;
this.nInputLength = nInputLength;
}
public char getLetter() { return this.cLetter; }
public int getInputLength() { return this.nInputLength; }
public String getLaTeXCode() { return this.sLaTeXCode; }
public int getFontencs() { return this.nFontencs; }
protected void setLaTeXCode(String sLaTeXCode) {
this.sLaTeXCode = sLaTeXCode;
}
protected void setFontencs(int nFontencs) {
this.nFontencs = nFontencs;
}
protected ReplacementTrieNode getFirstChild() {
return this.son;
}
protected ReplacementTrieNode getNextSibling() {
return this.brother;
}
protected ReplacementTrieNode getChildByLetter(char cLetter) {
ReplacementTrieNode child = this.getFirstChild();
while (child!=null) {
if (cLetter==child.getLetter()) { return child; }
child = child.getNextSibling();
}
return null;
}
protected void appendChild(ReplacementTrieNode node) {
if (son==null) { son = node; }
else { son.appendSibling(node); }
}
protected void appendSibling(ReplacementTrieNode node) {
if (brother==null) { brother = node; }
else { brother.appendSibling(node); }
}
protected ReplacementTrieNode get(String sInput, int nStart, int nEnd) {
if (nStart>=nEnd) { return null; }
char c = sInput.charAt(nStart);
ReplacementTrieNode child = this.getFirstChild();
while (child!=null) {
if (child.getLetter()==c) {
if (child.getLaTeXCode()!=null) { return child; }
else { return child.get(sInput,nStart+1,nEnd); }
}
child = child.getNextSibling();
}
return null;
}
protected void put(String sInput, String sLaTeXCode, int nFontencs) {
char c = sInput.charAt(0);
ReplacementTrieNode child = this.getChildByLetter(c);
if (child==null) {
child = new ReplacementTrieNode(c,this.getInputLength()+1);
this.appendChild(child);
}
if (sInput.length()>1) {
child.put(sInput.substring(1),sLaTeXCode,nFontencs);
}
else {
child.setLaTeXCode(sLaTeXCode);
child.setFontencs(nFontencs);
}
}
protected void collectStrings(Set<String> strings, String sPrefix) {
ReplacementTrieNode child = this.getFirstChild();
while (child!=null) {
if (child.getLaTeXCode()!=null) {
strings.add(sPrefix+child.getLetter());
}
child.collectStrings(strings, sPrefix+child.getLetter());
child = child.getNextSibling();
}
}
public String toString() {
String s = Character.toString(cLetter);
if (brother!=null) { s+=brother.toString(); }
if (son!=null) { s+="\nInputLength "+(nInputLength+1)+", "+son.toString(); }
else { s+="\n"; }
return s;
}
}

View file

@ -0,0 +1,51 @@
/************************************************************************
*
* UnicodeCharacter.java
*
* Copyright: 2002-2010 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.2 (2010-05-11)
*
*/
package writer2latex.latex.i18n;
// Helper class: A struct to hold the LaTeX representations of a unicode character
class UnicodeCharacter implements Cloneable {
final static int NORMAL = 0; // this is a normal character
final static int COMBINING = 1; // this character should be ignored
final static int IGNORE = 2; // this is a combining character
final static int UNKNOWN = 3; // this character is unknown
int nType; // The type of character
String sMath; // LaTeX representation in math mode
String sText; // LaTeX representation in text mode
int nFontencs; // Valid font encoding(s) for the text mode representation
char cProtect; // This character is represented by this character which may produce unwanted ligatures (-, ', `)
protected Object clone() {
UnicodeCharacter uc = new UnicodeCharacter();
uc.nType = this.nType;
uc.sMath = this.sMath;
uc.sText = this.sText;
uc.nFontencs = this.nFontencs;
uc.cProtect = this.cProtect;
return uc;
}
}

View file

@ -0,0 +1,42 @@
/************************************************************************
*
* UnicodeRow.java
*
* Copyright: 2002-2007 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 0.5 (2007-07-24)
*
*/
package writer2latex.latex.i18n;
// Helper class: A row of 256 unicode characters
class UnicodeRow implements Cloneable {
UnicodeCharacter[] entries;
UnicodeRow(){ entries=new UnicodeCharacter[256]; }
protected Object clone() {
UnicodeRow ur = new UnicodeRow();
for (int i=0; i<256; i++) {
if (this.entries[i]!=null) {
ur.entries[i] = (UnicodeCharacter) this.entries[i].clone();
}
}
return ur;
}
}

View file

@ -0,0 +1,78 @@
/************************************************************************
*
* UnicodeStringParser.java
*
* Copyright: 2002-2007 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 0.5 (2007-07-24)
*
*/
package writer2latex.latex.i18n;
// Helper class: Parse a unicode string.
// Note: Some 8-bit fonts have additional "spacer" characters that are used
// for manual placement of accents. These are ignored between the base character
// and the combining character, thus we are parsing according to the rule
// <base char> <spacer char>* <combining char>?
class UnicodeStringParser {
private UnicodeTable table; // the table to use
private String s; // the string
private int i; // the current index
private int nEnd; // the maximal index
private char c; // the current character
private char cc; // the current combining character
protected void reset(UnicodeTable table, String s, int i, int nEnd) {
this.table=table;
this.s=s;
this.i=i;
this.nEnd=nEnd;
}
protected boolean next() {
if (i>=nEnd) { return false; }
// Pick up base character
c = s.charAt(i++);
if (table.getCharType(c)==UnicodeCharacter.COMBINING) {
// Lonely combining character - combine with space
cc = c;
c = ' ';
return true;
}
// Skip characters that should be ignored
while (i<s.length() && table.getCharType(s.charAt(i))==UnicodeCharacter.IGNORE) { i++; }
// Pick up combining character, if any
if (i<s.length() && table.getCharType(s.charAt(i))==UnicodeCharacter.COMBINING) {
cc = s.charAt(i++);
}
else {
cc = '\u0000';
}
return true;
}
protected char getChar() { return c; }
protected boolean hasCombiningChar() { return cc!='\u0000'; }
protected char getCombiningChar() { return cc; }
}

View file

@ -0,0 +1,168 @@
/************************************************************************
*
* UnicodeTable.java
*
* Copyright: 2002-2010 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.2 (2010-05-11)
*
*/
package writer2latex.latex.i18n;
// Helper class: Table of up to 65536 unicode characters
class UnicodeTable {
protected UnicodeRow[] table=new UnicodeRow[256];
private UnicodeTable parent;
// Constructor; creates a new table, possibly based on a parent
// Note: The parent must be fully loaded before the child is created.
public UnicodeTable(UnicodeTable parent){
this.parent = parent;
if (parent!=null) {
// *Copy* the rows from the parent
for (int i=0; i<256; i++) {
table[i] = parent.table[i];
}
}
}
// Make sure the required entry exists
private void createEntry(int nRow, int nCol) {
if (table[nRow]==null) {
table[nRow]=new UnicodeRow();
}
else if (parent!=null && table[nRow]==parent.table[nRow]) {
// Before changing a row it must be *cloned*
table[nRow] = (UnicodeRow) parent.table[nRow].clone();
}
if (table[nRow].entries[nCol]==null) {
table[nRow].entries[nCol]=new UnicodeCharacter();
}
}
// Addd a single character (type only), by number
protected void addCharType(char c, int nType) {
int nRow=c/256; int nCol=c%256;
createEntry(nRow,nCol);
table[nRow].entries[nCol].nType = nType;
}
// Addd a single character (type only), by name
protected void addCharType(char c, String sType) {
int nRow=c/256; int nCol=c%256;
createEntry(nRow,nCol);
if ("combining".equals(sType)) {
table[nRow].entries[nCol].nType = UnicodeCharacter.COMBINING;
}
else if ("ignore".equals(sType)) {
table[nRow].entries[nCol].nType = UnicodeCharacter.IGNORE;
}
else {
table[nRow].entries[nCol].nType = UnicodeCharacter.NORMAL;
}
}
// Add a single math character to the table
protected void addMathChar(char c, String sLaTeX){
int nRow=c/256; int nCol=c%256;
createEntry(nRow,nCol);
table[nRow].entries[nCol].sMath=sLaTeX;
}
// Add a single text character to the table
protected void addTextChar(char c, String sLaTeX, int nFontencs, char cProtect){
int nRow=c/256; int nCol=c%256;
createEntry(nRow,nCol);
table[nRow].entries[nCol].sText=sLaTeX;
table[nRow].entries[nCol].nFontencs=nFontencs;
table[nRow].entries[nCol].cProtect=cProtect;
}
// Retrieve entry for a character (or null)
private UnicodeCharacter getEntry(char c) {
int nRow=c/256; int nCol=c%256;
if (table[nRow]==null) return null;
return table[nRow].entries[nCol];
}
// Get character type
public int getCharType(char c) {
UnicodeCharacter entry = getEntry(c);
if (entry==null) return UnicodeCharacter.UNKNOWN;
return entry.nType;
}
// Check to see if this math character exists?
public boolean hasMathChar(char c) {
UnicodeCharacter entry = getEntry(c);
if (entry==null) return false;
return entry.sMath!=null;
}
// Get math character (or null)
public String getMathChar(char c) {
UnicodeCharacter entry = getEntry(c);
if (entry==null) return null;
return entry.sMath;
}
// Check to see if this text character exists?
public boolean hasTextChar(char c) {
UnicodeCharacter entry = getEntry(c);
if (entry==null) return false;
return entry.sText!=null;
}
// Get text character (or null)
public String getTextChar(char c) {
UnicodeCharacter entry = getEntry(c);
if (entry==null) return null;
return entry.sText;
}
// Get font encoding(s) for text character (or 0)
public int getFontencs(char c) {
UnicodeCharacter entry = getEntry(c);
if (entry==null) return 0;
return entry.nFontencs;
}
// Get ligature protect character for text character
public char getProtectChar(char c) {
UnicodeCharacter entry = getEntry(c);
if (entry==null) return '\u0000';
return entry.cProtect;
}
// Get number of defined characters
public int getCharCount() {
int nCount = 0;
for (int nRow=0; nRow<256; nRow++) {
if (table[nRow]!=null) {
for (int nCol=0; nCol<256; nCol++) {
UnicodeCharacter entry = table[nRow].entries[nCol];
if (entry!=null) nCount++;
}
}
}
return nCount;
}
}

View file

@ -0,0 +1,172 @@
/************************************************************************
*
* UnicodeTableHandler.java
*
* Copyright: 2002-2010 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.2 (2010-05-11)
*
*/
package writer2latex.latex.i18n;
import java.util.Hashtable;
import org.xml.sax.Attributes;
import org.xml.sax.helpers.DefaultHandler;
/** Helper classs: SAX handler to parse symbols.xml from jar
*/
public class UnicodeTableHandler extends DefaultHandler{
private Hashtable<String,UnicodeTable> tableSet; // collection of all tables
private UnicodeTable table; // the current table
private String sSymbolSets;
private boolean bGlobalReadThisSet;
private boolean bReadThisSet;
private int nGlobalFontencs = 0; // The global fontencodings for current symbol set
private int nFontencs = 0; // The currently active fontencodings
private boolean b8bit = false;
/** Create a new <code>UnicodeTableHandler</code>
*
* @param tableSet the <code>Hashtable</code> to fill with tables read from the file
* @param sSymbolSets string containing table names to read (separated by |)
*/
public UnicodeTableHandler(Hashtable<String,UnicodeTable> tableSet, String sSymbolSets){
this.sSymbolSets = sSymbolSets;
this.tableSet = tableSet;
}
public void startElement(String nameSpace, String localName, String qName, Attributes attributes){
if (qName.equals("symbols")) {
//root element - create root table!
table = new UnicodeTable(null);
tableSet.put("root",table);
}
else if (qName.equals("symbol-set")) {
// start a new symbol set; maybe we want to include it?
bGlobalReadThisSet = sSymbolSets.indexOf(attributes.getValue("name")) >= 0;
bReadThisSet = bGlobalReadThisSet;
// Change global and current fontencodings
nGlobalFontencs = ClassicI18n.readFontencs(attributes.getValue("fontenc"));
nFontencs = nGlobalFontencs;
}
else if (qName.equals("special-symbol-set")) {
// start a new special symbol set; this requires a new table
table = new UnicodeTable(tableSet.get("root"));
tableSet.put(attributes.getValue("name"),table);
// Read it if it requires nothing, or something we read
bGlobalReadThisSet = attributes.getValue("requires")==null ||
sSymbolSets.indexOf(attributes.getValue("requires")) >= 0;
bReadThisSet = bGlobalReadThisSet;
b8bit = "true".equals(attributes.getValue("eight-bit"));
// Change global and current fontencodings
nGlobalFontencs = ClassicI18n.readFontencs(attributes.getValue("fontenc"));
nFontencs = nGlobalFontencs;
}
else if (qName.equals("symbol-subset")) {
// Do we requires something here?
if (attributes.getValue("requires")!=null) {
bReadThisSet = sSymbolSets.indexOf(attributes.getValue("requires")) >= 0;
}
// Change current fontencodings
nFontencs = ClassicI18n.readFontencs(attributes.getValue("fontenc"));
}
else if (qName.equals("symbol")) {
if (bReadThisSet) {
char c=(char)Integer.parseInt(attributes.getValue("char"),16);
String sEqChar=attributes.getValue("eq-char");
if (sEqChar!=null) { // copy existing definitions, if any
char eqc = (char)Integer.parseInt(sEqChar,16);
if (table.getCharType(eqc)!=UnicodeCharacter.UNKNOWN) {
table.addCharType(c,table.getCharType(eqc));
}
if (table.hasMathChar(eqc)) {
table.addMathChar(c,table.getMathChar(eqc));
}
if (table.hasTextChar(eqc)) {
table.addTextChar(c,table.getTextChar(eqc),table.getFontencs(eqc),table.getProtectChar(eqc));
}
}
else {
String sType=attributes.getValue("char-type");
String sMath=attributes.getValue("math");
String sText=attributes.getValue("text");
String sProtect=attributes.getValue("protect");
char cProtect = sProtect!=null && sProtect.length()>0 ? sProtect.charAt(0) : '\u0000';
if (sType!=null) table.addCharType(c,sType);
if (sMath!=null) table.addMathChar(c,sMath);
if (sText!=null) table.addTextChar(c,sText,nFontencs,cProtect);
}
}
}
else if (qName.equals("preserve-symbol")) {
if (bReadThisSet) {
String sMode=attributes.getValue("mode");
char c=(char)Integer.parseInt(attributes.getValue("char"),16);
table.addCharType(c,attributes.getValue("char-type"));
if ("math".equals(sMode) || "both".equals(sMode)) {
table.addMathChar(c,Character.toString(c));
}
if ("text".equals(sMode) || "both".equals(sMode)) {
table.addTextChar(c,Character.toString(c),nFontencs,'\u0000');
}
}
}
else if (qName.equals("preserve-symbols")) {
if (bReadThisSet) {
String sMode=attributes.getValue("mode");
String sType=attributes.getValue("char-type");
char c1=(char)Integer.parseInt(attributes.getValue("first-char"),16);
char c2=(char)Integer.parseInt(attributes.getValue("last-char"),16);
boolean bMath = "math".equals(sMode) || "both".equals(sMode);
boolean bText = "text".equals(sMode) || "both".equals(sMode);
for (char c=c1; c<=c2; c++) {
table.addCharType(c,sType);
if (bMath) {
table.addMathChar(c,Character.toString(c));
}
if (bText) {
table.addTextChar(c,Character.toString(c),nFontencs,'\u0000');
}
}
}
}
}
public void endElement(String nameSpace, String localName, String qName){
if (qName.equals("symbol-subset")) {
// Revert to global setting of reading status
bReadThisSet = bGlobalReadThisSet;
// Revert to global fontencoding
nFontencs = nGlobalFontencs;
}
else if (qName.equals("special-symbol-set")) {
if (b8bit) {
// Row 0 = Row 240 (F0)
// Note: 8-bit fonts are supposed to be relocated to F000..F0FF
// This may fail on import from msword, hence this hack
table.table[0] = table.table[240];
}
b8bit = false;
}
}
}

View file

@ -0,0 +1,231 @@
/************************************************************************
*
* XeTeXI18n.java
*
* Copyright: 2002-2015 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.6 (2015-06-30)
*
*/
package writer2latex.latex.i18n;
import java.text.Bidi;
import writer2latex.office.*;
import writer2latex.latex.LaTeXConfig;
import writer2latex.latex.LaTeXDocumentPortion;
import writer2latex.latex.ConverterPalette;
import writer2latex.latex.util.BeforeAfter;
/** This class takes care of i18n in XeLaTeX
*/
public class XeTeXI18n extends I18n {
private Polyglossia polyglossia;
private boolean bLTR;
private boolean bUseXepersian;
private String sLTRCommand=null;
private String sRTLCommand=null;
/** Construct a new XeTeXI18n as ConverterHelper
* @param ofr the OfficeReader to get language information from
* @param config the configuration which determines the symbols to use
* @param palette the ConverterPalette (unused)
*/
public XeTeXI18n(OfficeReader ofr, LaTeXConfig config, ConverterPalette palette) {
super(ofr,config,palette);
// CTL support: Currently two CTL languages are supported
// - hebrew (he_IL) using polyglossia.sty
// - farsi (fa_IR) using xepersian.sty
// TODO: Add a use_xepersian option, using polyglossia if false
// For these languages currently only monolingual documents are supported
// For LTR languages, multilingual documents are supported using polyglossia
polyglossia = new Polyglossia();
bLTR = !"fa".equals(sDefaultCTLLanguage) && !"he".equals(sDefaultCTLLanguage);
if (bLTR) {
polyglossia.applyLanguage(sDefaultLanguage, sDefaultCountry);
}
else {
polyglossia.applyLanguage(sDefaultCTLLanguage, sDefaultCTLCountry);
}
// For farsi, we load xepersian.sty
bUseXepersian = "fa".equals(sDefaultCTLLanguage);
if (bUseXepersian) {
sLTRCommand = "\\lr";
sRTLCommand = "\\rl";
}
}
/** Add declarations to the preamble to load the required packages
* @param pack usepackage declarations
* @param decl other declarations
*/
public void appendDeclarations(LaTeXDocumentPortion pack, LaTeXDocumentPortion decl) {
pack.append("\\usepackage{amsmath,amssymb,amsfonts}").nl()
.append("\\usepackage{fontspec}").nl()
.append("\\usepackage{xunicode}").nl()
.append("\\usepackage{xltxtra}").nl();
// xepersian.sty and polyglossia (or rather bidi) should be loaded as the last package
// We put it them the declarations part to achieve this
if (!bUseXepersian) {
String[] polyglossiaDeclarations = polyglossia.getDeclarations();
for (String s: polyglossiaDeclarations) {
decl.append(s).nl();
}
if (!bLTR) { // Use a default font set for hebrew
decl.append("\\setmainfont[Script=Hebrew]{Frank Ruehl CLM}").nl();
decl.append("\\setsansfont[Script=Hebrew]{Nachlieli CLM}").nl();
decl.append("\\setmonofont[Script=Hebrew]{Miriam Mono CLM}").nl();
}
}
else {
decl.append("\\usepackage{xepersian}").nl();
// Set the default font to the default CTL font defined in the document
StyleWithProperties defaultStyle = ofr.getDefaultParStyle();
if (defaultStyle!=null) {
String sDefaultCTLFont = defaultStyle.getProperty(XMLString.STYLE_FONT_NAME_COMPLEX);
if (sDefaultCTLFont!=null) {
decl.append("\\settextfont{").append(sDefaultCTLFont).append("}").nl();
}
}
}
}
/** Apply a language
* @param style the OOo style to read attributes from
* @param bDecl true if declaration form is required
* @param bInherit true if inherited properties should be used
* @param ba the <code>BeforeAfter</code> to add LaTeX code to.
*/
public void applyLanguage(StyleWithProperties style, boolean bDecl, boolean bInherit, BeforeAfter ba) {
if (bLTR && !bAlwaysUseDefaultLang && style!=null) {
// TODO: Support CTL and CJK
String sISOLang = style.getProperty(XMLString.FO_LANGUAGE,bInherit);
String sISOCountry = style.getProperty(XMLString.FO_COUNTRY, bInherit);
if (sISOLang!=null) {
String[] sCommand = polyglossia.applyLanguage(sISOLang, sISOCountry);
if (bDecl) {
ba.add(sCommand[1],sCommand[2]);
}
else {
ba.add(sCommand[0]+"{","}");
}
}
}
}
/** Push a font to the font stack
* @param sName the name of the font
*/
public void pushSpecialTable(String sName) {
// TODO
}
/** Pop a font from the font stack
*/
public void popSpecialTable() {
// TODO
}
/** Convert a string of characters into LaTeX
* @param s the source string
* @param bMathMode true if the string should be rendered in math mode
* @param sLang the ISO language of the string
* @return the LaTeX string
*/
public String convert(String s, boolean bMathMode, String sLang){
StringBuilder buf = new StringBuilder();
int nLen = s.length();
char c;
if (bMathMode) {
// No string replace or writing direction in math mode
for (int i=0; i<nLen; i++) {
convert(s.charAt(i),buf);
}
}
else if (!bUseXepersian) {
int i = 0;
while (i<nLen) {
ReplacementTrieNode node = stringReplace.get(s,i,nLen);
if (node!=null) {
buf.append(node.getLaTeXCode());
i += node.getInputLength();
}
else {
c = s.charAt(i++);
convert (c,buf);
}
}
}
else {
// TODO: Add support for string replace
Bidi bidi = new Bidi(s,Bidi.DIRECTION_RIGHT_TO_LEFT);
int nCurrentLevel = bidi.getBaseLevel();
int nNestingLevel = 0;
for (int i=0; i<nLen; i++) {
int nLevel = bidi.getLevelAt(i);
if (nLevel>nCurrentLevel) {
if (nLevel%2==0) { // even is LTR
buf.append(sLTRCommand).append("{");
}
else { // odd is RTL
buf.append(sRTLCommand).append("{");
}
nCurrentLevel=nLevel;
nNestingLevel++;
}
else if (nLevel<nCurrentLevel) {
buf.append("}");
nCurrentLevel=nLevel;
nNestingLevel--;
}
convert(s.charAt(i),buf);
}
while (nNestingLevel>0) {
buf.append("}");
nNestingLevel--;
}
}
return buf.toString();
}
private void convert(char c, StringBuilder buf) {
switch (c) {
case '#' : buf.append("\\#"); break; // Parameter
case '$' : buf.append("\\$"); break; // Math shift
case '%' : buf.append("\\%"); break; // Comment
case '&' : buf.append("\\&"); break; // Alignment tab
case '\\' : buf.append("\\textbackslash{}"); break; // Escape
case '^' : buf.append("\\^{}"); break; // Superscript
case '_' : buf.append("\\_"); break; // Subscript
case '{' : buf.append("\\{"); break; // Begin group
case '}' : buf.append("\\}"); break; // End group
case '~' : buf.append("\\textasciitilde{}"); break; // Active (non-breaking space)
case '\u00A0' : buf.append('~'); break; // Make non-breaking spaces visible
default: buf.append(c);
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,96 @@
/************************************************************************
*
* BeforeAfter.java
*
* Copyright: 2002-2011 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.2 (2011-03-29)
*
*/
package writer2latex.latex.util;
/** Utility class to hold LaTeX code to put before/after other LaTeX code
*/
public class BeforeAfter {
private String sBefore="";
private String sAfter="";
/** Constructor to initialize the object with a pair of strings
* @param sBefore1 LaTeX code to put before
* @param sAfter1 LaTeX code to put after
*/
public BeforeAfter(String sBefore1, String sAfter1) {
sBefore=sBefore1; sAfter=sAfter1;
}
/** Default constructor: Create with empty strings
*/
public BeforeAfter() { }
/** <p>Add data to the <code>BeforeAfter</code></p>
* <p>The new data will be be added "inside", thus for example</p>
* <ul><li><code>add("\textsf{","}");</code>
* <li><code>add("\textit{","}");</code></ul>
* <p>will create the pair <code>\textsf{\textit{</code>, <code>}}</code></p>
*
* @param sBefore1 LaTeX code to put before
* @param sAfter1 LaTeX code to put after
*/
public void add(String sBefore1, String sAfter1) {
sBefore+=sBefore1; sAfter=sAfter1+sAfter;
}
/** <p>Add data to the <code>BeforeAfter</code></p>
* <p>The new data will be be added "outside", thus for example</p>
* <ul><li><code>enclose("\textsf{","}");</code>
* <li><code>enclose("\textit{","}");</code></ul>
* <p>will create the pair <code>\textit{\textsf{</code>, <code>}}</code></p>
*
* @param sBefore1 LaTeX code to put before
* @param sAfter1 LaTeX code to put after
*/
public void enclose(String sBefore1, String sAfter1) {
sBefore=sBefore1+sBefore; sAfter+=sAfter1;
}
/** <p>Add the content of another <code>BeforeAfter</code> to this <code>BeforeAfter</code></p>
* <p>The new data will be be added "inside"</p>
*
* @param ba the code to add
*/
public void add(BeforeAfter ba) {
add(ba.getBefore(), ba.getAfter());
}
/** Get LaTeX code to put before
* @return then LaTeX code
*/
public String getBefore() { return sBefore; }
/** Get LaTeX code to put after
* @return then LaTeX code
*/
public String getAfter() { return sAfter; }
/** Check if this <code>BeforeAfter</code> contains any data
* @return true if there is data in at least one part
*/
public boolean isEmpty() { return sBefore.length()==0 && sAfter.length()==0; }
}

View file

@ -0,0 +1,325 @@
/************************************************************************
*
* Context.java
*
* Copyright: 2002-2011 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.2 (2011-01-24)
*
*/
package writer2latex.latex.util;
import writer2latex.office.XMLString;
import writer2latex.office.StyleWithProperties;
/** <p>LaTeX code is in general very context dependent. This class tracks the
* current context, which is the used by the converter to create valid and
* optimal LaTeX code.</p>
*/
public class Context {
// *** Formatting Info (current values in the source OOo document) ***
// Current list style
private String sListStyleName = null;
// Current background color
private String sBgColor = null;
// Current character formatting attributes
private String sFontName = null;
private String sFontStyle = null;
private String sFontVariant = null;
private String sFontWeight = null;
private String sFontSize = null;
private String sFontColor = null;
private String sLang = null;
private String sCountry = null;
// *** Structural Info (identifies contructions in the LaTeX document) ***
// within the header or footer of a pagestyle
private boolean bInHeaderFooter = false;
// within a table cell
private boolean bInTable = false; // any column
private boolean bInLastTableColumn = false; // last column
private boolean bInSimpleTable = false; // l, c or r-column
// within a multicols environment
private boolean bInMulticols = false;
// within a list of this level
private int nListLevel = 0;
// within a list that continues numbering
private boolean bInContinuedList = false;
// within a section command
private boolean bInSection = false;
// within a caption
private boolean bInCaption = false;
// within a Zotero/JabRef citation
private boolean bInZoteroJabRefText = false;
// within a floating figure (figure environment)
private boolean bInFigureFloat = false;
// within a floating table (table environment)
private boolean bInTableFloat = false;
// within a minipage environment
private boolean bInFrame = false;
// within a \footnote or \endnote
private boolean bInFootnote = false;
// in verbatim mode
private boolean bVerbatim = false;
// in math mode
private boolean bMathMode = false;
// *** Special Info ***
// Inside (inline) verbatim text, where line breaks are disallowed
private boolean bNoLineBreaks = false;
// Inside a construction, where footnotes are disallowed
private boolean bNoFootnotes = false;
// Inside an area, where lists are ignored
private boolean bIgnoreLists = false;
// *** Accessor Methods ***
public void setBgColor(String sBgColor) { this.sBgColor = sBgColor; }
public String getBgColor() { return sBgColor; }
public void setListStyleName(String sListStyleName) { this.sListStyleName = sListStyleName; }
public String getListStyleName() { return sListStyleName; }
public void setFontName(String sFontName) { this.sFontName = sFontName; }
public String getFontName() { return sFontName; }
public void setFontStyle(String sFontStyle) { this.sFontStyle = sFontStyle; }
public String getFontStyle() { return sFontStyle; }
public void setFontVariant(String sFontVariant) { this.sFontVariant = sFontVariant; }
public String getFontVariant() { return sFontVariant; }
public void setFontWeight(String sFontWeight) { this.sFontWeight = sFontWeight; }
public String getFontWeight() { return sFontWeight; }
public void setFontSize(String sFontSize) { this.sFontSize = sFontSize; }
public String getFontSize() { return sFontSize; }
public void setFontColor(String sFontColor) { this.sFontColor = sFontColor; }
public String getFontColor() { return sFontColor; }
public void setLang(String sLang) { this.sLang = sLang; }
public String getLang() { return sLang; }
public void setCountry(String sCountry) { this.sCountry = sCountry; }
public String getCountry() { return sCountry; }
public void setInHeaderFooter(boolean bInHeaderFooter) {
this.bInHeaderFooter = bInHeaderFooter;
}
public boolean isInHeaderFooter() { return bInHeaderFooter; }
public void setInTable(boolean bInTable) { this.bInTable = bInTable; }
public boolean isInTable() { return bInTable; }
public void setInLastTableColumn(boolean bInLastTableColumn) { this.bInLastTableColumn = bInLastTableColumn; }
public boolean isInLastTableColumn() { return bInLastTableColumn; }
public void setInSimpleTable(boolean bInSimpleTable) { this.bInSimpleTable = bInSimpleTable; }
public boolean isInSimpleTable() { return bInSimpleTable; }
public void setInMulticols(boolean bInMulticols) {
this.bInMulticols = bInMulticols;
}
public boolean isInMulticols() { return bInMulticols; }
public void setListLevel(int nListLevel) { this.nListLevel = nListLevel; }
public void incListLevel() { nListLevel++; }
public int getListLevel() { return nListLevel; }
public void setInContinuedList(boolean bInContinuedList) { this.bInContinuedList=bInContinuedList; }
public boolean isInContinuedList() { return this.bInContinuedList; }
public void setInSection(boolean bInSection) { this.bInSection = bInSection; }
public boolean isInSection() { return bInSection; }
public void setInCaption(boolean bInCaption) { this.bInCaption = bInCaption; }
public boolean isInCaption() { return bInCaption; }
public void setInZoteroJabRefText(boolean bInZoteroJabRefText) { this.bInZoteroJabRefText = bInZoteroJabRefText; }
public boolean isInZoteroJabRefText() { return bInZoteroJabRefText; }
public void setInFigureFloat(boolean bInFigureFloat) { this.bInFigureFloat = bInFigureFloat; }
public boolean isInFigureFloat() { return bInFigureFloat; }
public void setInTableFloat(boolean bInTableFloat) { this.bInTableFloat = bInTableFloat; }
public boolean isInTableFloat() { return bInTableFloat; }
public void setInFrame(boolean bInFrame) { this.bInFrame = bInFrame; }
public boolean isInFrame() { return bInFrame; }
public void setInFootnote(boolean bInFootnote) {
this.bInFootnote = bInFootnote;
}
public boolean isInFootnote() { return bInFootnote; }
public void setNoFootnotes(boolean bNoFootnotes) {
this.bNoFootnotes = bNoFootnotes;
}
public boolean isNoFootnotes() { return bNoFootnotes; }
public void setIgnoreLists(boolean bIgnoreLists) {
this.bIgnoreLists = bIgnoreLists;
}
public boolean isIgnoreLists() { return bIgnoreLists; }
public void setNoLineBreaks(boolean bNoLineBreaks) {
this.bNoLineBreaks = bNoLineBreaks;
}
public boolean isNoLineBreaks() { return bNoLineBreaks; }
public boolean isVerbatim() { return bVerbatim; }
public void setVerbatim(boolean bVerbatim) { this.bVerbatim = bVerbatim; }
public boolean isMathMode() { return bMathMode; }
public void setMathMode(boolean bMathMode) { this.bMathMode = bMathMode; }
// update context
public void updateFormattingFromStyle(StyleWithProperties style) {
String s;
if (style==null) { return; }
s = style.getProperty(XMLString.STYLE_FONT_NAME);
if (s!=null) { setFontName(s); }
s = style.getProperty(XMLString.FO_FONT_STYLE);
if (s!=null) { setFontStyle(s); }
s = style.getProperty(XMLString.FO_FONT_VARIANT);
if (s!=null) { setFontVariant(s); }
s = style.getProperty(XMLString.FO_FONT_WEIGHT);
if (s!=null) { setFontWeight(s); }
s = style.getProperty(XMLString.FO_FONT_SIZE);
if (s!=null) { setFontSize(s); }
s = style.getProperty(XMLString.FO_COLOR);
if (s!=null) { setFontColor(s); }
s = style.getProperty(XMLString.FO_LANGUAGE);
if (s!=null) { setLang(s); }
s = style.getProperty(XMLString.FO_COUNTRY);
if (s!=null) { setCountry(s); }
}
public void resetFormattingFromStyle(StyleWithProperties style) {
setFontName(null);
setFontStyle(null);
setFontVariant(null);
setFontWeight(null);
setFontSize(null);
setFontColor(null);
setLang(null);
setCountry(null);
updateFormattingFromStyle(style);
}
// clone this Context
public Object clone() {
Context newContext = new Context();
newContext.setListStyleName(sListStyleName);
newContext.setBgColor(sBgColor);
newContext.setFontName(sFontName);
newContext.setFontStyle(sFontStyle);
newContext.setFontVariant(sFontVariant);
newContext.setFontWeight(sFontWeight);
newContext.setFontSize(sFontSize);
newContext.setFontColor(sFontColor);
newContext.setLang(sLang);
newContext.setCountry(sCountry);
newContext.setInHeaderFooter(bInHeaderFooter);
newContext.setInTable(bInTable);
newContext.setInLastTableColumn(bInLastTableColumn);
newContext.setInSimpleTable(bInSimpleTable);
newContext.setInMulticols(bInMulticols);
newContext.setListLevel(nListLevel);
newContext.setInContinuedList(bInContinuedList);
newContext.setInSection(bInSection);
newContext.setInCaption(bInCaption);
newContext.setInZoteroJabRefText(bInZoteroJabRefText);
newContext.setInFigureFloat(bInFigureFloat);
newContext.setInTableFloat(bInTableFloat);
newContext.setInFrame(bInFrame);
newContext.setInFootnote(bInFootnote);
newContext.setVerbatim(bVerbatim);
newContext.setMathMode(bMathMode);
newContext.setNoFootnotes(bNoFootnotes);
newContext.setIgnoreLists(bIgnoreLists);
newContext.setNoLineBreaks(bNoLineBreaks);
return newContext;
}
}

View file

@ -0,0 +1,68 @@
/************************************************************************
*
* HeadingMap.java
*
* Copyright: 2002-2006 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 0.5 (2006-11-02)
*
*/
package writer2latex.latex.util;
/** This class contains data for the mapping of OOo headings to LaTeX headings.
A LaTeX heading is characterized by a name and a level.
The heading is inserted with \name{...} or \name[...]{...}
The headings are supposed to be "normal" LaTeX headings,
ie. the names are also counter names, and the headings
can be reformatted using \@startsection etc.
Otherwise max-level should be zero.
*/
public class HeadingMap {
private int nMaxLevel;
private String[] sName;
private int[] nLevel;
/** Constructor: Create a new HeadingMap
@param nMaxLevel the maximal level of headings that are mapped */
public HeadingMap(int nMaxLevel) {
reset(nMaxLevel);
}
/** Clear all data associated with this HeadingMap (in order to reuse it) */
public void reset(int nMaxLevel) {
this.nMaxLevel = nMaxLevel;
sName = new String[nMaxLevel+1];
nLevel = new int[nMaxLevel+1];
}
/** Set data associated with a specific heading level */
public void setLevelData(int nWriterLevel, String sName, int nLevel) {
this.sName[nWriterLevel] = sName;
this.nLevel[nWriterLevel] = nLevel;
}
/** Returns the maximal Writer level associated with this HeadingMap */
public int getMaxLevel() { return nMaxLevel; }
/** Return the name (for counter and \@startsection) for this level */
public String getName(int nWriterLevel) { return sName[nWriterLevel]; }
/** Return the LaTeX level for this Writer level (for \@startsection) */
public int getLevel(int nWriterLevel) { return nLevel[nWriterLevel]; }
}

View file

@ -0,0 +1,11 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title>The package writer2latex.xhtml.util</title>
</head>
<body>
<p>Some general utility classes for LaTeX export.</p>
</body>
</html>

View file

@ -0,0 +1,116 @@
/************************************************************************
*
* StyleMap.java
*
* Copyright: 2002-2011 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.2 (2011-03-30)
*
*/
package writer2latex.latex.util;
import java.util.Hashtable;
import java.util.Enumeration;
public class StyleMap {
private Hashtable<String, StyleMapItem> items = new Hashtable<String, StyleMapItem>();
public void put(String sName, String sBefore, String sAfter, String sNext, boolean bLineBreak, int nBreakAfter, boolean bVerbatim) {
StyleMapItem item = new StyleMapItem();
item.sBefore = sBefore;
item.sAfter = sAfter;
item.sNext = ";"+sNext+";";
item.bLineBreak = bLineBreak;
item.nBreakAfter = nBreakAfter;
item.bVerbatim = bVerbatim;
items.put(sName,item);
}
public void put(String sName, String sBefore, String sAfter, boolean bLineBreak, int nBreakAfter, boolean bVerbatim) {
StyleMapItem item = new StyleMapItem();
item.sBefore = sBefore;
item.sAfter = sAfter;
item.sNext = ";;";
item.bLineBreak = bLineBreak;
item.nBreakAfter = nBreakAfter;
item.bVerbatim = bVerbatim;
items.put(sName,item);
}
public void put(String sName, String sBefore, String sAfter, String sNext, boolean bVerbatim) {
StyleMapItem item = new StyleMapItem();
item.sBefore = sBefore;
item.sAfter = sAfter;
item.sNext = ";"+sNext+";";
item.bLineBreak = true;
item.nBreakAfter = StyleMapItem.PAR;
item.bVerbatim = bVerbatim;
items.put(sName,item);
}
public void put(String sName, String sBefore, String sAfter) {
StyleMapItem item = new StyleMapItem();
item.sBefore = sBefore;
item.sAfter = sAfter;
item.sNext = ";;";
item.bLineBreak = true;
item.nBreakAfter = StyleMapItem.PAR;
item.bVerbatim = false;
items.put(sName,item);
}
public boolean contains(String sName) {
return sName!=null && items.containsKey(sName);
}
public String getBefore(String sName) {
return items.get(sName).sBefore;
}
public String getAfter(String sName) {
return items.get(sName).sAfter;
}
public String getNext(String sName) {
String sNext = items.get(sName).sNext;
return sNext.substring(1,sNext.length()-1);
}
public boolean isNext(String sName, String sNext) {
String sNext1 = items.get(sName).sNext;
return sNext1.indexOf(";"+sNext+";")>-1;
}
public boolean getLineBreak(String sName) {
return contains(sName) && items.get(sName).bLineBreak;
}
public int getBreakAfter(String sName) {
return contains(sName) ? items.get(sName).nBreakAfter : StyleMapItem.PAR;
}
public boolean getVerbatim(String sName) {
return contains(sName) && items.get(sName).bVerbatim;
}
public Enumeration<String> getNames() {
return items.keys();
}
}

View file

@ -0,0 +1,40 @@
/************************************************************************
*
* StyleMapItem.java
*
* Copyright: 2002-2011 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.2 (2011-03-30)
*
*/
package writer2latex.latex.util;
// A struct to hold data about a style map
public class StyleMapItem {
public static final int NONE = 0;
public static final int LINE = 1;
public static final int PAR = 2;
String sBefore;
String sAfter;
String sNext;
int nBreakAfter;
boolean bLineBreak;
boolean bVerbatim;
}

View file

@ -0,0 +1,131 @@
/************************************************************************
*
* BibMark.java
*
* Copyright: 2002-2014 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.6 (2014-11-24)
*
*/
package writer2latex.office;
import java.util.HashMap;
import java.util.Map;
import org.w3c.dom.Node;
import writer2latex.util.Misc;
/** This class represents a single bibliography-mark in an ODF document
*/
public final class BibMark {
/** Entry types in an ODF bibliography marks. These are more or less modeled on BibTeX with
* the following exceptions: organizations is organization in BibTeX, report_type is report in BibTeX,
* the BibTeX fields crossref and key are missing in ODF, the ODF fields
* url, custom1, custom2, custom3, custom4, custom5, isbn are not standard in BibTeX
*
*/
public enum EntryType { address, annote, author, booktitle, chapter,
edition, editor, howpublished, institution, journal, month,
note, number, organizations, pages, publisher, school, series,
title, report_type, volume, year,
url, custom1, custom2, custom3, custom4, custom5, isbn
}
// The data of the bibliography mark
private String sIdentifier;
private String sEntryType;
private Map<EntryType,String> fields = new HashMap<EntryType,String>();
/** Create a new BibMark from scratch.
* @param sIdentifier the unique identifier for this BibMark
* @param sEntryType the type of entry such as book or article
*/
public BibMark(String sIdentifier, String sEntryType) {
this.sIdentifier = sIdentifier;
this.sEntryType = sEntryType;
}
/** Create a new <code>BibMark</code> from a text:bibliography-mark node.
*/
public BibMark(Node node) {
sIdentifier = Misc.getAttribute(node,XMLString.TEXT_IDENTIFIER);
sEntryType = Misc.getAttribute(node,XMLString.TEXT_BIBLIOGRAPHY_TYPE);
if (sEntryType==null) { // bug in OOo 1.0!
sEntryType = Misc.getAttribute(node,XMLString.TEXT_BIBILIOGRAPHIC_TYPE);
}
fields.put(EntryType.address, Misc.getAttribute(node,XMLString.TEXT_ADDRESS));
fields.put(EntryType.annote, Misc.getAttribute(node,XMLString.TEXT_ANNOTE));
fields.put(EntryType.author, Misc.getAttribute(node,XMLString.TEXT_AUTHOR));
fields.put(EntryType.booktitle, Misc.getAttribute(node,XMLString.TEXT_BOOKTITLE));
fields.put(EntryType.chapter, Misc.getAttribute(node,XMLString.TEXT_CHAPTER));
fields.put(EntryType.edition, Misc.getAttribute(node,XMLString.TEXT_EDITION));
fields.put(EntryType.editor, Misc.getAttribute(node,XMLString.TEXT_EDITOR));
fields.put(EntryType.howpublished, Misc.getAttribute(node,XMLString.TEXT_HOWPUBLISHED));
fields.put(EntryType.institution, Misc.getAttribute(node,XMLString.TEXT_INSTITUTION));
fields.put(EntryType.journal, Misc.getAttribute(node,XMLString.TEXT_JOURNAL));
fields.put(EntryType.month, Misc.getAttribute(node,XMLString.TEXT_MONTH));
fields.put(EntryType.note, Misc.getAttribute(node,XMLString.TEXT_NOTE));
fields.put(EntryType.number, Misc.getAttribute(node,XMLString.TEXT_NUMBER));
fields.put(EntryType.organizations, Misc.getAttribute(node,XMLString.TEXT_ORGANIZATIONS));
fields.put(EntryType.pages, Misc.getAttribute(node,XMLString.TEXT_PAGES));
fields.put(EntryType.publisher, Misc.getAttribute(node,XMLString.TEXT_PUBLISHER));
fields.put(EntryType.school, Misc.getAttribute(node,XMLString.TEXT_SCHOOL));
fields.put(EntryType.series, Misc.getAttribute(node,XMLString.TEXT_SERIES));
fields.put(EntryType.title, Misc.getAttribute(node,XMLString.TEXT_TITLE));
fields.put(EntryType.report_type, Misc.getAttribute(node,XMLString.TEXT_REPORT_TYPE));
fields.put(EntryType.volume, Misc.getAttribute(node,XMLString.TEXT_VOLUME));
fields.put(EntryType.year, Misc.getAttribute(node,XMLString.TEXT_YEAR));
fields.put(EntryType.url, Misc.getAttribute(node,XMLString.TEXT_URL));
fields.put(EntryType.custom1, Misc.getAttribute(node,XMLString.TEXT_CUSTOM1));
fields.put(EntryType.custom2, Misc.getAttribute(node,XMLString.TEXT_CUSTOM2));
fields.put(EntryType.custom3, Misc.getAttribute(node,XMLString.TEXT_CUSTOM3));
fields.put(EntryType.custom4, Misc.getAttribute(node,XMLString.TEXT_CUSTOM4));
fields.put(EntryType.custom5, Misc.getAttribute(node,XMLString.TEXT_CUSTOM5));
fields.put(EntryType.isbn, Misc.getAttribute(node,XMLString.TEXT_ISBN));
}
/** Get the identifier.
*
* @return the unique identifier of this <code>BibMark</code>
*/
public String getIdentifier() { return sIdentifier; }
/** Get the entry type.
*
* @return the entry type of this <code>BibMark</code>
*/
public String getEntryType() { return sEntryType; }
/** Set a specific field.
*
* @param entryType the type of field to set
* @param sValue the new value of the field
*/
public void setField(EntryType entryType,String sValue) { fields.put(entryType, sValue); }
/** Return a specific field.
*
* @param entryType the type of the field to get
* @return the value of the field, or null if the field is not set
*/
public String getField(EntryType entryType) {
return fields.containsKey(entryType) ? fields.get(entryType) : null;
}
}

View file

@ -0,0 +1,39 @@
/************************************************************************
*
* CellView.java
*
* Copyright: 2002-2008 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.0 (2008-09-07)
*
*/
package writer2latex.office;
import org.w3c.dom.Element;
/**
* This class represent a cell in a table view
*/
public class CellView {
public Element cell = null;
public int nRowSpan = 1;
public int nColSpan = 1;
public int nOriginalRow = -1;
public int nOriginalCol = -1;
}

View file

@ -0,0 +1,150 @@
/************************************************************************
*
* ControlReader.java
*
* Copyright: 2002-2008 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.0 (2008-11-23)
*
*/
package writer2latex.office;
//import java.util.Hashtable;
import java.util.Vector;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import writer2latex.util.Misc;
/** <p> This class reads a form control in an OOo document (a form:control
* node). A control always has an owner form.
* Properties and events are ignored.</p>
*/
public class ControlReader {
private FormReader ownerForm; // a control always belongs to a form
private String sId; // a control is identified by id
private Element control; // the control element
private Element controlType; // the type specific child element
private Vector<Node> items = new Vector<Node>(); // the options/items of a list/combobox
/** <p>The constructor reads the content of a control element</p>
* The representation in OpenDocument differs slightly from OOo 1.x.
* @param control a DOM element, which must be control node
*/
public ControlReader(Element control, FormReader ownerForm) {
this.ownerForm = ownerForm;
this.control = control;
sId = control.getAttribute(XMLString.FORM_ID);
// Read the control type specific info
if (control.getTagName().equals(XMLString.FORM_CONTROL)) { // old format
controlType = Misc.getFirstChildElement(control);
}
else { // oasos
controlType = control;
}
if (controlType!=null) { // must always be the case!
// Collect options/items
Node child = controlType.getFirstChild();
while (child!=null) {
if (child.getNodeType()==Node.ELEMENT_NODE && (
child.getNodeName().equals(XMLString.FORM_OPTION) ||
child.getNodeName().equals(XMLString.FORM_ITEM))) {
items.add(child);
}
child = child.getNextSibling();
}
}
}
/** <p>A control in OOo is identified by id (<code>form:control-id</code>
* attribute. The id is accessed by this method.</p>
* @return the id of the control
*/
public String getId() { return sId; }
/** <p>A control in OOo belongs to a form.</p>
* @return the form containing this control
*/
public FormReader getOwnerForm() { return ownerForm; }
/** <p>Get an attribute of the control. If the attribute does not exist,
* this method returns <code>null</code>.
* @param sName the name of the attribute
* @return the value of the attribute, or <code>null</code>
*/
public String getAttribute(String sName) {
return control.hasAttribute(sName) ? control.getAttribute(sName) : null;
}
/** <p>The type of the control is identified by a name, eg. form:submit</p>
* @return the type of this control
*/
public String getControlType() { return controlType.getTagName(); }
/** <p>Get an attribute specific to this type of control.
* If the attribute does not exist, this method returns <code>null</code>.
* @param sName the name of the attribute
* @return the value of the attribute, or <code>null</code>
*/
public String getTypeAttribute(String sName) {
return controlType!=null && controlType.hasAttribute(sName) ?
controlType.getAttribute(sName) : null;
}
/** <p>Return the number of options/items in this control.
* Only listbox (options) and combobox (items) controls can have these,
* for other controls this will return 0.
* @return the number of options/items
*/
public int getItemCount() { return items.size(); }
/** <p>Get an attribute of an option/item.
* If the index and/or the attribute does not exist, this method returns
* <code>null</code>.
* @param nIndex the index of the option/item
* @param sName the name of the attribute
* @return the value of the attribute, or <code>null</code>
*/
public String getItemAttribute(int nIndex, String sName) {
if (0<=nIndex && nIndex<=items.size()) {
return ((Element)items.get(nIndex)).hasAttribute(sName) ?
((Element)items.get(nIndex)).getAttribute(sName) : null;
}
else {
return null;
}
}
/** <p>Get the value of an option/item.
* If the index does not exist, this method returns
* <code>null</code>.
* @param nIndex the index of the option/item
* @return the value of the option/item, or <code>null</code>
*/
public String getItemValue(int nIndex) {
if (0<=nIndex && nIndex<=items.size()) {
return Misc.getPCDATA((Element)items.get(nIndex));
}
else {
return null;
}
}
}

View file

@ -0,0 +1,65 @@
/************************************************************************
*
* EmbeddedBinaryObject.java
*
* Copyright: 2002-2014 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.4 (2012-03-28)
*
*/
package writer2latex.office;
import writer2latex.util.SimpleZipReader;
/**
* This class represents an embedded object with a binary representation in an ODF package document
*/
public class EmbeddedBinaryObject extends EmbeddedObject {
/** The object's binary representation. */
private byte[] blob = null;
/**
* Package private constructor for use when reading an object from a
* package ODF file
*
* @param sName The name of the object.
* @param sType The MIME-type of the object.
* @param doc The document containing the object.
* @param source A <code>SimpleZipReader</code> containing the object
*/
protected EmbeddedBinaryObject(String sName, String sType, OfficeDocument doc, SimpleZipReader source) {
super(sName,sType,doc);
blob = source.getEntry(sName);
}
/** Get the binary data for this object
*
* @return A <code>byte</code> array containing the object's data.
*/
public byte[] getBinaryData() {
return blob;
}
public void dispose() {
super.dispose();
blob = null;
}
}

View file

@ -0,0 +1,71 @@
/************************************************************************
*
* EmbeddedObject.java
*
* Copyright: 2002-2014 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.4 (2014-08-27)
*
*/
package writer2latex.office;
/** This class represents and embedded object within an ODF package document
*/
public abstract class EmbeddedObject {
private OfficeDocument doc;
private String sName;
private String sType;
/** Construct a new embedded object
*
* @param sName The name of the object.
* @param sType The MIME-type of the object.
* @param doc The document to which the object belongs.
*/
protected EmbeddedObject(String sName, String sType, OfficeDocument doc) {
this.sName = sName;
this.sType = sType;
this.doc = doc;
}
/** Get the name of the embedded object represented by this instance.
* The name refers to the manifest.xml file
*
* @return The name of the object.
*/
public final String getName() {
return sName;
}
/** Get the MIME type of the embedded object represented by this instance.
* The MIME type refers to the manifest.xml file
*/
public final String getType() {
return sType;
}
/** Dispose this <code>EmbeddedObject</code>. This implies that the content is nullified and the object
* is removed from the collection in the <code>OfficeDocument</code>.
*
*/
public void dispose() {
doc.removeEmbeddedObject(sName);
}
}

View file

@ -0,0 +1,111 @@
/************************************************************************
*
* EmbeddedXMLObject.java
*
* Copyright: 2002-2014 by Henrik Just
*
* This file is part of Writer2LaTeX.
*
* Writer2LaTeX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Writer2LaTeX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
*
* Version 1.4 (2014-08-28)
*
*/
package writer2latex.office;
import java.io.IOException;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;
import writer2latex.util.SimpleZipReader;
/** This class represents those embedded objects in an ODF document that have an XML representation:
* Formulas, charts, spreadsheets, text, drawings and presentations.
* These object types are stored using a combination of content, settings and styles XML files.
* The settings are application specific and ignored.
*/
public class EmbeddedXMLObject extends EmbeddedObject {
// Byte entries for the XML streams of this object
private byte[] contentBytes = null;
private byte[] stylesBytes = null;
// DOM trees representing the XML parts of this object
protected Document contentDOM = null;
protected Document stylesDOM = null;
/** Read an object from an ODF package document
*
* @param sName The name of the object.
* @param sType The MIME-type of the object.
* @param source A ZIP reader providing the contents of the package
*/
protected EmbeddedXMLObject(String sName, String sType, OfficeDocument doc, SimpleZipReader source) {
super(sName, sType, doc);
// Read the bytes, but defer parsing until required (at that point, the bytes are nullified)
contentBytes = source.getEntry(sName+"/"+OfficeDocument.CONTENTXML);
stylesBytes = source.getEntry(sName+"/"+OfficeDocument.STYLESXML);
}
/**
* 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=getDOM(contentBytes);
contentBytes=null;
}
return contentDOM;
}
/**
* 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 = getDOM(stylesBytes);
stylesBytes=null;
}
return stylesDOM;
}
private Document getDOM(byte[] data) throws SAXException, IOException {
if (data!=null) {
return OfficeDocument.parse(data);
}
return null;
}
public void dispose() {
super.dispose();
contentBytes = null;
stylesBytes = null;
contentDOM = null;
stylesDOM = null;
}
}

Some files were not shown because too many files have changed in this diff Show more