/************************************************************************ * * FieldConverter.java * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License version 2.1, as published by the Free Software Foundation. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, * MA 02111-1307 USA * * Copyright: 2002-2008 by Henrik Just * * All Rights Reserved. * * Version 1.0 (2008-11-23) * */ package writer2latex.latex; //import java.io.UnsupportedEncodingException; import java.util.Enumeration; import java.util.Hashtable; import java.util.Vector; import org.w3c.dom.Element; import org.w3c.dom.Node; import writer2latex.latex.util.Context; import writer2latex.latex.util.HeadingMap; import writer2latex.office.OfficeReader; import writer2latex.office.XMLString; import writer2latex.util.CSVList; 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, oooref (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 { // 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 seqDecl = new Hashtable(); // first usage of sequence (maps name->text:sequence element) private Hashtable seqFirst = new Hashtable(); private Vector postponedReferenceMarks = new Vector(); private Vector postponedBookmarks = new Vector(); private boolean bUseHyperref = false; private boolean bUsesPageCount = false; private boolean bUsesTitleref = false; private boolean bUsesOooref = false; public FieldConverter(OfficeReader ofr, LaTeXConfig config, ConverterPalette palette) { super(ofr,config,palette); // hyperref.sty is not compatible with titleref.sty and oooref.sty: bUseHyperref = config.useHyperref() && !config.useTitleref() && !config.useOooref(); } /**
Append declarations needed by the FieldConverter
to
* the preamble.
LaTeXDocumentPortion
to which
* declarations of packages should be added (\\usepackage
).
* @param decl the LaTeXDocumentPortion
to which
* other declarations should be added.
*/
public void appendDeclarations(LaTeXDocumentPortion pack, LaTeXDocumentPortion decl) {
// use lastpage.sty
if (bUsesPageCount) {
pack.append("\\usepackage{lastpage}").nl();
}
// use titleref.sty
if (bUsesTitleref) {
pack.append("\\usepackage{titleref}").nl();
}
// use oooref.sty
if (bUsesOooref) {
pack.append("\\usepackage[");
HeadingMap hm = config.getHeadingMap();
CSVList opt = new CSVList(",");
for (int i=0; i<=hm.getMaxLevel(); i++) { opt.addValue(hm.getName(i)); }
pack.append(opt.toString()).append("]{oooref}").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 names = seqFirst.keys();
while (names.hasMoreElements()) {
// Get first text:sequence element
String sName = (String) names.nextElement();
Element first = (Element) 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(ListStyleConverter.numFormat(sNumFormat))
.append("{").append(seqnames.getExportName(sName))
.append("}}").nl();
}
}
/** Process sequence declarations
* @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(); } } /**Process a sequence field (text:sequence tag)
* @param node The element containing the sequence field * @param ldp theLaTeXDocumentPortion
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("}");
}
}
}
}
/** Create label for a sequence field (text:sequence tag)
* @param node The element containing the sequence field * @param ldp theLaTeXDocumentPortion
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:Process a sequence reference (text:sequence-ref tag)
* @param node The element containing the sequence reference * @param ldp theLaTeXDocumentPortion
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 ("chapter".equals(sFormat) && config.useOooref()) {
ldp.append("\\chapterref{seq:")
.append(seqrefnames.getExportName(sRefName))
.append("}");
bUsesOooref = true;
}
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);
}
}
}
/** Process a reference mark (text:reference-mark tag)
* @param node The element containing the reference mark * @param ldp theLaTeXDocumentPortion
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()) {
// Note: Always include \label here, even when it's not used
String sName = node.getAttribute(XMLString.TEXT_NAME);
if (sName!=null) {
ldp.append("\\label{ref:"+refnames.getExportName(sName)+"}");
}
}
else {
// Reference marks should not appear within \section or \caption
postponedReferenceMarks.add(node);
}
}
/** Process a reference (text:reference-ref tag)
* @param node The element containing the reference * @param ldp theLaTeXDocumentPortion
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(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(sName)+"}");
}
else { // use current value
palette.getInlineCv().traversePCDATA(node,ldp,oc);
}
}
/** Process a bookmark (text:bookmark tag)
*A bookmark may be the target for either a hyperlink or a reference,
* so this will generate a \\hyperref
and/or a \\label
LaTeXDocumentPortion
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);
}
}
/** Process a bookmark reference (text:bookmark-ref tag).
* @param node The element containing the bookmark reference * @param ldp theLaTeXDocumentPortion
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 { // use current value
palette.getInlineCv().traversePCDATA(node,ldp,oc);
}
}
/** Process pending reference marks and bookmarks (which may have been * postponed within sections, captions or verbatim text.
* @param ldp theLaTeXDocumentPortion
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; iLaTeXDocumentPortion
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 (ofr.getTextContent(node).trim().equals(sHref)) {
// The link text equals the url
ldp.append("\\url{")
.append(oc.isInFootnote() ? escapeHref(Misc.urlDecode(sHref)) : Misc.urlDecode(sHref))
.append("}");
}
else {
ldp.append("\\href{")
.append(oc.isInFootnote() ? escapeHref(Misc.urlDecode(sHref)) : Misc.urlDecode(sHref))
.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);
}
}
/** Add a \\hypertarget
LaTeXDocumentPortion
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("}{}");
}
/** Add a \\hypertarget
LaTeXDocumentPortion
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("}{}");
}
}
/** Process a page number field (text:page-number tag)
* @param node The element containing the page number field * @param ldp theLaTeXDocumentPortion
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{}");
}
/** Process a page count field (text:page-count tag)
* @param node The element containing the page count field * @param ldp theLaTeXDocumentPortion
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 href within footnote, we have to escape the #
private String escapeHref(String s) {
StringBuffer buf = new StringBuffer();
for (int i=0; i