/******************************************************************************* * Copyright (c) 2009, Adobe Systems Incorporated * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * · Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * · Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * · Neither the name of Adobe Systems Incorporated nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *******************************************************************************/ package com.adobe.dp.epub.ops; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Hashtable; import java.util.Iterator; import java.util.Vector; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.XMLReader; import com.adobe.dp.css.CSSStylesheet; import com.adobe.dp.css.CascadeEngine; import com.adobe.dp.epub.dtd.EPUBEntityResolver; import com.adobe.dp.epub.io.DataSource; import com.adobe.dp.epub.opf.OPSResource; import com.adobe.dp.epub.opf.Resource; import com.adobe.dp.epub.opf.ResourceRef; import com.adobe.dp.epub.opf.StyleResource; import com.adobe.dp.epub.otf.FontSubsetter; import com.adobe.dp.epub.style.Stylesheet; import com.adobe.dp.xml.util.SMapImpl; import com.adobe.dp.xml.util.XMLSerializer; public class OPSDocument { OPSResource resource; Hashtable idMap = new Hashtable(); Element body; Vector styleResources = new Vector(); XRef rootXRef; int nextid = 1; public static final String xhtmlns = "http://www.w3.org/1999/xhtml"; public static final String svgns = "http://www.w3.org/2000/svg"; public static final String xlinkns = "http://www.w3.org/1999/xlink"; public static final String xmlns = "http://www.w3.org/XML/1998/namespace"; public OPSDocument(OPSResource resource) { this.resource = resource; if (resource.getMediaType().equals("image/svg+xml")) body = new SVGElement(this, "svg"); else body = new HTMLElement(this, "body"); } public int assignPlayOrder(int playOrder) { XRef rootXRef = getRootXRef(); if (rootXRef.playOrderNeeded()) rootXRef.setPlayOrder(++playOrder); return body.assignPlayOrder(playOrder); } public Element getBody() { return body; } public Element getElementById(String id) { return (Element) idMap.get(id); } void setElementId(Element e, String id) { if (e.id != null) idMap.remove(e.id); Element old = (Element) idMap.put(id, e); ResourceRef ref = resource.getResourceRef(); XRef xref = ref.takeOverUnresolvedXRef(id); if (xref != null) { xref.targetElement = e; } e.id = id; if (old != null) { old.id = null; if (old.selfRef != null) assignId(old); } } private String newId() { while (true) { String id = "id" + nextid; if (idMap.get(id) == null) return id; nextid++; } } String assignId(Element e) { String id = e.id; if (id == null) { id = newId(); e.id = id; idMap.put(id, e); } return id; } public Iterator styleResources() { return styleResources.iterator(); } public void addStyleResource(ResourceRef style) { if (style == null) throw new IllegalArgumentException("null style"); styleResources.add(style); } public void addStyleResource(Resource style) { if (style == null) throw new IllegalArgumentException("null style"); styleResources.add(style.getResourceRef()); } public XRef getRootXRef() { if (rootXRef == null) { rootXRef = resource.getResourceRef().takeOverUnresolvedXRef(null); if (rootXRef == null) rootXRef = new XRef(resource, (Element) null); } return rootXRef; } public HTMLElement createElement(String name) { return new HTMLElement(this, name); } public HyperlinkElement createHyperlinkElement(String name) { return new HyperlinkElement(this, name); } public TableCellElement createTableCellElement(String name, String align, int colSpan, int rowSpan) { return new TableCellElement(this, name, align, colSpan, rowSpan); } public ImageElement createImageElement(String name) { return new ImageElement(this, name); } public SVGElement createSVGElement(String name) { return new SVGElement(this, name); } public SVGImageElement createSVGImageElement(String name) { return new SVGImageElement(this, name); } public void addFonts(FontSubsetter subsetter, StyleResource styleResource) { if (!styleResources.contains(styleResource.getResourceRef())) return; subsetter.setStyles(styleResources); body.addFonts(subsetter); } public void serialize(OutputStream out) throws IOException { XMLSerializer ser = new XMLSerializer(out); serialize(ser); } public int getEstimatedSize() { return 200 + getBody().getEstimatedSize(); } /** * Split the document. Leave enough content in this document to produce * resource about targetSize bytes; peel off the rest of the content and * place it in the newResource. * * @param newDoc * document where peeled content should be placed * @param targetSize * target size of this document after split * @return true if something was peeled */ public boolean peelOffBack(OPSDocument newDoc, int targetSize) { newDoc.styleResources.addAll(styleResources); Element newBody = body.peelElements(newDoc, targetSize, true); if (newBody == null) return false; newDoc.body = newBody; return true; } public void cascadeStyles() { CascadeEngine engine = new CascadeEngine(); Iterator s = styleResources(); while (s.hasNext()) { ResourceRef ref = (ResourceRef) s.next(); Resource r = ref.getResource(); if (r instanceof StyleResource) { StyleResource sr = (StyleResource) r; Stylesheet stylesheet = sr.getStylesheet(); if (stylesheet != null) { CSSStylesheet css = stylesheet.getCSS(); if (css != null) engine.add(css, null); } } } // TODO: SVG style elements boolean notSVG = resource.getMediaType().equals("image/svg+xml"); if (notSVG) { engine.pushElement(xhtmlns, "html", null); engine.pushElement(xhtmlns, "head", null); engine.popElement(); } getBody().cascade(engine); if (notSVG) { engine.popElement(); } } public void generateStyles(Stylesheet stylesheet) { getBody().generateStyles(stylesheet); } public void setAssignStylesFlag() { getBody().setAssignStylesFlag(); } public void serialize(XMLSerializer ser) { ser.startDocument("1.0", "UTF-8"); boolean isSVG = resource.getMediaType().equals("image/svg+xml"); if (isSVG) { Iterator s = styleResources(); while (s.hasNext()) { ResourceRef sr = (ResourceRef) s.next(); String href = resource.makeReference(sr.getResourceName(), null); ser.processingInstruction("xml-stylesheet", "href=\"" + href + "\" type=\"text/css\""); } getBody().serialize(ser); } else { ser.startElement(xhtmlns, "html", null, true); ser.newLine(); ser.startElement(xhtmlns, "head", null, false); ser.newLine(); ser.startElement(xhtmlns, "title", null, false); ser.endElement(xhtmlns, "title"); ser.newLine(); Iterator s = styleResources(); while (s.hasNext()) { ResourceRef sr = (ResourceRef) s.next(); String href = resource.makeReference(sr.getResourceName(), null); SMapImpl attr = new SMapImpl(); attr.put(null, "rel", "stylesheet"); attr.put(null, "type", "text/css"); attr.put(null, "href", href); ser.startElement(xhtmlns, "link", attr, false); ser.endElement(xhtmlns, "link"); ser.newLine(); } ser.endElement(xhtmlns, "head"); ser.newLine(); getBody().serialize(ser); ser.newLine(); ser.endElement(xhtmlns, "html"); } ser.newLine(); ser.endDocument(); } public void load(DataSource data) throws IOException { OPSDocumentBuilder builder = new OPSDocumentBuilder(this); InputStream in = data.getInputStream(); SAXParserFactory factory = SAXParserFactory.newInstance(); factory.setNamespaceAware(true); try { SAXParser parser = factory.newSAXParser(); XMLReader reader = parser.getXMLReader(); reader.setContentHandler(builder); reader.setEntityResolver(EPUBEntityResolver.instance); InputSource source = new InputSource(in); reader.parse(source); } catch (ParserConfigurationException e) { e.printStackTrace(); throw new RuntimeException(e.toString()); } catch (SAXException e) { throw new IOException("XML Syntax error in " + resource.getName() + ": " + e.getMessage()); } finally { try { in.close(); } catch (IOException e) { e.printStackTrace(); } } } public void removeAllStyleResources() { styleResources.clear(); } }