/******************************************************************************* * 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.UnsupportedEncodingException; import java.util.Iterator; import java.util.Stack; import java.util.Vector; import com.adobe.dp.css.CSSValue; import com.adobe.dp.css.CascadeEngine; import com.adobe.dp.css.CascadeResult; import com.adobe.dp.css.InlineRule; 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; abstract public class Element { OPSDocument document; String className; InlineRule style; CascadeResult cascade; boolean assignStyle; String elementName; String id; Vector children = new Vector(); XRef selfRef; String lang; private static class SizeRemains { int size; } Element(OPSDocument document, String name) { this.document = document; elementName = name; } abstract public String getNamespaceURI(); abstract Element cloneElementShallow(OPSDocument newDoc); public Element cloneElementShallow() { return cloneElementShallow(document); } protected Object getBuiltInProperty(String propName) { return null; } public String getLang() { return lang; } public void setLang(String lang) { this.lang = lang; } public Object getCascadedProperty(String propName) { // style attribute: highest specificity InlineRule style = getStyle(); if (style != null) { Object value = style.get(propName); if (value != null) return value; } // CSS cascade from stylesheets if (cascade != null) { Object value = cascade.getProperties().getPropertySet().get(propName); if (value != null) return value; } // built-in stylesheet return getBuiltInProperty(propName); } public String getElementName() { return elementName; } public String getClassName() { return className; } public void setClassName(String className) { this.className = className; } public void add(Object child) { children.add(child); } public String getId() { return id; } public void setId(String id) { document.setElementId(this, id); } public Iterator content() { return children.iterator(); } public Object getLastChild() { if (children.isEmpty()) return null; return children.lastElement(); } public XRef getSelfRef() { if (selfRef == null) { document.assignId(this); selfRef = new XRef(document.resource, this); } return selfRef; } SMapImpl getAttributes() { SMapImpl attrs = new SMapImpl(); if (className != null) attrs.put(null, "class", className); if (id != null) attrs.put(null, "id", id); if (style != null) attrs.put(null, "style", style); if (lang != null) attrs.put(OPSDocument.xmlns, "lang", lang); return attrs; } public void addFonts(FontSubsetter subsetter) { subsetter.push(this); try { Iterator children = content(); if (children != null) { while (children.hasNext()) { Object child = children.next(); if (child instanceof Element) { ((Element) child).addFonts(subsetter); } else if (child instanceof String) { subsetter.play((String) child); } } } } finally { subsetter.pop(this); } } boolean isSection() { return false; } private static int getUTF8Length(String s) { try { return s.getBytes("UTF-8").length; } catch (UnsupportedEncodingException e) { throw new Error("UTF-8 unsupported???"); } } int getElementSize() { int size = elementName.length() + 3; if (className != null) { size += className.length() + 9; } if (selfRef != null) size += 10; if (children.size() == 0) size++; else size += elementName.length() + 5; return size; } int getPeelingBonus() { return 0; } boolean forcePeel() { return false; } boolean canPeelChild() { return false; } final int getEstimatedSize() { int size = getElementSize(); Iterator it = content(); if (!it.hasNext()) return size; while (it.hasNext()) { Object next = it.next(); if (next instanceof Element) size += ((Element) next).getEstimatedSize(); else if (next instanceof String) { size += getUTF8Length((String) next); } size++; } return size; } void transferToDocument(OPSDocument newDoc) { if (id != null) document.idMap.remove(id); document = newDoc; if (id != null) document.idMap.put(id, this); if (selfRef != null) { selfRef.targetResource = newDoc.resource.getResourceRef(); } Iterator it = content(); while (it.hasNext()) { Object next = it.next(); if (next instanceof Element) ((Element) next).transferToDocument(newDoc); } } final Element peelElements(OPSDocument newDoc, int targetSize, boolean first) { SizeRemains sr = new SizeRemains(); sr.size = targetSize + 1000; return peelElements(newDoc, sr, first); } final Element peelElements(OPSDocument newDoc, SizeRemains remains, boolean first) { int size = getElementSize(); int bonus = getPeelingBonus(); remains.size -= size; if (!first && (forcePeel() || (bonus >= 0 && bonus > remains.size))) { // System.out.println("Break for bonus " + bonus); transferToDocument(newDoc); return this; } Element result = null; boolean canPeelChild = canPeelChild(); int i = 0; while (i < children.size()) { Object next = children.elementAt(i); if (next instanceof Element) { Element child = (Element) next; if (result == null) { if (canPeelChild) { Element p = child.peelElements(newDoc, remains, i == 0); if (p != null) { result = cloneElementShallow(newDoc); result.add(p); if (p == child) { children.remove(i); continue; } } } else { remains.size -= child.getEstimatedSize(); } } else { children.remove(i); child.transferToDocument(newDoc); result.add(child); continue; } } else if (next instanceof String) { remains.size -= getUTF8Length((String) next); } remains.size--; i++; } return result; } public void generateTOCFromHeadings(Stack headings, int depth) { Iterator it = content(); while (it.hasNext()) { Object next = it.next(); if (next instanceof Element) ((Element) next).generateTOCFromHeadings(headings, depth); } } void serializeChildren(XMLSerializer ser) { boolean section = isSection(); Iterator it = content(); while (it.hasNext()) { Object next = it.next(); if (next instanceof Element) ((Element) next).serialize(ser); else if (next instanceof String) { char[] arr = ((String) next).toCharArray(); ser.text(arr, 0, arr.length); } if (section) ser.newLine(); } } boolean makeNSDefault() { return false; } void serialize(XMLSerializer ser) { boolean section = isSection(); String ns = getNamespaceURI(); ser.startElement(ns, elementName, getAttributes(), makeNSDefault()); if (section) ser.newLine(); serializeChildren(ser); ser.endElement(ns, elementName); } public String getText() { StringBuffer sb = new StringBuffer(); Iterator it = content(); while (it.hasNext()) { Object next = it.next(); if (next instanceof Element) sb.append(((Element) next).getText()); else if (next instanceof String) { sb.append((String) next); } } return sb.toString(); } public CascadeResult getCascadeResult() { if (cascade == null) cascade = new CascadeResult(); return cascade; } public void setDesiredCascadeResult(InlineRule s) { CascadeResult cr = new CascadeResult(); InlineRule t = cr.getProperties().getPropertySet(); if (s != null) { Iterator it = s.properties(); if (it != null) { while (it.hasNext()) { String p = (String) it.next(); CSSValue value = s.get(p); t.set(p, value); } } } setDesiredCascadeResult(cr); } public void setDesiredCascadeResult(CascadeResult cascade) { this.cascade = cascade; this.assignStyle = true; } public InlineRule getStyle() { return style; } public void setStyle(InlineRule style) { this.style = style; } public int assignPlayOrder(int playOrder) { if (selfRef != null && selfRef.playOrderNeeded()) { selfRef.setPlayOrder(++playOrder); } Iterator it = content(); while (it.hasNext()) { Object next = it.next(); if (next instanceof Element) playOrder = ((Element) next).assignPlayOrder(playOrder); } return playOrder; } public void cascade(CascadeEngine engine) { engine.pushElement(getNamespaceURI(), getElementName(), getAttributes()); cascade = engine.getCascadeResult(); Iterator it = content(); while (it.hasNext()) { Object next = it.next(); if (next instanceof Element) ((Element) next).cascade(engine); } engine.popElement(); } public void generateStyles(Stylesheet stylesheet) { if (assignStyle) { if (cascade != null && !cascade.isEmpty()) className = stylesheet.makeClass(className, cascade); else className = null; assignStyle = false; } Iterator it = content(); while (it.hasNext()) { Object next = it.next(); if (next instanceof Element) ((Element) next).generateStyles(stylesheet); } } public void setAssignStylesFlag() { assignStyle = true; Iterator it = content(); while (it.hasNext()) { Object next = it.next(); if (next instanceof Element) ((Element) next).setAssignStylesFlag(); } } }