Package com.adobe.dp.epub.opf

Source Code of com.adobe.dp.epub.opf.Publication$OCFHandler

/*******************************************************************************
* 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.opf;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.Random;
import java.util.Stack;
import java.util.Vector;
import java.util.Map.Entry;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;

import com.adobe.dp.epub.dtd.EPUBEntityResolver;
import com.adobe.dp.epub.io.ContainerSource;
import com.adobe.dp.epub.io.ContainerWriter;
import com.adobe.dp.epub.io.DataSource;
import com.adobe.dp.epub.io.ZipContainerSource;
import com.adobe.dp.epub.ncx.TOCEntry;
import com.adobe.dp.epub.ops.OPSDocument;
import com.adobe.dp.epub.otf.FontEmbeddingReport;
import com.adobe.dp.epub.otf.FontSubsetter;
import com.adobe.dp.epub.util.TOCLevel;
import com.adobe.dp.otf.DefaultFontLocator;
import com.adobe.dp.otf.FontLocator;
import com.adobe.dp.xml.util.SMap;
import com.adobe.dp.xml.util.SMapImpl;
import com.adobe.dp.xml.util.XMLSerializer;

/**
* Publication represents an EPUB document. At a minimum, Publication must have
* OPF resource (that contains metadata, manifest and a spine), NCX resource
* (that contains table of contents) and some content resources (also called
* chapters).
*/
public class Publication {

  /**
   * Dublin Core namespace
   */
  public static final String dcns = "http://purl.org/dc/elements/1.1/";

  /**
   * OCF namespace
   */
  public static final String ocfns = "urn:oasis:names:tc:opendocument:xmlns:container";

  /**
   * XML encryption namespace
   */
  public static final String encns = "http://www.w3.org/2001/04/xmlenc#";

  /**
   * Adobe Digital Edition encoding namespace (for font embedding)
   */
  public static final String deencns = "http://ns.adobe.com/digitaleditions/enc";

  boolean translit;

  boolean useIDPFFontMangling = true;
 
  boolean useNOFontMangling = false;

  Hashtable resourcesByName = new Hashtable();

  Hashtable resourceRefsByName = new Hashtable();

  Hashtable resourcesById = new Hashtable();

  Hashtable namingIndices = new Hashtable();

  Vector spine = new Vector();

  Vector metadata = new Vector();

  Resource toc; // can be unparsed

  OPFResource opf; // has to be parsed!

  int idCount = 1;

  private String contentFolder;

  private byte[] idpfMask;

  private byte[] adobeMask;

  PageMapResource pageMap;

  ContainerSource containerSource;

  static class SimpleMetadata {

    String ns;

    String name;

    String value;
   
    SMap attribs;
   
    SimpleMetadata(String ns, String name, String value) {
      this.name = name;
      this.ns = ns;
      this.value = value;
    }
   
    SimpleMetadata(String ns, String name, String value, SMap attrs) {
      this.name = name;
      this.ns = ns;
      this.value = value;
      this.attribs=attrs;
    }

   
  }
//  static class AttribsMetadata extends SimpleMetadata{
//   
//
//    SMap attribs;
//    AttribsMetadata(String ns, String name, String value) {
//      super(ns, name, value);
//    }
//    AttribsMetadata(String ns, String name, String value,SMap attribs) {
//      super(ns, name, value);
//      this.attribs=attribs;
//    }
//  }

  /**
   * Create a new empty EPUB document. Content folder is set to "OPS".
   */
  public Publication() {
    this("OPS");
  }

  /**
   * Create a new empty EPUB document with the given content folder.
   */
  public Publication(String contentFolder) {
    this.contentFolder = contentFolder;
    toc = new NCXResource(this, contentFolder + "/toc.ncx");
    opf = new OPFResource(this, contentFolder + "/content.opf");
    resourcesByName.put(toc.name, toc);
    resourcesByName.put(opf.name, opf);
  }

  /**
   * Create a new EPUB document by reading it from a file.
   */
  public Publication(File file) throws Exception {
    this(new ZipContainerSource(file));
  }

  public Publication(ContainerSource containerSource) throws Exception {
    this.containerSource = containerSource;
    DataSource cont = containerSource.getDataSource("META-INF/container.xml");
    if (cont == null)
      throw new IOException("Not an EPUB file: META-INF/container.xml missing");
    String opfName = processOCF(cont.getInputStream());
    opf = new OPFResource(this, opfName);
    resourcesByName.put(opfName, opf);
    opf.load(containerSource, opfName);
    Iterator entries = containerSource.getResourceList();
    while (entries.hasNext()) {
      String name = (String) entries.next();
      if (name.startsWith("META-INF/") || name.equals("mimetype") || name.equals(opfName))
        continue;
      if (resourcesByName.get(name) == null) {
        loadMissingResource(name);
      }
    }
  }

  private void loadMissingResource(String name) throws Exception {
    String s = name.toLowerCase();
    if (s.endsWith(".jpeg") || s.endsWith(".jpg"))
      loadResource(name, "image/jpeg");
    else if (s.endsWith(".png"))
      loadResource(name, "image/png");
    else if (s.endsWith(".gif"))
      loadResource(name, "image/gif");
    else if (s.endsWith(".xml"))
      loadResource(name, "text/xml");
    else if (s.endsWith(".opf"))
      ; // ignore
    else if (s.endsWith(".ncx"))
      loadResource(name, "application/x-dtbncx+xml");
    else if (s.endsWith(".svg"))
      loadResource(name, "image/svg+xml");
    else if (s.endsWith(".htm") || s.endsWith(".html") || s.endsWith(".xhtml"))
      loadResource(name, "application/xhtml+xml");
    else if (s.endsWith(".ttf") || s.endsWith(".otf"))
      loadResource(name, "application/vnd.ms-opentype");
    else
      loadResource(name, "application/octet-stream");
  }

  /**
   * Return this EPUB package content folder. OPF file is located in that
   * folder; typically all other content is stored in this folder or its
   * subfolders as well.
   */
  public String getContentFolder() {
    return contentFolder;
  }

  /**
   * Make this EPUB document use Adobe proprietary font mangling method when
   * serializing. This method works in all versions of Digital Editions and
   * Adobe Reader Mobile SDK-based devices
   *
   * @see <a href=
   *      "http://www.adobe.com/devnet/digitalpublishing/pdfs/content_protection.pdf"
   *      >Adobe&#32;documentation</a>
   */
  public void useAdobeFontMangling() {
    useIDPFFontMangling = false;
      useNOFontMangling=false;
  }

  /**
   * Make this EPUB document use standard font mangling method when
   * serializing. This method is not supported in any reading system as of
   * today (May 2009), but that is expected to change.
   *
   * @see <a href=
   *      "http://www.openebook.org/doc_library/informationaldocs/FontManglingSpec.html"
   *      >IDPF&#32;documentation</a>
   */
  public void useIDPFFontMangling() {
    useIDPFFontMangling = true;
    useNOFontMangling=false;
  }

   public void useNOFontMangling() {
     useNOFontMangling=true;
   }

  /**
   * Transliterate cyrillic metadata when serializing. This is useful when
   * formatting books written in cyrillic for reading on non-localized reading
   * devices. This setting should be avoided.
   *
   * @param translit
   *            true if cyrillic metadata should be transliterated
   */
  public void setTranslit(boolean translit) {
    this.translit = translit;
  }

  public void splitLargeChapters(int sizeToSplit) {
    for (int i = 0; i < spine.size(); i++) {
      Resource item = (Resource) spine.elementAt(i);
      if (item instanceof OPSResource) {
        OPSResource[] split = ((OPSResource) item).splitLargeChapter(this, sizeToSplit);
        if (split != null) {
          spine.remove(i);
          for (int j = 0; j < split.length; j++) {
            spine.insertElementAt(split[j], i);
            i++;
          }
          i--;
        }
      }
    }
  }

  public void splitLargeChapters() {
    splitLargeChapters(100000);
  }

  public void generateTOCFromHeadings(int depth) {
    TOCEntry entry = getTOC().getRootTOCEntry();
    entry.removeAll();
    Iterator spine = spine();
    Stack headings = new Stack();
    headings.push(new TOCLevel(0, entry));
    while (spine.hasNext()) {
      Resource r = (Resource) spine.next();
      if (r instanceof OPSResource)
        ((OPSResource) r).generateTOCFromHeadings(headings, depth);
    }
  }

  public void generateTOCFromHeadings() {
    generateTOCFromHeadings(6);
  }

  /**
   * Determine if cyrillic metadata should be transliterated when serializing.
   *
   * @see #setTranslit
   * @return true if cyrillic metadata should be transliterated
   */
  public boolean isTranslit() {
    return translit;
  }

  private static void addRandomHexDigit(StringBuffer sb, Random r, int count, int mask, int value) {
    for (int i = 0; i < count; i++) {
      int v = (r.nextInt(16) & mask) | value;
      if (v < 10)
        sb.append((char) ('0' + v));
      else
        sb.append((char) (('a' - 10) + v));
    }
  }

  /**
   * Generate random UUID and add it as a publication's identifier (in URN
   * format, i.e. prefixed with "urn:uuid:")
   *
   * @return generated identifier
   */
  public String generateRandomIdentifier() {
    // generate v4 UUID
    //StringBuffer sb = new StringBuffer("urn:uuid:");
    StringBuffer sb = new StringBuffer("");
    SecureRandom sr = new SecureRandom();
    addRandomHexDigit(sb, sr, 8, 0xF, 0);
    sb.append('-');
    addRandomHexDigit(sb, sr, 4, 0xF, 0);
    sb.append('-');
    sb.append('4');
    addRandomHexDigit(sb, sr, 3, 0xF, 0);
    sb.append('-');
    addRandomHexDigit(sb, sr, 1, 0x3, 0x8);
    addRandomHexDigit(sb, sr, 3, 0xF, 0);
    sb.append('-');
    addRandomHexDigit(sb, sr, 12, 0xF, 0);
    String id = sb.toString();
    SMapImpl att=new SMapImpl();
    att.put(OPFResource.opfns, "scheme", "UUID");
    this.addDCMetadata("identifier", id,att);
    return id;
  }

  /**
   * Get table-of-content resource. Each Publication has one.
   *
   * @return table-of-content resource
   */
  public NCXResource getTOC() {
    if (!(toc instanceof NCXResource)) {
      return null;
    }
    return (NCXResource) toc;
  }

  /**
   * Get OPF resource. Each Publication has one. OPF resource lists other
   * resources in the Publication, defines their types and determines chapter
   * order.
   *
   * @return OPF resource
   */
  public OPFResource getOPF() {
    return opf;
  }

  /**
   * Get Dublin Core metadata value. Note that multiple metadata values of the
   * same type are allowed and this method returns only the first one.
   *
   * @param name
   *            type of Dublin Core metadata, such as "creator" or "title"
   * @return metadata element value; null if no such value exists
   */
  public String getDCMetadata(String name) {
    return getMetadata(dcns, name, 0);
  }

  /**
   * Add Dublin Core metadata value. Note that multiple metadata values of the
   * same type are allowed.
   *
   * @param name
   *            type of Dublin Core metadata, such as "creator" or "title"
   * @param value
   *            metadata element value
   */
  public void addDCMetadata(String name, String value) {
    if (value != null) {
      addMetadata(dcns, name, value);
    }
  }
  public void addDCMetadata(String name, String value,SMap attrs) {
    if (value != null&&attrs!=null) {
      addMetadata(dcns, name, value,attrs);
    }else if(value!=null){
      addMetadata(dcns, name, value);
    }
  }
  /**
   * Get metadata value. Note that multiple metadata values of the same type
   * are allowed. This method can be used to iterate over
   *
   * @param ns
   *            metadata element namespace, i.e Publication.dcns
   * @param name
   *            type of the metadata element
   * @return metadata element value; null if no such value exists
   */
  public String getMetadata(String ns, String name, int index) {
    Iterator it = metadata.iterator();
    while (it.hasNext()) {
      SimpleMetadata md = (SimpleMetadata) it.next();
      if ((ns == null ? md.ns == null : md.ns != null && md.ns.equals(ns)) && md.name.equals(name)) {
        if (index == 0)
          return md.value;
        index--;
      }
    }
    return null;
  }

  /**
   * Add Dublin Core metadata value. Note that multiple metadata values of the
   * same type are allowed.
   *
   * @param ns
   *            metadata element namespace, i.e Publication.dcns
   * @param name
   *            type of the metadata element
   * @param value
   *            metadata element value
   */
  public void addMetadata(String ns, String name, String value) {
    if (value == null)
      return;
    metadata.add(new SimpleMetadata(ns, name, value));
  }
  public void addMetadata(String ns, String name, String value,SMap attributes) {
    if (value == null)
      return;
    metadata.add(new SimpleMetadata(ns, name, value, attributes));
  }
  public void addPrimaryIdentifier(String value) {
    SMapImpl attr=new SMapImpl();
    attr.put(OPFResource.opfns, "scheme", "UUID");
    metadata.add(0, new SimpleMetadata(OPFResource.dcns, "identifier", value,attr));
  }

  private String getAdobePrimaryUUID() {
    Iterator it = metadata.iterator();
    while (it.hasNext()) {
      SimpleMetadata item = (SimpleMetadata) it.next();
      if (item.ns != null && item.ns.equals(dcns) && item.name.equals("identifier")
          && item.value.startsWith("urn:uuid:"))
        //return item.value.substring(9);
        return item.value;
    }
    String value = generateRandomIdentifier();
    //return value.substring(9);
    return value;
  }

  /**
   * Return Publication unique identifier; create one (in the form
   * "urn:uuid:UUID") if it does not exist
   *
   * @return unique identifier
   */
  public String getPrimaryIdentifier() {
    Iterator it = metadata.iterator();
    while (it.hasNext()) {
      SimpleMetadata item = (SimpleMetadata) it.next();
      if (item.ns != null && item.ns.equals(dcns) && item.name.equals("identifier")&& item.attribs.get(OPFResource.opfns, "UUID")!=null)
        return item.value;
    }
    return generateRandomIdentifier();
  }

  /**
   * Create a unique resource name using baseName as a template. If baseName
   * looks like "name.foo", "name.foo" name will be tried first. If it already
   * exists, names like "name-1.foo", "name-2.foo" will be tried until unused
   * resource name is found.
   *
   * @param baseName
   *            desired resource name
   * @return unique resource name based on the desired one
   */
  public String makeUniqueResourceName(String baseName) {
    if (resourcesByName.get(baseName) == null)
      return baseName;
    int index = baseName.lastIndexOf('.');
    String suffix;
    if (index < 0) {
      suffix = "";
    } else {
      suffix = baseName.substring(index);
      baseName = baseName.substring(0, index);
    }
    Integer lastIndex = (Integer) namingIndices.get(baseName);
    if (lastIndex == null)
      index = 1;
    else
      index = lastIndex.intValue() + 1;
    String name;
    while (true) {
      name = baseName + "-" + index + suffix;
      if (resourcesByName.get(name) == null)
        break;
      index++;
    }
    namingIndices.put(baseName, new Integer(index));
    return name;
  }

  /**
   * Create a new XHTML OPS resource and insert it into this Publication (but
   * not spine!).
   *
   * @param name
   *            OPS resource name
   * @return new OPSResource
   */
  public OPSResource createOPSResource(String name) {
    if (resourcesByName.get(name) != null)
      throw new RuntimeException("Resource already exists: " + name);
    return createOPSResource(name, "application/xhtml+xml");
  }

  public OPSResource createOPSResource(String name, String mediaType) {
    if (resourcesByName.get(name) != null)
      throw new RuntimeException("Resource already exists: " + name);
    OPSResource resource = new OPSResource(this, name, mediaType);
    resourcesByName.put(name, resource);
    return resource;
  }

  public void removeResource(Resource r) {
    resourcesByName.remove(r.name);
    if (r.id != null) {
      resourcesById.remove(r.id);
      r.id = null;
    }
    if (toc == r)
      toc = null;
  }

  public void renameResource(Resource r, String newName) {
    if (resourcesByName.get(newName) != null)
      throw new RuntimeException("Resource already exists: " + newName);
    resourcesByName.remove(r.name);
    ResourceRef ref = (ResourceRef) resourceRefsByName.remove(r.name);
    r.name = newName;
    resourcesByName.put(newName, r);
    if (ref != null) {
      ref.name = newName;
      resourceRefsByName.put(newName, ref);
    }
  }

  /**
   * Create new bitmap image resource and insert it into this Publication.
   *
   * @param name
   *            resource name
   * @param mediaType
   *            resource MIME type, i.g. "image/jpeg"
   * @param data
   *            resource data
   * @return new BitmapImageResource
   */
  public BitmapImageResource createBitmapImageResource(String name, String mediaType, DataSource data) {
    if (resourcesByName.get(name) != null)
      throw new RuntimeException("Resource already exists: " + name);
    BitmapImageResource resource = new BitmapImageResource(this, name, mediaType, data);
    resourcesByName.put(name, resource);
    return resource;
  }

  /**
   * Create new CSS resource and insert it into this Publication
   *
   * @param name
   *            resource name
   * @return new StyleResource
   */
  public StyleResource createStyleResource(String name) {
    if (resourcesByName.get(name) != null)
      throw new RuntimeException("Resource already exists: " + name);
    StyleResource resource = new StyleResource(this, name);
    resourcesByName.put(name, resource);
    return resource;
  }

  /**
   * Create new generic resource and insert it into this Publication.
   *
   * @param name
   *            resource name
   * @param mediaType
   *            resource MIME type
   * @param data
   *            resource data
   * @return new Resource
   */
  public Resource createGenericResource(String name, String mediaType, DataSource data) {
    if (resourcesByName.get(name) != null)
      throw new RuntimeException("Resource already exists: " + name);
    Resource resource = new Resource(this, name, mediaType, data);
    resourcesByName.put(name, resource);
    return resource;
  }

  /**
   * Create new embedded font resource and insert it into this Publication.
   * Font resources differ from genertic binary resources in that they get
   * "mangled" during serialization.
   *
   * @param name
   *            resource name
   * @param data
   *            resource data
   * @return new FontResource
   */
  public FontResource createFontResource(String name, DataSource data) {
    if (resourcesByName.get(name) != null)
      throw new RuntimeException("Resource already exists: " + name);
    FontResource resource = new FontResource(this, name, data);
    resourcesByName.put(name, resource);
    return resource;
  }

  /**
   * Add another resource to the spine (chapter list)
   *
   * @param resource
   *            resource to add; must be one of the existing OPS resources in
   *            this Publication
   */
  public void addToSpine(Resource resource) {
    spine.add(resource);
  }

  /**
   * Iterate all resources in this Publication.
   *
   * @return resource iterator
   */
  public Iterator resources() {
    return resourcesByName.values().iterator();
  }

  /**
   * Iterate resources in the spine (chapter list).
   *
   * @return spine iterator
   */
  public Iterator spine() {
    return spine.iterator();
  }

  public Resource getResourceByName(String name) {
    return (Resource) resourcesByName.get(name);
  }

  String assignId(Resource res) {
    if (res.id == null) {
      res.id = makeId();
      resourcesById.put(res.id, res);
    }
    return res.id;
  }

  String makeId() {
    while (true) {
      String id = "id" + (idCount++);
      if (resourcesById.get(id) == null)
        return id;
    }
  }

  public void usePageMap() {
    if (pageMap == null) {
      String name = makeUniqueResourceName("OPS/pageMap.xml");
      pageMap = new PageMapResource(this, name);
      resourcesByName.put(name, pageMap);
    }
  }

  /**
   * Add embedded font resources to this Publication and add corresponding
   * &#064;font-face rules for the embedded fonts. Fonts are taken from the
   * default font locator (see {@link DefaultFontLocator.getInstance}).
   *
   * @param styleResource
   *            style resource where &#064;font-face rules will be added
   */
  public FontEmbeddingReport addFonts(StyleResource styleResource) {
    return addFonts(styleResource, DefaultFontLocator.getInstance());
  }

  /**
   * Add embedded font resources to this Publication and add corresponding
   * &#064;font-face rules for the embedded fonts to a newly created
   * stylesheet. Fonts are taken from the default font locator (see
   * {@link DefaultFontLocator.getInstance}).
   */
  public FontEmbeddingReport addFonts() {
    String name = makeUniqueResourceName((contentFolder == null ? "" : contentFolder + "/") + "fonts.css");
    StyleResource styleResource = createStyleResource(name);
    return addFonts(styleResource, DefaultFontLocator.getInstance(), true);
  }

  /**
   * Add embedded font resources to this Publication and add corresponding
   * &#064;font-face rules for the embedded fonts. Fonts are taken from the
   * supplied font locator.
   *
   * @param styleResource
   *            style resource where &#064;font-face rules will be added
   * @param fontLocator
   *            FontLocator object to lookup font files
   */
  public FontEmbeddingReport addFonts(StyleResource styleResource, FontLocator locator) {
    return addFonts(styleResource, locator, false);
  }

  /**
   * Add embedded font resources to this Publication and add corresponding
   * &#064;font-face rules for the embedded fonts. Fonts are taken from the
   * supplied font locator.
   *
   * @param styleResource
   *            style resource where &#064;font-face rules will be added
   * @param fontLocator
   *            FontLocator object to lookup font files
   * @param addStyle
   *            If a reference to the style resource should be automatically
   *            added to each OPS resource
   */
  public FontEmbeddingReport addFonts(StyleResource styleResource, FontLocator locator, boolean addStyle) {
    FontSubsetter subsetter = new FontSubsetter(this, styleResource, locator);
    Iterator res = resources();
    while (res.hasNext()) {
      Object next = res.next();
      if (next instanceof OPSResource) {
        OPSResource ops = (OPSResource) next;
        OPSDocument doc = ops.getDocument();
        doc.addStyleResource(styleResource.getResourceRef());
        doc.addFonts(subsetter, styleResource);
      }
    }
    subsetter.addFonts(this);
    return subsetter;
  }

  static String strip(String source) {
    return source.replaceAll("\\s+", "");
  }

  /*
   * This starts with the "unique-identifier", strips the whitespace, and
   * applies SHA1 hash giving a 20 byte key that we can apply to the font
   * file.
   *
   * See:
   * http://www.openebook.org/doc_library/informationaldocs/FontManglingSpec
   * .html
   */
  byte[] makeIDPFXORMask() {
    if (idpfMask == null) {
      try {
        MessageDigest sha = MessageDigest.getInstance("SHA-1");
        String temp = strip(getPrimaryIdentifier());
        sha.update(temp.getBytes("UTF-8"), 0, temp.length());
        idpfMask = sha.digest();
      } catch (NoSuchAlgorithmException e) {
        System.err.println("No such Algorithm (really, did I misspell SHA-1?");
        System.err.println(e.toString());
        return null;
      } catch (IOException e) {
        System.err.println("IO Exception. check out mask.write...");
        System.err.println(e.toString());
        return null;
      }
    }
    return idpfMask;
  }

  byte[] makeAdobeXORMask() {
    if (adobeMask != null)
      return adobeMask;
    ByteArrayOutputStream mask = new ByteArrayOutputStream();
    String opfUID = getAdobePrimaryUUID();
    int acc = 0;
    int len = opfUID.length();
    for (int i = 0; i < len; i++) {
      char c = opfUID.charAt(i);
      int n;
      if ('0' <= c && c <= '9')
        n = c - '0';
      else if ('a' <= c && c <= 'f')
        n = c - ('a' - 10);
      else if ('A' <= c && c <= 'F')
        n = c - ('A' - 10);
      else
        continue;
      if (acc == 0) {
        acc = 0x100 | (n << 4);
      } else {
        mask.write(acc | n);
        acc = 0;
      }
    }
    if (mask.size() != 16)
      return null;
    adobeMask = mask.toByteArray();
    return adobeMask;
  }

  /**
   * Serialize Publication into a container such as OCF container or folder.
   *
   * @param container
   *            container writing interface
   * @throws IOException
   *             if I/O error occurs while writing
   */
  public void serialize(ContainerWriter container) throws IOException {
    getPrimaryIdentifier(); // if no unique id, make one
    NCXResource ncx = getTOC();
    if (ncx != null)
      ncx.prepareTOC();
    Iterator spine = spine();
    int playOrder = 0;
    while (spine.hasNext()) {
      Object sp = spine.next();
      if (sp instanceof OPSResource) {
        playOrder = ((OPSResource) sp).getDocument().assignPlayOrder(playOrder);
      }
    }
    Enumeration names = resourcesByName.keys();
    boolean needEnc = false;
    while (names.hasMoreElements()) {
      String name = (String) names.nextElement();
      Resource res = (Resource) resourcesByName.get(name);
      if (res instanceof FontResource && !useNOFontMangling) {
        //TODO: FONTS commented for exporting font as are don't care about licensing
        needEnc = true;
      }
      OutputStream out = container.getOutputStream(name, res.canCompress());
      res.serialize(out);
    }
    if (needEnc) {
      XMLSerializer ser = new XMLSerializer(container.getOutputStream("META-INF/encryption.xml"));
      ser.startDocument("1.0", "UTF-8");
      ser.startElement(ocfns, "encryption", null, true);
      ser.newLine();
      names = resourcesByName.keys();
      while (names.hasMoreElements()) {
        String name = (String) names.nextElement();
        Resource res = (Resource) resourcesByName.get(name);
        if (res instanceof FontResource) {
          SMapImpl attrs = new SMapImpl();
          ser.startElement(encns, "EncryptedData", null, true);
          ser.newLine();
          if (useIDPFFontMangling)
            attrs.put(null, "Algorithm", "http://www.idpf.org/2008/embedding");
          else
            attrs.put(null, "Algorithm", "http://ns.adobe.com/pdf/enc#RC");
          ser.startElement(encns, "EncryptionMethod", attrs, false);
          ser.endElement(encns, "EncryptionMethod");
          ser.newLine();
          ser.startElement(encns, "CipherData", null, false);
          ser.newLine();
          attrs = new SMapImpl();
          attrs.put(null, "URI", name);
          ser.startElement(encns, "CipherReference", attrs, false);
          ser.endElement(encns, "CipherReference");
          ser.newLine();
          ser.endElement(encns, "CipherData");
          ser.newLine();
          ser.endElement(encns, "EncryptedData");
          ser.newLine();
        }
      }
      ser.endElement(ocfns, "encryption");
      ser.newLine();
      ser.endDocument();
    }
    XMLSerializer ser = new XMLSerializer(container.getOutputStream("META-INF/container.xml"));
    ser.startDocument("1.0", "UTF-8");
    SMapImpl attrs = new SMapImpl();
    attrs.put(null, "version", "1.0");
    ser.startElement(ocfns, "container", attrs, true);
    ser.newLine();
    ser.startElement(ocfns, "rootfiles", null, false);
    ser.newLine();
    attrs = new SMapImpl();
    attrs.put(null, "full-path", opf.name);
    attrs.put(null, "media-type", opf.mediaType);
    ser.startElement(ocfns, "rootfile", attrs, true);
    ser.endElement(ocfns, "rootfile");
    ser.newLine();
    ser.endElement(ocfns, "rootfiles");
    ser.newLine();
    ser.endElement(ocfns, "container");
    ser.newLine();
    ser.endDocument();
    container.close();
  }

  public void cascadeStyles() {
    Enumeration names = resourcesByName.keys();
    while (names.hasMoreElements()) {
      String name = (String) names.nextElement();
      Resource res = (Resource) resourcesByName.get(name);
      if (res instanceof OPSResource) {
        ((OPSResource) res).getDocument().cascadeStyles();
      }
    }
  }

  public void generateStyles(StyleResource styleResource) {
    Enumeration names = resourcesByName.keys();
    while (names.hasMoreElements()) {
      String name = (String) names.nextElement();
      Resource res = (Resource) resourcesByName.get(name);
      if (res instanceof OPSResource) {
        ((OPSResource) res).getDocument().generateStyles(styleResource.getStylesheet());
      }
    }
  }

  static class OCFHandler extends DefaultHandler {

    String opf;

    public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException {
      if (opf != null)
        return;
      if (uri.equals(ocfns) && localName.equals("rootfile")) {
        String path = attributes.getValue("full-path");
        String type = attributes.getValue("media-type");
        if (type != null && type.equals(OPFResource.opfmedia))
          opf = path;
      }
    }
  }

  private static String processOCF(InputStream ocfStream) throws Exception {
    SAXParserFactory factory = SAXParserFactory.newInstance();
    factory.setNamespaceAware(true);
    SAXParser parser = factory.newSAXParser();
    XMLReader reader = parser.getXMLReader();
    OCFHandler handler = new OCFHandler();
    reader.setContentHandler(handler);
    reader.setEntityResolver(EPUBEntityResolver.instance);
    InputSource source = new InputSource(ocfStream);
    reader.parse(source);
    if (handler.opf == null)
      throw new RuntimeException("No OPF file found");
    return handler.opf;
  }

  Resource loadResource(String href, String mediaType) throws Exception {
    DataSource data = containerSource.getDataSource(href);
    if (data == null)
      return null;
    return createGenericResource(href, mediaType, data);
  }

  public Resource parseResource(Resource res) {
    Resource r = parseResourceRaw(res);
    if (r != null) {
      resourcesByName.put(res.getName(), r);
      int spineIndex = spine.indexOf(res);
      if (spineIndex >= 0)
        spine.set(spineIndex, r);
      if (res == toc)
        toc = r;
      if (res.id != null) {
        r.id = res.id;
        resourcesById.put(res.id, r);
      }
    }
    return r;
  }

  private Resource parseResourceRaw(Resource res) {
    if (res.getClass() != Resource.class)
      return null;
    String mediaType = res.getMediaType();
    DataSource data = res.source;
    String href = res.getName();
    try {
      if (mediaType.equals("application/xhtml+xml") || mediaType.equals("image/svg+xml")
          || mediaType.equals("text/html")) {
        if (mediaType.equals("text/html"))
          mediaType = "application/xhtml+xml";
        OPSResource resource = new OPSResource(this, href, mediaType);
        resource.load(data);
        return resource;
      }
      if (mediaType.equals("application/x-dtbncx+xml")) {
        NCXResource toc = new NCXResource(this, href);
        resourcesByName.put(href, toc);
        toc.load(data);
        return toc;
      }
      if (mediaType.equals("text/css")) {
        StyleResource resource = new StyleResource(this, href);
        resource.load(data);
        return resource;
      }
      if (mediaType.startsWith("image/"))
        return new BitmapImageResource(this, href, mediaType, data);
      if (mediaType.equals("application/vnd.ms-opentype"))
        return new FontResource(this, href, data);
      if (mediaType.equals("application/octet-stream")) {
        String s = href.toLowerCase();
        if (s.endsWith(".ttf") || s.endsWith(".otf"))
          return new FontResource(this, href, data);
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
    return null;
  }

  public ResourceRef getResourceRef(String name) {
    ResourceRef ref = (ResourceRef) resourceRefsByName.get(name);
    if (ref == null) {
      ref = new ResourceRef(this, name);
      resourceRefsByName.put(name, ref);
    }
    return ref;
  }

  public void parseSpine() {
    for (int i = 0; i < spine.size(); i++) {
      Resource r = (Resource) spine.get(i);
      try {
        parseResource(r);
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
  }

  public void parseAll() {
    Vector resources = new Vector();
    resources.addAll(resourcesByName.values());
    Iterator list = resources.iterator();
    while (list.hasNext()) {
      Resource r = (Resource) list.next();
      try {
        parseResource(r);
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
  }

  /**
   * Apply existing stylesheets, refactor all properties into new CSS classes,
   * remove all existing stylesheets and create a single one in place of them.
   */
  public StyleResource refactorStyles() {
    cascadeStyles();
    HashSet resources = new HashSet();
    Iterator list = resourcesByName.values().iterator();
    while (list.hasNext()) {
      Resource r = (Resource) list.next();
      if (r instanceof StyleResource) {
        resources.add(r);
      }
    }
    String name = makeUniqueResourceName((contentFolder == null ? "" : contentFolder + "/") + "common.css");
    StyleResource styleResource = createStyleResource(name);
    list = resources.iterator();
    while (list.hasNext()) {
      removeResource((Resource) list.next());
    }
    list = resourcesByName.values().iterator();
    while (list.hasNext()) {
      Object next = list.next();
      if (next instanceof OPSResource) {
        OPSResource ops = (OPSResource) next;
        OPSDocument doc = ops.getDocument();
        doc.removeAllStyleResources();
        doc.setAssignStylesFlag();
        doc.addStyleResource(styleResource.getResourceRef());
        doc.generateStyles(styleResource.getStylesheet());
      }
    }
    return styleResource;
  }

}
TOP

Related Classes of com.adobe.dp.epub.opf.Publication$OCFHandler

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.