/************************************************************************ * * Converter.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 . * * Version 1.6 (2015-06-11) * */ package writer2latex.xhtml; import java.io.File; import java.io.FileInputStream; import java.util.HashSet; import java.util.ListIterator; import java.util.LinkedList; import java.util.Set; import java.util.Stack; import java.util.Vector; import java.util.Hashtable; import java.util.Iterator; import java.io.InputStream; import java.io.IOException; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.Text; import writer2latex.api.Config; import writer2latex.api.ContentEntry; import writer2latex.api.ConverterFactory; import writer2latex.api.OutputFile; import writer2latex.base.ContentEntryImpl; import writer2latex.base.BasicConverter; import writer2latex.office.MIMETypes; import writer2latex.office.OfficeReader; import writer2latex.office.StyleWithProperties; import writer2latex.office.XMLString; import writer2latex.util.ExportNameCollection; import writer2latex.util.Misc; import writer2latex.xhtml.content.DrawParser; import writer2latex.xhtml.content.MathParser; import writer2latex.xhtml.content.PageContainer; import writer2latex.xhtml.content.TableParser; import writer2latex.xhtml.content.TextParser; import writer2latex.xhtml.l10n.L10n; import writer2latex.xhtml.style.Styles; /** *

This class converts an OpenDocument file to an XHTML(+MathML) or EPUB document.

* */ public class Converter extends BasicConverter { private static final String EPUB_STYLES_FOLDER = "styles/"; private static final String EPUB_STYLESHEET = "styles/styles1.css"; private static final String EPUB_CUSTOM_STYLESHEET = "styles/styles.css"; // Config private XhtmlConfig config; public Config getConfig() { return config; } protected XhtmlConfig getXhtmlConfig() { return config; } // The locale private L10n l10n; // The helpers private Styles styles; private TextParser textParser; private TableParser tableParser; private DrawParser drawParser; private MathParser mathParser; // The template private XhtmlDocument template = null; // The included style sheet and associated resources private CssDocument styleSheet = null; private Set resources = new HashSet(); // The xhtml output file(s) public int nType = XhtmlDocument.XHTML10; // the doctype private boolean bOPS = false; // Do we need to be OPS conforming? public Vector outFiles; private int outFileIndex; private XhtmlDocument htmlDoc; // current outfile private Document htmlDOM; // current DOM, usually within htmlDoc public PageContainer pageContainer = null; public boolean isRDF = false; //private int nTocFileIndex = -1; //private int nAlphabeticalIndex = -1; // Hyperlinks Hashtable targets = new Hashtable(); LinkedList links = new LinkedList(); // Strip illegal characters from internal hyperlink targets private ExportNameCollection targetNames = new ExportNameCollection(true); // The current context (currently we only track the content width, but this might be expanded with formatting // attributes - at least background color and font size later) private Stack contentWidth = new Stack(); // Constructor setting the DOCTYPE public Converter(int nType) { super(); config = new XhtmlConfig(); this.nType = nType; pageContainer = new PageContainer(); } // override methods to read templates, style sheets and resources @Override public void readTemplate(InputStream is) throws IOException { template = new XhtmlDocument("Template",nType); template.read(is); } @Override public void readTemplate(File file) throws IOException { readTemplate(new FileInputStream(file)); } @Override public void readStyleSheet(InputStream is) throws IOException { if (styleSheet==null) { styleSheet = new CssDocument(EPUB_CUSTOM_STYLESHEET); } styleSheet.read(is); } @Override public void readStyleSheet(File file) throws IOException { readStyleSheet(new FileInputStream(file)); } @Override public void readResource(InputStream is, String sFileName, String sMediaType) throws IOException { if (sMediaType==null) { // Guess the media type from the file extension sMediaType=""; String sfilename = sFileName.toLowerCase(); // PNG, JPEG and GIF are the basic raster image formats that must be supported by EPUB readers if (sfilename.endsWith(MIMETypes.PNG_EXT)) { sMediaType = MIMETypes.PNG; } else if (sfilename.endsWith(MIMETypes.JPEG_EXT)) { sMediaType = MIMETypes.JPEG; } else if (sfilename.endsWith(".jpeg")) { sMediaType = MIMETypes.JPEG; } else if (sfilename.endsWith(MIMETypes.GIF_EXT)) { sMediaType = MIMETypes.GIF; } // The OPS 2.0.1 specification recommends (and only mentions) OpenType with this media type else if (sfilename.endsWith(".otf")) { sMediaType = "application/vnd.ms-opentype"; } // For convenience we also include a media type for true type fonts (the most common, it seems) else if (sfilename.endsWith(".ttf")) { sMediaType = "application/x-font-ttf"; } } ResourceDocument doc = new ResourceDocument(EPUB_STYLES_FOLDER+sFileName, sMediaType); doc.read(is); resources.add(doc); } @Override public void readResource(File file, String sFileName, String sMediaType) throws IOException { readResource(new FileInputStream(file), sFileName, sMediaType); } public String getContentWidth() { return contentWidth.peek(); } public String pushContentWidth(String sWidth) { return contentWidth.push(sWidth); } public void popContentWidth() { contentWidth.pop(); } public boolean isTopLevel() { return contentWidth.size()==1; } public Styles getStylesParser() { return styles; } public TextParser getTextParser() { return textParser; } public TableParser getTableParser() { return tableParser; } public DrawParser getDrawParser() { return drawParser; } public MathParser getMathParser() { return mathParser; } public int getType() { return nType; } public boolean isHTML5() { return nType==XhtmlDocument.HTML5; } public int getOutFileIndex() { return outFileIndex; } public void addContentEntry(String sTitle, int nLevel, String sTarget) { converterResult.addContentEntry(new ContentEntryImpl(sTitle,nLevel,htmlDoc,sTarget)); } public void setTocFile(String sTarget) { converterResult.setTocFile(new ContentEntryImpl(l10n.get(L10n.CONTENTS),1,htmlDoc,sTarget)); //nTocFileIndex = nOutFileIndex; } protected void setLofFile(String sTarget) { converterResult.setLofFile(new ContentEntryImpl("Figures",1,htmlDoc,sTarget)); } protected void setLotFile(String sTarget) { converterResult.setLotFile(new ContentEntryImpl("Tables",1,htmlDoc,sTarget)); } public void setIndexFile(String sTarget) { converterResult.setIndexFile(new ContentEntryImpl(l10n.get(L10n.INDEX),1,htmlDoc,sTarget)); //nAlphabeticalIndex = nOutFileIndex; } public void setCoverFile(String sTarget) { converterResult.setCoverFile(new ContentEntryImpl("Cover",0,htmlDoc,sTarget)); } public void setCoverImageFile(OutputFile file, String sTarget) { converterResult.setCoverImageFile(new ContentEntryImpl("Cover image",0,file,sTarget)); } public Element createElement(String s) { return htmlDOM.createElement(s); } public Text createTextNode(String s) { return htmlDOM.createTextNode(s); } public Node importNode(Node node, boolean bDeep) { return htmlDOM.importNode(node,bDeep); } public L10n getL10n() { return l10n; } public void setOpenPubStructure(boolean b) { bOPS = true; } public boolean isOPS() { return bOPS; } @Override public void convertInner() throws IOException { sTargetFileName = Misc.trimDocumentName(sTargetFileName,XhtmlDocument.getExtension(nType)); outFiles = new Vector(); outFileIndex = -1; l10n = new L10n(); if (isOPS()) { imageConverter.setBaseFileName("image"); imageConverter.setUseSubdir("images"); } else { imageConverter.setBaseFileName(sTargetFileName+"-img"); if (config.saveImagesInSubdir()) { imageConverter.setUseSubdir(sTargetFileName+"-img"); } } imageConverter.setDefaultFormat(MIMETypes.PNG); imageConverter.addAcceptedFormat(MIMETypes.JPEG); imageConverter.addAcceptedFormat(MIMETypes.GIF); if (nType==XhtmlDocument.HTML5) { // HTML supports SVG as well imageConverter.setDefaultVectorFormat(MIMETypes.SVG); } styles = new Styles(ofr,config,this,nType); textParser = new TextParser(ofr,config,this); tableParser = new TableParser(ofr,config,this); drawParser = new DrawParser(ofr,config,this); mathParser = new MathParser(ofr,config,this,nType!=XhtmlDocument.XHTML10 && nType!=XhtmlDocument.XHTML11); // Set locale to document language StyleWithProperties style = ofr.getDefaultParStyle(); if (style!=null) { // The only CTL language recognized currently is farsi if ("fa".equals(style.getProperty(XMLString.STYLE_LANGUAGE_COMPLEX))) { l10n.setLocale("fa", "IR"); } else { l10n.setLocale(style.getProperty(XMLString.FO_LANGUAGE), style.getProperty(XMLString.FO_COUNTRY)); } } // Set the main content width pushContentWidth(getStylesParser().getPageSc().getTextWidth()); // Traverse the body Element body = ofr.getContent(); textParser.convertDocumentContent(body); // Set the title page and text page entries if (converterResult.getContent().isEmpty()) { // No headings in the document: There is no title page and the text page is the first page converterResult.setTextFile(new ContentEntryImpl("Text", 1, outFiles.get(0), null)); // We also have to add a toc entry (the ncx file cannot be empty) converterResult.addContentEntry(new ContentEntryImpl("Text", 1, outFiles.get(0), null)); } else { ContentEntry firstHeading = converterResult.getContent().get(0); // The title page is the first page after the cover, unless the first page starts with a heading int nFirstPage = converterResult.getCoverFile()!=null ? 1 : 0; if (outFiles.get(nFirstPage)!=firstHeading.getFile() || firstHeading.getTarget()!=null) { converterResult.setTitlePageFile(new ContentEntryImpl("Title page", 1, outFiles.get(nFirstPage), null)); } // The text page is the one containing the first heading converterResult.setTextFile(new ContentEntryImpl("Text", 1, firstHeading.getFile(), firstHeading.getTarget())); } // Resolve links ListIterator iter = links.listIterator(); while (iter.hasNext()) { LinkDescriptor ld = iter.next(); Integer targetIndex = targets.get(ld.sId); if (targetIndex!=null) { int nTargetIndex = targetIndex.intValue(); targetNames.addName(ld.sId); if (nTargetIndex == ld.nIndex) { // same file ld.element.setAttribute("href","#"+targetNames.getName(ld.sId)); } else { ld.element.setAttribute("href",getOutFileName(nTargetIndex,true) +"#"+targetNames.getName(ld.sId)); } } } // Add included style sheet, if any - and we are creating OPS content if (bOPS && styleSheet!=null) { converterResult.addDocument(styleSheet); for (ResourceDocument doc : resources) { converterResult.addDocument(doc); } } // Export styles (XHTML) if (!isOPS() && !config.separateStylesheet()) { for (int i=0; i<=outFileIndex; i++) { Element head = outFiles.get(i).getHeadNode(); if (head!=null) { Node styleNode = styles.getStylesNode(outFiles.get(i).getContentDOM()); if (styleNode!=null) { head.appendChild(styleNode); } } } } // Load MathJax // TODO: Should we support different configurations of MathJax? if ((nType==XhtmlDocument.HTML5 || nType==XhtmlDocument.XHTML_MATHML) && config.useMathJax()) { for (int i=0; i<=outFileIndex; i++) { if (outFiles.get(i).hasMath()) { XhtmlDocument doc = outFiles.get(i); Element head = doc.getHeadNode(); if (head!=null) { Element script = doc.getContentDOM().createElement("script"); head.appendChild(script); script.setAttribute("type", "text/javascript"); script.setAttribute("src", "http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"); } } } } // Create headers & footers (if nodes are available) if (outFileIndex > 0) { for (int i = 0; i <= outFileIndex; i++) { XhtmlDocument doc = outFiles.get(i); Document dom = doc.getContentDOM(); // Element content = doc.getContentNode(); // Header links Element header = doc.getHeaderNode(); if (header != null) { if (config.getXhtmlUplink().length() > 0) { Element a = dom.createElement("a"); a.setAttribute("href", config.getXhtmlUplink()); a.appendChild(dom.createTextNode(l10n.get(L10n.UP))); header.appendChild(a); header.appendChild(dom.createTextNode(" ")); } addNavigationLink(dom, header, l10n.get(L10n.FIRST), 0); addNavigationLink(dom, header, l10n.get(L10n.PREVIOUS), i - 1); addNavigationLink(dom, header, l10n.get(L10n.NEXT), i + 1); addNavigationLink(dom, header, l10n.get(L10n.LAST), outFileIndex); if (textParser.getTocIndex() >= 0) { addNavigationLink(dom, header, l10n.get(L10n.CONTENTS), textParser.getTocIndex()); } if (textParser.getAlphabeticalIndex() >= 0) { addNavigationLink(dom, header, l10n.get(L10n.INDEX), textParser.getAlphabeticalIndex()); } } // Footer links Element footer = doc.getFooterNode(); if (footer != null) { // No footer in presentations if (config.getXhtmlUplink().length() > 0) { Element a = dom.createElement("a"); a.setAttribute("href", config.getXhtmlUplink()); a.appendChild(dom.createTextNode(l10n.get(L10n.UP))); footer.appendChild(a); footer.appendChild(dom.createTextNode(" ")); } addNavigationLink(dom, footer, l10n.get(L10n.FIRST), 0); addNavigationLink(dom, footer, l10n.get(L10n.PREVIOUS), i - 1); addNavigationLink(dom, footer, l10n.get(L10n.NEXT), i + 1); addNavigationLink(dom, footer, l10n.get(L10n.LAST), outFileIndex); if (textParser.getTocIndex() >= 0) { addNavigationLink(dom, footer, l10n.get(L10n.CONTENTS), textParser.getTocIndex()); } if (textParser.getAlphabeticalIndex() >= 0) { addNavigationLink(dom, footer, l10n.get(L10n.INDEX), textParser.getAlphabeticalIndex()); } } } } else if (config.getXhtmlUplink().length() > 0) { for (int i = 0; i <= outFileIndex; i++) { XhtmlDocument doc = outFiles.get(i); Document dom = doc.getContentDOM(); // Element content = doc.getContentNode(); Element header = doc.getHeaderNode(); if (header != null) { Element a = dom.createElement("a"); a.setAttribute("href", config.getXhtmlUplink()); a.appendChild(dom.createTextNode(l10n.get(L10n.UP))); header.appendChild(a); header.appendChild(dom.createTextNode(" ")); } Element footer = doc.getFooterNode(); if (footer != null) { Element a = dom.createElement("a"); a.setAttribute("href", config.getXhtmlUplink()); a.appendChild(dom.createTextNode(l10n.get(L10n.UP))); footer.appendChild(a); footer.appendChild(dom.createTextNode(" ")); } } } // Export styles if (config.xhtmlFormatting() > XhtmlConfig.IGNORE_STYLES) { if (isOPS()) { // EPUB CssDocument cssDoc = new CssDocument(EPUB_STYLESHEET); cssDoc.read(styles.allStyleSelectors(false)); converterResult.addDocument(cssDoc); } else if (config.separateStylesheet()) { // XHTML CssDocument cssDoc = new CssDocument(sTargetFileName + "-styles.css"); cssDoc.read(styles.allStyleSelectors(false)); converterResult.addDocument(cssDoc); } } } private void addNavigationLink(Document dom, Node node, String s, int nIndex) { if (nIndex>=0 && nIndex<=outFileIndex) { Element a = dom.createElement("a"); a.setAttribute("href",Misc.makeHref(getOutFileName(nIndex,true))); a.appendChild(dom.createTextNode(s)); //node.appendChild(dom.createTextNode("[")); node.appendChild(a); node.appendChild(dom.createTextNode(" ")); //node.appendChild(dom.createTextNode("] ")); } else { Element span = dom.createElement("span"); span.setAttribute("class","nolink"); node.appendChild(span); span.appendChild(dom.createTextNode(s)); node.appendChild(dom.createTextNode(" ")); //node.appendChild(dom.createTextNode("["+s+"] ")); } } private void addInternalNavigationLink(Document dom, Node node, String s, String sLink) { Element a = dom.createElement("a"); a.setAttribute("href","#"+sLink); a.appendChild(dom.createTextNode(s)); //node.appendChild(dom.createTextNode("[")); node.appendChild(a); node.appendChild(dom.createTextNode(" ")); //node.appendChild(dom.createTextNode("] ")); } /* get inline text, ignoring any draw objects, footnotes, formatting and hyperlinks */ public String getPlainInlineText(Node node) { StringBuilder buf = new StringBuilder(); Node child = node.getFirstChild(); while (child!=null) { short nodeType = child.getNodeType(); switch (nodeType) { case Node.TEXT_NODE: buf.append(child.getNodeValue()); break; case Node.ELEMENT_NODE: String sName = child.getNodeName(); if (sName.equals(XMLString.TEXT_S)) { buf.append(" "); } else if (sName.equals(XMLString.TEXT_LINE_BREAK) || sName.equals(XMLString.TEXT_TAB_STOP) || sName.equals(XMLString.TEXT_TAB)) { // text:tab in oasis buf.append(" "); } else if (OfficeReader.isNoteElement(child)) { // ignore } else if (OfficeReader.isTextElement(child)) { buf.append(getPlainInlineText(child)); } break; default: // Do nothing } child = child.getNextSibling(); } return buf.toString(); } public void handleOfficeAnnotation(Node onode, Node hnode) { if (config.xhtmlNotes()) { // Extract the text from the paragraphs, separate paragraphs with newline StringBuilder buf = new StringBuilder(); Element creator = null; Element date = null; Node child = onode.getFirstChild(); while (child!=null) { if (Misc.isElement(child, XMLString.TEXT_P)) { if (buf.length()>0) { buf.append('\n'); } buf.append(getPlainInlineText(child)); } 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 (buf.length()>0) { buf.append('\n'); } buf.append(getPlainInlineText(creator)); } if (date!=null) { if (buf.length()>0) { buf.append('\n'); } buf.append(Misc.formatDate(OfficeReader.getTextContent(date), l10n.getLocale().getLanguage(), l10n.getLocale().getCountry())); } Node commentNode = htmlDOM.createComment(buf.toString()); hnode.appendChild(commentNode); } } ///////////////////////////////////////////////////////////////////////// // UTILITY METHODS // Create output file name (docname.html, docname1.html, docname2.html etc.) public String getOutFileName(int nIndex, boolean bWithExt) { return sTargetFileName + (nIndex>0 ? Integer.toString(nIndex) : "") + (bWithExt ? htmlDoc.getFileExtension() : ""); } // Return true if the current outfile has a non-empty body public boolean outFileHasContent() { return htmlDoc.getContentNode().hasChildNodes(); } // Use another document. TODO: This is very ugly; clean it up!!! public void changeOutFile(int nIndex) { outFileIndex = nIndex; htmlDoc = outFiles.get(nIndex); htmlDOM = htmlDoc.getContentDOM(); } public Element getPanelNode() { return htmlDoc.getPanelNode(); } public String getTitle(){ String title = metaData.getTitle(); if (title==null) { // use filename as fallback title = htmlDoc.getFileName(); } return title; } // Prepare next output file public Element nextOutFile() { htmlDoc = new XhtmlDocument(getOutFileName(++outFileIndex,false),nType); htmlDoc.setConfig(config); if (template!=null) { htmlDoc.readFromTemplate(template); } outFiles.add(outFileIndex,htmlDoc); converterResult.addDocument(htmlDoc); Element rootElement = createHeadAndBody(); addEpubNs(rootElement); applyWritingDirection(); addTitle(); addHeadData(); return htmlDoc.getContentNode(); } private void addTitle() { Element title = htmlDoc.getTitleNode(); if (title!=null) { String sTitle = metaData.getTitle(); if (sTitle==null) { // use filename as fallback sTitle = htmlDoc.getFileName(); } title.appendChild( htmlDOM.createTextNode(sTitle) ); } } private Element createHeadAndBody() { htmlDOM = htmlDoc.getContentDOM(); Element rootElement = htmlDOM.getDocumentElement(); styles.applyDefaultLanguage(rootElement); return rootElement; } private void applyWritingDirection() { StyleInfo pageInfo = new StyleInfo(); styles.getPageSc().applyDefaultWritingDirection(pageInfo); styles.getPageSc().writeStyle(pageInfo, htmlDoc.getContentNode()); } private void addHeadData() { Element head = htmlDoc.getHeadNode(); if (head!=null) { // Declare charset (we need this for XHTML 1.0 strict and HTML5 because we have no ) if (nType==XhtmlDocument.XHTML10) { Element meta = htmlDOM.createElement("meta"); meta.setAttribute("http-equiv","Content-Type"); meta.setAttribute("content","text/html; charset="+htmlDoc.getEncoding().toLowerCase()); head.appendChild(meta); } else if (nType==XhtmlDocument.HTML5) { // The encoding should be UTF-8, but we still respect the user's setting Element meta = htmlDOM.createElement("meta"); meta.setAttribute("charset",htmlDoc.getEncoding().toUpperCase()); head.appendChild(meta); } // Add meta data (for EPUB the meta data belongs to the .opf file) if (!bOPS) { // "Traditional" meta data //createMeta("generator","Writer2LaTeX "+Misc.VERSION); createMeta(head,"description",metaData.getDescription()); createMeta(head,"keywords",metaData.getKeywords()); // Dublin core meta data (optional) // Format as recommended on dublincore.org (http://dublincore.org/documents/dc-html/) // Declare meta data profile if (config.xhtmlUseDublinCore()) { head.setAttribute("profile","http://dublincore.org/documents/2008/08/04/dc-html/"); // Add link to declare namespace Element dclink = htmlDOM.createElement("link"); dclink.setAttribute("rel","schema.DC"); dclink.setAttribute("href","http://purl.org/dc/elements/1.1/"); head.appendChild(dclink); // Insert the actual meta data createMeta(head,"DC.title",metaData.getTitle()); // DC.subject actually contains subject+keywords, so we merge them String sDCSubject = ""; if (metaData.getSubject()!=null && metaData.getSubject().length()>0) { sDCSubject = metaData.getSubject(); } if (metaData.getKeywords()!=null && metaData.getKeywords().length()>0) { if (sDCSubject.length()>0) { sDCSubject+=", "; } sDCSubject += metaData.getKeywords(); } createMeta(head,"DC.subject",sDCSubject); createMeta(head,"DC.description",metaData.getDescription()); createMeta(head,"DC.creator",metaData.getCreator()); createMeta(head,"DC.date",metaData.getDate()); createMeta(head,"DC.language",metaData.getLanguage()); } } // Add link to custom stylesheet, if producing normal XHTML if (!bOPS && config.xhtmlCustomStylesheet().length()>0) { Element htmlStyle = htmlDOM.createElement("link"); htmlStyle.setAttribute("rel","stylesheet"); htmlStyle.setAttribute("type","text/css"); htmlStyle.setAttribute("media","all"); htmlStyle.setAttribute("href",config.xhtmlCustomStylesheet()); head.appendChild(htmlStyle); } // Add link to generated stylesheet if producing normal XHTML and the user wants separate css if (!bOPS && config.separateStylesheet() && config.xhtmlFormatting()>XhtmlConfig.IGNORE_STYLES) { Element htmlStyle = htmlDOM.createElement("link"); htmlStyle.setAttribute("rel","stylesheet"); htmlStyle.setAttribute("type","text/css"); htmlStyle.setAttribute("media","all"); htmlStyle.setAttribute("href",sTargetFileName+"-styles.css"); head.appendChild(htmlStyle); } // Add link to included style sheet if producing OPS content if (bOPS && styleSheet!=null) { Element sty = htmlDOM.createElement("link"); sty.setAttribute("rel", "stylesheet"); sty.setAttribute("type", "text/css"); sty.setAttribute("href", EPUB_CUSTOM_STYLESHEET); head.appendChild(sty); } // Add link to generated stylesheet if producing OPS content if (isOPS() && config.xhtmlFormatting()>XhtmlConfig.IGNORE_STYLES) { Element htmlStyle = htmlDOM.createElement("link"); htmlStyle.setAttribute("rel","stylesheet"); htmlStyle.setAttribute("type","text/css"); htmlStyle.setAttribute("href",EPUB_STYLESHEET); head.appendChild(htmlStyle); } } } // Add epub namespace for the purpose of semantic inflection in EPUB 3 public void addEpubNs(Element elm) { if (bOPS && nType==XhtmlDocument.HTML5) { elm.setAttribute("xmlns:epub", "http://www.idpf.org/2007/ops"); } } // Add a type from the structural semantics vocabulary of EPUB 3 public void addEpubType(Element elm, String sType) { if (bOPS && nType==XhtmlDocument.HTML5 && sType!=null) { elm.setAttribute("epub:type", sType); } } // create a target public Element createTarget(String sId) { Element a = htmlDOM.createElement("a"); targetNames.addName(sId); a.setAttribute("id",targetNames.getName(sId)); targets.put(sId, new Integer(outFileIndex)); return a; } // put a target id on an existing element public void addTarget(Element node,String sId) { targetNames.addName(sId); node.setAttribute("id",targetNames.getName(sId)); targets.put(sId, new Integer(outFileIndex)); } // create an internal link public Element createLink(String sId) { Element a = htmlDOM.createElement("a"); LinkDescriptor ld = new LinkDescriptor(); ld.element = a; ld.sId = sId; ld.nIndex = outFileIndex; links.add(ld); return a; } // create a link public Element createLink(Element onode) { // First create the anchor String sHref = onode.getAttribute(XMLString.XLINK_HREF); Element anchor; if (sHref.startsWith("#")) { // internal link anchor = createLink(sHref.substring(1)); } else { // external link anchor = htmlDOM.createElement("a"); sHref = ofr.fixRelativeLink(sHref); // Workaround for an OOo problem: if (sHref.indexOf("?")==-1) { // No question mark int n3F=sHref.indexOf("%3F"); if (n3F>0) { // encoded question mark sHref = sHref.substring(0,n3F)+"?"+sHref.substring(n3F+3); } } anchor.setAttribute("href",sHref); String sName = Misc.getAttribute(onode,XMLString.OFFICE_NAME); if (sName!=null) { // The name attribute is rarely use (usually the user will insert a bookmark) // Hence we (mis)use it to set additional attributes that are not supported by OOo if (sName.indexOf(";")==-1 && sName.indexOf("=")==-1) { // Simple case, use the value to set name and title anchor.setAttribute("name",sName); anchor.setAttribute("title",sName); } else { // Complex case - using the syntax: name=value;name=value;... String[] sElements = sName.split(";"); for (String sElement : sElements) { String[] sNameVal = sElement.split("="); if (sNameVal.length>=2) { anchor.setAttribute(sNameVal[0].trim(),sNameVal[1].trim()); } } } } // TODO: target has been deprecated in xhtml 1.0 strict String sTarget = Misc.getAttribute(onode,XMLString.OFFICE_TARGET_FRAME_NAME); if (sTarget!=null) { anchor.setAttribute("target",sTarget); } } // Then style it String sStyleName = onode.getAttribute(XMLString.TEXT_STYLE_NAME); String sVisitedStyleName = onode.getAttribute(XMLString.TEXT_VISITED_STYLE_NAME); StyleInfo anchorInfo = new StyleInfo(); styles.getTextSP().applyAnchorStyle(sStyleName,sVisitedStyleName,anchorInfo); styles.getTextSP().writeStyle(anchorInfo,anchor); return anchor; } private void createMeta(Element head, String sName, String sValue) { if (sValue==null) { return; } Element meta = htmlDOM.createElement("meta"); meta.setAttribute("name",sName); meta.setAttribute("content",sValue); head.appendChild(meta); } }