diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml
new file mode 100644
index 00000000..91106d3f
--- /dev/null
+++ b/.github/workflows/maven.yml
@@ -0,0 +1,17 @@
+name: Java CI
+
+on: [push]
+
+jobs:
+ build:
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v1
+ - name: Set up JDK 1.8
+ uses: actions/setup-java@v1
+ with:
+ java-version: 1.8
+ - name: Build with Maven
+ run: mvn -B package --file pom.xml
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..b3644e4a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,11 @@
+epublib-core/bin/
+epublib-core/bin/
+epublib-tools/bin/
+epublib-core/.classpath
+epublib-core/.project
+epublib-tools/.classpath
+epublib-tools/.project
+epublib-core/.settings
+epublib-tools/.settings/
+!/.idea/
+*.iml
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 00000000..18f095e2
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,9 @@
+language: java
+
+script: mvn -f epublib-parent/pom.xml clean package
+
+dist: trusty
+
+jdk:
+ - openjdk7
+ - openjdk8
\ No newline at end of file
diff --git a/CREDITS b/CREDITS
new file mode 100644
index 00000000..496b8665
--- /dev/null
+++ b/CREDITS
@@ -0,0 +1,4 @@
+Some Icons are Copyright Yusuke Kamiyamane. All rights reserved. Licensed under a Creative Commons Attribution 3.0 license.
+
+Contains the class org.apache.commons.io.XmlStreamReader from the apache commons io class.
+See http://commons.apache.org/io/ for more info.
diff --git a/README b/README
deleted file mode 100644
index f4112a0a..00000000
--- a/README
+++ /dev/null
@@ -1 +0,0 @@
-Hello, worldl
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 00000000..b0cc05c1
--- /dev/null
+++ b/README.md
@@ -0,0 +1,116 @@
+# epublib
+Epublib is a java library for reading/writing/manipulating epub files.
+
+It consists of 2 parts: a core that reads/writes epub and a collection of tools.
+The tools contain an epub cleanup tool, a tool to create epubs from html files, a tool to create an epub from an uncompress html file.
+It also contains a swing-based epub viewer.
+![Epublib viewer](http://www.siegmann.nl/wp-content/uploads/Alice%E2%80%99s-Adventures-in-Wonderland_2011-01-30_18-17-30.png)
+
+The core runs both on android and a standard java environment. The tools run only on a standard java environment.
+
+This means that reading/writing epub files works on Android.
+
+## Build status
+* Travis Build Status: [![Build Status](https://travis-ci.org/psiegman/epublib.svg?branch=master)](https://travis-ci.org/psiegman/epublib)
+
+## Command line examples
+
+Set the author of an existing epub
+ java -jar epublib-3.0-SNAPSHOT.one-jar.jar --in input.epub --out result.epub --author Tester,Joe
+
+Set the cover image of an existing epub
+ java -jar epublib-3.0-SNAPSHOT.one-jar.jar --in input.epub --out result.epub --cover-image my_cover.jpg
+
+## Creating an epub programmatically
+
+ package nl.siegmann.epublib.examples;
+
+ import java.io.InputStream;
+ import java.io.FileOutputStream;
+
+ import nl.siegmann.epublib.domain.Author;
+ import nl.siegmann.epublib.domain.Book;
+ import nl.siegmann.epublib.domain.Metadata;
+ import nl.siegmann.epublib.domain.Resource;
+ import nl.siegmann.epublib.domain.TOCReference;
+
+ import nl.siegmann.epublib.epub.EpubWriter;
+
+ public class Translator {
+ private static InputStream getResource( String path ) {
+ return Translator.class.getResourceAsStream( path );
+ }
+
+ private static Resource getResource( String path, String href ) {
+ return new Resource( getResource( path ), href );
+ }
+
+ public static void main(String[] args) {
+ try {
+ // Create new Book
+ Book book = new Book();
+ Metadata metadata = book.getMetadata();
+
+ // Set the title
+ metadata.addTitle("Epublib test book 1");
+
+ // Add an Author
+ metadata.addAuthor(new Author("Joe", "Tester"));
+
+ // Set cover image
+ book.setCoverImage(
+ getResource("/book1/test_cover.png", "cover.png") );
+
+ // Add Chapter 1
+ book.addSection("Introduction",
+ getResource("/book1/chapter1.html", "chapter1.html") );
+
+ // Add css file
+ book.getResources().add(
+ getResource("/book1/book1.css", "book1.css") );
+
+ // Add Chapter 2
+ TOCReference chapter2 = book.addSection( "Second Chapter",
+ getResource("/book1/chapter2.html", "chapter2.html") );
+
+ // Add image used by Chapter 2
+ book.getResources().add(
+ getResource("/book1/flowers_320x240.jpg", "flowers.jpg"));
+
+ // Add Chapter2, Section 1
+ book.addSection(chapter2, "Chapter 2, section 1",
+ getResource("/book1/chapter2_1.html", "chapter2_1.html"));
+
+ // Add Chapter 3
+ book.addSection("Conclusion",
+ getResource("/book1/chapter3.html", "chapter3.html"));
+
+ // Create EpubWriter
+ EpubWriter epubWriter = new EpubWriter();
+
+ // Write the Book as Epub
+ epubWriter.write(book, new FileOutputStream("test1_book1.epub"));
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+
+## Usage in Android
+
+Add the following lines to your `app` module's `build.gradle` file:
+
+ repositories {
+ maven {
+ url 'https://github.com/psiegman/mvn-repo/raw/master/releases'
+ }
+ }
+
+ dependencies {
+ implementation('nl.siegmann.epublib:epublib-core:4.0') {
+ exclude group: 'org.slf4j'
+ exclude group: 'xmlpull'
+ }
+ implementation 'org.slf4j:slf4j-android:1.7.25'
+ }
diff --git a/epublib-core/.gitignore b/epublib-core/.gitignore
new file mode 100644
index 00000000..3dfdcd84
--- /dev/null
+++ b/epublib-core/.gitignore
@@ -0,0 +1,2 @@
+/target
+/test1_book1.epub
diff --git a/epublib-core/build.sbt b/epublib-core/build.sbt
new file mode 100644
index 00000000..b75cb493
--- /dev/null
+++ b/epublib-core/build.sbt
@@ -0,0 +1,25 @@
+autoScalaLibrary := false
+
+crossPaths := false
+
+name := "epublib-core"
+
+organization := "nl.siegmann.epublib"
+
+version := "4.0"
+
+publishMavenStyle := true
+
+javacOptions in doc += "-Xdoclint:none"
+
+libraryDependencies += "net.sf.kxml" % "kxml2" % "2.3.0"
+
+libraryDependencies += "xmlpull" % "xmlpull" % "1.1.3.4d_b4_min"
+
+libraryDependencies += "org.slf4j" % "slf4j-api" % "1.6.1"
+
+libraryDependencies += "org.slf4j" % "slf4j-simple" % "1.6.1"
+
+libraryDependencies += "junit" % "junit" % "4.10"
+
+
diff --git a/epublib-core/pom.xml b/epublib-core/pom.xml
new file mode 100644
index 00000000..be1dc21c
--- /dev/null
+++ b/epublib-core/pom.xml
@@ -0,0 +1,105 @@
+
+
+
+ * The specification for Adler32 may be found in RFC 1950. (ZLIB Compressed Data + * Format Specification version 3.3) + *
+ *
+ * From that document: + *
+ * "ADLER32 (Adler-32 checksum) This contains a checksum value of the + * uncompressed data (excluding any dictionary data) computed according to + * Adler-32 algorithm. This algorithm is a 32-bit extension and improvement of + * the Fletcher algorithm, used in the ITU-T X.224 / ISO 8073 standard. + *
+ * Adler-32 is composed of two sums accumulated per byte: s1 is the sum of all + * bytes, s2 is the sum of all s1 values. Both sums are done modulo 65521. s1 is + * initialized to 1, s2 to zero. The Adler-32 checksum is stored as s2*65536 + + * s1 in most- significant-byte first (network) order." + *
+ * "8.2. The Adler-32 algorithm + *
+ * The Adler-32 algorithm is much faster than the CRC32 algorithm yet still + * provides an extremely low probability of undetected errors. + *
+ * The modulo on unsigned long accumulators can be delayed for 5552 bytes, so + * the modulo operation time is negligible. If the bytes are a, b, c, the second + * sum is 3a + 2b + c + 3, and so is position and order sensitive, unlike the + * first sum, which is just a checksum. That 65521 is prime is important to + * avoid a possible large class of two-byte errors that leave the check + * unchanged. (The Fletcher checksum uses 255, which is not prime and which also + * makes the Fletcher check insensitive to single byte changes 0 <-> 255.) + *
+ * The sum s1 is initialized to 1 instead of zero to make the length of the
+ * sequence part of s2, so that the length does not have to be checked
+ * separately. (Any sequence of zeroes has a Fletcher checksum of zero.)"
+ *
+ * @author John Leuner, Per Bothner
+ * @since JDK 1.1
+ *
+ * @see InflaterInputStream
+ * @see DeflaterOutputStream
+ */
+public class Adler32 implements Checksum {
+
+ /** largest prime smaller than 65536 */
+ private static final int BASE = 65521;
+
+ private int checksum; // we do all in int.
+
+ // Note that java doesn't have unsigned integers,
+ // so we have to be careful with what arithmetic
+ // we do. We return the checksum as a long to
+ // avoid sign confusion.
+
+ /**
+ * Creates a new instance of the Adler32
class. The checksum
+ * starts off with a value of 1.
+ */
+ public Adler32() {
+ reset();
+ }
+
+ /**
+ * Resets the Adler32 checksum to the initial value.
+ */
+ @Override
+ public void reset() {
+ checksum = 1; // Initialize to 1
+ }
+
+ /**
+ * Updates the checksum with the byte b.
+ *
+ * @param bval
+ * the data value to add. The high byte of the int is ignored.
+ */
+ @Override
+ public void update(final int bval) {
+ // We could make a length 1 byte array and call update again, but I
+ // would rather not have that overhead
+ int s1 = checksum & 0xffff;
+ int s2 = checksum >>> 16;
+
+ s1 = (s1 + (bval & 0xFF)) % BASE;
+ s2 = (s1 + s2) % BASE;
+
+ checksum = (s2 << 16) + s1;
+ }
+
+ /**
+ * Updates the checksum with the bytes taken from the array.
+ *
+ * @param buffer
+ * an array of bytes
+ */
+ public void update(final byte[] buffer) {
+ update(buffer, 0, buffer.length);
+ }
+
+ /**
+ * Updates the checksum with the bytes taken from the array.
+ *
+ * @param buf
+ * an array of bytes
+ * @param off
+ * the start of the data used for this update
+ * @param len
+ * the number of bytes to use for this update
+ */
+ @Override
+ public void update(final byte[] buf, int off, int len) {
+ // (By Per Bothner)
+ int s1 = checksum & 0xffff;
+ int s2 = checksum >>> 16;
+
+ while (len > 0) {
+ // We can defer the modulo operation:
+ // s1 maximally grows from 65521 to 65521 + 255 * 3800
+ // s2 maximally grows by 3800 * median(s1) = 2090079800 < 2^31
+ int n = 3800;
+ if (n > len) {
+ n = len;
+ }
+ len -= n;
+ while (--n >= 0) {
+ s1 = s1 + (buf[off++] & 0xFF);
+ s2 = s2 + s1;
+ }
+ s1 %= BASE;
+ s2 %= BASE;
+ }
+
+ /*
+ * Old implementation, borrowed from somewhere: int n;
+ *
+ * while (len-- > 0) {
+ *
+ * s1 = (s1 + (bs[offset++] & 0xff)) % BASE; s2 = (s2 + s1) % BASE; }
+ */
+
+ checksum = (s2 << 16) | s1;
+ }
+
+ /**
+ * Returns the Adler32 data checksum computed so far.
+ */
+ @Override
+ public long getValue() {
+ return checksum & 0xffffffffL;
+ }
+}
diff --git a/epublib-core/src/main/java/net/sf/jazzlib/CRC32.java b/epublib-core/src/main/java/net/sf/jazzlib/CRC32.java
new file mode 100644
index 00000000..f5d40cd4
--- /dev/null
+++ b/epublib-core/src/main/java/net/sf/jazzlib/CRC32.java
@@ -0,0 +1,138 @@
+/* CRC32.java - Computes CRC32 data checksum of a data stream
+ Copyright (C) 1999. 2000, 2001 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath 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 2, or (at your option)
+any later version.
+
+GNU Classpath 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 GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+02111-1307 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+package net.sf.jazzlib;
+
+/*
+ * Written using on-line Java Platform 1.2 API Specification, as well
+ * as "The Java Class Libraries", 2nd edition (Addison-Wesley, 1998).
+ * The actual CRC32 algorithm is taken from RFC 1952.
+ * Status: Believed complete and correct.
+ */
+
+/**
+ * Computes CRC32 data checksum of a data stream. The actual CRC32 algorithm is
+ * described in RFC 1952 (GZIP file format specification version 4.3). Can be
+ * used to get the CRC32 over a stream if used with checked input/output
+ * streams.
+ *
+ * @see InflaterInputStream
+ * @see DeflaterOutputStream
+ *
+ * @author Per Bothner
+ * @date April 1, 1999.
+ */
+public class CRC32 implements Checksum {
+ /** The crc data checksum so far. */
+ private int crc = 0;
+
+ /** The fast CRC table. Computed once when the CRC32 class is loaded. */
+ private static int[] crc_table = make_crc_table();
+
+ /** Make the table for a fast CRC. */
+ private static int[] make_crc_table() {
+ final int[] crc_table = new int[256];
+ for (int n = 0; n < 256; n++) {
+ int c = n;
+ for (int k = 8; --k >= 0;) {
+ if ((c & 1) != 0) {
+ c = 0xedb88320 ^ (c >>> 1);
+ } else {
+ c = c >>> 1;
+ }
+ }
+ crc_table[n] = c;
+ }
+ return crc_table;
+ }
+
+ /**
+ * Returns the CRC32 data checksum computed so far.
+ */
+ @Override
+ public long getValue() {
+ return crc & 0xffffffffL;
+ }
+
+ /**
+ * Resets the CRC32 data checksum as if no update was ever called.
+ */
+ @Override
+ public void reset() {
+ crc = 0;
+ }
+
+ /**
+ * Updates the checksum with the int bval.
+ *
+ * @param bval
+ * (the byte is taken as the lower 8 bits of bval)
+ */
+
+ @Override
+ public void update(final int bval) {
+ int c = ~crc;
+ c = crc_table[(c ^ bval) & 0xff] ^ (c >>> 8);
+ crc = ~c;
+ }
+
+ /**
+ * Adds the byte array to the data checksum.
+ *
+ * @param buf
+ * the buffer which contains the data
+ * @param off
+ * the offset in the buffer where the data starts
+ * @param len
+ * the length of the data
+ */
+ @Override
+ public void update(final byte[] buf, int off, int len) {
+ int c = ~crc;
+ while (--len >= 0) {
+ c = crc_table[(c ^ buf[off++]) & 0xff] ^ (c >>> 8);
+ }
+ crc = ~c;
+ }
+
+ /**
+ * Adds the complete byte array to the data checksum.
+ */
+ public void update(final byte[] buf) {
+ update(buf, 0, buf.length);
+ }
+}
diff --git a/epublib-core/src/main/java/net/sf/jazzlib/CheckedInputStream.java b/epublib-core/src/main/java/net/sf/jazzlib/CheckedInputStream.java
new file mode 100644
index 00000000..80289057
--- /dev/null
+++ b/epublib-core/src/main/java/net/sf/jazzlib/CheckedInputStream.java
@@ -0,0 +1,135 @@
+/* CheckedInputStream.java - Compute checksum of data being read
+ Copyright (C) 1999, 2000 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath 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 2, or (at your option)
+any later version.
+
+GNU Classpath 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 GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+02111-1307 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+package net.sf.jazzlib;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/* Written using on-line Java Platform 1.2 API Specification
+ * and JCL book.
+ * Believed complete and correct.
+ */
+
+/**
+ * InputStream that computes a checksum of the data being read using a supplied
+ * Checksum object.
+ *
+ * @see Checksum
+ *
+ * @author Tom Tromey
+ * @date May 17, 1999
+ */
+public class CheckedInputStream extends FilterInputStream {
+ /**
+ * Creates a new CheckInputStream on top of the supplied OutputStream using
+ * the supplied Checksum.
+ */
+ public CheckedInputStream(final InputStream in, final Checksum sum) {
+ super(in);
+ this.sum = sum;
+ }
+
+ /**
+ * Returns the Checksum object used. To get the data checksum computed so
+ * far call getChecksum.getValue()
.
+ */
+ public Checksum getChecksum() {
+ return sum;
+ }
+
+ /**
+ * Reads one byte, updates the checksum and returns the read byte (or -1
+ * when the end of file was reached).
+ */
+ @Override
+ public int read() throws IOException {
+ final int x = in.read();
+ if (x != -1) {
+ sum.update(x);
+ }
+ return x;
+ }
+
+ /**
+ * Reads at most len bytes in the supplied buffer and updates the checksum
+ * with it. Returns the number of bytes actually read or -1 when the end of
+ * file was reached.
+ */
+ @Override
+ public int read(final byte[] buf, final int off, final int len)
+ throws IOException {
+ final int r = in.read(buf, off, len);
+ if (r != -1) {
+ sum.update(buf, off, r);
+ }
+ return r;
+ }
+
+ /**
+ * Skips n bytes by reading them in a temporary buffer and updating the the
+ * checksum with that buffer. Returns the actual number of bytes skiped
+ * which can be less then requested when the end of file is reached.
+ */
+ @Override
+ public long skip(long n) throws IOException {
+ if (n == 0) {
+ return 0;
+ }
+
+ int min = (int) Math.min(n, 1024);
+ final byte[] buf = new byte[min];
+
+ long s = 0;
+ while (n > 0) {
+ final int r = in.read(buf, 0, min);
+ if (r == -1) {
+ break;
+ }
+ n -= r;
+ s += r;
+ min = (int) Math.min(n, 1024);
+ sum.update(buf, 0, r);
+ }
+
+ return s;
+ }
+
+ /** The checksum object. */
+ private final Checksum sum;
+}
diff --git a/epublib-core/src/main/java/net/sf/jazzlib/CheckedOutputStream.java b/epublib-core/src/main/java/net/sf/jazzlib/CheckedOutputStream.java
new file mode 100644
index 00000000..7077ec09
--- /dev/null
+++ b/epublib-core/src/main/java/net/sf/jazzlib/CheckedOutputStream.java
@@ -0,0 +1,97 @@
+/* CheckedOutputStream.java - Compute checksum of data being written.
+ Copyright (C) 1999, 2000 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath 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 2, or (at your option)
+any later version.
+
+GNU Classpath 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 GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+02111-1307 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+package net.sf.jazzlib;
+
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/* Written using on-line Java Platform 1.2 API Specification
+ * and JCL book.
+ * Believed complete and correct.
+ */
+
+/**
+ * OutputStream that computes a checksum of data being written using a supplied
+ * Checksum object.
+ *
+ * @see Checksum
+ *
+ * @author Tom Tromey
+ * @date May 17, 1999
+ */
+public class CheckedOutputStream extends FilterOutputStream {
+ /**
+ * Creates a new CheckInputStream on top of the supplied OutputStream using
+ * the supplied Checksum.
+ */
+ public CheckedOutputStream(final OutputStream out, final Checksum cksum) {
+ super(out);
+ this.sum = cksum;
+ }
+
+ /**
+ * Returns the Checksum object used. To get the data checksum computed so
+ * far call getChecksum.getValue()
.
+ */
+ public Checksum getChecksum() {
+ return sum;
+ }
+
+ /**
+ * Writes one byte to the OutputStream and updates the Checksum.
+ */
+ @Override
+ public void write(final int bval) throws IOException {
+ out.write(bval);
+ sum.update(bval);
+ }
+
+ /**
+ * Writes the byte array to the OutputStream and updates the Checksum.
+ */
+ @Override
+ public void write(final byte[] buf, final int off, final int len)
+ throws IOException {
+ out.write(buf, off, len);
+ sum.update(buf, off, len);
+ }
+
+ /** The checksum object. */
+ private final Checksum sum;
+}
diff --git a/epublib-core/src/main/java/net/sf/jazzlib/Checksum.java b/epublib-core/src/main/java/net/sf/jazzlib/Checksum.java
new file mode 100644
index 00000000..7bae782c
--- /dev/null
+++ b/epublib-core/src/main/java/net/sf/jazzlib/Checksum.java
@@ -0,0 +1,89 @@
+/* Checksum.java - Interface to compute a data checksum
+ Copyright (C) 1999, 2000, 2001 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath 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 2, or (at your option)
+any later version.
+
+GNU Classpath 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 GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+02111-1307 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+package net.sf.jazzlib;
+
+/*
+ * Written using on-line Java Platform 1.2 API Specification, as well
+ * as "The Java Class Libraries", 2nd edition (Addison-Wesley, 1998).
+ * Status: Believed complete and correct.
+ */
+
+/**
+ * Interface to compute a data checksum used by checked input/output streams. A
+ * data checksum can be updated by one byte or with a byte array. After each
+ * update the value of the current checksum can be returned by calling
+ * getValue
. The complete checksum object can also be reset so it
+ * can be used again with new data.
+ *
+ * @see CheckedInputStream
+ * @see CheckedOutputStream
+ *
+ * @author Per Bothner
+ * @author Jochen Hoenicke
+ */
+public interface Checksum {
+ /**
+ * Returns the data checksum computed so far.
+ */
+ long getValue();
+
+ /**
+ * Resets the data checksum as if no update was ever called.
+ */
+ void reset();
+
+ /**
+ * Adds one byte to the data checksum.
+ *
+ * @param bval
+ * the data value to add. The high byte of the int is ignored.
+ */
+ void update(int bval);
+
+ /**
+ * Adds the byte array to the data checksum.
+ *
+ * @param buf
+ * the buffer which contains the data
+ * @param off
+ * the offset in the buffer where the data starts
+ * @param len
+ * the length of the data
+ */
+ void update(byte[] buf, int off, int len);
+}
diff --git a/epublib-core/src/main/java/net/sf/jazzlib/DataFormatException.java b/epublib-core/src/main/java/net/sf/jazzlib/DataFormatException.java
new file mode 100644
index 00000000..79501ec8
--- /dev/null
+++ b/epublib-core/src/main/java/net/sf/jazzlib/DataFormatException.java
@@ -0,0 +1,69 @@
+/* DataformatException.java -- thrown when compressed data is corrupt
+ Copyright (C) 1999, 2000, 2001, 2002 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath 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 2, or (at your option)
+any later version.
+
+GNU Classpath 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 GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+02111-1307 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+package net.sf.jazzlib;
+
+/**
+ * Exception thrown when compressed data is corrupt.
+ *
+ * @author Tom Tromey
+ * @author John Leuner
+ * @since 1.1
+ * @status updated to 1.4
+ */
+public class DataFormatException extends Exception {
+ /**
+ * Compatible with JDK 1.1+.
+ */
+ private static final long serialVersionUID = 2219632870893641452L;
+
+ /**
+ * Create an exception without a message.
+ */
+ public DataFormatException() {
+ }
+
+ /**
+ * Create an exception with a message.
+ *
+ * @param msg
+ * the message
+ */
+ public DataFormatException(final String msg) {
+ super(msg);
+ }
+}
diff --git a/epublib-core/src/main/java/net/sf/jazzlib/Deflater.java b/epublib-core/src/main/java/net/sf/jazzlib/Deflater.java
new file mode 100644
index 00000000..c9af5fe0
--- /dev/null
+++ b/epublib-core/src/main/java/net/sf/jazzlib/Deflater.java
@@ -0,0 +1,511 @@
+/* Deflater.java - Compress a data stream
+ Copyright (C) 1999, 2000, 2001, 2004 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath 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 2, or (at your option)
+any later version.
+
+GNU Classpath 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 GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+02111-1307 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+package net.sf.jazzlib;
+
+/**
+ * This is the Deflater class. The deflater class compresses input with the
+ * deflate algorithm described in RFC 1951. It has several compression levels
+ * and three different strategies described below.
+ *
+ * This class is not thread safe. This is inherent in the API, due to the
+ * split of deflate and setInput.
+ *
+ * @author Jochen Hoenicke
+ * @author Tom Tromey
+ */
+public class Deflater {
+ /**
+ * The best and slowest compression level. This tries to find very long and
+ * distant string repetitions.
+ */
+ public static final int BEST_COMPRESSION = 9;
+ /**
+ * The worst but fastest compression level.
+ */
+ public static final int BEST_SPEED = 1;
+ /**
+ * The default compression level.
+ */
+ public static final int DEFAULT_COMPRESSION = -1;
+ /**
+ * This level won't compress at all but output uncompressed blocks.
+ */
+ public static final int NO_COMPRESSION = 0;
+
+ /**
+ * The default strategy.
+ */
+ public static final int DEFAULT_STRATEGY = 0;
+ /**
+ * This strategy will only allow longer string repetitions. It is useful for
+ * random data with a small character set.
+ */
+ public static final int FILTERED = 1;
+
+ /**
+ * This strategy will not look for string repetitions at all. It only
+ * encodes with Huffman trees (which means, that more common characters get
+ * a smaller encoding.
+ */
+ public static final int HUFFMAN_ONLY = 2;
+
+ /**
+ * The compression method. This is the only method supported so far. There
+ * is no need to use this constant at all.
+ */
+ public static final int DEFLATED = 8;
+
+ /*
+ * The Deflater can do the following state transitions:
+ *
+ * (1) -> INIT_STATE ----> INIT_FINISHING_STATE ---. / | (2) (5) | / v (5) |
+ * (3)| SETDICT_STATE ---> SETDICT_FINISHING_STATE |(3) \ | (3) | ,-------'
+ * | | | (3) / v v (5) v v (1) -> BUSY_STATE ----> FINISHING_STATE | (6) v
+ * FINISHED_STATE \_____________________________________/ | (7) v
+ * CLOSED_STATE
+ *
+ * (1) If we should produce a header we start in INIT_STATE, otherwise we
+ * start in BUSY_STATE. (2) A dictionary may be set only when we are in
+ * INIT_STATE, then we change the state as indicated. (3) Whether a
+ * dictionary is set or not, on the first call of deflate we change to
+ * BUSY_STATE. (4) -- intentionally left blank -- :) (5) FINISHING_STATE is
+ * entered, when flush() is called to indicate that there is no more INPUT.
+ * There are also states indicating, that the header wasn't written yet. (6)
+ * FINISHED_STATE is entered, when everything has been flushed to the
+ * internal pending output buffer. (7) At any time (7)
+ */
+
+ private static final int IS_SETDICT = 0x01;
+ private static final int IS_FLUSHING = 0x04;
+ private static final int IS_FINISHING = 0x08;
+
+ private static final int INIT_STATE = 0x00;
+ private static final int SETDICT_STATE = 0x01;
+ private static final int BUSY_STATE = 0x10;
+ private static final int FLUSHING_STATE = 0x14;
+ private static final int FINISHING_STATE = 0x1c;
+ private static final int FINISHED_STATE = 0x1e;
+ private static final int CLOSED_STATE = 0x7f;
+
+ /** Compression level. */
+ private int level;
+
+ /** should we include a header. */
+ private final boolean noHeader;
+
+ /** The current state. */
+ private int state;
+
+ /** The total bytes of output written. */
+ private int totalOut;
+
+ /** The pending output. */
+ private DeflaterPending pending;
+
+ /** The deflater engine. */
+ private DeflaterEngine engine;
+
+ /**
+ * Creates a new deflater with default compression level.
+ */
+ public Deflater() {
+ this(DEFAULT_COMPRESSION, false);
+ }
+
+ /**
+ * Creates a new deflater with given compression level.
+ *
+ * @param lvl
+ * the compression level, a value between NO_COMPRESSION and
+ * BEST_COMPRESSION, or DEFAULT_COMPRESSION.
+ * @exception IllegalArgumentException
+ * if lvl is out of range.
+ */
+ public Deflater(final int lvl) {
+ this(lvl, false);
+ }
+
+ /**
+ * Creates a new deflater with given compression level.
+ *
+ * @param lvl
+ * the compression level, a value between NO_COMPRESSION and
+ * BEST_COMPRESSION.
+ * @param nowrap
+ * true, iff we should suppress the deflate header at the
+ * beginning and the adler checksum at the end of the output.
+ * This is useful for the GZIP format.
+ * @exception IllegalArgumentException
+ * if lvl is out of range.
+ */
+ public Deflater(int lvl, final boolean nowrap) {
+ if (lvl == DEFAULT_COMPRESSION) {
+ lvl = 6;
+ } else if ((lvl < NO_COMPRESSION) || (lvl > BEST_COMPRESSION)) {
+ throw new IllegalArgumentException();
+ }
+
+ pending = new DeflaterPending();
+ engine = new DeflaterEngine(pending);
+ this.noHeader = nowrap;
+ setStrategy(DEFAULT_STRATEGY);
+ setLevel(lvl);
+ reset();
+ }
+
+ /**
+ * Resets the deflater. The deflater acts afterwards as if it was just
+ * created with the same compression level and strategy as it had before.
+ */
+ public void reset() {
+ state = (noHeader ? BUSY_STATE : INIT_STATE);
+ totalOut = 0;
+ pending.reset();
+ engine.reset();
+ }
+
+ /**
+ * Frees all objects allocated by the compressor. There's no reason to call
+ * this, since you can just rely on garbage collection. Exists only for
+ * compatibility against Sun's JDK, where the compressor allocates native
+ * memory. If you call any method (even reset) afterwards the behaviour is
+ * undefined.
+ *
+ * @deprecated Just clear all references to deflater instead.
+ */
+ @Deprecated
+ public void end() {
+ engine = null;
+ pending = null;
+ state = CLOSED_STATE;
+ }
+
+ /**
+ * Gets the current adler checksum of the data that was processed so far.
+ */
+ public int getAdler() {
+ return engine.getAdler();
+ }
+
+ /**
+ * Gets the number of input bytes processed so far.
+ */
+ public int getTotalIn() {
+ return engine.getTotalIn();
+ }
+
+ /**
+ * Gets the number of output bytes so far.
+ */
+ public int getTotalOut() {
+ return totalOut;
+ }
+
+ /**
+ * Finalizes this object.
+ */
+ @Override
+ protected void finalize() {
+ /* Exists solely for compatibility. We don't have any native state. */
+ }
+
+ /**
+ * Flushes the current input block. Further calls to deflate() will produce
+ * enough output to inflate everything in the current input block. This is
+ * not part of Sun's JDK so I have made it package private. It is used by
+ * DeflaterOutputStream to implement flush().
+ */
+ void flush() {
+ state |= IS_FLUSHING;
+ }
+
+ /**
+ * Finishes the deflater with the current input block. It is an error to
+ * give more input after this method was called. This method must be called
+ * to force all bytes to be flushed.
+ */
+ public void finish() {
+ state |= IS_FLUSHING | IS_FINISHING;
+ }
+
+ /**
+ * Returns true iff the stream was finished and no more output bytes are
+ * available.
+ */
+ public boolean finished() {
+ return (state == FINISHED_STATE) && pending.isFlushed();
+ }
+
+ /**
+ * Returns true, if the input buffer is empty. You should then call
+ * setInput().
+ *
+ * NOTE: This method can also return true when the stream was
+ * finished.
+ */
+ public boolean needsInput() {
+ return engine.needsInput();
+ }
+
+ /**
+ * Sets the data which should be compressed next. This should be only called
+ * when needsInput indicates that more input is needed. If you call setInput
+ * when needsInput() returns false, the previous input that is still pending
+ * will be thrown away. The given byte array should not be changed, before
+ * needsInput() returns true again. This call is equivalent to
+ * setInput(input, 0, input.length)
.
+ *
+ * @param input
+ * the buffer containing the input data.
+ * @exception IllegalStateException
+ * if the buffer was finished() or ended().
+ */
+ public void setInput(final byte[] input) {
+ setInput(input, 0, input.length);
+ }
+
+ /**
+ * Sets the data which should be compressed next. This should be only called
+ * when needsInput indicates that more input is needed. The given byte array
+ * should not be changed, before needsInput() returns true again.
+ *
+ * @param input
+ * the buffer containing the input data.
+ * @param off
+ * the start of the data.
+ * @param len
+ * the length of the data.
+ * @exception IllegalStateException
+ * if the buffer was finished() or ended() or if previous
+ * input is still pending.
+ */
+ public void setInput(final byte[] input, final int off, final int len) {
+ if ((state & IS_FINISHING) != 0) {
+ throw new IllegalStateException("finish()/end() already called");
+ }
+ engine.setInput(input, off, len);
+ }
+
+ /**
+ * Sets the compression level. There is no guarantee of the exact position
+ * of the change, but if you call this when needsInput is true the change of
+ * compression level will occur somewhere near before the end of the so far
+ * given input.
+ *
+ * @param lvl
+ * the new compression level.
+ */
+ public void setLevel(int lvl) {
+ if (lvl == DEFAULT_COMPRESSION) {
+ lvl = 6;
+ } else if ((lvl < NO_COMPRESSION) || (lvl > BEST_COMPRESSION)) {
+ throw new IllegalArgumentException();
+ }
+
+ if (level != lvl) {
+ level = lvl;
+ engine.setLevel(lvl);
+ }
+ }
+
+ /**
+ * Sets the compression strategy. Strategy is one of DEFAULT_STRATEGY,
+ * HUFFMAN_ONLY and FILTERED. For the exact position where the strategy is
+ * changed, the same as for setLevel() applies.
+ *
+ * @param stgy
+ * the new compression strategy.
+ */
+ public void setStrategy(final int stgy) {
+ if ((stgy != DEFAULT_STRATEGY) && (stgy != FILTERED)
+ && (stgy != HUFFMAN_ONLY)) {
+ throw new IllegalArgumentException();
+ }
+ engine.setStrategy(stgy);
+ }
+
+ /**
+ * Deflates the current input block to the given array. It returns the
+ * number of bytes compressed, or 0 if either needsInput() or finished()
+ * returns true or length is zero.
+ *
+ * @param output
+ * the buffer where to write the compressed data.
+ */
+ public int deflate(final byte[] output) {
+ return deflate(output, 0, output.length);
+ }
+
+ /**
+ * Deflates the current input block to the given array. It returns the
+ * number of bytes compressed, or 0 if either needsInput() or finished()
+ * returns true or length is zero.
+ *
+ * @param output
+ * the buffer where to write the compressed data.
+ * @param offset
+ * the offset into the output array.
+ * @param length
+ * the maximum number of bytes that may be written.
+ * @exception IllegalStateException
+ * if end() was called.
+ * @exception IndexOutOfBoundsException
+ * if offset and/or length don't match the array length.
+ */
+ public int deflate(final byte[] output, int offset, int length) {
+ final int origLength = length;
+
+ if (state == CLOSED_STATE) {
+ throw new IllegalStateException("Deflater closed");
+ }
+
+ if (state < BUSY_STATE) {
+ /* output header */
+ int header = (DEFLATED + ((DeflaterConstants.MAX_WBITS - 8) << 4)) << 8;
+ int level_flags = (level - 1) >> 1;
+ if ((level_flags < 0) || (level_flags > 3)) {
+ level_flags = 3;
+ }
+ header |= level_flags << 6;
+ if ((state & IS_SETDICT) != 0) {
+ /* Dictionary was set */
+ header |= DeflaterConstants.PRESET_DICT;
+ }
+ header += 31 - (header % 31);
+
+ pending.writeShortMSB(header);
+ if ((state & IS_SETDICT) != 0) {
+ final int chksum = engine.getAdler();
+ engine.resetAdler();
+ pending.writeShortMSB(chksum >> 16);
+ pending.writeShortMSB(chksum & 0xffff);
+ }
+
+ state = BUSY_STATE | (state & (IS_FLUSHING | IS_FINISHING));
+ }
+
+ for (;;) {
+ final int count = pending.flush(output, offset, length);
+ offset += count;
+ totalOut += count;
+ length -= count;
+ if ((length == 0) || (state == FINISHED_STATE)) {
+ break;
+ }
+
+ if (!engine.deflate((state & IS_FLUSHING) != 0,
+ (state & IS_FINISHING) != 0)) {
+ if (state == BUSY_STATE) {
+ /* We need more input now */
+ return origLength - length;
+ } else if (state == FLUSHING_STATE) {
+ if (level != NO_COMPRESSION) {
+ /*
+ * We have to supply some lookahead. 8 bit lookahead are
+ * needed by the zlib inflater, and we must fill the
+ * next byte, so that all bits are flushed.
+ */
+ int neededbits = 8 + ((-pending.getBitCount()) & 7);
+ while (neededbits > 0) {
+ /*
+ * write a static tree block consisting solely of an
+ * EOF:
+ */
+ pending.writeBits(2, 10);
+ neededbits -= 10;
+ }
+ }
+ state = BUSY_STATE;
+ } else if (state == FINISHING_STATE) {
+ pending.alignToByte();
+ /* We have completed the stream */
+ if (!noHeader) {
+ final int adler = engine.getAdler();
+ pending.writeShortMSB(adler >> 16);
+ pending.writeShortMSB(adler & 0xffff);
+ }
+ state = FINISHED_STATE;
+ }
+ }
+ }
+
+ return origLength - length;
+ }
+
+ /**
+ * Sets the dictionary which should be used in the deflate process. This
+ * call is equivalent to setDictionary(dict, 0,
+ * dict.length)
.
+ *
+ * @param dict
+ * the dictionary.
+ * @exception IllegalStateException
+ * if setInput () or deflate () were already called or
+ * another dictionary was already set.
+ */
+ public void setDictionary(final byte[] dict) {
+ setDictionary(dict, 0, dict.length);
+ }
+
+ /**
+ * Sets the dictionary which should be used in the deflate process. The
+ * dictionary should be a byte array containing strings that are likely to
+ * occur in the data which should be compressed. The dictionary is not
+ * stored in the compressed output, only a checksum. To decompress the
+ * output you need to supply the same dictionary again.
+ *
+ * @param dict
+ * the dictionary.
+ * @param offset
+ * an offset into the dictionary.
+ * @param length
+ * the length of the dictionary.
+ * @exception IllegalStateException
+ * if setInput () or deflate () were already called or
+ * another dictionary was already set.
+ */
+ public void setDictionary(final byte[] dict, final int offset,
+ final int length) {
+ if (state != INIT_STATE) {
+ throw new IllegalStateException();
+ }
+
+ state = SETDICT_STATE;
+ engine.setDictionary(dict, offset, length);
+ }
+}
diff --git a/epublib-core/src/main/java/net/sf/jazzlib/DeflaterConstants.java b/epublib-core/src/main/java/net/sf/jazzlib/DeflaterConstants.java
new file mode 100644
index 00000000..b3985f99
--- /dev/null
+++ b/epublib-core/src/main/java/net/sf/jazzlib/DeflaterConstants.java
@@ -0,0 +1,77 @@
+/* net.sf.jazzlib.DeflaterConstants
+ Copyright (C) 2001 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath 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 2, or (at your option)
+any later version.
+
+GNU Classpath 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 GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+02111-1307 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+package net.sf.jazzlib;
+
+interface DeflaterConstants {
+ final static boolean DEBUGGING = false;
+
+ final static int STORED_BLOCK = 0;
+ final static int STATIC_TREES = 1;
+ final static int DYN_TREES = 2;
+ final static int PRESET_DICT = 0x20;
+
+ final static int DEFAULT_MEM_LEVEL = 8;
+
+ final static int MAX_MATCH = 258;
+ final static int MIN_MATCH = 3;
+
+ final static int MAX_WBITS = 15;
+ final static int WSIZE = 1 << MAX_WBITS;
+ final static int WMASK = WSIZE - 1;
+
+ final static int HASH_BITS = DEFAULT_MEM_LEVEL + 7;
+ final static int HASH_SIZE = 1 << HASH_BITS;
+ final static int HASH_MASK = HASH_SIZE - 1;
+ final static int HASH_SHIFT = ((HASH_BITS + MIN_MATCH) - 1) / MIN_MATCH;
+
+ final static int MIN_LOOKAHEAD = MAX_MATCH + MIN_MATCH + 1;
+ final static int MAX_DIST = WSIZE - MIN_LOOKAHEAD;
+
+ final static int PENDING_BUF_SIZE = 1 << (DEFAULT_MEM_LEVEL + 8);
+ final static int MAX_BLOCK_SIZE = Math.min(65535, PENDING_BUF_SIZE - 5);
+
+ final static int DEFLATE_STORED = 0;
+ final static int DEFLATE_FAST = 1;
+ final static int DEFLATE_SLOW = 2;
+
+ final static int GOOD_LENGTH[] = { 0, 4, 4, 4, 4, 8, 8, 8, 32, 32 };
+ final static int MAX_LAZY[] = { 0, 4, 5, 6, 4, 16, 16, 32, 128, 258 };
+ final static int NICE_LENGTH[] = { 0, 8, 16, 32, 16, 32, 128, 128, 258, 258 };
+ final static int MAX_CHAIN[] = { 0, 4, 8, 32, 16, 32, 128, 256, 1024, 4096 };
+ final static int COMPR_FUNC[] = { 0, 1, 1, 1, 1, 2, 2, 2, 2, 2 };
+}
diff --git a/epublib-core/src/main/java/net/sf/jazzlib/DeflaterEngine.java b/epublib-core/src/main/java/net/sf/jazzlib/DeflaterEngine.java
new file mode 100644
index 00000000..814d0c32
--- /dev/null
+++ b/epublib-core/src/main/java/net/sf/jazzlib/DeflaterEngine.java
@@ -0,0 +1,674 @@
+/* net.sf.jazzlib.DeflaterEngine
+ Copyright (C) 2001 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath 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 2, or (at your option)
+any later version.
+
+GNU Classpath 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 GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+02111-1307 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+package net.sf.jazzlib;
+
+class DeflaterEngine implements DeflaterConstants {
+ private final static int TOO_FAR = 4096;
+
+ private int ins_h;
+
+ /**
+ * Hashtable, hashing three characters to an index for window, so that
+ * window[index]..window[index+2] have this hash code. Note that the array
+ * should really be unsigned short, so you need to and the values with
+ * 0xffff.
+ */
+ private final short[] head;
+
+ /**
+ * prev[index & WMASK] points to the previous index that has the same hash
+ * code as the string starting at index. This way entries with the same hash
+ * code are in a linked list. Note that the array should really be unsigned
+ * short, so you need to and the values with 0xffff.
+ */
+ private final short[] prev;
+
+ private int matchStart, matchLen;
+ private boolean prevAvailable;
+ private int blockStart;
+
+ /**
+ * strstart points to the current character in window.
+ */
+ private int strstart;
+
+ /**
+ * lookahead is the number of characters starting at strstart in window that
+ * are valid. So window[strstart] until window[strstart+lookahead-1] are
+ * valid characters.
+ */
+ private int lookahead;
+
+ /**
+ * This array contains the part of the uncompressed stream that is of
+ * relevance. The current character is indexed by strstart.
+ */
+ private final byte[] window;
+
+ private int strategy, max_chain, max_lazy, niceLength, goodLength;
+
+ /** The current compression function. */
+ private int comprFunc;
+
+ /** The input data for compression. */
+ private byte[] inputBuf;
+
+ /** The total bytes of input read. */
+ private int totalIn;
+
+ /** The offset into inputBuf, where input data starts. */
+ private int inputOff;
+
+ /** The end offset of the input data. */
+ private int inputEnd;
+
+ private final DeflaterPending pending;
+ private final DeflaterHuffman huffman;
+
+ /** The adler checksum */
+ private final Adler32 adler;
+
+ /*
+ * DEFLATE ALGORITHM:
+ *
+ * The uncompressed stream is inserted into the window array. When the
+ * window array is full the first half is thrown away and the second half is
+ * copied to the beginning.
+ *
+ * The head array is a hash table. Three characters build a hash value and
+ * they the value points to the corresponding index in window of the last
+ * string with this hash. The prev array implements a linked list of matches
+ * with the same hash: prev[index & WMASK] points to the previous index with
+ * the same hash.
+ */
+
+ DeflaterEngine(final DeflaterPending pending) {
+ this.pending = pending;
+ huffman = new DeflaterHuffman(pending);
+ adler = new Adler32();
+
+ window = new byte[2 * WSIZE];
+ head = new short[HASH_SIZE];
+ prev = new short[WSIZE];
+
+ /*
+ * We start at index 1, to avoid a implementation deficiency, that we
+ * cannot build a repeat pattern at index 0.
+ */
+ blockStart = strstart = 1;
+ }
+
+ public void reset() {
+ huffman.reset();
+ adler.reset();
+ blockStart = strstart = 1;
+ lookahead = 0;
+ totalIn = 0;
+ prevAvailable = false;
+ matchLen = MIN_MATCH - 1;
+ for (int i = 0; i < HASH_SIZE; i++) {
+ head[i] = 0;
+ }
+ for (int i = 0; i < WSIZE; i++) {
+ prev[i] = 0;
+ }
+ }
+
+ public final void resetAdler() {
+ adler.reset();
+ }
+
+ public final int getAdler() {
+ final int chksum = (int) adler.getValue();
+ return chksum;
+ }
+
+ public final int getTotalIn() {
+ return totalIn;
+ }
+
+ public final void setStrategy(final int strat) {
+ strategy = strat;
+ }
+
+ public void setLevel(final int lvl) {
+ goodLength = DeflaterConstants.GOOD_LENGTH[lvl];
+ max_lazy = DeflaterConstants.MAX_LAZY[lvl];
+ niceLength = DeflaterConstants.NICE_LENGTH[lvl];
+ max_chain = DeflaterConstants.MAX_CHAIN[lvl];
+
+ if (DeflaterConstants.COMPR_FUNC[lvl] != comprFunc) {
+ if (DeflaterConstants.DEBUGGING) {
+ System.err.println("Change from " + comprFunc + " to "
+ + DeflaterConstants.COMPR_FUNC[lvl]);
+ }
+ switch (comprFunc) {
+ case DEFLATE_STORED:
+ if (strstart > blockStart) {
+ huffman.flushStoredBlock(window, blockStart, strstart
+ - blockStart, false);
+ blockStart = strstart;
+ }
+ updateHash();
+ break;
+ case DEFLATE_FAST:
+ if (strstart > blockStart) {
+ huffman.flushBlock(window, blockStart, strstart
+ - blockStart, false);
+ blockStart = strstart;
+ }
+ break;
+ case DEFLATE_SLOW:
+ if (prevAvailable) {
+ huffman.tallyLit(window[strstart - 1] & 0xff);
+ }
+ if (strstart > blockStart) {
+ huffman.flushBlock(window, blockStart, strstart
+ - blockStart, false);
+ blockStart = strstart;
+ }
+ prevAvailable = false;
+ matchLen = MIN_MATCH - 1;
+ break;
+ }
+ comprFunc = COMPR_FUNC[lvl];
+ }
+ }
+
+ private final void updateHash() {
+ if (DEBUGGING) {
+ System.err.println("updateHash: " + strstart);
+ }
+ ins_h = (window[strstart] << HASH_SHIFT) ^ window[strstart + 1];
+ }
+
+ /**
+ * Inserts the current string in the head hash and returns the previous
+ * value for this hash.
+ */
+ private final int insertString() {
+ short match;
+ final int hash = ((ins_h << HASH_SHIFT) ^ window[strstart
+ + (MIN_MATCH - 1)])
+ & HASH_MASK;
+
+ if (DEBUGGING) {
+ if (hash != (((window[strstart] << (2 * HASH_SHIFT))
+ ^ (window[strstart + 1] << HASH_SHIFT) ^ (window[strstart + 2])) & HASH_MASK)) {
+ throw new InternalError("hash inconsistent: " + hash + "/"
+ + window[strstart] + "," + window[strstart + 1] + ","
+ + window[strstart + 2] + "," + HASH_SHIFT);
+ }
+ }
+
+ prev[strstart & WMASK] = match = head[hash];
+ head[hash] = (short) strstart;
+ ins_h = hash;
+ return match & 0xffff;
+ }
+
+ private void slideWindow() {
+ System.arraycopy(window, WSIZE, window, 0, WSIZE);
+ matchStart -= WSIZE;
+ strstart -= WSIZE;
+ blockStart -= WSIZE;
+
+ /*
+ * Slide the hash table (could be avoided with 32 bit values at the
+ * expense of memory usage).
+ */
+ for (int i = 0; i < HASH_SIZE; i++) {
+ final int m = head[i] & 0xffff;
+ head[i] = m >= WSIZE ? (short) (m - WSIZE) : 0;
+ }
+
+ /*
+ * Slide the prev table.
+ */
+ for (int i = 0; i < WSIZE; i++) {
+ final int m = prev[i] & 0xffff;
+ prev[i] = m >= WSIZE ? (short) (m - WSIZE) : 0;
+ }
+ }
+
+ /**
+ * Fill the window when the lookahead becomes insufficient. Updates strstart
+ * and lookahead.
+ *
+ * OUT assertions: strstart + lookahead <= 2*WSIZE lookahead >=
+ * MIN_LOOKAHEAD or inputOff == inputEnd
+ */
+ private void fillWindow() {
+ /*
+ * If the window is almost full and there is insufficient lookahead,
+ * move the upper half to the lower one to make room in the upper half.
+ */
+ if (strstart >= (WSIZE + MAX_DIST)) {
+ slideWindow();
+ }
+
+ /*
+ * If there is not enough lookahead, but still some input left, read in
+ * the input
+ */
+ while ((lookahead < DeflaterConstants.MIN_LOOKAHEAD)
+ && (inputOff < inputEnd)) {
+ int more = (2 * WSIZE) - lookahead - strstart;
+
+ if (more > (inputEnd - inputOff)) {
+ more = inputEnd - inputOff;
+ }
+
+ System.arraycopy(inputBuf, inputOff, window, strstart + lookahead,
+ more);
+ adler.update(inputBuf, inputOff, more);
+ inputOff += more;
+ totalIn += more;
+ lookahead += more;
+ }
+
+ if (lookahead >= MIN_MATCH) {
+ updateHash();
+ }
+ }
+
+ /**
+ * Find the best (longest) string in the window matching the string starting
+ * at strstart.
+ *
+ * Preconditions: strstart + MAX_MATCH <= window.length.
+ *
+ *
+ * @param curMatch
+ */
+ private boolean findLongestMatch(int curMatch) {
+ int chainLength = this.max_chain;
+ int niceLength = this.niceLength;
+ final short[] prev = this.prev;
+ int scan = this.strstart;
+ int match;
+ int best_end = this.strstart + matchLen;
+ int best_len = Math.max(matchLen, MIN_MATCH - 1);
+
+ final int limit = Math.max(strstart - MAX_DIST, 0);
+
+ final int strend = (scan + MAX_MATCH) - 1;
+ byte scan_end1 = window[best_end - 1];
+ byte scan_end = window[best_end];
+
+ /* Do not waste too much time if we already have a good match: */
+ if (best_len >= this.goodLength) {
+ chainLength >>= 2;
+ }
+
+ /*
+ * Do not look for matches beyond the end of the input. This is
+ * necessary to make deflate deterministic.
+ */
+ if (niceLength > lookahead) {
+ niceLength = lookahead;
+ }
+
+ if (DeflaterConstants.DEBUGGING
+ && (strstart > ((2 * WSIZE) - MIN_LOOKAHEAD))) {
+ throw new InternalError("need lookahead");
+ }
+
+ do {
+ if (DeflaterConstants.DEBUGGING && (curMatch >= strstart)) {
+ throw new InternalError("future match");
+ }
+ if ((window[curMatch + best_len] != scan_end)
+ || (window[(curMatch + best_len) - 1] != scan_end1)
+ || (window[curMatch] != window[scan])
+ || (window[curMatch + 1] != window[scan + 1])) {
+ continue;
+ }
+
+ match = curMatch + 2;
+ scan += 2;
+
+ /*
+ * We check for insufficient lookahead only every 8th comparison;
+ * the 256th check will be made at strstart+258.
+ */
+ while ((window[++scan] == window[++match])
+ && (window[++scan] == window[++match])
+ && (window[++scan] == window[++match])
+ && (window[++scan] == window[++match])
+ && (window[++scan] == window[++match])
+ && (window[++scan] == window[++match])
+ && (window[++scan] == window[++match])
+ && (window[++scan] == window[++match]) && (scan < strend)) {
+ ;
+ }
+
+ if (scan > best_end) {
+ // if (DeflaterConstants.DEBUGGING && ins_h == 0)
+ // System.err.println("Found match: "+curMatch+"-"+(scan-strstart));
+ matchStart = curMatch;
+ best_end = scan;
+ best_len = scan - strstart;
+ if (best_len >= niceLength) {
+ break;
+ }
+
+ scan_end1 = window[best_end - 1];
+ scan_end = window[best_end];
+ }
+ scan = strstart;
+ } while (((curMatch = (prev[curMatch & WMASK] & 0xffff)) > limit)
+ && (--chainLength != 0));
+
+ matchLen = Math.min(best_len, lookahead);
+ return matchLen >= MIN_MATCH;
+ }
+
+ void setDictionary(final byte[] buffer, int offset, int length) {
+ if (DeflaterConstants.DEBUGGING && (strstart != 1)) {
+ throw new IllegalStateException("strstart not 1");
+ }
+ adler.update(buffer, offset, length);
+ if (length < MIN_MATCH) {
+ return;
+ }
+ if (length > MAX_DIST) {
+ offset += length - MAX_DIST;
+ length = MAX_DIST;
+ }
+
+ System.arraycopy(buffer, offset, window, strstart, length);
+
+ updateHash();
+ length--;
+ while (--length > 0) {
+ insertString();
+ strstart++;
+ }
+ strstart += 2;
+ blockStart = strstart;
+ }
+
+ private boolean deflateStored(final boolean flush, final boolean finish) {
+ if (!flush && (lookahead == 0)) {
+ return false;
+ }
+
+ strstart += lookahead;
+ lookahead = 0;
+
+ int storedLen = strstart - blockStart;
+
+ if ((storedLen >= DeflaterConstants.MAX_BLOCK_SIZE)
+ /* Block is full */
+ || ((blockStart < WSIZE) && (storedLen >= MAX_DIST))
+ /* Block may move out of window */
+ || flush) {
+ boolean lastBlock = finish;
+ if (storedLen > DeflaterConstants.MAX_BLOCK_SIZE) {
+ storedLen = DeflaterConstants.MAX_BLOCK_SIZE;
+ lastBlock = false;
+ }
+
+ if (DeflaterConstants.DEBUGGING) {
+ System.err.println("storedBlock[" + storedLen + "," + lastBlock
+ + "]");
+ }
+
+ huffman.flushStoredBlock(window, blockStart, storedLen, lastBlock);
+ blockStart += storedLen;
+ return !lastBlock;
+ }
+ return true;
+ }
+
+ private boolean deflateFast(final boolean flush, final boolean finish) {
+ if ((lookahead < MIN_LOOKAHEAD) && !flush) {
+ return false;
+ }
+
+ while ((lookahead >= MIN_LOOKAHEAD) || flush) {
+ if (lookahead == 0) {
+ /* We are flushing everything */
+ huffman.flushBlock(window, blockStart, strstart - blockStart,
+ finish);
+ blockStart = strstart;
+ return false;
+ }
+
+ if (strstart > ((2 * WSIZE) - MIN_LOOKAHEAD)) {
+ /*
+ * slide window, as findLongestMatch need this. This should only
+ * happen when flushing and the window is almost full.
+ */
+ slideWindow();
+ }
+
+ int hashHead;
+ if ((lookahead >= MIN_MATCH) && ((hashHead = insertString()) != 0)
+ && (strategy != Deflater.HUFFMAN_ONLY)
+ && ((strstart - hashHead) <= MAX_DIST)
+ && findLongestMatch(hashHead)) {
+ /* longestMatch sets matchStart and matchLen */
+ if (DeflaterConstants.DEBUGGING) {
+ for (int i = 0; i < matchLen; i++) {
+ if (window[strstart + i] != window[matchStart + i]) {
+ throw new InternalError();
+ }
+ }
+ }
+ huffman.tallyDist(strstart - matchStart, matchLen);
+
+ lookahead -= matchLen;
+ if ((matchLen <= max_lazy) && (lookahead >= MIN_MATCH)) {
+ while (--matchLen > 0) {
+ strstart++;
+ insertString();
+ }
+ strstart++;
+ } else {
+ strstart += matchLen;
+ if (lookahead >= (MIN_MATCH - 1)) {
+ updateHash();
+ }
+ }
+ matchLen = MIN_MATCH - 1;
+ continue;
+ } else {
+ /* No match found */
+ huffman.tallyLit(window[strstart] & 0xff);
+ strstart++;
+ lookahead--;
+ }
+
+ if (huffman.isFull()) {
+ final boolean lastBlock = finish && (lookahead == 0);
+ huffman.flushBlock(window, blockStart, strstart - blockStart,
+ lastBlock);
+ blockStart = strstart;
+ return !lastBlock;
+ }
+ }
+ return true;
+ }
+
+ private boolean deflateSlow(final boolean flush, final boolean finish) {
+ if ((lookahead < MIN_LOOKAHEAD) && !flush) {
+ return false;
+ }
+
+ while ((lookahead >= MIN_LOOKAHEAD) || flush) {
+ if (lookahead == 0) {
+ if (prevAvailable) {
+ huffman.tallyLit(window[strstart - 1] & 0xff);
+ }
+ prevAvailable = false;
+
+ /* We are flushing everything */
+ if (DeflaterConstants.DEBUGGING && !flush) {
+ throw new InternalError("Not flushing, but no lookahead");
+ }
+ huffman.flushBlock(window, blockStart, strstart - blockStart,
+ finish);
+ blockStart = strstart;
+ return false;
+ }
+
+ if (strstart >= ((2 * WSIZE) - MIN_LOOKAHEAD)) {
+ /*
+ * slide window, as findLongestMatch need this. This should only
+ * happen when flushing and the window is almost full.
+ */
+ slideWindow();
+ }
+
+ final int prevMatch = matchStart;
+ int prevLen = matchLen;
+ if (lookahead >= MIN_MATCH) {
+ final int hashHead = insertString();
+ if ((strategy != Deflater.HUFFMAN_ONLY) && (hashHead != 0)
+ && ((strstart - hashHead) <= MAX_DIST)
+ && findLongestMatch(hashHead)) {
+ /* longestMatch sets matchStart and matchLen */
+
+ /* Discard match if too small and too far away */
+ if ((matchLen <= 5)
+ && ((strategy == Deflater.FILTERED) || ((matchLen == MIN_MATCH) && ((strstart - matchStart) > TOO_FAR)))) {
+ matchLen = MIN_MATCH - 1;
+ }
+ }
+ }
+
+ /* previous match was better */
+ if ((prevLen >= MIN_MATCH) && (matchLen <= prevLen)) {
+ if (DeflaterConstants.DEBUGGING) {
+ for (int i = 0; i < matchLen; i++) {
+ if (window[(strstart - 1) + i] != window[prevMatch + i]) {
+ throw new InternalError();
+ }
+ }
+ }
+ huffman.tallyDist(strstart - 1 - prevMatch, prevLen);
+ prevLen -= 2;
+ do {
+ strstart++;
+ lookahead--;
+ if (lookahead >= MIN_MATCH) {
+ insertString();
+ }
+ } while (--prevLen > 0);
+ strstart++;
+ lookahead--;
+ prevAvailable = false;
+ matchLen = MIN_MATCH - 1;
+ } else {
+ if (prevAvailable) {
+ huffman.tallyLit(window[strstart - 1] & 0xff);
+ }
+ prevAvailable = true;
+ strstart++;
+ lookahead--;
+ }
+
+ if (huffman.isFull()) {
+ int len = strstart - blockStart;
+ if (prevAvailable) {
+ len--;
+ }
+ final boolean lastBlock = (finish && (lookahead == 0) && !prevAvailable);
+ huffman.flushBlock(window, blockStart, len, lastBlock);
+ blockStart += len;
+ return !lastBlock;
+ }
+ }
+ return true;
+ }
+
+ public boolean deflate(final boolean flush, final boolean finish) {
+ boolean progress;
+ do {
+ fillWindow();
+ final boolean canFlush = flush && (inputOff == inputEnd);
+ if (DeflaterConstants.DEBUGGING) {
+ System.err.println("window: [" + blockStart + "," + strstart
+ + "," + lookahead + "], " + comprFunc + "," + canFlush);
+ }
+ switch (comprFunc) {
+ case DEFLATE_STORED:
+ progress = deflateStored(canFlush, finish);
+ break;
+ case DEFLATE_FAST:
+ progress = deflateFast(canFlush, finish);
+ break;
+ case DEFLATE_SLOW:
+ progress = deflateSlow(canFlush, finish);
+ break;
+ default:
+ throw new InternalError();
+ }
+ } while (pending.isFlushed() /* repeat while we have no pending output */
+ && progress); /* and progress was made */
+
+ return progress;
+ }
+
+ public void setInput(final byte[] buf, final int off, final int len) {
+ if (inputOff < inputEnd) {
+ throw new IllegalStateException(
+ "Old input was not completely processed");
+ }
+
+ final int end = off + len;
+
+ /*
+ * We want to throw an ArrayIndexOutOfBoundsException early. The check
+ * is very tricky: it also handles integer wrap around.
+ */
+ if ((0 > off) || (off > end) || (end > buf.length)) {
+ throw new ArrayIndexOutOfBoundsException();
+ }
+
+ inputBuf = buf;
+ inputOff = off;
+ inputEnd = end;
+ }
+
+ public final boolean needsInput() {
+ return inputEnd == inputOff;
+ }
+}
diff --git a/epublib-core/src/main/java/net/sf/jazzlib/DeflaterHuffman.java b/epublib-core/src/main/java/net/sf/jazzlib/DeflaterHuffman.java
new file mode 100644
index 00000000..75913ac6
--- /dev/null
+++ b/epublib-core/src/main/java/net/sf/jazzlib/DeflaterHuffman.java
@@ -0,0 +1,748 @@
+/* net.sf.jazzlib.DeflaterHuffman
+ Copyright (C) 2001 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath 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 2, or (at your option)
+any later version.
+
+GNU Classpath 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 GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+02111-1307 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+package net.sf.jazzlib;
+
+/**
+ * This is the DeflaterHuffman class.
+ *
+ * This class is not thread safe. This is inherent in the API, due to the
+ * split of deflate and setInput.
+ *
+ * @author Jochen Hoenicke
+ * @date Jan 6, 2000
+ */
+class DeflaterHuffman {
+ private static final int BUFSIZE = 1 << (DeflaterConstants.DEFAULT_MEM_LEVEL + 6);
+ private static final int LITERAL_NUM = 286;
+ private static final int DIST_NUM = 30;
+ private static final int BITLEN_NUM = 19;
+ private static final int REP_3_6 = 16;
+ private static final int REP_3_10 = 17;
+ private static final int REP_11_138 = 18;
+ private static final int EOF_SYMBOL = 256;
+ private static final int[] BL_ORDER = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5,
+ 11, 4, 12, 3, 13, 2, 14, 1, 15 };
+
+ private final static String bit4Reverse = "\000\010\004\014\002\012\006\016\001\011\005\015\003\013\007\017";
+
+ class Tree {
+ short[] freqs;
+ short[] codes;
+ byte[] length;
+ int[] bl_counts;
+ int minNumCodes, numCodes;
+ int maxLength;
+
+ Tree(final int elems, final int minCodes, final int maxLength) {
+ this.minNumCodes = minCodes;
+ this.maxLength = maxLength;
+ freqs = new short[elems];
+ bl_counts = new int[maxLength];
+ }
+
+ void reset() {
+ for (int i = 0; i < freqs.length; i++) {
+ freqs[i] = 0;
+ }
+ codes = null;
+ length = null;
+ }
+
+ final void writeSymbol(final int code) {
+ if (DeflaterConstants.DEBUGGING) {
+ freqs[code]--;
+ // System.err.print("writeSymbol("+freqs.length+","+code+"): ");
+ }
+ pending.writeBits(codes[code] & 0xffff, length[code]);
+ }
+
+ final void checkEmpty() {
+ boolean empty = true;
+ for (int i = 0; i < freqs.length; i++) {
+ if (freqs[i] != 0) {
+ System.err.println("freqs[" + i + "] == " + freqs[i]);
+ empty = false;
+ }
+ }
+ if (!empty) {
+ throw new InternalError();
+ }
+ System.err.println("checkEmpty suceeded!");
+ }
+
+ void setStaticCodes(final short[] stCodes, final byte[] stLength) {
+ codes = stCodes;
+ length = stLength;
+ }
+
+ public void buildCodes() {
+ final int[] nextCode = new int[maxLength];
+ int code = 0;
+ codes = new short[freqs.length];
+
+ if (DeflaterConstants.DEBUGGING) {
+ System.err.println("buildCodes: " + freqs.length);
+ }
+ for (int bits = 0; bits < maxLength; bits++) {
+ nextCode[bits] = code;
+ code += bl_counts[bits] << (15 - bits);
+ if (DeflaterConstants.DEBUGGING) {
+ System.err.println("bits: " + (bits + 1) + " count: "
+ + bl_counts[bits] + " nextCode: "
+ + Integer.toHexString(code));
+ }
+ }
+ if (DeflaterConstants.DEBUGGING && (code != 65536)) {
+ throw new RuntimeException("Inconsistent bl_counts!");
+ }
+
+ for (int i = 0; i < numCodes; i++) {
+ final int bits = length[i];
+ if (bits > 0) {
+ if (DeflaterConstants.DEBUGGING) {
+ System.err.println("codes[" + i + "] = rev("
+ + Integer.toHexString(nextCode[bits - 1])
+ + ")," + bits);
+ }
+ codes[i] = bitReverse(nextCode[bits - 1]);
+ nextCode[bits - 1] += 1 << (16 - bits);
+ }
+ }
+ }
+
+ private void buildLength(final int childs[]) {
+ this.length = new byte[freqs.length];
+ final int numNodes = childs.length / 2;
+ final int numLeafs = (numNodes + 1) / 2;
+ int overflow = 0;
+
+ for (int i = 0; i < maxLength; i++) {
+ bl_counts[i] = 0;
+ }
+
+ /* First calculate optimal bit lengths */
+ final int lengths[] = new int[numNodes];
+ lengths[numNodes - 1] = 0;
+ for (int i = numNodes - 1; i >= 0; i--) {
+ if (childs[(2 * i) + 1] != -1) {
+ int bitLength = lengths[i] + 1;
+ if (bitLength > maxLength) {
+ bitLength = maxLength;
+ overflow++;
+ }
+ lengths[childs[2 * i]] = lengths[childs[(2 * i) + 1]] = bitLength;
+ } else {
+ /* A leaf node */
+ final int bitLength = lengths[i];
+ bl_counts[bitLength - 1]++;
+ this.length[childs[2 * i]] = (byte) lengths[i];
+ }
+ }
+
+ if (DeflaterConstants.DEBUGGING) {
+ System.err.println("Tree " + freqs.length + " lengths:");
+ for (int i = 0; i < numLeafs; i++) {
+ System.err.println("Node " + childs[2 * i] + " freq: "
+ + freqs[childs[2 * i]] + " len: "
+ + length[childs[2 * i]]);
+ }
+ }
+
+ if (overflow == 0) {
+ return;
+ }
+
+ int incrBitLen = maxLength - 1;
+ do {
+ /* Find the first bit length which could increase: */
+ while (bl_counts[--incrBitLen] == 0) {
+ ;
+ }
+
+ /*
+ * Move this node one down and remove a corresponding amount of
+ * overflow nodes.
+ */
+ do {
+ bl_counts[incrBitLen]--;
+ bl_counts[++incrBitLen]++;
+ overflow -= 1 << (maxLength - 1 - incrBitLen);
+ } while ((overflow > 0) && (incrBitLen < (maxLength - 1)));
+ } while (overflow > 0);
+
+ /*
+ * We may have overshot above. Move some nodes from maxLength to
+ * maxLength-1 in that case.
+ */
+ bl_counts[maxLength - 1] += overflow;
+ bl_counts[maxLength - 2] -= overflow;
+
+ /*
+ * Now recompute all bit lengths, scanning in increasing frequency.
+ * It is simpler to reconstruct all lengths instead of fixing only
+ * the wrong ones. This idea is taken from 'ar' written by Haruhiko
+ * Okumura.
+ *
+ * The nodes were inserted with decreasing frequency into the childs
+ * array.
+ */
+ int nodePtr = 2 * numLeafs;
+ for (int bits = maxLength; bits != 0; bits--) {
+ int n = bl_counts[bits - 1];
+ while (n > 0) {
+ final int childPtr = 2 * childs[nodePtr++];
+ if (childs[childPtr + 1] == -1) {
+ /* We found another leaf */
+ length[childs[childPtr]] = (byte) bits;
+ n--;
+ }
+ }
+ }
+ if (DeflaterConstants.DEBUGGING) {
+ System.err.println("*** After overflow elimination. ***");
+ for (int i = 0; i < numLeafs; i++) {
+ System.err.println("Node " + childs[2 * i] + " freq: "
+ + freqs[childs[2 * i]] + " len: "
+ + length[childs[2 * i]]);
+ }
+ }
+ }
+
+ void buildTree() {
+ final int numSymbols = freqs.length;
+
+ /*
+ * heap is a priority queue, sorted by frequency, least frequent
+ * nodes first. The heap is a binary tree, with the property, that
+ * the parent node is smaller than both child nodes. This assures
+ * that the smallest node is the first parent.
+ *
+ * The binary tree is encoded in an array: 0 is root node and the
+ * nodes 2*n+1, 2*n+2 are the child nodes of node n.
+ */
+ final int[] heap = new int[numSymbols];
+ int heapLen = 0;
+ int maxCode = 0;
+ for (int n = 0; n < numSymbols; n++) {
+ final int freq = freqs[n];
+ if (freq != 0) {
+ /* Insert n into heap */
+ int pos = heapLen++;
+ int ppos;
+ while ((pos > 0)
+ && (freqs[heap[ppos = (pos - 1) / 2]] > freq)) {
+ heap[pos] = heap[ppos];
+ pos = ppos;
+ }
+ heap[pos] = n;
+ maxCode = n;
+ }
+ }
+
+ /*
+ * We could encode a single literal with 0 bits but then we don't
+ * see the literals. Therefore we force at least two literals to
+ * avoid this case. We don't care about order in this case, both
+ * literals get a 1 bit code.
+ */
+ while (heapLen < 2) {
+ final int node = maxCode < 2 ? ++maxCode : 0;
+ heap[heapLen++] = node;
+ }
+
+ numCodes = Math.max(maxCode + 1, minNumCodes);
+
+ final int numLeafs = heapLen;
+ final int[] childs = new int[(4 * heapLen) - 2];
+ final int[] values = new int[(2 * heapLen) - 1];
+ int numNodes = numLeafs;
+ for (int i = 0; i < heapLen; i++) {
+ final int node = heap[i];
+ childs[2 * i] = node;
+ childs[(2 * i) + 1] = -1;
+ values[i] = freqs[node] << 8;
+ heap[i] = i;
+ }
+
+ /*
+ * Construct the Huffman tree by repeatedly combining the least two
+ * frequent nodes.
+ */
+ do {
+ final int first = heap[0];
+ int last = heap[--heapLen];
+
+ /* Propagate the hole to the leafs of the heap */
+ int ppos = 0;
+ int path = 1;
+ while (path < heapLen) {
+ if (((path + 1) < heapLen)
+ && (values[heap[path]] > values[heap[path + 1]])) {
+ path++;
+ }
+
+ heap[ppos] = heap[path];
+ ppos = path;
+ path = (path * 2) + 1;
+ }
+
+ /*
+ * Now propagate the last element down along path. Normally it
+ * shouldn't go too deep.
+ */
+ int lastVal = values[last];
+ while (((path = ppos) > 0)
+ && (values[heap[ppos = (path - 1) / 2]] > lastVal)) {
+ heap[path] = heap[ppos];
+ }
+ heap[path] = last;
+
+ final int second = heap[0];
+
+ /* Create a new node father of first and second */
+ last = numNodes++;
+ childs[2 * last] = first;
+ childs[(2 * last) + 1] = second;
+ final int mindepth = Math.min(values[first] & 0xff,
+ values[second] & 0xff);
+ values[last] = lastVal = ((values[first] + values[second]) - mindepth) + 1;
+
+ /* Again, propagate the hole to the leafs */
+ ppos = 0;
+ path = 1;
+ while (path < heapLen) {
+ if (((path + 1) < heapLen)
+ && (values[heap[path]] > values[heap[path + 1]])) {
+ path++;
+ }
+
+ heap[ppos] = heap[path];
+ ppos = path;
+ path = (ppos * 2) + 1;
+ }
+
+ /* Now propagate the new element down along path */
+ while (((path = ppos) > 0)
+ && (values[heap[ppos = (path - 1) / 2]] > lastVal)) {
+ heap[path] = heap[ppos];
+ }
+ heap[path] = last;
+ } while (heapLen > 1);
+
+ if (heap[0] != ((childs.length / 2) - 1)) {
+ throw new RuntimeException("Weird!");
+ }
+
+ buildLength(childs);
+ }
+
+ int getEncodedLength() {
+ int len = 0;
+ for (int i = 0; i < freqs.length; i++) {
+ len += freqs[i] * length[i];
+ }
+ return len;
+ }
+
+ void calcBLFreq(final Tree blTree) {
+ int max_count; /* max repeat count */
+ int min_count; /* min repeat count */
+ int count; /* repeat count of the current code */
+ int curlen = -1; /* length of current code */
+
+ int i = 0;
+ while (i < numCodes) {
+ count = 1;
+ final int nextlen = length[i];
+ if (nextlen == 0) {
+ max_count = 138;
+ min_count = 3;
+ } else {
+ max_count = 6;
+ min_count = 3;
+ if (curlen != nextlen) {
+ blTree.freqs[nextlen]++;
+ count = 0;
+ }
+ }
+ curlen = nextlen;
+ i++;
+
+ while ((i < numCodes) && (curlen == length[i])) {
+ i++;
+ if (++count >= max_count) {
+ break;
+ }
+ }
+
+ if (count < min_count) {
+ blTree.freqs[curlen] += count;
+ } else if (curlen != 0) {
+ blTree.freqs[REP_3_6]++;
+ } else if (count <= 10) {
+ blTree.freqs[REP_3_10]++;
+ } else {
+ blTree.freqs[REP_11_138]++;
+ }
+ }
+ }
+
+ void writeTree(final Tree blTree) {
+ int max_count; /* max repeat count */
+ int min_count; /* min repeat count */
+ int count; /* repeat count of the current code */
+ int curlen = -1; /* length of current code */
+
+ int i = 0;
+ while (i < numCodes) {
+ count = 1;
+ final int nextlen = length[i];
+ if (nextlen == 0) {
+ max_count = 138;
+ min_count = 3;
+ } else {
+ max_count = 6;
+ min_count = 3;
+ if (curlen != nextlen) {
+ blTree.writeSymbol(nextlen);
+ count = 0;
+ }
+ }
+ curlen = nextlen;
+ i++;
+
+ while ((i < numCodes) && (curlen == length[i])) {
+ i++;
+ if (++count >= max_count) {
+ break;
+ }
+ }
+
+ if (count < min_count) {
+ while (count-- > 0) {
+ blTree.writeSymbol(curlen);
+ }
+ } else if (curlen != 0) {
+ blTree.writeSymbol(REP_3_6);
+ pending.writeBits(count - 3, 2);
+ } else if (count <= 10) {
+ blTree.writeSymbol(REP_3_10);
+ pending.writeBits(count - 3, 3);
+ } else {
+ blTree.writeSymbol(REP_11_138);
+ pending.writeBits(count - 11, 7);
+ }
+ }
+ }
+ }
+
+ DeflaterPending pending;
+ private final Tree literalTree, distTree, blTree;
+
+ private final short d_buf[];
+ private final byte l_buf[];
+ private int last_lit;
+ private int extra_bits;
+
+ private static short staticLCodes[];
+ private static byte staticLLength[];
+ private static short staticDCodes[];
+ private static byte staticDLength[];
+
+ /**
+ * Reverse the bits of a 16 bit value.
+ */
+ static short bitReverse(final int value) {
+ return (short) ((bit4Reverse.charAt(value & 0xf) << 12)
+ | (bit4Reverse.charAt((value >> 4) & 0xf) << 8)
+ | (bit4Reverse.charAt((value >> 8) & 0xf) << 4) | bit4Reverse
+ .charAt(value >> 12));
+ }
+
+ static {
+ /* See RFC 1951 3.2.6 */
+ /* Literal codes */
+ staticLCodes = new short[LITERAL_NUM];
+ staticLLength = new byte[LITERAL_NUM];
+ int i = 0;
+ while (i < 144) {
+ staticLCodes[i] = bitReverse((0x030 + i) << 8);
+ staticLLength[i++] = 8;
+ }
+ while (i < 256) {
+ staticLCodes[i] = bitReverse(((0x190 - 144) + i) << 7);
+ staticLLength[i++] = 9;
+ }
+ while (i < 280) {
+ staticLCodes[i] = bitReverse(((0x000 - 256) + i) << 9);
+ staticLLength[i++] = 7;
+ }
+ while (i < LITERAL_NUM) {
+ staticLCodes[i] = bitReverse(((0x0c0 - 280) + i) << 8);
+ staticLLength[i++] = 8;
+ }
+
+ /* Distant codes */
+ staticDCodes = new short[DIST_NUM];
+ staticDLength = new byte[DIST_NUM];
+ for (i = 0; i < DIST_NUM; i++) {
+ staticDCodes[i] = bitReverse(i << 11);
+ staticDLength[i] = 5;
+ }
+ }
+
+ public DeflaterHuffman(final DeflaterPending pending) {
+ this.pending = pending;
+
+ literalTree = new Tree(LITERAL_NUM, 257, 15);
+ distTree = new Tree(DIST_NUM, 1, 15);
+ blTree = new Tree(BITLEN_NUM, 4, 7);
+
+ d_buf = new short[BUFSIZE];
+ l_buf = new byte[BUFSIZE];
+ }
+
+ public final void reset() {
+ last_lit = 0;
+ extra_bits = 0;
+ literalTree.reset();
+ distTree.reset();
+ blTree.reset();
+ }
+
+ private final int l_code(int len) {
+ if (len == 255) {
+ return 285;
+ }
+
+ int code = 257;
+ while (len >= 8) {
+ code += 4;
+ len >>= 1;
+ }
+ return code + len;
+ }
+
+ private final int d_code(int distance) {
+ int code = 0;
+ while (distance >= 4) {
+ code += 2;
+ distance >>= 1;
+ }
+ return code + distance;
+ }
+
+ public void sendAllTrees(final int blTreeCodes) {
+ blTree.buildCodes();
+ literalTree.buildCodes();
+ distTree.buildCodes();
+ pending.writeBits(literalTree.numCodes - 257, 5);
+ pending.writeBits(distTree.numCodes - 1, 5);
+ pending.writeBits(blTreeCodes - 4, 4);
+ for (int rank = 0; rank < blTreeCodes; rank++) {
+ pending.writeBits(blTree.length[BL_ORDER[rank]], 3);
+ }
+ literalTree.writeTree(blTree);
+ distTree.writeTree(blTree);
+ if (DeflaterConstants.DEBUGGING) {
+ blTree.checkEmpty();
+ }
+ }
+
+ public void compressBlock() {
+ for (int i = 0; i < last_lit; i++) {
+ final int litlen = l_buf[i] & 0xff;
+ int dist = d_buf[i];
+ if (dist-- != 0) {
+ if (DeflaterConstants.DEBUGGING) {
+ System.err.print("[" + (dist + 1) + "," + (litlen + 3)
+ + "]: ");
+ }
+
+ final int lc = l_code(litlen);
+ literalTree.writeSymbol(lc);
+
+ int bits = (lc - 261) / 4;
+ if ((bits > 0) && (bits <= 5)) {
+ pending.writeBits(litlen & ((1 << bits) - 1), bits);
+ }
+
+ final int dc = d_code(dist);
+ distTree.writeSymbol(dc);
+
+ bits = (dc / 2) - 1;
+ if (bits > 0) {
+ pending.writeBits(dist & ((1 << bits) - 1), bits);
+ }
+ } else {
+ if (DeflaterConstants.DEBUGGING) {
+ if ((litlen > 32) && (litlen < 127)) {
+ System.err.print("(" + (char) litlen + "): ");
+ } else {
+ System.err.print("{" + litlen + "}: ");
+ }
+ }
+ literalTree.writeSymbol(litlen);
+ }
+ }
+ if (DeflaterConstants.DEBUGGING) {
+ System.err.print("EOF: ");
+ }
+ literalTree.writeSymbol(EOF_SYMBOL);
+ if (DeflaterConstants.DEBUGGING) {
+ literalTree.checkEmpty();
+ distTree.checkEmpty();
+ }
+ }
+
+ public void flushStoredBlock(final byte[] stored, final int stored_offset,
+ final int stored_len, final boolean lastBlock) {
+ if (DeflaterConstants.DEBUGGING) {
+ System.err.println("Flushing stored block " + stored_len);
+ }
+ pending.writeBits((DeflaterConstants.STORED_BLOCK << 1)
+ + (lastBlock ? 1 : 0), 3);
+ pending.alignToByte();
+ pending.writeShort(stored_len);
+ pending.writeShort(~stored_len);
+ pending.writeBlock(stored, stored_offset, stored_len);
+ reset();
+ }
+
+ public void flushBlock(final byte[] stored, final int stored_offset,
+ final int stored_len, final boolean lastBlock) {
+ literalTree.freqs[EOF_SYMBOL]++;
+
+ /* Build trees */
+ literalTree.buildTree();
+ distTree.buildTree();
+
+ /* Calculate bitlen frequency */
+ literalTree.calcBLFreq(blTree);
+ distTree.calcBLFreq(blTree);
+
+ /* Build bitlen tree */
+ blTree.buildTree();
+
+ int blTreeCodes = 4;
+ for (int i = 18; i > blTreeCodes; i--) {
+ if (blTree.length[BL_ORDER[i]] > 0) {
+ blTreeCodes = i + 1;
+ }
+ }
+ int opt_len = 14 + (blTreeCodes * 3) + blTree.getEncodedLength()
+ + literalTree.getEncodedLength() + distTree.getEncodedLength()
+ + extra_bits;
+
+ int static_len = extra_bits;
+ for (int i = 0; i < LITERAL_NUM; i++) {
+ static_len += literalTree.freqs[i] * staticLLength[i];
+ }
+ for (int i = 0; i < DIST_NUM; i++) {
+ static_len += distTree.freqs[i] * staticDLength[i];
+ }
+ if (opt_len >= static_len) {
+ /* Force static trees */
+ opt_len = static_len;
+ }
+
+ if ((stored_offset >= 0) && ((stored_len + 4) < (opt_len >> 3))) {
+ /* Store Block */
+ if (DeflaterConstants.DEBUGGING) {
+ System.err.println("Storing, since " + stored_len + " < "
+ + opt_len + " <= " + static_len);
+ }
+ flushStoredBlock(stored, stored_offset, stored_len, lastBlock);
+ } else if (opt_len == static_len) {
+ /* Encode with static tree */
+ pending.writeBits((DeflaterConstants.STATIC_TREES << 1)
+ + (lastBlock ? 1 : 0), 3);
+ literalTree.setStaticCodes(staticLCodes, staticLLength);
+ distTree.setStaticCodes(staticDCodes, staticDLength);
+ compressBlock();
+ reset();
+ } else {
+ /* Encode with dynamic tree */
+ pending.writeBits((DeflaterConstants.DYN_TREES << 1)
+ + (lastBlock ? 1 : 0), 3);
+ sendAllTrees(blTreeCodes);
+ compressBlock();
+ reset();
+ }
+ }
+
+ public final boolean isFull() {
+ return last_lit == BUFSIZE;
+ }
+
+ public final boolean tallyLit(final int lit) {
+ if (DeflaterConstants.DEBUGGING) {
+ if ((lit > 32) && (lit < 127)) {
+ System.err.println("(" + (char) lit + ")");
+ } else {
+ System.err.println("{" + lit + "}");
+ }
+ }
+ d_buf[last_lit] = 0;
+ l_buf[last_lit++] = (byte) lit;
+ literalTree.freqs[lit]++;
+ return last_lit == BUFSIZE;
+ }
+
+ public final boolean tallyDist(final int dist, final int len) {
+ if (DeflaterConstants.DEBUGGING) {
+ System.err.println("[" + dist + "," + len + "]");
+ }
+
+ d_buf[last_lit] = (short) dist;
+ l_buf[last_lit++] = (byte) (len - 3);
+
+ final int lc = l_code(len - 3);
+ literalTree.freqs[lc]++;
+ if ((lc >= 265) && (lc < 285)) {
+ extra_bits += (lc - 261) / 4;
+ }
+
+ final int dc = d_code(dist - 1);
+ distTree.freqs[dc]++;
+ if (dc >= 4) {
+ extra_bits += (dc / 2) - 1;
+ }
+ return last_lit == BUFSIZE;
+ }
+}
diff --git a/epublib-core/src/main/java/net/sf/jazzlib/DeflaterOutputStream.java b/epublib-core/src/main/java/net/sf/jazzlib/DeflaterOutputStream.java
new file mode 100644
index 00000000..bbc021fd
--- /dev/null
+++ b/epublib-core/src/main/java/net/sf/jazzlib/DeflaterOutputStream.java
@@ -0,0 +1,210 @@
+/* DeflaterOutputStream.java - Output filter for compressing.
+ Copyright (C) 1999, 2000, 2001, 2004 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath 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 2, or (at your option)
+any later version.
+
+GNU Classpath 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 GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+02111-1307 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+package net.sf.jazzlib;
+
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/* Written using on-line Java Platform 1.2 API Specification
+ * and JCL book.
+ * Believed complete and correct.
+ */
+
+/**
+ * This is a special FilterOutputStream deflating the bytes that are written
+ * through it. It uses the Deflater for deflating.
+ *
+ * A special thing to be noted is that flush() doesn't flush everything in Sun's
+ * JDK, but it does so in jazzlib. This is because Sun's Deflater doesn't have a
+ * way to flush() everything, without finishing the stream.
+ *
+ * @author Tom Tromey, Jochen Hoenicke
+ * @date Jan 11, 2001
+ */
+public class DeflaterOutputStream extends FilterOutputStream {
+ /**
+ * This buffer is used temporarily to retrieve the bytes from the deflater
+ * and write them to the underlying output stream.
+ */
+ protected byte[] buf;
+
+ /**
+ * The deflater which is used to deflate the stream.
+ */
+ protected Deflater def;
+
+ /**
+ * Deflates everything in the def's input buffers. This will call
+ * def.deflate()
until all bytes from the input buffers are
+ * processed.
+ */
+ protected void deflate() throws IOException {
+ while (!def.needsInput()) {
+ final int len = def.deflate(buf, 0, buf.length);
+
+ // System.err.println("DOS deflated " + len + " out of " +
+ // buf.length);
+ if (len <= 0) {
+ break;
+ }
+ out.write(buf, 0, len);
+ }
+
+ if (!def.needsInput()) {
+ throw new InternalError("Can't deflate all input?");
+ }
+ }
+
+ /**
+ * Creates a new DeflaterOutputStream with a default Deflater and default
+ * buffer size.
+ *
+ * @param out
+ * the output stream where deflated output should be written.
+ */
+ public DeflaterOutputStream(final OutputStream out) {
+ this(out, new Deflater(), 512);
+ }
+
+ /**
+ * Creates a new DeflaterOutputStream with the given Deflater and default
+ * buffer size.
+ *
+ * @param out
+ * the output stream where deflated output should be written.
+ * @param defl
+ * the underlying deflater.
+ */
+ public DeflaterOutputStream(final OutputStream out, final Deflater defl) {
+ this(out, defl, 512);
+ }
+
+ /**
+ * Creates a new DeflaterOutputStream with the given Deflater and buffer
+ * size.
+ *
+ * @param out
+ * the output stream where deflated output should be written.
+ * @param defl
+ * the underlying deflater.
+ * @param bufsize
+ * the buffer size.
+ * @exception IllegalArgumentException
+ * if bufsize isn't positive.
+ */
+ public DeflaterOutputStream(final OutputStream out, final Deflater defl,
+ final int bufsize) {
+ super(out);
+ if (bufsize <= 0) {
+ throw new IllegalArgumentException("bufsize <= 0");
+ }
+ buf = new byte[bufsize];
+ def = defl;
+ }
+
+ /**
+ * Flushes the stream by calling flush() on the deflater and then on the
+ * underlying stream. This ensures that all bytes are flushed. This function
+ * doesn't work in Sun's JDK, but only in jazzlib.
+ */
+ @Override
+ public void flush() throws IOException {
+ def.flush();
+ deflate();
+ out.flush();
+ }
+
+ /**
+ * Finishes the stream by calling finish() on the deflater. This was the
+ * only way to ensure that all bytes are flushed in Sun's JDK.
+ */
+ public void finish() throws IOException {
+ def.finish();
+ while (!def.finished()) {
+ final int len = def.deflate(buf, 0, buf.length);
+ if (len <= 0) {
+ break;
+ }
+ out.write(buf, 0, len);
+ }
+ if (!def.finished()) {
+ throw new InternalError("Can't deflate all input?");
+ }
+ out.flush();
+ }
+
+ /**
+ * Calls finish () and closes the stream.
+ */
+ @Override
+ public void close() throws IOException {
+ finish();
+ out.close();
+ }
+
+ /**
+ * Writes a single byte to the compressed output stream.
+ *
+ * @param bval
+ * the byte value.
+ */
+ @Override
+ public void write(final int bval) throws IOException {
+ final byte[] b = new byte[1];
+ b[0] = (byte) bval;
+ write(b, 0, 1);
+ }
+
+ /**
+ * Writes a len bytes from an array to the compressed stream.
+ *
+ * @param buf
+ * the byte array.
+ * @param off
+ * the offset into the byte array where to start.
+ * @param len
+ * the number of bytes to write.
+ */
+ @Override
+ public void write(final byte[] buf, final int off, final int len)
+ throws IOException {
+ def.setInput(buf, off, len);
+ deflate();
+ }
+}
diff --git a/epublib-core/src/main/java/net/sf/jazzlib/DeflaterPending.java b/epublib-core/src/main/java/net/sf/jazzlib/DeflaterPending.java
new file mode 100644
index 00000000..e3f0dcaa
--- /dev/null
+++ b/epublib-core/src/main/java/net/sf/jazzlib/DeflaterPending.java
@@ -0,0 +1,51 @@
+/* net.sf.jazzlib.DeflaterPending
+ Copyright (C) 2001 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath 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 2, or (at your option)
+any later version.
+
+GNU Classpath 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 GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+02111-1307 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+package net.sf.jazzlib;
+
+/**
+ * This class stores the pending output of the Deflater.
+ *
+ * @author Jochen Hoenicke
+ * @date Jan 5, 2000
+ */
+
+class DeflaterPending extends PendingBuffer {
+ public DeflaterPending() {
+ super(DeflaterConstants.PENDING_BUF_SIZE);
+ }
+}
diff --git a/epublib-core/src/main/java/net/sf/jazzlib/GZIPInputStream.java b/epublib-core/src/main/java/net/sf/jazzlib/GZIPInputStream.java
new file mode 100644
index 00000000..e9111ede
--- /dev/null
+++ b/epublib-core/src/main/java/net/sf/jazzlib/GZIPInputStream.java
@@ -0,0 +1,369 @@
+/* GZIPInputStream.java - Input filter for reading gzip file
+ Copyright (C) 1999, 2000, 2001, 2002, 2004 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath 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 2, or (at your option)
+any later version.
+
+GNU Classpath 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 GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+02111-1307 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+package net.sf.jazzlib;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * This filter stream is used to decompress a "GZIP" format stream. The "GZIP"
+ * format is described in RFC 1952.
+ *
+ * @author John Leuner
+ * @author Tom Tromey
+ * @since JDK 1.1
+ */
+public class GZIPInputStream extends InflaterInputStream {
+ /**
+ * The magic number found at the start of a GZIP stream.
+ */
+ public static final int GZIP_MAGIC = 0x1f8b;
+
+ /**
+ * The mask for bit 0 of the flag byte.
+ */
+ static final int FTEXT = 0x1;
+
+ /**
+ * The mask for bit 1 of the flag byte.
+ */
+ static final int FHCRC = 0x2;
+
+ /**
+ * The mask for bit 2 of the flag byte.
+ */
+ static final int FEXTRA = 0x4;
+
+ /**
+ * The mask for bit 3 of the flag byte.
+ */
+ static final int FNAME = 0x8;
+
+ /**
+ * The mask for bit 4 of the flag byte.
+ */
+ static final int FCOMMENT = 0x10;
+
+ /**
+ * The CRC-32 checksum value for uncompressed data.
+ */
+ protected CRC32 crc;
+
+ /**
+ * Indicates whether or not the end of the stream has been reached.
+ */
+ protected boolean eos;
+
+ /**
+ * Indicates whether or not the GZIP header has been read in.
+ */
+ private boolean readGZIPHeader;
+
+ /**
+ * Creates a GZIPInputStream with the default buffer size.
+ *
+ * @param in
+ * The stream to read compressed data from (in GZIP format).
+ *
+ * @throws IOException
+ * if an error occurs during an I/O operation.
+ */
+ public GZIPInputStream(final InputStream in) throws IOException {
+ this(in, 4096);
+ }
+
+ /**
+ * Creates a GZIPInputStream with the specified buffer size.
+ *
+ * @param in
+ * The stream to read compressed data from (in GZIP format).
+ * @param size
+ * The size of the buffer to use.
+ *
+ * @throws IOException
+ * if an error occurs during an I/O operation.
+ * @throws IllegalArgumentException
+ * if size
is less than or equal to 0.
+ */
+ public GZIPInputStream(final InputStream in, final int size)
+ throws IOException {
+ super(in, new Inflater(true), size);
+ crc = new CRC32();
+ }
+
+ /**
+ * Closes the input stream.
+ *
+ * @throws IOException
+ * if an error occurs during an I/O operation.
+ */
+ @Override
+ public void close() throws IOException {
+ // Nothing to do here.
+ super.close();
+ }
+
+ /**
+ * Reads in GZIP-compressed data and stores it in uncompressed form into an
+ * array of bytes. The method will block until either enough input data
+ * becomes available or the compressed stream reaches its end.
+ *
+ * @param buf
+ * the buffer into which the uncompressed data will be stored.
+ * @param offset
+ * the offset indicating where in buf
the
+ * uncompressed data should be placed.
+ * @param len
+ * the number of uncompressed bytes to be read.
+ */
+ @Override
+ public int read(final byte[] buf, final int offset, final int len)
+ throws IOException {
+ // We first have to slurp in the GZIP header, then we feed all the
+ // rest of the data to the superclass.
+ //
+ // As we do that we continually update the CRC32. Once the data is
+ // finished, we check the CRC32.
+ //
+ // This means we don't need our own buffer, as everything is done
+ // in the superclass.
+ if (!readGZIPHeader) {
+ readHeader();
+ }
+
+ if (eos) {
+ return -1;
+ }
+
+ // System.err.println("GZIPIS.read(byte[], off, len ... " + offset +
+ // " and len " + len);
+
+ /*
+ * We don't have to read the header, so we just grab data from the
+ * superclass.
+ */
+ final int numRead = super.read(buf, offset, len);
+ if (numRead > 0) {
+ crc.update(buf, offset, numRead);
+ }
+
+ if (inf.finished()) {
+ readFooter();
+ }
+ return numRead;
+ }
+
+ /**
+ * Reads in the GZIP header.
+ */
+ private void readHeader() throws IOException {
+ /* 1. Check the two magic bytes */
+ final CRC32 headCRC = new CRC32();
+ int magic = in.read();
+ if (magic < 0) {
+ eos = true;
+ return;
+ }
+ headCRC.update(magic);
+ if (magic != (GZIP_MAGIC >> 8)) {
+ throw new IOException(
+ "Error in GZIP header, first byte doesn't match");
+ }
+
+ magic = in.read();
+ if (magic != (GZIP_MAGIC & 0xff)) {
+ throw new IOException(
+ "Error in GZIP header, second byte doesn't match");
+ }
+ headCRC.update(magic);
+
+ /* 2. Check the compression type (must be 8) */
+ final int CM = in.read();
+ if (CM != 8) {
+ throw new IOException(
+ "Error in GZIP header, data not in deflate format");
+ }
+ headCRC.update(CM);
+
+ /* 3. Check the flags */
+ final int flags = in.read();
+ if (flags < 0) {
+ throw new EOFException("Early EOF in GZIP header");
+ }
+ headCRC.update(flags);
+
+ /*
+ * This flag byte is divided into individual bits as follows:
+ *
+ * bit 0 FTEXT bit 1 FHCRC bit 2 FEXTRA bit 3 FNAME bit 4 FCOMMENT bit 5
+ * reserved bit 6 reserved bit 7 reserved
+ */
+
+ /* 3.1 Check the reserved bits are zero */
+ if ((flags & 0xd0) != 0) {
+ throw new IOException("Reserved flag bits in GZIP header != 0");
+ }
+
+ /* 4.-6. Skip the modification time, extra flags, and OS type */
+ for (int i = 0; i < 6; i++) {
+ final int readByte = in.read();
+ if (readByte < 0) {
+ throw new EOFException("Early EOF in GZIP header");
+ }
+ headCRC.update(readByte);
+ }
+
+ /* 7. Read extra field */
+ if ((flags & FEXTRA) != 0) {
+ /* Skip subfield id */
+ for (int i = 0; i < 2; i++) {
+ final int readByte = in.read();
+ if (readByte < 0) {
+ throw new EOFException("Early EOF in GZIP header");
+ }
+ headCRC.update(readByte);
+ }
+ if ((in.read() < 0) || (in.read() < 0)) {
+ throw new EOFException("Early EOF in GZIP header");
+ }
+
+ int len1, len2, extraLen;
+ len1 = in.read();
+ len2 = in.read();
+ if ((len1 < 0) || (len2 < 0)) {
+ throw new EOFException("Early EOF in GZIP header");
+ }
+ headCRC.update(len1);
+ headCRC.update(len2);
+
+ extraLen = (len1 << 8) | len2;
+ for (int i = 0; i < extraLen; i++) {
+ final int readByte = in.read();
+ if (readByte < 0) {
+ throw new EOFException("Early EOF in GZIP header");
+ }
+ headCRC.update(readByte);
+ }
+ }
+
+ /* 8. Read file name */
+ if ((flags & FNAME) != 0) {
+ int readByte;
+ while ((readByte = in.read()) > 0) {
+ headCRC.update(readByte);
+ }
+ if (readByte < 0) {
+ throw new EOFException("Early EOF in GZIP file name");
+ }
+ headCRC.update(readByte);
+ }
+
+ /* 9. Read comment */
+ if ((flags & FCOMMENT) != 0) {
+ int readByte;
+ while ((readByte = in.read()) > 0) {
+ headCRC.update(readByte);
+ }
+
+ if (readByte < 0) {
+ throw new EOFException("Early EOF in GZIP comment");
+ }
+ headCRC.update(readByte);
+ }
+
+ /* 10. Read header CRC */
+ if ((flags & FHCRC) != 0) {
+ int tempByte;
+ int crcval = in.read();
+ if (crcval < 0) {
+ throw new EOFException("Early EOF in GZIP header");
+ }
+
+ tempByte = in.read();
+ if (tempByte < 0) {
+ throw new EOFException("Early EOF in GZIP header");
+ }
+
+ crcval = (crcval << 8) | tempByte;
+ if (crcval != ((int) headCRC.getValue() & 0xffff)) {
+ throw new IOException("Header CRC value mismatch");
+ }
+ }
+
+ readGZIPHeader = true;
+ // System.err.println("Read GZIP header");
+ }
+
+ private void readFooter() throws IOException {
+ final byte[] footer = new byte[8];
+ int avail = inf.getRemaining();
+ if (avail > 8) {
+ avail = 8;
+ }
+ System.arraycopy(buf, len - inf.getRemaining(), footer, 0, avail);
+ int needed = 8 - avail;
+ while (needed > 0) {
+ final int count = in.read(footer, 8 - needed, needed);
+ if (count <= 0) {
+ throw new EOFException("Early EOF in GZIP footer");
+ }
+ needed -= count; // Jewel Jan 16
+ }
+
+ final int crcval = (footer[0] & 0xff) | ((footer[1] & 0xff) << 8)
+ | ((footer[2] & 0xff) << 16) | (footer[3] << 24);
+ if (crcval != (int) crc.getValue()) {
+ throw new IOException("GZIP crc sum mismatch, theirs \""
+ + Integer.toHexString(crcval) + "\" and ours \""
+ + Integer.toHexString((int) crc.getValue()));
+ }
+
+ final int total = (footer[4] & 0xff) | ((footer[5] & 0xff) << 8)
+ | ((footer[6] & 0xff) << 16) | (footer[7] << 24);
+ if (total != inf.getTotalOut()) {
+ throw new IOException("Number of bytes mismatch");
+ }
+
+ /*
+ * FIXME" XXX Should we support multiple members. Difficult, since there
+ * may be some bytes still in buf
+ */
+ eos = true;
+ }
+}
diff --git a/epublib-core/src/main/java/net/sf/jazzlib/GZIPOutputStream.java b/epublib-core/src/main/java/net/sf/jazzlib/GZIPOutputStream.java
new file mode 100644
index 00000000..26d27c4d
--- /dev/null
+++ b/epublib-core/src/main/java/net/sf/jazzlib/GZIPOutputStream.java
@@ -0,0 +1,150 @@
+/* GZIPOutputStream.java - Create a file in gzip format
+ Copyright (C) 1999, 2000, 2001 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath 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 2, or (at your option)
+any later version.
+
+GNU Classpath 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 GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+02111-1307 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+package net.sf.jazzlib;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * This filter stream is used to compress a stream into a "GZIP" stream. The
+ * "GZIP" format is described in RFC 1952.
+ *
+ * @author John Leuner
+ * @author Tom Tromey
+ * @since JDK 1.1
+ */
+
+/*
+ * Written using on-line Java Platform 1.2 API Specification and JCL book.
+ * Believed complete and correct.
+ */
+
+public class GZIPOutputStream extends DeflaterOutputStream {
+ /**
+ * CRC-32 value for uncompressed data
+ */
+ protected CRC32 crc;
+
+ /*
+ * Creates a GZIPOutputStream with the default buffer size
+ *
+ *
+ * @param out The stream to read data (to be compressed) from
+ */
+ public GZIPOutputStream(final OutputStream out) throws IOException {
+ this(out, 4096);
+ }
+
+ /**
+ * Creates a GZIPOutputStream with the specified buffer size
+ *
+ * @param out
+ * The stream to read compressed data from
+ * @param size
+ * Size of the buffer to use
+ */
+ public GZIPOutputStream(final OutputStream out, final int size)
+ throws IOException {
+ super(out, new Deflater(Deflater.DEFAULT_COMPRESSION, true), size);
+
+ crc = new CRC32();
+ final int mod_time = (int) (System.currentTimeMillis() / 1000L);
+ final byte[] gzipHeader = {
+ /* The two magic bytes */
+ (byte) (GZIPInputStream.GZIP_MAGIC >> 8),
+ (byte) GZIPInputStream.GZIP_MAGIC,
+
+ /* The compression type */
+ (byte) Deflater.DEFLATED,
+
+ /* The flags (not set) */
+ 0,
+
+ /* The modification time */
+ (byte) mod_time, (byte) (mod_time >> 8),
+ (byte) (mod_time >> 16), (byte) (mod_time >> 24),
+
+ /* The extra flags */
+ 0,
+
+ /* The OS type (unknown) */
+ (byte) 255 };
+
+ out.write(gzipHeader);
+ // System.err.println("wrote GZIP header (" + gzipHeader.length +
+ // " bytes )");
+ }
+
+ @Override
+ public synchronized void write(final byte[] buf, final int off,
+ final int len) throws IOException {
+ super.write(buf, off, len);
+ crc.update(buf, off, len);
+ }
+
+ /**
+ * Writes remaining compressed output data to the output stream and closes
+ * it.
+ */
+ @Override
+ public void close() throws IOException {
+ finish();
+ out.close();
+ }
+
+ @Override
+ public void finish() throws IOException {
+ super.finish();
+
+ final int totalin = def.getTotalIn();
+ final int crcval = (int) (crc.getValue() & 0xffffffff);
+
+ // System.err.println("CRC val is " + Integer.toHexString( crcval ) +
+ // " and length " + Integer.toHexString(totalin));
+
+ final byte[] gzipFooter = { (byte) crcval, (byte) (crcval >> 8),
+ (byte) (crcval >> 16), (byte) (crcval >> 24),
+
+ (byte) totalin, (byte) (totalin >> 8), (byte) (totalin >> 16),
+ (byte) (totalin >> 24) };
+
+ out.write(gzipFooter);
+ // System.err.println("wrote GZIP trailer (" + gzipFooter.length +
+ // " bytes )");
+ }
+}
diff --git a/epublib-core/src/main/java/net/sf/jazzlib/Inflater.java b/epublib-core/src/main/java/net/sf/jazzlib/Inflater.java
new file mode 100644
index 00000000..9e5b9b61
--- /dev/null
+++ b/epublib-core/src/main/java/net/sf/jazzlib/Inflater.java
@@ -0,0 +1,710 @@
+/* Inflater.java - Decompress a data stream
+ Copyright (C) 1999, 2000, 2001, 2003 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath 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 2, or (at your option)
+any later version.
+
+GNU Classpath 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 GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+02111-1307 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+package net.sf.jazzlib;
+
+/* Written using on-line Java Platform 1.2 API Specification
+ * and JCL book.
+ * Believed complete and correct.
+ */
+
+/**
+ * Inflater is used to decompress data that has been compressed according to the
+ * "deflate" standard described in rfc1950.
+ *
+ * The usage is as following. First you have to set some input with
+ * setInput()
, then inflate() it. If inflate doesn't inflate any
+ * bytes there may be three reasons:
+ *
setInput()
. NOTE: needsInput() also
+ * returns true when, the stream is finished.setDictionary()
.GZIPInputStream
.
+ *
+ * @author John Leuner
+ * @author Tom Tromey
+ * @since 1.1
+ */
+public class InflaterInputStream extends FilterInputStream {
+ /**
+ * Decompressor for this filter
+ */
+ protected Inflater inf;
+
+ /**
+ * Byte array used as a buffer
+ */
+ protected byte[] buf;
+
+ /**
+ * Size of buffer
+ */
+ protected int len;
+
+ /*
+ * We just use this if we are decoding one byte at a time with the read()
+ * call
+ */
+ private final byte[] onebytebuffer = new byte[1];
+
+ /**
+ * Create an InflaterInputStream with the default decompresseor and a
+ * default buffer size.
+ *
+ * @param in
+ * the InputStream to read bytes from
+ */
+ public InflaterInputStream(final InputStream in) {
+ this(in, new Inflater(), 4096);
+ }
+
+ /**
+ * Create an InflaterInputStream with the specified decompresseor and a
+ * default buffer size.
+ *
+ * @param in
+ * the InputStream to read bytes from
+ * @param inf
+ * the decompressor used to decompress data read from in
+ */
+ public InflaterInputStream(final InputStream in, final Inflater inf) {
+ this(in, inf, 4096);
+ }
+
+ /**
+ * Create an InflaterInputStream with the specified decompresseor and a
+ * specified buffer size.
+ *
+ * @param in
+ * the InputStream to read bytes from
+ * @param inf
+ * the decompressor used to decompress data read from in
+ * @param size
+ * size of the buffer to use
+ */
+ public InflaterInputStream(final InputStream in, final Inflater inf,
+ final int size) {
+ super(in);
+ this.len = 0;
+
+ if (in == null) {
+ throw new NullPointerException("in may not be null");
+ }
+ if (inf == null) {
+ throw new NullPointerException("inf may not be null");
+ }
+ if (size < 0) {
+ throw new IllegalArgumentException("size may not be negative");
+ }
+
+ this.inf = inf;
+ this.buf = new byte[size];
+ }
+
+ /**
+ * Returns 0 once the end of the stream (EOF) has been reached. Otherwise
+ * returns 1.
+ */
+ @Override
+ public int available() throws IOException {
+ // According to the JDK 1.2 docs, this should only ever return 0
+ // or 1 and should not be relied upon by Java programs.
+ return inf.finished() ? 0 : 1;
+ }
+
+ /**
+ * Closes the input stream
+ */
+ @Override
+ public synchronized void close() throws IOException {
+ if (in != null) {
+ in.close();
+ }
+ in = null;
+ }
+
+ /**
+ * Fills the buffer with more data to decompress.
+ */
+ protected void fill() throws IOException {
+ if (in == null) {
+ throw new ZipException("InflaterInputStream is closed");
+ }
+
+ len = in.read(buf, 0, buf.length);
+
+ if (len < 0) {
+ throw new ZipException("Deflated stream ends early.");
+ }
+
+ inf.setInput(buf, 0, len);
+ }
+
+ /**
+ * Reads one byte of decompressed data.
+ *
+ * The byte is in the lower 8 bits of the int.
+ */
+ @Override
+ public int read() throws IOException {
+ final int nread = read(onebytebuffer, 0, 1); // read one byte
+
+ if (nread > 0) {
+ return onebytebuffer[0] & 0xff;
+ }
+
+ return -1;
+ }
+
+ /**
+ * Decompresses data into the byte array
+ *
+ * @param b
+ * the array to read and decompress data into
+ * @param off
+ * the offset indicating where the data should be placed
+ * @param len
+ * the number of bytes to decompress
+ */
+ @Override
+ public int read(final byte[] b, final int off, final int len)
+ throws IOException {
+ if (len == 0) {
+ return 0;
+ }
+
+ for (;;) {
+ int count;
+
+ try {
+ count = inf.inflate(b, off, len);
+ } catch (final DataFormatException dfe) {
+ throw new ZipException(dfe.getMessage());
+ }
+
+ if (count > 0) {
+ return count;
+ }
+
+ if (inf.needsDictionary() | inf.finished()) {
+ return -1;
+ } else if (inf.needsInput()) {
+ fill();
+ } else {
+ throw new InternalError("Don't know what to do");
+ }
+ }
+ }
+
+ /**
+ * Skip specified number of bytes of uncompressed data
+ *
+ * @param n
+ * number of bytes to skip
+ */
+ @Override
+ public long skip(long n) throws IOException {
+ if (n < 0) {
+ throw new IllegalArgumentException();
+ }
+
+ if (n == 0) {
+ return 0;
+ }
+
+ // Implementation copied from InputStream
+ // Throw away n bytes by reading them into a temp byte[].
+ // Limit the temp array to 2Kb so we don't grab too much memory.
+ final int buflen = n > 2048 ? 2048 : (int) n;
+ final byte[] tmpbuf = new byte[buflen];
+ final long origN = n;
+
+ while (n > 0L) {
+ final int numread = read(tmpbuf, 0, n > buflen ? buflen : (int) n);
+ if (numread <= 0) {
+ break;
+ }
+ n -= numread;
+ }
+
+ return origN - n;
+ }
+}
diff --git a/epublib-core/src/main/java/net/sf/jazzlib/OutputWindow.java b/epublib-core/src/main/java/net/sf/jazzlib/OutputWindow.java
new file mode 100644
index 00000000..c06b33ae
--- /dev/null
+++ b/epublib-core/src/main/java/net/sf/jazzlib/OutputWindow.java
@@ -0,0 +1,168 @@
+/* net.sf.jazzlib.OutputWindow
+ Copyright (C) 2001 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath 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 2, or (at your option)
+any later version.
+
+GNU Classpath 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 GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+02111-1307 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+package net.sf.jazzlib;
+
+/*
+ * Contains the output from the Inflation process.
+ *
+ * We need to have a window so that we can refer backwards into the output stream
+ * to repeat stuff.
+ *
+ * @author John Leuner
+ * @since JDK 1.1
+ */
+
+class OutputWindow {
+ private final int WINDOW_SIZE = 1 << 15;
+ private final int WINDOW_MASK = WINDOW_SIZE - 1;
+
+ private final byte[] window = new byte[WINDOW_SIZE]; // The window is 2^15
+ // bytes
+ private int window_end = 0;
+ private int window_filled = 0;
+
+ public void write(final int abyte) {
+ if (window_filled++ == WINDOW_SIZE) {
+ throw new IllegalStateException("Window full");
+ }
+ window[window_end++] = (byte) abyte;
+ window_end &= WINDOW_MASK;
+ }
+
+ private final void slowRepeat(int rep_start, int len, final int dist) {
+ while (len-- > 0) {
+ window[window_end++] = window[rep_start++];
+ window_end &= WINDOW_MASK;
+ rep_start &= WINDOW_MASK;
+ }
+ }
+
+ public void repeat(int len, final int dist) {
+ if ((window_filled += len) > WINDOW_SIZE) {
+ throw new IllegalStateException("Window full");
+ }
+
+ int rep_start = (window_end - dist) & WINDOW_MASK;
+ final int border = WINDOW_SIZE - len;
+ if ((rep_start <= border) && (window_end < border)) {
+ if (len <= dist) {
+ System.arraycopy(window, rep_start, window, window_end, len);
+ window_end += len;
+ } else {
+ /*
+ * We have to copy manually, since the repeat pattern overlaps.
+ */
+ while (len-- > 0) {
+ window[window_end++] = window[rep_start++];
+ }
+ }
+ } else {
+ slowRepeat(rep_start, len, dist);
+ }
+ }
+
+ public int copyStored(final StreamManipulator input, int len) {
+ len = Math.min(Math.min(len, WINDOW_SIZE - window_filled),
+ input.getAvailableBytes());
+ int copied;
+
+ final int tailLen = WINDOW_SIZE - window_end;
+ if (len > tailLen) {
+ copied = input.copyBytes(window, window_end, tailLen);
+ if (copied == tailLen) {
+ copied += input.copyBytes(window, 0, len - tailLen);
+ }
+ } else {
+ copied = input.copyBytes(window, window_end, len);
+ }
+
+ window_end = (window_end + copied) & WINDOW_MASK;
+ window_filled += copied;
+ return copied;
+ }
+
+ public void copyDict(final byte[] dict, int offset, int len) {
+ if (window_filled > 0) {
+ throw new IllegalStateException();
+ }
+
+ if (len > WINDOW_SIZE) {
+ offset += len - WINDOW_SIZE;
+ len = WINDOW_SIZE;
+ }
+ System.arraycopy(dict, offset, window, 0, len);
+ window_end = len & WINDOW_MASK;
+ }
+
+ public int getFreeSpace() {
+ return WINDOW_SIZE - window_filled;
+ }
+
+ public int getAvailable() {
+ return window_filled;
+ }
+
+ public int copyOutput(final byte[] output, int offset, int len) {
+ int copy_end = window_end;
+ if (len > window_filled) {
+ len = window_filled;
+ } else {
+ copy_end = ((window_end - window_filled) + len) & WINDOW_MASK;
+ }
+
+ final int copied = len;
+ final int tailLen = len - copy_end;
+
+ if (tailLen > 0) {
+ System.arraycopy(window, WINDOW_SIZE - tailLen, output, offset,
+ tailLen);
+ offset += tailLen;
+ len = copy_end;
+ }
+ System.arraycopy(window, copy_end - len, output, offset, len);
+ window_filled -= copied;
+ if (window_filled < 0) {
+ throw new IllegalStateException();
+ }
+ return copied;
+ }
+
+ public void reset() {
+ window_filled = window_end = 0;
+ }
+}
diff --git a/epublib-core/src/main/java/net/sf/jazzlib/PendingBuffer.java b/epublib-core/src/main/java/net/sf/jazzlib/PendingBuffer.java
new file mode 100644
index 00000000..8966d860
--- /dev/null
+++ b/epublib-core/src/main/java/net/sf/jazzlib/PendingBuffer.java
@@ -0,0 +1,199 @@
+/* net.sf.jazzlib.PendingBuffer
+ Copyright (C) 2001 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath 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 2, or (at your option)
+any later version.
+
+GNU Classpath 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 GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+02111-1307 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+package net.sf.jazzlib;
+
+/**
+ * This class is general purpose class for writing data to a buffer.
+ *
+ * It allows you to write bits as well as bytes
+ *
+ * Based on DeflaterPending.java
+ *
+ * @author Jochen Hoenicke
+ * @date Jan 5, 2000
+ */
+
+class PendingBuffer {
+ protected byte[] buf;
+ int start;
+ int end;
+
+ int bits;
+ int bitCount;
+
+ public PendingBuffer() {
+ this(4096);
+ }
+
+ public PendingBuffer(final int bufsize) {
+ buf = new byte[bufsize];
+ }
+
+ public final void reset() {
+ start = end = bitCount = 0;
+ }
+
+ public final void writeByte(final int b) {
+ if (DeflaterConstants.DEBUGGING && (start != 0)) {
+ throw new IllegalStateException();
+ }
+ buf[end++] = (byte) b;
+ }
+
+ public final void writeShort(final int s) {
+ if (DeflaterConstants.DEBUGGING && (start != 0)) {
+ throw new IllegalStateException();
+ }
+ buf[end++] = (byte) s;
+ buf[end++] = (byte) (s >> 8);
+ }
+
+ public final void writeInt(final int s) {
+ if (DeflaterConstants.DEBUGGING && (start != 0)) {
+ throw new IllegalStateException();
+ }
+ buf[end++] = (byte) s;
+ buf[end++] = (byte) (s >> 8);
+ buf[end++] = (byte) (s >> 16);
+ buf[end++] = (byte) (s >> 24);
+ }
+
+ public final void writeBlock(final byte[] block, final int offset,
+ final int len) {
+ if (DeflaterConstants.DEBUGGING && (start != 0)) {
+ throw new IllegalStateException();
+ }
+ System.arraycopy(block, offset, buf, end, len);
+ end += len;
+ }
+
+ public final int getBitCount() {
+ return bitCount;
+ }
+
+ public final void alignToByte() {
+ if (DeflaterConstants.DEBUGGING && (start != 0)) {
+ throw new IllegalStateException();
+ }
+ if (bitCount > 0) {
+ buf[end++] = (byte) bits;
+ if (bitCount > 8) {
+ buf[end++] = (byte) (bits >>> 8);
+ }
+ }
+ bits = 0;
+ bitCount = 0;
+ }
+
+ public final void writeBits(final int b, final int count) {
+ if (DeflaterConstants.DEBUGGING && (start != 0)) {
+ throw new IllegalStateException();
+ }
+ if (DeflaterConstants.DEBUGGING) {
+ System.err.println("writeBits(" + Integer.toHexString(b) + ","
+ + count + ")");
+ }
+ bits |= b << bitCount;
+ bitCount += count;
+ if (bitCount >= 16) {
+ buf[end++] = (byte) bits;
+ buf[end++] = (byte) (bits >>> 8);
+ bits >>>= 16;
+ bitCount -= 16;
+ }
+ }
+
+ public final void writeShortMSB(final int s) {
+ if (DeflaterConstants.DEBUGGING && (start != 0)) {
+ throw new IllegalStateException();
+ }
+ buf[end++] = (byte) (s >> 8);
+ buf[end++] = (byte) s;
+ }
+
+ public final boolean isFlushed() {
+ return end == 0;
+ }
+
+ /**
+ * Flushes the pending buffer into the given output array. If the output
+ * array is to small, only a partial flush is done.
+ *
+ * @param output
+ * the output array;
+ * @param offset
+ * the offset into output array;
+ * @param length
+ * the maximum number of bytes to store;
+ * @exception IndexOutOfBoundsException
+ * if offset or length are invalid.
+ */
+ public final int flush(final byte[] output, final int offset, int length) {
+ if (bitCount >= 8) {
+ buf[end++] = (byte) bits;
+ bits >>>= 8;
+ bitCount -= 8;
+ }
+ if (length > (end - start)) {
+ length = end - start;
+ System.arraycopy(buf, start, output, offset, length);
+ start = 0;
+ end = 0;
+ } else {
+ System.arraycopy(buf, start, output, offset, length);
+ start += length;
+ }
+ return length;
+ }
+
+ /**
+ * Flushes the pending buffer and returns that data in a new array
+ *
+ * @param output
+ * the output stream
+ */
+
+ public final byte[] toByteArray() {
+ final byte[] ret = new byte[end - start];
+ System.arraycopy(buf, start, ret, 0, ret.length);
+ start = 0;
+ end = 0;
+ return ret;
+ }
+
+}
diff --git a/epublib-core/src/main/java/net/sf/jazzlib/StreamManipulator.java b/epublib-core/src/main/java/net/sf/jazzlib/StreamManipulator.java
new file mode 100644
index 00000000..d0a8fc8c
--- /dev/null
+++ b/epublib-core/src/main/java/net/sf/jazzlib/StreamManipulator.java
@@ -0,0 +1,215 @@
+/* net.sf.jazzlib.StreamManipulator
+ Copyright (C) 2001 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath 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 2, or (at your option)
+any later version.
+
+GNU Classpath 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 GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+02111-1307 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+package net.sf.jazzlib;
+
+/**
+ * This class allows us to retrieve a specified amount of bits from the input
+ * buffer, as well as copy big byte blocks.
+ *
+ * It uses an int buffer to store up to 31 bits for direct manipulation. This
+ * guarantees that we can get at least 16 bits, but we only need at most 15, so
+ * this is all safe.
+ *
+ * There are some optimizations in this class, for example, you must never peek
+ * more then 8 bits more than needed, and you must first peek bits before you
+ * may drop them. This is not a general purpose class but optimized for the
+ * behaviour of the Inflater.
+ *
+ * @author John Leuner, Jochen Hoenicke
+ */
+
+class StreamManipulator {
+ private byte[] window;
+ private int window_start = 0;
+ private int window_end = 0;
+
+ private int buffer = 0;
+ private int bits_in_buffer = 0;
+
+ /**
+ * Get the next n bits but don't increase input pointer. n must be less or
+ * equal 16 and if you if this call succeeds, you must drop at least n-8
+ * bits in the next call.
+ *
+ * @return the value of the bits, or -1 if not enough bits available.
+ */
+ public final int peekBits(final int n) {
+ if (bits_in_buffer < n) {
+ if (window_start == window_end) {
+ return -1;
+ }
+ buffer |= ((window[window_start++] & 0xff) | ((window[window_start++] & 0xff) << 8)) << bits_in_buffer;
+ bits_in_buffer += 16;
+ }
+ return buffer & ((1 << n) - 1);
+ }
+
+ /*
+ * Drops the next n bits from the input. You should have called peekBits
+ * with a bigger or equal n before, to make sure that enough bits are in the
+ * bit buffer.
+ */
+ public final void dropBits(final int n) {
+ buffer >>>= n;
+ bits_in_buffer -= n;
+ }
+
+ /**
+ * Gets the next n bits and increases input pointer. This is equivalent to
+ * peekBits followed by dropBits, except for correct error handling.
+ *
+ * @return the value of the bits, or -1 if not enough bits available.
+ */
+ public final int getBits(final int n) {
+ final int bits = peekBits(n);
+ if (bits >= 0) {
+ dropBits(n);
+ }
+ return bits;
+ }
+
+ /**
+ * Gets the number of bits available in the bit buffer. This must be only
+ * called when a previous peekBits() returned -1.
+ *
+ * @return the number of bits available.
+ */
+ public final int getAvailableBits() {
+ return bits_in_buffer;
+ }
+
+ /**
+ * Gets the number of bytes available.
+ *
+ * @return the number of bytes available.
+ */
+ public final int getAvailableBytes() {
+ return (window_end - window_start) + (bits_in_buffer >> 3);
+ }
+
+ /**
+ * Skips to the next byte boundary.
+ */
+ public void skipToByteBoundary() {
+ buffer >>= (bits_in_buffer & 7);
+ bits_in_buffer &= ~7;
+ }
+
+ public final boolean needsInput() {
+ return window_start == window_end;
+ }
+
+ /*
+ * Copies length bytes from input buffer to output buffer starting at
+ * output[offset]. You have to make sure, that the buffer is byte aligned.
+ * If not enough bytes are available, copies fewer bytes.
+ *
+ * @param length the length to copy, 0 is allowed.
+ *
+ * @return the number of bytes copied, 0 if no byte is available.
+ */
+ public int copyBytes(final byte[] output, int offset, int length) {
+ if (length < 0) {
+ throw new IllegalArgumentException("length negative");
+ }
+ if ((bits_in_buffer & 7) != 0) {
+ /* bits_in_buffer may only be 0 or 8 */
+ throw new IllegalStateException("Bit buffer is not aligned!");
+ }
+
+ int count = 0;
+ while ((bits_in_buffer > 0) && (length > 0)) {
+ output[offset++] = (byte) buffer;
+ buffer >>>= 8;
+ bits_in_buffer -= 8;
+ length--;
+ count++;
+ }
+ if (length == 0) {
+ return count;
+ }
+
+ final int avail = window_end - window_start;
+ if (length > avail) {
+ length = avail;
+ }
+ System.arraycopy(window, window_start, output, offset, length);
+ window_start += length;
+
+ if (((window_start - window_end) & 1) != 0) {
+ /* We always want an even number of bytes in input, see peekBits */
+ buffer = (window[window_start++] & 0xff);
+ bits_in_buffer = 8;
+ }
+ return count + length;
+ }
+
+ public StreamManipulator() {
+ }
+
+ public void reset() {
+ window_start = window_end = buffer = bits_in_buffer = 0;
+ }
+
+ public void setInput(final byte[] buf, int off, final int len) {
+ if (window_start < window_end) {
+ throw new IllegalStateException(
+ "Old input was not completely processed");
+ }
+
+ final int end = off + len;
+
+ /*
+ * We want to throw an ArrayIndexOutOfBoundsException early. The check
+ * is very tricky: it also handles integer wrap around.
+ */
+ if ((0 > off) || (off > end) || (end > buf.length)) {
+ throw new ArrayIndexOutOfBoundsException();
+ }
+
+ if ((len & 1) != 0) {
+ /* We always want an even number of bytes in input, see peekBits */
+ buffer |= (buf[off++] & 0xff) << bits_in_buffer;
+ bits_in_buffer += 8;
+ }
+
+ window = buf;
+ window_start = off;
+ window_end = end;
+ }
+}
diff --git a/epublib-core/src/main/java/net/sf/jazzlib/ZipConstants.java b/epublib-core/src/main/java/net/sf/jazzlib/ZipConstants.java
new file mode 100644
index 00000000..bc2a803c
--- /dev/null
+++ b/epublib-core/src/main/java/net/sf/jazzlib/ZipConstants.java
@@ -0,0 +1,95 @@
+/* net.sf.jazzlib.ZipConstants
+ Copyright (C) 2001 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath 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 2, or (at your option)
+any later version.
+
+GNU Classpath 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 GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+02111-1307 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+package net.sf.jazzlib;
+
+interface ZipConstants {
+ /* The local file header */
+ int LOCHDR = 30;
+ int LOCSIG = 'P' | ('K' << 8) | (3 << 16) | (4 << 24);
+
+ int LOCVER = 4;
+ int LOCFLG = 6;
+ int LOCHOW = 8;
+ int LOCTIM = 10;
+ int LOCCRC = 14;
+ int LOCSIZ = 18;
+ int LOCLEN = 22;
+ int LOCNAM = 26;
+ int LOCEXT = 28;
+
+ /* The Data descriptor */
+ int EXTSIG = 'P' | ('K' << 8) | (7 << 16) | (8 << 24);
+ int EXTHDR = 16;
+
+ int EXTCRC = 4;
+ int EXTSIZ = 8;
+ int EXTLEN = 12;
+
+ /* The central directory file header */
+ int CENSIG = 'P' | ('K' << 8) | (1 << 16) | (2 << 24);
+ int CENHDR = 46;
+
+ int CENVEM = 4;
+ int CENVER = 6;
+ int CENFLG = 8;
+ int CENHOW = 10;
+ int CENTIM = 12;
+ int CENCRC = 16;
+ int CENSIZ = 20;
+ int CENLEN = 24;
+ int CENNAM = 28;
+ int CENEXT = 30;
+ int CENCOM = 32;
+ int CENDSK = 34;
+ int CENATT = 36;
+ int CENATX = 38;
+ int CENOFF = 42;
+
+ /* The entries in the end of central directory */
+ int ENDSIG = 'P' | ('K' << 8) | (5 << 16) | (6 << 24);
+ int ENDHDR = 22;
+
+ /* The following two fields are missing in SUN JDK */
+ int ENDNRD = 4;
+ int ENDDCD = 6;
+ int ENDSUB = 8;
+ int ENDTOT = 10;
+ int ENDSIZ = 12;
+ int ENDOFF = 16;
+ int ENDCOM = 20;
+}
diff --git a/epublib-core/src/main/java/net/sf/jazzlib/ZipEntry.java b/epublib-core/src/main/java/net/sf/jazzlib/ZipEntry.java
new file mode 100644
index 00000000..33f5c9dd
--- /dev/null
+++ b/epublib-core/src/main/java/net/sf/jazzlib/ZipEntry.java
@@ -0,0 +1,409 @@
+/* net.sf.jazzlib.ZipEntry
+ Copyright (C) 2001, 2002 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath 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 2, or (at your option)
+any later version.
+
+GNU Classpath 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 GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+02111-1307 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+package net.sf.jazzlib;
+
+import java.util.Calendar;
+import java.util.Date;
+
+/**
+ * This class represents a member of a zip archive. ZipFile and ZipInputStream
+ * will give you instances of this class as information about the members in an
+ * archive. On the other hand ZipOutputStream needs an instance of this class to
+ * create a new member.
+ *
+ * @author Jochen Hoenicke
+ */
+public class ZipEntry implements ZipConstants, Cloneable {
+ private static int KNOWN_SIZE = 1;
+ private static int KNOWN_CSIZE = 2;
+ private static int KNOWN_CRC = 4;
+ private static int KNOWN_TIME = 8;
+
+ private static Calendar cal;
+
+ private final String name;
+ private int size;
+ private int compressedSize;
+ private int crc;
+ private int dostime;
+ private short known = 0;
+ private short method = -1;
+ private byte[] extra = null;
+ private String comment = null;
+
+ int flags; /* used by ZipOutputStream */
+ int offset; /* used by ZipFile and ZipOutputStream */
+
+ /**
+ * Compression method. This method doesn't compress at all.
+ */
+ public final static int STORED = 0;
+ /**
+ * Compression method. This method uses the Deflater.
+ */
+ public final static int DEFLATED = 8;
+
+ /**
+ * Creates a zip entry with the given name.
+ *
+ * @param name
+ * the name. May include directory components separated by '/'.
+ *
+ * @exception NullPointerException
+ * when name is null.
+ * @exception IllegalArgumentException
+ * when name is bigger then 65535 chars.
+ */
+ public ZipEntry(final String name) {
+ final int length = name.length();
+ if (length > 65535) {
+ throw new IllegalArgumentException("name length is " + length);
+ }
+ this.name = name;
+ }
+
+ /**
+ * Creates a copy of the given zip entry.
+ *
+ * @param e
+ * the entry to copy.
+ */
+ public ZipEntry(final ZipEntry e) {
+ name = e.name;
+ known = e.known;
+ size = e.size;
+ compressedSize = e.compressedSize;
+ crc = e.crc;
+ dostime = e.dostime;
+ method = e.method;
+ extra = e.extra;
+ comment = e.comment;
+ }
+
+ final void setDOSTime(final int dostime) {
+ this.dostime = dostime;
+ known |= KNOWN_TIME;
+ }
+
+ final int getDOSTime() {
+ if ((known & KNOWN_TIME) == 0) {
+ return 0;
+ } else {
+ return dostime;
+ }
+ }
+
+ /**
+ * Creates a copy of this zip entry.
+ */
+ /**
+ * Clones the entry.
+ */
+ @Override
+ public Object clone() {
+ try {
+ // The JCL says that the `extra' field is also copied.
+ final ZipEntry clone = (ZipEntry) super.clone();
+ if (extra != null) {
+ clone.extra = extra.clone();
+ }
+ return clone;
+ } catch (final CloneNotSupportedException ex) {
+ throw new InternalError();
+ }
+ }
+
+ /**
+ * Returns the entry name. The path components in the entry are always
+ * separated by slashes ('/').
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Sets the time of last modification of the entry.
+ *
+ * @time the time of last modification of the entry.
+ */
+ public void setTime(final long time) {
+ final Calendar cal = getCalendar();
+ synchronized (cal) {
+ cal.setTime(new Date(time * 1000L));
+ dostime = (((cal.get(Calendar.YEAR) - 1980) & 0x7f) << 25)
+ | ((cal.get(Calendar.MONTH) + 1) << 21)
+ | ((cal.get(Calendar.DAY_OF_MONTH)) << 16)
+ | ((cal.get(Calendar.HOUR_OF_DAY)) << 11)
+ | ((cal.get(Calendar.MINUTE)) << 5)
+ | ((cal.get(Calendar.SECOND)) >> 1);
+ }
+ dostime = (int) (dostime / 1000L);
+ this.known |= KNOWN_TIME;
+ }
+
+ /**
+ * Gets the time of last modification of the entry.
+ *
+ * @return the time of last modification of the entry, or -1 if unknown.
+ */
+ public long getTime() {
+ if ((known & KNOWN_TIME) == 0) {
+ return -1;
+ }
+
+ final int sec = 2 * (dostime & 0x1f);
+ final int min = (dostime >> 5) & 0x3f;
+ final int hrs = (dostime >> 11) & 0x1f;
+ final int day = (dostime >> 16) & 0x1f;
+ final int mon = ((dostime >> 21) & 0xf) - 1;
+ final int year = ((dostime >> 25) & 0x7f) + 1980; /* since 1900 */
+
+ try {
+ cal = getCalendar();
+ synchronized (cal) {
+ cal.set(year, mon, day, hrs, min, sec);
+ return cal.getTime().getTime();
+ }
+ } catch (final RuntimeException ex) {
+ /* Ignore illegal time stamp */
+ known &= ~KNOWN_TIME;
+ return -1;
+ }
+ }
+
+ private static synchronized Calendar getCalendar() {
+ if (cal == null) {
+ cal = Calendar.getInstance();
+ }
+
+ return cal;
+ }
+
+ /**
+ * Sets the size of the uncompressed data.
+ *
+ * @exception IllegalArgumentException
+ * if size is not in 0..0xffffffffL
+ */
+ public void setSize(final long size) {
+ if ((size & 0xffffffff00000000L) != 0) {
+ throw new IllegalArgumentException();
+ }
+ this.size = (int) size;
+ this.known |= KNOWN_SIZE;
+ }
+
+ /**
+ * Gets the size of the uncompressed data.
+ *
+ * @return the size or -1 if unknown.
+ */
+ public long getSize() {
+ return (known & KNOWN_SIZE) != 0 ? size & 0xffffffffL : -1L;
+ }
+
+ /**
+ * Sets the size of the compressed data.
+ *
+ * @exception IllegalArgumentException
+ * if size is not in 0..0xffffffffL
+ */
+ public void setCompressedSize(final long csize) {
+ if ((csize & 0xffffffff00000000L) != 0) {
+ throw new IllegalArgumentException();
+ }
+ this.compressedSize = (int) csize;
+ this.known |= KNOWN_CSIZE;
+ }
+
+ /**
+ * Gets the size of the compressed data.
+ *
+ * @return the size or -1 if unknown.
+ */
+ public long getCompressedSize() {
+ return (known & KNOWN_CSIZE) != 0 ? compressedSize & 0xffffffffL : -1L;
+ }
+
+ /**
+ * Sets the crc of the uncompressed data.
+ *
+ * @exception IllegalArgumentException
+ * if crc is not in 0..0xffffffffL
+ */
+ public void setCrc(final long crc) {
+ if ((crc & 0xffffffff00000000L) != 0) {
+ throw new IllegalArgumentException();
+ }
+ this.crc = (int) crc;
+ this.known |= KNOWN_CRC;
+ }
+
+ /**
+ * Gets the crc of the uncompressed data.
+ *
+ * @return the crc or -1 if unknown.
+ */
+ public long getCrc() {
+ return (known & KNOWN_CRC) != 0 ? crc & 0xffffffffL : -1L;
+ }
+
+ /**
+ * Sets the compression method. Only DEFLATED and STORED are supported.
+ *
+ * @exception IllegalArgumentException
+ * if method is not supported.
+ * @see ZipOutputStream#DEFLATED
+ * @see ZipOutputStream#STORED
+ */
+ public void setMethod(final int method) {
+ if ((method != ZipOutputStream.STORED)
+ && (method != ZipOutputStream.DEFLATED)) {
+ throw new IllegalArgumentException();
+ }
+ this.method = (short) method;
+ }
+
+ /**
+ * Gets the compression method.
+ *
+ * @return the compression method or -1 if unknown.
+ */
+ public int getMethod() {
+ return method;
+ }
+
+ /**
+ * Sets the extra data.
+ *
+ * @exception IllegalArgumentException
+ * if extra is longer than 0xffff bytes.
+ */
+ public void setExtra(final byte[] extra) {
+ if (extra == null) {
+ this.extra = null;
+ return;
+ }
+
+ if (extra.length > 0xffff) {
+ throw new IllegalArgumentException();
+ }
+ this.extra = extra;
+ try {
+ int pos = 0;
+ while (pos < extra.length) {
+ final int sig = (extra[pos++] & 0xff)
+ | ((extra[pos++] & 0xff) << 8);
+ final int len = (extra[pos++] & 0xff)
+ | ((extra[pos++] & 0xff) << 8);
+ if (sig == 0x5455) {
+ /* extended time stamp */
+ final int flags = extra[pos];
+ if ((flags & 1) != 0) {
+ final long time = ((extra[pos + 1] & 0xff)
+ | ((extra[pos + 2] & 0xff) << 8)
+ | ((extra[pos + 3] & 0xff) << 16) | ((extra[pos + 4] & 0xff) << 24));
+ setTime(time);
+ }
+ }
+ pos += len;
+ }
+ } catch (final ArrayIndexOutOfBoundsException ex) {
+ /* be lenient */
+ return;
+ }
+ }
+
+ /**
+ * Gets the extra data.
+ *
+ * @return the extra data or null if not set.
+ */
+ public byte[] getExtra() {
+ return extra;
+ }
+
+ /**
+ * Sets the entry comment.
+ *
+ * @exception IllegalArgumentException
+ * if comment is longer than 0xffff.
+ */
+ public void setComment(final String comment) {
+ if ((comment != null) && (comment.length() > 0xffff)) {
+ throw new IllegalArgumentException();
+ }
+ this.comment = comment;
+ }
+
+ /**
+ * Gets the comment.
+ *
+ * @return the comment or null if not set.
+ */
+ public String getComment() {
+ return comment;
+ }
+
+ /**
+ * Gets true, if the entry is a directory. This is solely determined by the
+ * name, a trailing slash '/' marks a directory.
+ */
+ public boolean isDirectory() {
+ final int nlen = name.length();
+ return (nlen > 0) && (name.charAt(nlen - 1) == '/');
+ }
+
+ /**
+ * Gets the string representation of this ZipEntry. This is just the name as
+ * returned by getName().
+ */
+ @Override
+ public String toString() {
+ return name;
+ }
+
+ /**
+ * Gets the hashCode of this ZipEntry. This is just the hashCode of the
+ * name. Note that the equals method isn't changed, though.
+ */
+ @Override
+ public int hashCode() {
+ return name.hashCode();
+ }
+}
diff --git a/epublib-core/src/main/java/net/sf/jazzlib/ZipException.java b/epublib-core/src/main/java/net/sf/jazzlib/ZipException.java
new file mode 100644
index 00000000..61d8b157
--- /dev/null
+++ b/epublib-core/src/main/java/net/sf/jazzlib/ZipException.java
@@ -0,0 +1,70 @@
+/* ZipException.java - exception representing a zip related error
+ Copyright (C) 1998, 1999, 2000, 2001, 2002 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath 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 2, or (at your option)
+any later version.
+
+GNU Classpath 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 GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+02111-1307 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+package net.sf.jazzlib;
+
+import java.io.IOException;
+
+/**
+ * Thrown during the creation or input of a zip file.
+ *
+ * @author Jochen Hoenicke
+ * @author Per Bothner
+ * @status updated to 1.4
+ */
+public class ZipException extends IOException {
+ /**
+ * Compatible with JDK 1.0+.
+ */
+ private static final long serialVersionUID = 8000196834066748623L;
+
+ /**
+ * Create an exception without a message.
+ */
+ public ZipException() {
+ }
+
+ /**
+ * Create an exception with a message.
+ *
+ * @param msg
+ * the message
+ */
+ public ZipException(final String msg) {
+ super(msg);
+ }
+}
diff --git a/epublib-core/src/main/java/net/sf/jazzlib/ZipFile.java b/epublib-core/src/main/java/net/sf/jazzlib/ZipFile.java
new file mode 100644
index 00000000..2b6b0482
--- /dev/null
+++ b/epublib-core/src/main/java/net/sf/jazzlib/ZipFile.java
@@ -0,0 +1,557 @@
+/* net.sf.jazzlib.ZipFile
+ Copyright (C) 2001, 2002, 2003 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath 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 2, or (at your option)
+any later version.
+
+GNU Classpath 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 GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+02111-1307 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+package net.sf.jazzlib;
+
+import java.io.BufferedInputStream;
+import java.io.DataInput;
+import java.io.EOFException;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.RandomAccessFile;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.NoSuchElementException;
+
+/**
+ * This class represents a Zip archive. You can ask for the contained entries,
+ * or get an input stream for a file entry. The entry is automatically
+ * decompressed.
+ *
+ * This class is thread safe: You can open input streams for arbitrary entries
+ * in different threads.
+ *
+ * @author Jochen Hoenicke
+ * @author Artur Biesiadowski
+ */
+public class ZipFile implements ZipConstants {
+
+ /**
+ * Mode flag to open a zip file for reading.
+ */
+ public static final int OPEN_READ = 0x1;
+
+ /**
+ * Mode flag to delete a zip file after reading.
+ */
+ public static final int OPEN_DELETE = 0x4;
+
+ // Name of this zip file.
+ private final String name;
+
+ // File from which zip entries are read.
+ private final RandomAccessFile raf;
+
+ // The entries of this zip file when initialized and not yet closed.
+ private Mapraf
.
+ *
+ * @exception IOException
+ * if a i/o error occured.
+ * @exception ZipException
+ * if the central directory is malformed
+ */
+ private void readEntries() throws ZipException, IOException {
+ /*
+ * Search for the End Of Central Directory. When a zip comment is
+ * present the directory may start earlier. FIXME: This searches the
+ * whole file in a very slow manner if the file isn't a zip file.
+ */
+ long pos = raf.length() - ENDHDR;
+ final byte[] ebs = new byte[CENHDR];
+
+ do {
+ if (pos < 0) {
+ throw new ZipException(
+ "central directory not found, probably not a zip file: "
+ + name);
+ }
+ raf.seek(pos--);
+ } while (readLeInt(raf, ebs) != ENDSIG);
+
+ if (raf.skipBytes(ENDTOT - ENDNRD) != (ENDTOT - ENDNRD)) {
+ throw new EOFException(name);
+ }
+ final int count = readLeShort(raf, ebs);
+ if (raf.skipBytes(ENDOFF - ENDSIZ) != (ENDOFF - ENDSIZ)) {
+ throw new EOFException(name);
+ }
+ final int centralOffset = readLeInt(raf, ebs);
+
+ entries = new HashMapclose()
method when this ZipFile has not yet been
+ * explicitly closed.
+ */
+ @Override
+ protected void finalize() throws IOException {
+ if (!closed && (raf != null)) {
+ close();
+ }
+ }
+
+ /**
+ * Returns an enumeration of all Zip entries in this Zip file.
+ */
+ public Enumeration entries() {
+ try {
+ return new ZipEntryEnumeration(getEntries().values().iterator());
+ } catch (final IOException ioe) {
+ return null;
+ }
+ }
+
+ /**
+ * Checks that the ZipFile is still open and reads entries when necessary.
+ *
+ * @exception IllegalStateException
+ * when the ZipFile has already been closed.
+ * @exception IOEexception
+ * when the entries could not be read.
+ */
+ private Mapprimary or auxiliary is useful for Reading Systems which + * opt to present auxiliary content differently than primary content. + * For example, a Reading System might opt to render auxiliary content in + * a popup window apart from the main window which presents the primary + * content. (For an example of the types of content that may be considered + * auxiliary, refer to the example below and the subsequent discussion.)+ * @see OPF Spine specification + * + * @return whether the section is Primary or Auxiliary. + */ + public boolean isLinear() { + return linear; + } + + public void setLinear(boolean linear) { + this.linear = linear; + } + +} diff --git a/epublib-core/src/main/java/nl/siegmann/epublib/domain/TOCReference.java b/epublib-core/src/main/java/nl/siegmann/epublib/domain/TOCReference.java new file mode 100644 index 00000000..5dae8aa1 --- /dev/null +++ b/epublib-core/src/main/java/nl/siegmann/epublib/domain/TOCReference.java @@ -0,0 +1,64 @@ +package nl.siegmann.epublib.domain; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +/** + * An item in the Table of Contents. + * + * @see nl.siegmann.epublib.domain.TableOfContents + * + * @author paul + * + */ +public class TOCReference extends TitledResourceReference implements Serializable { + + /** + * + */ + private static final long serialVersionUID = 5787958246077042456L; + private List
+ * In its own separate class because the PackageDocumentReader became a bit large and unwieldy. + * + * @author paul + */ +// package +class PackageDocumentMetadataReader extends PackageDocumentBase { + + private static final Logger log = LoggerFactory.getLogger(PackageDocumentMetadataReader.class); + + public static Metadata readMetadata(Document packageDocument) { + Metadata result = new Metadata(); + Element metadataElement = DOMUtil.getFirstElementByTagNameNS(packageDocument.getDocumentElement(), NAMESPACE_OPF, OPFTags.metadata); + if (metadataElement == null) { + log.error("Package does not contain element " + OPFTags.metadata); + return result; + } + result.setTitles(readTitles(metadataElement)); + result.setPublishers(DOMUtil.getElementsTextChild(metadataElement, NAMESPACE_DUBLIN_CORE, DCTags.publisher)); + result.setDescriptions(DOMUtil.getElementsTextChild(metadataElement, NAMESPACE_DUBLIN_CORE, DCTags.description)); + result.setRights(DOMUtil.getElementsTextChild(metadataElement, NAMESPACE_DUBLIN_CORE, DCTags.rights)); + result.setTypes(DOMUtil.getElementsTextChild(metadataElement, NAMESPACE_DUBLIN_CORE, DCTags.type)); + result.setSubjects(DOMUtil.getElementsTextChild(metadataElement, NAMESPACE_DUBLIN_CORE, DCTags.subject)); + result.setIdentifiers(readIdentifiers(metadataElement)); + result.setAuthors(readCreators(metadataElement)); + result.setContributors(readContributors(metadataElement)); + result.setDates(readDates(metadataElement)); + result.setOtherProperties(readOtherProperties(metadataElement)); + result.setMetaAttributes(readMetaProperties(metadataElement)); + Element languageTag = DOMUtil.getFirstElementByTagNameNS(metadataElement, NAMESPACE_DUBLIN_CORE, DCTags.language); + if (languageTag != null) { + result.setLanguage(DOMUtil.getTextChildrenContent(languageTag)); + } + + + return result; + } + + private static List
+ * Example:
+ * If the packageHref is "OEBPS/content.opf" then a resource href like "OEBPS/foo/bar.html" will be turned into "foo/bar.html"
+ *
+ * @param packageHref
+ * @param resourcesByHref
+ * @return The stripped package href
+ */
+ static Resources fixHrefs(String packageHref,
+ Resources resourcesByHref) {
+ int lastSlashPos = packageHref.lastIndexOf('/');
+ if (lastSlashPos < 0) {
+ return resourcesByHref;
+ }
+ Resources result = new Resources();
+ for (Resource resource : resourcesByHref.getAll()) {
+ if (StringUtil.isNotBlank(resource.getHref())
+ && resource.getHref().length() > lastSlashPos) {
+ resource.setHref(resource.getHref().substring(lastSlashPos + 1));
+ }
+ result.add(resource);
+ }
+ return result;
+ }
+
+ /**
+ * Reads the document's spine, containing all sections in reading order.
+ *
+ * @param packageDocument
+ * @param epubReader
+ * @param book
+ * @param resourcesById
+ * @return the document's spine, containing all sections in reading order.
+ */
+ private static Spine readSpine(Document packageDocument, Resources resources, Map
+ * Here we try several ways of finding this table of contents resource.
+ * We try the given attribute value, some often-used ones and finally look through all resources for the first resource with the table of contents mimetype.
+ *
+ * @param spineElement
+ * @param resourcesById
+ * @return the Resource containing the table of contents
+ */
+ static Resource findTableOfContentsResource(String tocResourceId, Resources resources) {
+ Resource tocResource = null;
+ if (StringUtil.isNotBlank(tocResourceId)) {
+ tocResource = resources.getByIdOrHref(tocResourceId);
+ }
+
+ if (tocResource != null) {
+ return tocResource;
+ }
+
+ // get the first resource with the NCX mediatype
+ tocResource = resources.findFirstResourceByMediaType(MediatypeService.NCX);
+
+ if (tocResource == null) {
+ for (int i = 0; i < POSSIBLE_NCX_ITEM_IDS.length; i++) {
+ tocResource = resources.getByIdOrHref(POSSIBLE_NCX_ITEM_IDS[i]);
+ if (tocResource != null) {
+ break;
+ }
+ tocResource = resources.getByIdOrHref(POSSIBLE_NCX_ITEM_IDS[i].toUpperCase());
+ if (tocResource != null) {
+ break;
+ }
+ }
+ }
+
+ if (tocResource == null) {
+ log.error("Could not find table of contents resource. Tried resource with id '" + tocResourceId + "', " + Constants.DEFAULT_TOC_ID + ", " + Constants.DEFAULT_TOC_ID.toUpperCase() + " and any NCX resource.");
+ }
+ return tocResource;
+ }
+
+
+ /**
+ * Find all resources that have something to do with the coverpage and the cover image.
+ * Search the meta tags and the guide references
+ *
+ * @param packageDocument
+ * @return all resources that have something to do with the coverpage and the cover image.
+ */
+ // package
+ static Set
+ * It is an alternative base class to FilterInputStream
+ * to increase reusability, because FilterInputStream changes the
+ * methods being called, such as read(byte[]) to read(byte[], int, int).
+ *
+ * See the protected methods for ways in which a subclass can easily decorate
+ * a stream with custom pre-, post- or error processing functionality.
+ *
+ * @author Stephen Colebourne
+ * @version $Id: ProxyInputStream.java 934041 2010-04-14 17:37:24Z jukka $
+ */
+public abstract class ProxyInputStream extends FilterInputStream {
+
+ /**
+ * Constructs a new ProxyInputStream.
+ *
+ * @param proxy the InputStream to delegate to
+ */
+ public ProxyInputStream(InputStream proxy) {
+ super(proxy);
+ // the proxy is stored in a protected superclass variable named 'in'
+ }
+
+ /**
+ * Invokes the delegate's
+ * Subclasses can override this method to add common pre-processing
+ * functionality without having to override all the read methods.
+ * The default implementation does nothing.
+ *
+ * Note this method is not called from {@link #skip(long)} or
+ * {@link #reset()}. You need to explicitly override those methods if
+ * you want to add pre-processing steps also to them.
+ *
+ * @since Commons IO 2.0
+ * @param n number of bytes that the caller asked to be read
+ * @throws IOException if the pre-processing fails
+ */
+ protected void beforeRead(int n) throws IOException {
+ }
+
+ /**
+ * Invoked by the read methods after the proxied call has returned
+ * successfully. The number of bytes returned to the caller (or -1 if
+ * the end of stream was reached) is given as an argument.
+ *
+ * Subclasses can override this method to add common post-processing
+ * functionality without having to override all the read methods.
+ * The default implementation does nothing.
+ *
+ * Note this method is not called from {@link #skip(long)} or
+ * {@link #reset()}. You need to explicitly override those methods if
+ * you want to add post-processing steps also to them.
+ *
+ * @since Commons IO 2.0
+ * @param n number of bytes read, or -1 if the end of stream was reached
+ * @throws IOException if the post-processing fails
+ */
+ protected void afterRead(int n) throws IOException {
+ }
+
+ /**
+ * Handle any IOExceptions thrown.
+ *
+ * This method provides a point to implement custom exception
+ * handling. The default behaviour is to re-throw the exception.
+ * @param e The IOException thrown
+ * @throws IOException if an I/O error occurs
+ * @since Commons IO 2.0
+ */
+ protected void handleIOException(IOException e) throws IOException {
+ throw e;
+ }
+
+}
diff --git a/epublib-core/src/main/java/nl/siegmann/epublib/util/commons/io/XmlStreamReader.java b/epublib-core/src/main/java/nl/siegmann/epublib/util/commons/io/XmlStreamReader.java
new file mode 100644
index 00000000..1a5f18c9
--- /dev/null
+++ b/epublib-core/src/main/java/nl/siegmann/epublib/util/commons/io/XmlStreamReader.java
@@ -0,0 +1,752 @@
+package nl.siegmann.epublib.util.commons.io;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.io.BufferedInputStream;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.StringReader;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.net.URLConnection;
+import java.text.MessageFormat;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+
+/**
+ * Character stream that handles all the necessary Voodo to figure out the
+ * charset encoding of the XML document within the stream.
+ *
+ * IMPORTANT: This class is not related in any way to the org.xml.sax.XMLReader.
+ * This one IS a character stream.
+ *
+ * All this has to be done without consuming characters from the stream, if not
+ * the XML parser will not recognized the document as a valid XML. This is not
+ * 100% true, but it's close enough (UTF-8 BOM is not handled by all parsers
+ * right now, XmlStreamReader handles it and things work in all parsers).
+ *
+ * The XmlStreamReader class handles the charset encoding of XML documents in
+ * Files, raw streams and HTTP streams by offering a wide set of constructors.
+ *
+ * By default the charset encoding detection is lenient, the constructor with
+ * the lenient flag can be used for an script (following HTTP MIME and XML
+ * specifications). All this is nicely explained by Mark Pilgrim in his blog,
+ * Determining the character encoding of a feed.
+ *
+ * Originally developed for ROME under
+ * Apache License 2.0.
+ *
+ * @author Alejandro Abdelnur
+ * @version $Id: XmlStreamReader.java 1052161 2010-12-23 03:12:09Z niallp $
+ * @see org.apache.commons.io.output.XmlStreamWriter
+ * @since Commons IO 2.0
+ */
+public class XmlStreamReader extends Reader {
+ private static final int BUFFER_SIZE = 4096;
+
+ private static final String UTF_8 = "UTF-8";
+
+ private static final String US_ASCII = "US-ASCII";
+
+ private static final String UTF_16BE = "UTF-16BE";
+
+ private static final String UTF_16LE = "UTF-16LE";
+
+ private static final String UTF_16 = "UTF-16";
+
+ private static final String EBCDIC = "CP1047";
+
+ private static final ByteOrderMark[] BOMS = new ByteOrderMark[] {
+ ByteOrderMark.UTF_8,
+ ByteOrderMark.UTF_16BE,
+ ByteOrderMark.UTF_16LE
+ };
+ private static final ByteOrderMark[] XML_GUESS_BYTES = new ByteOrderMark[] {
+ new ByteOrderMark(UTF_8, 0x3C, 0x3F, 0x78, 0x6D),
+ new ByteOrderMark(UTF_16BE, 0x00, 0x3C, 0x00, 0x3F),
+ new ByteOrderMark(UTF_16LE, 0x3C, 0x00, 0x3F, 0x00),
+ new ByteOrderMark(EBCDIC, 0x4C, 0x6F, 0xA7, 0x94)
+ };
+
+
+ private final Reader reader;
+
+ private final String encoding;
+
+ private final String defaultEncoding;
+
+ /**
+ * Returns the default encoding to use if none is set in HTTP content-type,
+ * XML prolog and the rules based on content-type are not adequate.
+ *
+ * If it is NULL the content-type based rules are used.
+ *
+ * @return the default encoding to use.
+ */
+ public String getDefaultEncoding() {
+ return defaultEncoding;
+ }
+
+ /**
+ * Creates a Reader for a File.
+ *
+ * It looks for the UTF-8 BOM first, if none sniffs the XML prolog charset,
+ * if this is also missing defaults to UTF-8.
+ *
+ * It does a lenient charset encoding detection, check the constructor with
+ * the lenient parameter for details.
+ *
+ * @param file File to create a Reader from.
+ * @throws IOException thrown if there is a problem reading the file.
+ */
+ public XmlStreamReader(File file) throws IOException {
+ this(new FileInputStream(file));
+ }
+
+ /**
+ * Creates a Reader for a raw InputStream.
+ *
+ * It follows the same logic used for files.
+ *
+ * It does a lenient charset encoding detection, check the constructor with
+ * the lenient parameter for details.
+ *
+ * @param is InputStream to create a Reader from.
+ * @throws IOException thrown if there is a problem reading the stream.
+ */
+ public XmlStreamReader(InputStream is) throws IOException {
+ this(is, true);
+ }
+
+ /**
+ * Creates a Reader for a raw InputStream.
+ *
+ * It follows the same logic used for files.
+ *
+ * If lenient detection is indicated and the detection above fails as per
+ * specifications it then attempts the following:
+ *
+ * If the content type was 'text/html' it replaces it with 'text/xml' and
+ * tries the detection again.
+ *
+ * Else if the XML prolog had a charset encoding that encoding is used.
+ *
+ * Else if the content type had a charset encoding that encoding is used.
+ *
+ * Else 'UTF-8' is used.
+ *
+ * If lenient detection is indicated an XmlStreamReaderException is never
+ * thrown.
+ *
+ * @param is InputStream to create a Reader from.
+ * @param lenient indicates if the charset encoding detection should be
+ * relaxed.
+ * @throws IOException thrown if there is a problem reading the stream.
+ * @throws XmlStreamReaderException thrown if the charset encoding could not
+ * be determined according to the specs.
+ */
+ public XmlStreamReader(InputStream is, boolean lenient) throws IOException {
+ this(is, lenient, null);
+ }
+
+ /**
+ * Creates a Reader for a raw InputStream.
+ *
+ * It follows the same logic used for files.
+ *
+ * If lenient detection is indicated and the detection above fails as per
+ * specifications it then attempts the following:
+ *
+ * If the content type was 'text/html' it replaces it with 'text/xml' and
+ * tries the detection again.
+ *
+ * Else if the XML prolog had a charset encoding that encoding is used.
+ *
+ * Else if the content type had a charset encoding that encoding is used.
+ *
+ * Else 'UTF-8' is used.
+ *
+ * If lenient detection is indicated an XmlStreamReaderException is never
+ * thrown.
+ *
+ * @param is InputStream to create a Reader from.
+ * @param lenient indicates if the charset encoding detection should be
+ * relaxed.
+ * @param defaultEncoding The default encoding
+ * @throws IOException thrown if there is a problem reading the stream.
+ * @throws XmlStreamReaderException thrown if the charset encoding could not
+ * be determined according to the specs.
+ */
+ public XmlStreamReader(InputStream is, boolean lenient, String defaultEncoding) throws IOException {
+ this.defaultEncoding = defaultEncoding;
+ BOMInputStream bom = new BOMInputStream(new BufferedInputStream(is, BUFFER_SIZE), false, BOMS);
+ BOMInputStream pis = new BOMInputStream(bom, true, XML_GUESS_BYTES);
+ this.encoding = doRawStream(bom, pis, lenient);
+ this.reader = new InputStreamReader(pis, encoding);
+ }
+
+ /**
+ * Creates a Reader using the InputStream of a URL.
+ *
+ * If the URL is not of type HTTP and there is not 'content-type' header in
+ * the fetched data it uses the same logic used for Files.
+ *
+ * If the URL is a HTTP Url or there is a 'content-type' header in the
+ * fetched data it uses the same logic used for an InputStream with
+ * content-type.
+ *
+ * It does a lenient charset encoding detection, check the constructor with
+ * the lenient parameter for details.
+ *
+ * @param url URL to create a Reader from.
+ * @throws IOException thrown if there is a problem reading the stream of
+ * the URL.
+ */
+ public XmlStreamReader(URL url) throws IOException {
+ this(url.openConnection(), null);
+ }
+
+ /**
+ * Creates a Reader using the InputStream of a URLConnection.
+ *
+ * If the URLConnection is not of type HttpURLConnection and there is not
+ * 'content-type' header in the fetched data it uses the same logic used for
+ * files.
+ *
+ * If the URLConnection is a HTTP Url or there is a 'content-type' header in
+ * the fetched data it uses the same logic used for an InputStream with
+ * content-type.
+ *
+ * It does a lenient charset encoding detection, check the constructor with
+ * the lenient parameter for details.
+ *
+ * @param conn URLConnection to create a Reader from.
+ * @param defaultEncoding The default encoding
+ * @throws IOException thrown if there is a problem reading the stream of
+ * the URLConnection.
+ */
+ public XmlStreamReader(URLConnection conn, String defaultEncoding) throws IOException {
+ this.defaultEncoding = defaultEncoding;
+ boolean lenient = true;
+ String contentType = conn.getContentType();
+ InputStream is = conn.getInputStream();
+ BOMInputStream bom = new BOMInputStream(new BufferedInputStream(is, BUFFER_SIZE), false, BOMS);
+ BOMInputStream pis = new BOMInputStream(bom, true, XML_GUESS_BYTES);
+ if (conn instanceof HttpURLConnection || contentType != null) {
+ this.encoding = doHttpStream(bom, pis, contentType, lenient);
+ } else {
+ this.encoding = doRawStream(bom, pis, lenient);
+ }
+ this.reader = new InputStreamReader(pis, encoding);
+ }
+
+ /**
+ * Creates a Reader using an InputStream an the associated content-type
+ * header.
+ *
+ * First it checks if the stream has BOM. If there is not BOM checks the
+ * content-type encoding. If there is not content-type encoding checks the
+ * XML prolog encoding. If there is not XML prolog encoding uses the default
+ * encoding mandated by the content-type MIME type.
+ *
+ * It does a lenient charset encoding detection, check the constructor with
+ * the lenient parameter for details.
+ *
+ * @param is InputStream to create the reader from.
+ * @param httpContentType content-type header to use for the resolution of
+ * the charset encoding.
+ * @throws IOException thrown if there is a problem reading the file.
+ */
+ public XmlStreamReader(InputStream is, String httpContentType)
+ throws IOException {
+ this(is, httpContentType, true);
+ }
+
+ /**
+ * Creates a Reader using an InputStream an the associated content-type
+ * header. This constructor is lenient regarding the encoding detection.
+ *
+ * First it checks if the stream has BOM. If there is not BOM checks the
+ * content-type encoding. If there is not content-type encoding checks the
+ * XML prolog encoding. If there is not XML prolog encoding uses the default
+ * encoding mandated by the content-type MIME type.
+ *
+ * If lenient detection is indicated and the detection above fails as per
+ * specifications it then attempts the following:
+ *
+ * If the content type was 'text/html' it replaces it with 'text/xml' and
+ * tries the detection again.
+ *
+ * Else if the XML prolog had a charset encoding that encoding is used.
+ *
+ * Else if the content type had a charset encoding that encoding is used.
+ *
+ * Else 'UTF-8' is used.
+ *
+ * If lenient detection is indicated an XmlStreamReaderException is never
+ * thrown.
+ *
+ * @param is InputStream to create the reader from.
+ * @param httpContentType content-type header to use for the resolution of
+ * the charset encoding.
+ * @param lenient indicates if the charset encoding detection should be
+ * relaxed.
+ * @param defaultEncoding The default encoding
+ * @throws IOException thrown if there is a problem reading the file.
+ * @throws XmlStreamReaderException thrown if the charset encoding could not
+ * be determined according to the specs.
+ */
+ public XmlStreamReader(InputStream is, String httpContentType,
+ boolean lenient, String defaultEncoding) throws IOException {
+ this.defaultEncoding = defaultEncoding;
+ BOMInputStream bom = new BOMInputStream(new BufferedInputStream(is, BUFFER_SIZE), false, BOMS);
+ BOMInputStream pis = new BOMInputStream(bom, true, XML_GUESS_BYTES);
+ this.encoding = doHttpStream(bom, pis, httpContentType, lenient);
+ this.reader = new InputStreamReader(pis, encoding);
+ }
+
+ /**
+ * Creates a Reader using an InputStream an the associated content-type
+ * header. This constructor is lenient regarding the encoding detection.
+ *
+ * First it checks if the stream has BOM. If there is not BOM checks the
+ * content-type encoding. If there is not content-type encoding checks the
+ * XML prolog encoding. If there is not XML prolog encoding uses the default
+ * encoding mandated by the content-type MIME type.
+ *
+ * If lenient detection is indicated and the detection above fails as per
+ * specifications it then attempts the following:
+ *
+ * If the content type was 'text/html' it replaces it with 'text/xml' and
+ * tries the detection again.
+ *
+ * Else if the XML prolog had a charset encoding that encoding is used.
+ *
+ * Else if the content type had a charset encoding that encoding is used.
+ *
+ * Else 'UTF-8' is used.
+ *
+ * If lenient detection is indicated an XmlStreamReaderException is never
+ * thrown.
+ *
+ * @param is InputStream to create the reader from.
+ * @param httpContentType content-type header to use for the resolution of
+ * the charset encoding.
+ * @param lenient indicates if the charset encoding detection should be
+ * relaxed.
+ * @throws IOException thrown if there is a problem reading the file.
+ * @throws XmlStreamReaderException thrown if the charset encoding could not
+ * be determined according to the specs.
+ */
+ public XmlStreamReader(InputStream is, String httpContentType,
+ boolean lenient) throws IOException {
+ this(is, httpContentType, lenient, null);
+ }
+
+ /**
+ * Returns the charset encoding of the XmlStreamReader.
+ *
+ * @return charset encoding.
+ */
+ public String getEncoding() {
+ return encoding;
+ }
+
+ /**
+ * Invokes the underlying reader's
+ * The exception returns the unconsumed InputStream to allow the application to
+ * do an alternate processing with the stream. Note that the original
+ * InputStream given to the XmlStreamReader cannot be used as that one has been
+ * already read.
+ *
+ * @author Alejandro Abdelnur
+ * @version $Id: XmlStreamReaderException.java 1004112 2010-10-04 04:48:25Z niallp $
+ * @since Commons IO 2.0
+ */
+public class XmlStreamReaderException extends IOException {
+
+ private static final long serialVersionUID = 1L;
+
+ private final String bomEncoding;
+
+ private final String xmlGuessEncoding;
+
+ private final String xmlEncoding;
+
+ private final String contentTypeMime;
+
+ private final String contentTypeEncoding;
+
+ /**
+ * Creates an exception instance if the charset encoding could not be
+ * determined.
+ *
+ * Instances of this exception are thrown by the XmlStreamReader.
+ *
+ * @param msg message describing the reason for the exception.
+ * @param bomEnc BOM encoding.
+ * @param xmlGuessEnc XML guess encoding.
+ * @param xmlEnc XML prolog encoding.
+ */
+ public XmlStreamReaderException(String msg, String bomEnc,
+ String xmlGuessEnc, String xmlEnc) {
+ this(msg, null, null, bomEnc, xmlGuessEnc, xmlEnc);
+ }
+
+ /**
+ * Creates an exception instance if the charset encoding could not be
+ * determined.
+ *
+ * Instances of this exception are thrown by the XmlStreamReader.
+ *
+ * @param msg message describing the reason for the exception.
+ * @param ctMime MIME type in the content-type.
+ * @param ctEnc encoding in the content-type.
+ * @param bomEnc BOM encoding.
+ * @param xmlGuessEnc XML guess encoding.
+ * @param xmlEnc XML prolog encoding.
+ */
+ public XmlStreamReaderException(String msg, String ctMime, String ctEnc,
+ String bomEnc, String xmlGuessEnc, String xmlEnc) {
+ super(msg);
+ contentTypeMime = ctMime;
+ contentTypeEncoding = ctEnc;
+ bomEncoding = bomEnc;
+ xmlGuessEncoding = xmlGuessEnc;
+ xmlEncoding = xmlEnc;
+ }
+
+ /**
+ * Returns the BOM encoding found in the InputStream.
+ *
+ * @return the BOM encoding, null if none.
+ */
+ public String getBomEncoding() {
+ return bomEncoding;
+ }
+
+ /**
+ * Returns the encoding guess based on the first bytes of the InputStream.
+ *
+ * @return the encoding guess, null if it couldn't be guessed.
+ */
+ public String getXmlGuessEncoding() {
+ return xmlGuessEncoding;
+ }
+
+ /**
+ * Returns the encoding found in the XML prolog of the InputStream.
+ *
+ * @return the encoding of the XML prolog, null if none.
+ */
+ public String getXmlEncoding() {
+ return xmlEncoding;
+ }
+
+ /**
+ * Returns the MIME type in the content-type used to attempt determining the
+ * encoding.
+ *
+ * @return the MIME type in the content-type, null if there was not
+ * content-type or the encoding detection did not involve HTTP.
+ */
+ public String getContentTypeMime() {
+ return contentTypeMime;
+ }
+
+ /**
+ * Returns the encoding in the content-type used to attempt determining the
+ * encoding.
+ *
+ * @return the encoding in the content-type, null if there was not
+ * content-type, no encoding in it or the encoding detection did not
+ * involve HTTP.
+ */
+ public String getContentTypeEncoding() {
+ return contentTypeEncoding;
+ }
+}
diff --git a/epublib-core/src/main/java/nl/siegmann/epublib/utilities/StreamWriterDelegate.java b/epublib-core/src/main/java/nl/siegmann/epublib/utilities/StreamWriterDelegate.java
new file mode 100644
index 00000000..9313b0fc
--- /dev/null
+++ b/epublib-core/src/main/java/nl/siegmann/epublib/utilities/StreamWriterDelegate.java
@@ -0,0 +1,202 @@
+package nl.siegmann.epublib.utilities;
+/*
+ * Copyright (c) 2006, John Kristian
+ * 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 StAX-Utils 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.
+ *
+ */
+
+import javax.xml.namespace.NamespaceContext;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+
+/**
+ * Abstract class for writing filtered XML streams. This class provides methods
+ * that merely delegate to the contained stream. Subclasses should override some
+ * of these methods, and may also provide additional methods and fields.
+ *
+ * @author John Kristian
+ */
+public abstract class StreamWriterDelegate implements XMLStreamWriter {
+
+ protected StreamWriterDelegate(XMLStreamWriter out) {
+ this .out = out;
+ }
+
+ protected XMLStreamWriter out;
+
+ public Object getProperty(String name)
+ throws IllegalArgumentException {
+ return out.getProperty(name);
+ }
+
+ public NamespaceContext getNamespaceContext() {
+ return out.getNamespaceContext();
+ }
+
+ public void setNamespaceContext(NamespaceContext context)
+ throws XMLStreamException {
+ out.setNamespaceContext(context);
+ }
+
+ public void setDefaultNamespace(String uri)
+ throws XMLStreamException {
+ out.setDefaultNamespace(uri);
+ }
+
+ public void writeStartDocument() throws XMLStreamException {
+ out.writeStartDocument();
+ }
+
+ public void writeStartDocument(String version)
+ throws XMLStreamException {
+ out.writeStartDocument(version);
+ }
+
+ public void writeStartDocument(String encoding, String version)
+ throws XMLStreamException {
+ out.writeStartDocument(encoding, version);
+ }
+
+ public void writeDTD(String dtd) throws XMLStreamException {
+ out.writeDTD(dtd);
+ }
+
+ public void writeProcessingInstruction(String target)
+ throws XMLStreamException {
+ out.writeProcessingInstruction(target);
+ }
+
+ public void writeProcessingInstruction(String target, String data)
+ throws XMLStreamException {
+ out.writeProcessingInstruction(target, data);
+ }
+
+ public void writeComment(String data) throws XMLStreamException {
+ out.writeComment(data);
+ }
+
+ public void writeEmptyElement(String localName)
+ throws XMLStreamException {
+ out.writeEmptyElement(localName);
+ }
+
+ public void writeEmptyElement(String namespaceURI, String localName)
+ throws XMLStreamException {
+ out.writeEmptyElement(namespaceURI, localName);
+ }
+
+ public void writeEmptyElement(String prefix, String localName,
+ String namespaceURI) throws XMLStreamException {
+ out.writeEmptyElement(prefix, localName, namespaceURI);
+ }
+
+ public void writeStartElement(String localName)
+ throws XMLStreamException {
+ out.writeStartElement(localName);
+ }
+
+ public void writeStartElement(String namespaceURI, String localName)
+ throws XMLStreamException {
+ out.writeStartElement(namespaceURI, localName);
+ }
+
+ public void writeStartElement(String prefix, String localName,
+ String namespaceURI) throws XMLStreamException {
+ out.writeStartElement(prefix, localName, namespaceURI);
+ }
+
+ public void writeDefaultNamespace(String namespaceURI)
+ throws XMLStreamException {
+ out.writeDefaultNamespace(namespaceURI);
+ }
+
+ public void writeNamespace(String prefix, String namespaceURI)
+ throws XMLStreamException {
+ out.writeNamespace(prefix, namespaceURI);
+ }
+
+ public String getPrefix(String uri) throws XMLStreamException {
+ return out.getPrefix(uri);
+ }
+
+ public void setPrefix(String prefix, String uri)
+ throws XMLStreamException {
+ out.setPrefix(prefix, uri);
+ }
+
+ public void writeAttribute(String localName, String value)
+ throws XMLStreamException {
+ out.writeAttribute(localName, value);
+ }
+
+ public void writeAttribute(String namespaceURI, String localName,
+ String value) throws XMLStreamException {
+ out.writeAttribute(namespaceURI, localName, value);
+ }
+
+ public void writeAttribute(String prefix, String namespaceURI,
+ String localName, String value) throws XMLStreamException {
+ out.writeAttribute(prefix, namespaceURI, localName, value);
+ }
+
+ public void writeCharacters(String text) throws XMLStreamException {
+ out.writeCharacters(text);
+ }
+
+ public void writeCharacters(char[] text, int start, int len)
+ throws XMLStreamException {
+ out.writeCharacters(text, start, len);
+ }
+
+ public void writeCData(String data) throws XMLStreamException {
+ out.writeCData(data);
+ }
+
+ public void writeEntityRef(String name) throws XMLStreamException {
+ out.writeEntityRef(name);
+ }
+
+ public void writeEndElement() throws XMLStreamException {
+ out.writeEndElement();
+ }
+
+ public void writeEndDocument() throws XMLStreamException {
+ out.writeEndDocument();
+ }
+
+ public void flush() throws XMLStreamException {
+ out.flush();
+ }
+
+ public void close() throws XMLStreamException {
+ out.close();
+ }
+
+}
+
diff --git a/epublib-core/src/main/resources/dtd/openebook.org/dtds/oeb-1.2/oeb12.ent b/epublib-core/src/main/resources/dtd/openebook.org/dtds/oeb-1.2/oeb12.ent
new file mode 100644
index 00000000..f7b58d25
--- /dev/null
+++ b/epublib-core/src/main/resources/dtd/openebook.org/dtds/oeb-1.2/oeb12.ent
@@ -0,0 +1,1135 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/epublib-core/src/main/resources/dtd/openebook.org/dtds/oeb-1.2/oebpkg12.dtd b/epublib-core/src/main/resources/dtd/openebook.org/dtds/oeb-1.2/oebpkg12.dtd
new file mode 100644
index 00000000..34cc2b10
--- /dev/null
+++ b/epublib-core/src/main/resources/dtd/openebook.org/dtds/oeb-1.2/oebpkg12.dtd
@@ -0,0 +1,390 @@
+
+
+
+
+
+
+
+
+
+%OEBEntities;
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/epublib-core/src/main/resources/dtd/www.daisy.org/z3986/2005/ncx-2005-1.dtd b/epublib-core/src/main/resources/dtd/www.daisy.org/z3986/2005/ncx-2005-1.dtd
new file mode 100644
index 00000000..b889c41a
--- /dev/null
+++ b/epublib-core/src/main/resources/dtd/www.daisy.org/z3986/2005/ncx-2005-1.dtd
@@ -0,0 +1,269 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/epublib-core/src/main/resources/dtd/www.w3.org/TR/ruby/xhtml-ruby-1.mod b/epublib-core/src/main/resources/dtd/www.w3.org/TR/ruby/xhtml-ruby-1.mod
new file mode 100644
index 00000000..a44bb3fa
--- /dev/null
+++ b/epublib-core/src/main/resources/dtd/www.w3.org/TR/ruby/xhtml-ruby-1.mod
@@ -0,0 +1,242 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+]]>
+
+]]>
+
+
+
+]]>
+
+
+
+
+
+
+
+
+
+
+
+
+
+]]>
+
+
+
+
+
+
+]]>
+
+
+
+
+
+
+]]>
+]]>
+
+
+
+
+
+
+
+]]>
+
+
+
+
+
+
+
+
+]]>
+
+
+
+
+]]>
+]]>
+
+
+
+
+
+
+]]>
+]]>
+
+
+
+
+
+
+
+
+
+
+]]>
+
+
+
+
+
+]]>
+
+
+
+
+
+]]>
+]]>
+
+
+
+
+
+]]>
+
+
+
+
+
+]]>
+
+
+
+
+
+]]>
+]]>
+]]>
+
+
diff --git a/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-arch-1.mod b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-arch-1.mod
new file mode 100644
index 00000000..4a4fa6ca
--- /dev/null
+++ b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-arch-1.mod
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-attribs-1.mod b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-attribs-1.mod
new file mode 100644
index 00000000..104e5700
--- /dev/null
+++ b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-attribs-1.mod
@@ -0,0 +1,142 @@
+
+
+
+
+
+
+
+
+
+]]>
+
+
+
+
+]]>
+
+
+
+
+]]>
+
+
+
+
+
+
+
+
+]]>
+
+
+
+
+
+
+
+
+
+
+
+]]>
+
+
+]]>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-base-1.mod b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-base-1.mod
new file mode 100644
index 00000000..dca21ca0
--- /dev/null
+++ b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-base-1.mod
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+]]>
+
+
+
+]]>
+
+
+
+
diff --git a/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-bdo-1.mod b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-bdo-1.mod
new file mode 100644
index 00000000..fcd67bf6
--- /dev/null
+++ b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-bdo-1.mod
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+]]>
+
+
+
+]]>
+
+
diff --git a/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-blkphras-1.mod b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-blkphras-1.mod
new file mode 100644
index 00000000..0eeb1641
--- /dev/null
+++ b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-blkphras-1.mod
@@ -0,0 +1,164 @@
+
+
+
+
+
+
+
+
+
+
+]]>
+
+
+
+]]>
+
+
+
+
+
+]]>
+
+
+
+]]>
+
+
+
+
+
+]]>
+
+
+
+]]>
+
+
+
+
+
+
+
+
+]]>
+
+
+
+]]>
+
+
+
+
+]]>
+
+
+
+]]>
+
+
+
+
+]]>
+
+
+
+]]>
+
+
+
+
+]]>
+
+
+
+]]>
+
+
+
+
+]]>
+
+
+
+]]>
+
+
+
+
+]]>
+
+
+
+]]>
+
+
diff --git a/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-blkpres-1.mod b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-blkpres-1.mod
new file mode 100644
index 00000000..30968bb7
--- /dev/null
+++ b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-blkpres-1.mod
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+]]>
+
+
+
+]]>
+
+
diff --git a/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-blkstruct-1.mod b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-blkstruct-1.mod
new file mode 100644
index 00000000..ab37c73c
--- /dev/null
+++ b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-blkstruct-1.mod
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+]]>
+
+
+
+]]>
+
+
+
+
+
+]]>
+
+
+
+]]>
+
+
diff --git a/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-charent-1.mod b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-charent-1.mod
new file mode 100644
index 00000000..b1faf15c
--- /dev/null
+++ b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-charent-1.mod
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+%xhtml-lat1;
+
+
+%xhtml-symbol;
+
+
+%xhtml-special;
+
+
diff --git a/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-csismap-1.mod b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-csismap-1.mod
new file mode 100644
index 00000000..5977f038
--- /dev/null
+++ b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-csismap-1.mod
@@ -0,0 +1,114 @@
+
+
+
+
+
+
+
+
+
+
+]]>
+
+
+
+
+
+
+]]>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+]]>
+
+
+
+]]>
+
+
diff --git a/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-datatypes-1.mod b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-datatypes-1.mod
new file mode 100644
index 00000000..a2ea3ae8
--- /dev/null
+++ b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-datatypes-1.mod
@@ -0,0 +1,103 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-datatypes-1.mod.1 b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-datatypes-1.mod.1
new file mode 100644
index 00000000..a2ea3ae8
--- /dev/null
+++ b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-datatypes-1.mod.1
@@ -0,0 +1,103 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-edit-1.mod b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-edit-1.mod
new file mode 100644
index 00000000..2d3d43f1
--- /dev/null
+++ b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-edit-1.mod
@@ -0,0 +1,66 @@
+
+
+
+
+
+
+
+
+
+
+
+
+]]>
+
+
+
+]]>
+
+
+
+
+
+
+
+]]>
+
+
+
+]]>
+
+
diff --git a/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-events-1.mod b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-events-1.mod
new file mode 100644
index 00000000..ad8a798c
--- /dev/null
+++ b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-events-1.mod
@@ -0,0 +1,135 @@
+
+
+
+
+
+
+
+
+
+
+]]>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-form-1.mod b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-form-1.mod
new file mode 100644
index 00000000..98b0b926
--- /dev/null
+++ b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-form-1.mod
@@ -0,0 +1,292 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+]]>
+
+
+
+]]>
+
+
+
+
+
+
+
+
+]]>
+
+
+
+]]>
+
+
+
+
+
+
+]]>
+
+
+
+
+
+]]>
+
+
+
+
+
+
+]]>
+
+
+
+]]>
+
+
+
+
+
+
+]]>
+
+
+
+]]>
+
+
+
+
+
+
+]]>
+
+
+
+]]>
+
+
+
+
+
+
+]]>
+
+
+
+]]>
+
+
+
+
+
+
+
+
+]]>
+
+
+
+]]>
+
+
+
+
+
+
+]]>
+
+
+
+]]>
+
+
+
+
+
+
+]]>
+
+
+
+]]>
+
+
diff --git a/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-framework-1.mod b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-framework-1.mod
new file mode 100644
index 00000000..f37976a6
--- /dev/null
+++ b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-framework-1.mod
@@ -0,0 +1,97 @@
+
+
+
+
+
+
+
+
+%xhtml-arch.mod;]]>
+
+
+
+%xhtml-notations.mod;]]>
+
+
+
+%xhtml-datatypes.mod;]]>
+
+
+
+%xhtml-xlink.mod;
+
+
+
+%xhtml-qname.mod;]]>
+
+
+
+%xhtml-events.mod;]]>
+
+
+
+%xhtml-attribs.mod;]]>
+
+
+
+%xhtml-model.redecl;
+
+
+
+%xhtml-model.mod;]]>
+
+
+
+%xhtml-charent.mod;]]>
+
+
diff --git a/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-hypertext-1.mod b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-hypertext-1.mod
new file mode 100644
index 00000000..85d8348f
--- /dev/null
+++ b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-hypertext-1.mod
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+]]>
+
+
+
+]]>
+
+
diff --git a/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-image-1.mod b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-image-1.mod
new file mode 100644
index 00000000..7eea4f9a
--- /dev/null
+++ b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-image-1.mod
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
+
+
+
+]]>
+
+
+
+]]>
+
+
diff --git a/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-inlphras-1.mod b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-inlphras-1.mod
new file mode 100644
index 00000000..ebada109
--- /dev/null
+++ b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-inlphras-1.mod
@@ -0,0 +1,203 @@
+
+
+
+
+
+
+
+
+
+
+]]>
+
+
+
+]]>
+
+
+
+
+
+]]>
+
+
+
+]]>
+
+
+
+
+
+]]>
+
+
+
+]]>
+
+
+
+
+
+]]>
+
+
+
+]]>
+
+
+
+
+
+]]>
+
+
+
+]]>
+
+
+
+
+
+]]>
+
+
+
+]]>
+
+
+
+
+
+]]>
+
+
+
+]]>
+
+
+
+
+
+]]>
+
+
+
+]]>
+
+
+
+
+
+]]>
+
+
+
+]]>
+
+
+
+
+
+]]>
+
+
+
+]]>
+
+
+
+
+
+]]>
+
+
+
+]]>
+
+
diff --git a/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-inlpres-1.mod b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-inlpres-1.mod
new file mode 100644
index 00000000..3e41322c
--- /dev/null
+++ b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-inlpres-1.mod
@@ -0,0 +1,138 @@
+
+
+
+
+
+
+
+
+
+
+]]>
+
+
+
+]]>
+
+
+
+
+
+]]>
+
+
+
+]]>
+
+
+
+
+
+]]>
+
+
+
+]]>
+
+
+
+
+
+]]>
+
+
+
+]]>
+
+
+
+
+
+]]>
+
+
+
+]]>
+
+
+
+
+
+]]>
+
+
+
+]]>
+
+
+
+
+
+]]>
+
+
+
+]]>
+
+
diff --git a/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-inlstruct-1.mod b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-inlstruct-1.mod
new file mode 100644
index 00000000..4d6bd01a
--- /dev/null
+++ b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-inlstruct-1.mod
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+]]>
+
+
+
+]]>
+
+
+
+
+
+
+
+]]>
+
+
+
+]]>
+
+
diff --git a/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-inlstyle-1.mod b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-inlstyle-1.mod
new file mode 100644
index 00000000..6d526cd1
--- /dev/null
+++ b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-inlstyle-1.mod
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-lat1.ent b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-lat1.ent
new file mode 100644
index 00000000..ffee223e
--- /dev/null
+++ b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-lat1.ent
@@ -0,0 +1,196 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-link-1.mod b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-link-1.mod
new file mode 100644
index 00000000..4a15f1dd
--- /dev/null
+++ b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-link-1.mod
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+
+
+
+
+]]>
+
+
+
+]]>
+
+
diff --git a/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-list-1.mod b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-list-1.mod
new file mode 100644
index 00000000..72bdb25c
--- /dev/null
+++ b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-list-1.mod
@@ -0,0 +1,129 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+]]>
+
+
+
+]]>
+
+
+
+
+
+
+]]>
+
+
+
+]]>
+
+
+
+
+
+
+]]>
+
+
+
+]]>
+
+
+
+
+
+
+]]>
+
+
+
+]]>
+
+
+
+
+
+
+]]>
+
+
+
+]]>
+
+
+
+
+
+
+]]>
+
+
+
+]]>
+
+
diff --git a/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-meta-1.mod b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-meta-1.mod
new file mode 100644
index 00000000..d2f6d2c6
--- /dev/null
+++ b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-meta-1.mod
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+]]>
+
+
+
+]]>
+
+
diff --git a/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-notations-1.mod b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-notations-1.mod
new file mode 100644
index 00000000..2da12d02
--- /dev/null
+++ b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-notations-1.mod
@@ -0,0 +1,114 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-object-1.mod b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-object-1.mod
new file mode 100644
index 00000000..bee7aeb0
--- /dev/null
+++ b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-object-1.mod
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+]]>
+
+
+
+]]>
+
+
diff --git a/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-param-1.mod b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-param-1.mod
new file mode 100644
index 00000000..4ba07916
--- /dev/null
+++ b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-param-1.mod
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
+]]>
+
+
+
+]]>
+
+
diff --git a/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-pres-1.mod b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-pres-1.mod
new file mode 100644
index 00000000..42a0d6df
--- /dev/null
+++ b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-pres-1.mod
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+%xhtml-inlpres.mod;]]>
+
+
+
+%xhtml-blkpres.mod;]]>
+
+
diff --git a/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-qname-1.mod b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-qname-1.mod
new file mode 100644
index 00000000..35c180a6
--- /dev/null
+++ b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-qname-1.mod
@@ -0,0 +1,318 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+]]>
+
+
+
+
+%xhtml-qname-extra.mod;
+
+
+
+
+
+
+
+
+
+]]>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+]]>
+
+
+
+
+
+
+
+]]>
+
+
+
+
+%xhtml-qname.redecl;
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-script-1.mod b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-script-1.mod
new file mode 100644
index 00000000..0152ab02
--- /dev/null
+++ b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-script-1.mod
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+]]>
+
+
+
+]]>
+
+
+
+
+
+
+
+]]>
+
+
+
+]]>
+
+
diff --git a/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-special.ent b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-special.ent
new file mode 100644
index 00000000..ca358b2f
--- /dev/null
+++ b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-special.ent
@@ -0,0 +1,80 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-ssismap-1.mod b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-ssismap-1.mod
new file mode 100644
index 00000000..45da878f
--- /dev/null
+++ b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-ssismap-1.mod
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-struct-1.mod b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-struct-1.mod
new file mode 100644
index 00000000..c826f0f0
--- /dev/null
+++ b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-struct-1.mod
@@ -0,0 +1,136 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+]]>
+
+
+
+]]>
+
+
+
+
+
+
+
+]]>
+
+
+
+
+
+
+]]>
+
+
+
+
+
+
+
+]]>
+
+
+
+]]>
+
+
+
+
+
+
+
+]]>
+
+
+
+]]>
+
+
+
+
+
+
+
+
+]]>
+
+
diff --git a/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-style-1.mod b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-style-1.mod
new file mode 100644
index 00000000..dc85a9e6
--- /dev/null
+++ b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-style-1.mod
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
+]]>
+
+
+
+]]>
+
+
diff --git a/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-symbol.ent b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-symbol.ent
new file mode 100644
index 00000000..63c2abfa
--- /dev/null
+++ b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-symbol.ent
@@ -0,0 +1,237 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-symbol.ent.1 b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-symbol.ent.1
new file mode 100644
index 00000000..63c2abfa
--- /dev/null
+++ b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-symbol.ent.1
@@ -0,0 +1,237 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-table-1.mod b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-table-1.mod
new file mode 100644
index 00000000..540b7346
--- /dev/null
+++ b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-table-1.mod
@@ -0,0 +1,333 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+]]>
+
+
+
+]]>
+
+
+
+
+
+
+]]>
+
+
+
+]]>
+
+
+
+
+
+
+
+
+]]>
+
+
+
+]]>
+
+
+
+
+
+
+
+
+]]>
+
+
+
+]]>
+
+
+
+
+
+
+
+
+]]>
+
+
+
+]]>
+
+
+
+
+
+
+
+
+]]>
+
+
+
+]]>
+
+
+
+
+
+
+
+
+]]>
+
+
+
+]]>
+
+
+
+
+
+
+]]>
+
+
+
+]]>
+
+
+
+
+
+
+
+
+]]>
+
+
+
+]]>
+
+
+
+
+
+
+]]>
+
+
+
+]]>
+
+
diff --git a/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-text-1.mod b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-text-1.mod
new file mode 100644
index 00000000..a461e1e1
--- /dev/null
+++ b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-text-1.mod
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+%xhtml-inlstruct.mod;]]>
+
+
+
+%xhtml-inlphras.mod;]]>
+
+
+
+%xhtml-blkstruct.mod;]]>
+
+
+
+%xhtml-blkphras.mod;]]>
+
+
diff --git a/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml11-model-1.mod b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml11-model-1.mod
new file mode 100644
index 00000000..eb834f3d
--- /dev/null
+++ b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml11-model-1.mod
@@ -0,0 +1,252 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml1/DTD/xhtml-lat1.ent b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml1/DTD/xhtml-lat1.ent
new file mode 100644
index 00000000..ffee223e
--- /dev/null
+++ b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml1/DTD/xhtml-lat1.ent
@@ -0,0 +1,196 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml1/DTD/xhtml-special.ent b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml1/DTD/xhtml-special.ent
new file mode 100644
index 00000000..ca358b2f
--- /dev/null
+++ b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml1/DTD/xhtml-special.ent
@@ -0,0 +1,80 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml1/DTD/xhtml-symbol.ent b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml1/DTD/xhtml-symbol.ent
new file mode 100644
index 00000000..63c2abfa
--- /dev/null
+++ b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml1/DTD/xhtml-symbol.ent
@@ -0,0 +1,237 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd
new file mode 100644
index 00000000..2927b9ec
--- /dev/null
+++ b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd
@@ -0,0 +1,978 @@
+
+
+
+
+
+%HTMLlat1;
+
+
+%HTMLsymbol;
+
+
+%HTMLspecial;
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd
new file mode 100644
index 00000000..628f27ac
--- /dev/null
+++ b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd
@@ -0,0 +1,1201 @@
+
+
+
+
+
+%HTMLlat1;
+
+
+%HTMLsymbol;
+
+
+%HTMLspecial;
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml11/DTD/xhtml11.dtd b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml11/DTD/xhtml11.dtd
new file mode 100644
index 00000000..2a999b5b
--- /dev/null
+++ b/epublib-core/src/main/resources/dtd/www.w3.org/TR/xhtml11/DTD/xhtml11.dtd
@@ -0,0 +1,294 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+]]>
+
+
+
+
+
+
+%xhtml-inlstyle.mod;]]>
+
+
+
+
+
+
+
+%xhtml-framework.mod;]]>
+
+
+
+
+]]>
+
+
+
+
+%xhtml-text.mod;]]>
+
+
+
+
+%xhtml-hypertext.mod;]]>
+
+
+
+
+%xhtml-list.mod;]]>
+
+
+
+
+
+
+%xhtml-edit.mod;]]>
+
+
+
+
+%xhtml-bdo.mod;]]>
+
+
+
+
+
+
+%xhtml-ruby.mod;]]>
+
+
+
+
+%xhtml-pres.mod;]]>
+
+
+
+
+%xhtml-link.mod;]]>
+
+
+
+
+%xhtml-meta.mod;]]>
+
+
+
+
+%xhtml-base.mod;]]>
+
+
+
+
+%xhtml-script.mod;]]>
+
+
+
+
+%xhtml-style.mod;]]>
+
+
+
+
+%xhtml-image.mod;]]>
+
+
+
+
+%xhtml-csismap.mod;]]>
+
+
+
+
+%xhtml-ssismap.mod;]]>
+
+
+
+
+%xhtml-param.mod;]]>
+
+
+
+
+%xhtml-object.mod;]]>
+
+
+
+
+%xhtml-table.mod;]]>
+
+
+
+
+%xhtml-form.mod;]]>
+
+
+
+
+%xhtml-legacy.mod;]]>
+
+
+
+
+%xhtml-struct.mod;]]>
+
+
+
diff --git a/epublib-core/src/main/resources/log4j.properties b/epublib-core/src/main/resources/log4j.properties
new file mode 100644
index 00000000..bdfcdfe7
--- /dev/null
+++ b/epublib-core/src/main/resources/log4j.properties
@@ -0,0 +1,55 @@
+#------------------------------------------------------------------------------
+#
+# The following properties set the logging levels and log appender. The
+# log4j.rootCategory variable defines the default log level and one or more
+# appenders. For the console, use 'S'. For the daily rolling file, use 'R'.
+# For an HTML formatted log, use 'H'.
+#
+# To override the default (rootCategory) log level, define a property of the
+# form (see below for available values):
+#
+# log4j.logger. =
+#
+# Available logger names:
+# TODO
+#
+# Possible Log Levels:
+# FATAL, ERROR, WARN, INFO, DEBUG
+#
+#------------------------------------------------------------------------------
+log4j.rootCategory=INFO, S
+
+#------------------------------------------------------------------------------
+#
+# The following properties configure the console (stdout) appender.
+# See http://logging.apache.org/log4j/docs/api/index.html for details.
+#
+#------------------------------------------------------------------------------
+log4j.appender.S = org.apache.log4j.ConsoleAppender
+log4j.appender.S.layout = org.apache.log4j.PatternLayout
+log4j.appender.S.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss} [%p] %l %m%n
+
+#------------------------------------------------------------------------------
+#
+# The following properties configure the Daily Rolling File appender.
+# See http://logging.apache.org/log4j/docs/api/index.html for details.
+#
+#------------------------------------------------------------------------------
+log4j.appender.R = org.apache.log4j.DailyRollingFileAppender
+log4j.appender.R.File = logs/epublib.log
+log4j.appender.R.Append = true
+log4j.appender.R.DatePattern = '.'yyy-MM-dd
+log4j.appender.R.layout = org.apache.log4j.PatternLayout
+log4j.appender.R.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss} %c{1} [%p] %m%n
+
+#------------------------------------------------------------------------------
+#
+# The following properties configure the Rolling File appender in HTML.
+# See http://logging.apache.org/log4j/docs/api/index.html for details.
+#
+#------------------------------------------------------------------------------
+log4j.appender.H = org.apache.log4j.RollingFileAppender
+log4j.appender.H.File = logs/epublib_log.html
+log4j.appender.H.MaxFileSize = 100KB
+log4j.appender.H.Append = false
+log4j.appender.H.layout = org.apache.log4j.HTMLLayout
\ No newline at end of file
diff --git a/epublib-core/src/test/java/nl/siegmann/epublib/browsersupport/NavigationHistoryTest.java b/epublib-core/src/test/java/nl/siegmann/epublib/browsersupport/NavigationHistoryTest.java
new file mode 100644
index 00000000..f0c75a7a
--- /dev/null
+++ b/epublib-core/src/test/java/nl/siegmann/epublib/browsersupport/NavigationHistoryTest.java
@@ -0,0 +1,213 @@
+package nl.siegmann.epublib.browsersupport;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import nl.siegmann.epublib.domain.Book;
+import nl.siegmann.epublib.domain.Resource;
+
+import org.junit.Test;
+
+public class NavigationHistoryTest {
+
+ private static final Resource mockResource = new Resource("mockResource.html");
+
+ private static class MockBook extends Book {
+ public Resource getCoverPage() {
+ return mockResource;
+ }
+ }
+
+
+ private static class MockSectionWalker extends Navigator {
+
+ private Map" + title + "
";
+ return new Resource(null, content.getBytes(), href, MediatypeService.XHTML, Constants.CHARACTER_ENCODING);
+ }
+
+ /**
+ * Creates a resource out of the given zipEntry and zipInputStream.
+ *
+ * @param zipEntry
+ * @param zipInputStream
+ * @return a resource created out of the given zipEntry and zipInputStream.
+ * @throws IOException
+ */
+ public static Resource createResource(ZipEntry zipEntry, ZipInputStream zipInputStream) throws IOException {
+ return new Resource(zipInputStream, zipEntry.getName());
+
+ }
+
+ public static Resource createResource(ZipEntry zipEntry, InputStream zipInputStream) throws IOException {
+ return new Resource(zipInputStream, zipEntry.getName());
+
+ }
+
+ /**
+ * Converts a given string from given input character encoding to the requested output character encoding.
+ *
+ * @param inputEncoding
+ * @param outputEncoding
+ * @param input
+ * @return the string from given input character encoding converted to the requested output character encoding.
+ * @throws UnsupportedEncodingException
+ */
+ public static byte[] recode(String inputEncoding, String outputEncoding, byte[] input) throws UnsupportedEncodingException {
+ return new String(input, inputEncoding).getBytes(outputEncoding);
+ }
+
+ /**
+ * Gets the contents of the Resource as an InputSource in a null-safe manner.
+ *
+ */
+ public static InputSource getInputSource(Resource resource) throws IOException {
+ if (resource == null) {
+ return null;
+ }
+ Reader reader = resource.getReader();
+ if (reader == null) {
+ return null;
+ }
+ InputSource inputSource = new InputSource(reader);
+ return inputSource;
+ }
+
+
+ /**
+ * Reads parses the xml therein and returns the result as a Document
+ */
+ public static Document getAsDocument(Resource resource) throws UnsupportedEncodingException, SAXException, IOException, ParserConfigurationException {
+ return getAsDocument(resource, EpubProcessorSupport.createDocumentBuilder());
+ }
+
+
+ /**
+ * Reads the given resources inputstream, parses the xml therein and returns the result as a Document
+ *
+ * @param resource
+ * @param documentBuilder
+ * @return the document created from the given resource
+ * @throws UnsupportedEncodingException
+ * @throws SAXException
+ * @throws IOException
+ * @throws ParserConfigurationException
+ */
+ public static Document getAsDocument(Resource resource, DocumentBuilder documentBuilder) throws UnsupportedEncodingException, SAXException, IOException, ParserConfigurationException {
+ InputSource inputSource = getInputSource(resource);
+ if (inputSource == null) {
+ return null;
+ }
+ Document result = documentBuilder.parse(inputSource);
+ return result;
+ }
+}
diff --git a/epublib-core/src/main/java/nl/siegmann/epublib/util/StringUtil.java b/epublib-core/src/main/java/nl/siegmann/epublib/util/StringUtil.java
new file mode 100644
index 00000000..0f60b923
--- /dev/null
+++ b/epublib-core/src/main/java/nl/siegmann/epublib/util/StringUtil.java
@@ -0,0 +1,275 @@
+package nl.siegmann.epublib.util;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Various String utility functions.
+ *
+ * Most of the functions herein are re-implementations of the ones in apache
+ * commons StringUtils. The reason for re-implementing this is that the
+ * functions are fairly simple and using my own implementation saves the
+ * inclusion of a 200Kb jar file.
+ *
+ * @author paul.siegmann
+ *
+ */
+public class StringUtil {
+
+ /**
+ * Changes a path containing '..', '.' and empty dirs into a path that
+ * doesn't. X/foo/../Y is changed into 'X/Y', etc. Does not handle invalid
+ * paths like "../".
+ *
+ * @param path
+ * @return the normalized path
+ */
+ public static String collapsePathDots(String path) {
+ String[] stringParts = path.split("/");
+ List
+ *
+ *
+ *
+ * Example 1 - Detect and exclude a UTF-8 BOM
+ *
+ * BOMInputStream bomIn = new BOMInputStream(in);
+ * if (bomIn.hasBOM()) {
+ * // has a UTF-8 BOM
+ * }
+ *
+ *
+ * Example 2 - Detect a UTF-8 BOM (but don't exclude it)
+ *
+ * boolean include = true;
+ * BOMInputStream bomIn = new BOMInputStream(in, include);
+ * if (bomIn.hasBOM()) {
+ * // has a UTF-8 BOM
+ * }
+ *
+ *
+ * Example 3 - Detect Multiple BOMs
+ *
+ * BOMInputStream bomIn = new BOMInputStream(in, ByteOrderMark.UTF_16LE, ByteOrderMark.UTF_16BE);
+ * if (bomIn.hasBOM() == false) {
+ * // No BOM found
+ * } else if (bomIn.hasBOM(ByteOrderMark.UTF_16LE)) {
+ * // has a UTF-16LE BOM
+ * } else if (bomIn.hasBOM(ByteOrderMark.UTF_16BE)) {
+ * // has a UTF-16BE BOM
+ * }
+ *
+ *
+ * @see ByteOrderMark
+ * @see Wikipedia - Byte Order Mark
+ * @version $Revision: 1052095 $ $Date: 2010-12-22 23:03:20 +0000 (Wed, 22 Dec 2010) $
+ * @since Commons IO 2.0
+ */
+public class BOMInputStream extends ProxyInputStream {
+ private final boolean include;
+ private final Listread()
method,
+ * either returning a valid byte or -1 to indicate that the initial bytes
+ * have been processed already.
+ * @return the byte read (excluding BOM) or -1 if the end of stream
+ * @throws IOException if an I/O error occurs
+ */
+ private int readFirstBytes() throws IOException {
+ getBOM();
+ return (fbIndex < fbLength) ? firstBytes[fbIndex++] : -1;
+ }
+
+ /**
+ * Find a BOM with the specified bytes.
+ *
+ * @return The matched BOM or null if none matched
+ */
+ private ByteOrderMark find() {
+ for (ByteOrderMark bom : boms) {
+ if (matches(bom)) {
+ return bom;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Check if the bytes match a BOM.
+ *
+ * @param bom The BOM
+ * @return true if the bytes match the bom, otherwise false
+ */
+ private boolean matches(ByteOrderMark bom) {
+ if (bom.length() != fbLength) {
+ return false;
+ }
+ for (int i = 0; i < bom.length(); i++) {
+ if (bom.get(i) != firstBytes[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ //----------------------------------------------------------------------------
+ // Implementation of InputStream
+ //----------------------------------------------------------------------------
+
+ /**
+ * Invokes the delegate's read()
method, detecting and
+ * optionally skipping BOM.
+ * @return the byte read (excluding BOM) or -1 if the end of stream
+ * @throws IOException if an I/O error occurs
+ */
+ @Override
+ public int read() throws IOException {
+ int b = readFirstBytes();
+ return (b >= 0) ? b : in.read();
+ }
+
+ /**
+ * Invokes the delegate's read(byte[], int, int)
method, detecting
+ * and optionally skipping BOM.
+ * @param buf the buffer to read the bytes into
+ * @param off The start offset
+ * @param len The number of bytes to read (excluding BOM)
+ * @return the number of bytes read or -1 if the end of stream
+ * @throws IOException if an I/O error occurs
+ */
+ @Override
+ public int read(byte[] buf, int off, int len) throws IOException {
+ int firstCount = 0;
+ int b = 0;
+ while ((len > 0) && (b >= 0)) {
+ b = readFirstBytes();
+ if (b >= 0) {
+ buf[off++] = (byte) (b & 0xFF);
+ len--;
+ firstCount++;
+ }
+ }
+ int secondCount = in.read(buf, off, len);
+ return (secondCount < 0) ? (firstCount > 0 ? firstCount : -1) : firstCount + secondCount;
+ }
+
+ /**
+ * Invokes the delegate's read(byte[])
method, detecting and
+ * optionally skipping BOM.
+ * @param buf the buffer to read the bytes into
+ * @return the number of bytes read (excluding BOM)
+ * or -1 if the end of stream
+ * @throws IOException if an I/O error occurs
+ */
+ @Override
+ public int read(byte[] buf) throws IOException {
+ return read(buf, 0, buf.length);
+ }
+
+ /**
+ * Invokes the delegate's mark(int)
method.
+ * @param readlimit read ahead limit
+ */
+ @Override
+ public synchronized void mark(int readlimit) {
+ markFbIndex = fbIndex;
+ markedAtStart = (firstBytes == null);
+ in.mark(readlimit);
+ }
+
+ /**
+ * Invokes the delegate's reset()
method.
+ * @throws IOException if an I/O error occurs
+ */
+ @Override
+ public synchronized void reset() throws IOException {
+ fbIndex = markFbIndex;
+ if (markedAtStart) {
+ firstBytes = null;
+ }
+
+ in.reset();
+ }
+
+ /**
+ * Invokes the delegate's skip(long)
method, detecting
+ * and optionallyskipping BOM.
+ * @param n the number of bytes to skip
+ * @return the number of bytes to skipped or -1 if the end of stream
+ * @throws IOException if an I/O error occurs
+ */
+ @Override
+ public long skip(long n) throws IOException {
+ while ((n > 0) && (readFirstBytes() >= 0)) {
+ n--;
+ }
+ return in.skip(n);
+ }
+}
diff --git a/epublib-core/src/main/java/nl/siegmann/epublib/util/commons/io/ByteOrderMark.java b/epublib-core/src/main/java/nl/siegmann/epublib/util/commons/io/ByteOrderMark.java
new file mode 100644
index 00000000..55ceeea8
--- /dev/null
+++ b/epublib-core/src/main/java/nl/siegmann/epublib/util/commons/io/ByteOrderMark.java
@@ -0,0 +1,170 @@
+package nl.siegmann.epublib.util.commons.io;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.io.Serializable;
+
+/**
+ * Byte Order Mark (BOM) representation -
+ * see {@link BOMInputStream}.
+ *
+ * @see BOMInputStream
+ * @see Wikipedia - Byte Order Mark
+ * @version $Id: ByteOrderMark.java 1005099 2010-10-06 16:13:01Z niallp $
+ * @since Commons IO 2.0
+ */
+public class ByteOrderMark implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ /** UTF-8 BOM */
+ public static final ByteOrderMark UTF_8 = new ByteOrderMark("UTF-8", 0xEF, 0xBB, 0xBF);
+ /** UTF-16BE BOM (Big Endian) */
+ public static final ByteOrderMark UTF_16BE = new ByteOrderMark("UTF-16BE", 0xFE, 0xFF);
+ /** UTF-16LE BOM (Little Endian) */
+ public static final ByteOrderMark UTF_16LE = new ByteOrderMark("UTF-16LE", 0xFF, 0xFE);
+
+ private final String charsetName;
+ private final int[] bytes;
+
+ /**
+ * Construct a new BOM.
+ *
+ * @param charsetName The name of the charset the BOM represents
+ * @param bytes The BOM's bytes
+ * @throws IllegalArgumentException if the charsetName is null or
+ * zero length
+ * @throws IllegalArgumentException if the bytes are null or zero
+ * length
+ */
+ public ByteOrderMark(String charsetName, int... bytes) {
+ if (charsetName == null || charsetName.length() == 0) {
+ throw new IllegalArgumentException("No charsetName specified");
+ }
+ if (bytes == null || bytes.length == 0) {
+ throw new IllegalArgumentException("No bytes specified");
+ }
+ this.charsetName = charsetName;
+ this.bytes = new int[bytes.length];
+ System.arraycopy(bytes, 0, this.bytes, 0, bytes.length);
+ }
+
+ /**
+ * Return the name of the {@link java.nio.charset.Charset} the BOM represents.
+ *
+ * @return the character set name
+ */
+ public String getCharsetName() {
+ return charsetName;
+ }
+
+ /**
+ * Return the length of the BOM's bytes.
+ *
+ * @return the length of the BOM's bytes
+ */
+ public int length() {
+ return bytes.length;
+ }
+
+ /**
+ * The byte at the specified position.
+ *
+ * @param pos The position
+ * @return The specified byte
+ */
+ public int get(int pos) {
+ return bytes[pos];
+ }
+
+ /**
+ * Return a copy of the BOM's bytes.
+ *
+ * @return a copy of the BOM's bytes
+ */
+ public byte[] getBytes() {
+ byte[] copy = new byte[bytes.length];
+ for (int i = 0; i < bytes.length; i++) {
+ copy[i] = (byte)bytes[i];
+ }
+ return copy;
+ }
+
+ /**
+ * Indicates if this BOM's bytes equals another.
+ *
+ * @param obj The object to compare to
+ * @return true if the bom's bytes are equal, otherwise
+ * false
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof ByteOrderMark)) {
+ return false;
+ }
+ ByteOrderMark bom = (ByteOrderMark)obj;
+ if (bytes.length != bom.length()) {
+ return false;
+ }
+ for (int i = 0; i < bytes.length; i++) {
+ if (bytes[i] != bom.get(i)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Return the hashcode for this BOM.
+ *
+ * @return the hashcode for this BOM.
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ int hashCode = getClass().hashCode();
+ for (int b : bytes) {
+ hashCode += b;
+ }
+ return hashCode;
+ }
+
+ /**
+ * Provide a String representation of the BOM.
+ *
+ * @return the length of the BOM's bytes
+ */
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append(getClass().getSimpleName());
+ builder.append('[');
+ builder.append(charsetName);
+ builder.append(": ");
+ for (int i = 0; i < bytes.length; i++) {
+ if (i > 0) {
+ builder.append(",");
+ }
+ builder.append("0x");
+ builder.append(Integer.toHexString(0xFF & bytes[i]).toUpperCase());
+ }
+ builder.append(']');
+ return builder.toString();
+ }
+
+}
diff --git a/epublib-core/src/main/java/nl/siegmann/epublib/util/commons/io/ProxyInputStream.java b/epublib-core/src/main/java/nl/siegmann/epublib/util/commons/io/ProxyInputStream.java
new file mode 100644
index 00000000..d8d58230
--- /dev/null
+++ b/epublib-core/src/main/java/nl/siegmann/epublib/util/commons/io/ProxyInputStream.java
@@ -0,0 +1,238 @@
+package nl.siegmann.epublib.util.commons.io;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * A Proxy stream which acts as expected, that is it passes the method
+ * calls on to the proxied stream and doesn't change which methods are
+ * being called.
+ * read()
method.
+ * @return the byte read or -1 if the end of stream
+ * @throws IOException if an I/O error occurs
+ */
+ @Override
+ public int read() throws IOException {
+ try {
+ beforeRead(1);
+ int b = in.read();
+ afterRead(b != -1 ? 1 : -1);
+ return b;
+ } catch (IOException e) {
+ handleIOException(e);
+ return -1;
+ }
+ }
+
+ /**
+ * Invokes the delegate's read(byte[])
method.
+ * @param bts the buffer to read the bytes into
+ * @return the number of bytes read or -1 if the end of stream
+ * @throws IOException if an I/O error occurs
+ */
+ @Override
+ public int read(byte[] bts) throws IOException {
+ try {
+ beforeRead(bts != null ? bts.length : 0);
+ int n = in.read(bts);
+ afterRead(n);
+ return n;
+ } catch (IOException e) {
+ handleIOException(e);
+ return -1;
+ }
+ }
+
+ /**
+ * Invokes the delegate's read(byte[], int, int)
method.
+ * @param bts the buffer to read the bytes into
+ * @param off The start offset
+ * @param len The number of bytes to read
+ * @return the number of bytes read or -1 if the end of stream
+ * @throws IOException if an I/O error occurs
+ */
+ @Override
+ public int read(byte[] bts, int off, int len) throws IOException {
+ try {
+ beforeRead(len);
+ int n = in.read(bts, off, len);
+ afterRead(n);
+ return n;
+ } catch (IOException e) {
+ handleIOException(e);
+ return -1;
+ }
+ }
+
+ /**
+ * Invokes the delegate's skip(long)
method.
+ * @param ln the number of bytes to skip
+ * @return the actual number of bytes skipped
+ * @throws IOException if an I/O error occurs
+ */
+ @Override
+ public long skip(long ln) throws IOException {
+ try {
+ return in.skip(ln);
+ } catch (IOException e) {
+ handleIOException(e);
+ return 0;
+ }
+ }
+
+ /**
+ * Invokes the delegate's available()
method.
+ * @return the number of available bytes
+ * @throws IOException if an I/O error occurs
+ */
+ @Override
+ public int available() throws IOException {
+ try {
+ return super.available();
+ } catch (IOException e) {
+ handleIOException(e);
+ return 0;
+ }
+ }
+
+ /**
+ * Invokes the delegate's close()
method.
+ * @throws IOException if an I/O error occurs
+ */
+ @Override
+ public void close() throws IOException {
+ try {
+ in.close();
+ } catch (IOException e) {
+ handleIOException(e);
+ }
+ }
+
+ /**
+ * Invokes the delegate's mark(int)
method.
+ * @param readlimit read ahead limit
+ */
+ @Override
+ public synchronized void mark(int readlimit) {
+ in.mark(readlimit);
+ }
+
+ /**
+ * Invokes the delegate's reset()
method.
+ * @throws IOException if an I/O error occurs
+ */
+ @Override
+ public synchronized void reset() throws IOException {
+ try {
+ in.reset();
+ } catch (IOException e) {
+ handleIOException(e);
+ }
+ }
+
+ /**
+ * Invokes the delegate's markSupported()
method.
+ * @return true if mark is supported, otherwise false
+ */
+ @Override
+ public boolean markSupported() {
+ return in.markSupported();
+ }
+
+ /**
+ * Invoked by the read methods before the call is proxied. The number
+ * of bytes that the caller wanted to read (1 for the {@link #read()}
+ * method, buffer length for {@link #read(byte[])}, etc.) is given as
+ * an argument.
+ * read(char[], int, int)
method.
+ * @param buf the buffer to read the characters into
+ * @param offset The start offset
+ * @param len The number of bytes to read
+ * @return the number of characters read or -1 if the end of stream
+ * @throws IOException if an I/O error occurs
+ */
+ @Override
+ public int read(char[] buf, int offset, int len) throws IOException {
+ return reader.read(buf, offset, len);
+ }
+
+ /**
+ * Closes the XmlStreamReader stream.
+ *
+ * @throws IOException thrown if there was a problem closing the stream.
+ */
+ @Override
+ public void close() throws IOException {
+ reader.close();
+ }
+
+ /**
+ * Process the raw stream.
+ *
+ * @param bom BOMInputStream to detect byte order marks
+ * @param pis BOMInputStream to guess XML encoding
+ * @param lenient indicates if the charset encoding detection should be
+ * relaxed.
+ * @return the encoding to be used
+ * @throws IOException thrown if there is a problem reading the stream.
+ */
+ private String doRawStream(BOMInputStream bom, BOMInputStream pis, boolean lenient)
+ throws IOException {
+ String bomEnc = bom.getBOMCharsetName();
+ String xmlGuessEnc = pis.getBOMCharsetName();
+ String xmlEnc = getXmlProlog(pis, xmlGuessEnc);
+ try {
+ return calculateRawEncoding(bomEnc, xmlGuessEnc, xmlEnc);
+ } catch (XmlStreamReaderException ex) {
+ if (lenient) {
+ return doLenientDetection(null, ex);
+ } else {
+ throw ex;
+ }
+ }
+ }
+
+ /**
+ * Process a HTTP stream.
+ *
+ * @param bom BOMInputStream to detect byte order marks
+ * @param pis BOMInputStream to guess XML encoding
+ * @param httpContentType The HTTP content type
+ * @param lenient indicates if the charset encoding detection should be
+ * relaxed.
+ * @return the encoding to be used
+ * @throws IOException thrown if there is a problem reading the stream.
+ */
+ private String doHttpStream(BOMInputStream bom, BOMInputStream pis, String httpContentType,
+ boolean lenient) throws IOException {
+ String bomEnc = bom.getBOMCharsetName();
+ String xmlGuessEnc = pis.getBOMCharsetName();
+ String xmlEnc = getXmlProlog(pis, xmlGuessEnc);
+ try {
+ return calculateHttpEncoding(httpContentType, bomEnc,
+ xmlGuessEnc, xmlEnc, lenient);
+ } catch (XmlStreamReaderException ex) {
+ if (lenient) {
+ return doLenientDetection(httpContentType, ex);
+ } else {
+ throw ex;
+ }
+ }
+ }
+
+ /**
+ * Do lenient detection.
+ *
+ * @param httpContentType content-type header to use for the resolution of
+ * the charset encoding.
+ * @param ex The thrown exception
+ * @return the encoding
+ * @throws IOException thrown if there is a problem reading the stream.
+ */
+ private String doLenientDetection(String httpContentType,
+ XmlStreamReaderException ex) throws IOException {
+ if (httpContentType != null && httpContentType.startsWith("text/html")) {
+ httpContentType = httpContentType.substring("text/html".length());
+ httpContentType = "text/xml" + httpContentType;
+ try {
+ return calculateHttpEncoding(httpContentType, ex.getBomEncoding(),
+ ex.getXmlGuessEncoding(), ex.getXmlEncoding(), true);
+ } catch (XmlStreamReaderException ex2) {
+ ex = ex2;
+ }
+ }
+ String encoding = ex.getXmlEncoding();
+ if (encoding == null) {
+ encoding = ex.getContentTypeEncoding();
+ }
+ if (encoding == null) {
+ encoding = (defaultEncoding == null) ? UTF_8 : defaultEncoding;
+ }
+ return encoding;
+ }
+
+ /**
+ * Calculate the raw encoding.
+ *
+ * @param bomEnc BOM encoding
+ * @param xmlGuessEnc XML Guess encoding
+ * @param xmlEnc XML encoding
+ * @return the raw encoding
+ * @throws IOException thrown if there is a problem reading the stream.
+ */
+ String calculateRawEncoding(String bomEnc, String xmlGuessEnc,
+ String xmlEnc) throws IOException {
+
+ // BOM is Null
+ if (bomEnc == null) {
+ if (xmlGuessEnc == null || xmlEnc == null) {
+ return (defaultEncoding == null ? UTF_8 : defaultEncoding);
+ }
+ if (xmlEnc.equals(UTF_16) &&
+ (xmlGuessEnc.equals(UTF_16BE) || xmlGuessEnc.equals(UTF_16LE))) {
+ return xmlGuessEnc;
+ }
+ return xmlEnc;
+ }
+
+ // BOM is UTF-8
+ if (bomEnc.equals(UTF_8)) {
+ if (xmlGuessEnc != null && !xmlGuessEnc.equals(UTF_8)) {
+ String msg = MessageFormat.format(RAW_EX_1, new Object[] { bomEnc, xmlGuessEnc, xmlEnc });
+ throw new XmlStreamReaderException(msg, bomEnc, xmlGuessEnc, xmlEnc);
+ }
+ if (xmlEnc != null && !xmlEnc.equals(UTF_8)) {
+ String msg = MessageFormat.format(RAW_EX_1, new Object[] { bomEnc, xmlGuessEnc, xmlEnc });
+ throw new XmlStreamReaderException(msg, bomEnc, xmlGuessEnc, xmlEnc);
+ }
+ return bomEnc;
+ }
+
+ // BOM is UTF-16BE or UTF-16LE
+ if (bomEnc.equals(UTF_16BE) || bomEnc.equals(UTF_16LE)) {
+ if (xmlGuessEnc != null && !xmlGuessEnc.equals(bomEnc)) {
+ String msg = MessageFormat.format(RAW_EX_1, new Object[] { bomEnc, xmlGuessEnc, xmlEnc });
+ throw new XmlStreamReaderException(msg, bomEnc, xmlGuessEnc, xmlEnc);
+ }
+ if (xmlEnc != null && !xmlEnc.equals(UTF_16) && !xmlEnc.equals(bomEnc)) {
+ String msg = MessageFormat.format(RAW_EX_1, new Object[] { bomEnc, xmlGuessEnc, xmlEnc });
+ throw new XmlStreamReaderException(msg, bomEnc, xmlGuessEnc, xmlEnc);
+ }
+ return bomEnc;
+ }
+
+ // BOM is something else
+ String msg = MessageFormat.format(RAW_EX_2, new Object[] { bomEnc, xmlGuessEnc, xmlEnc });
+ throw new XmlStreamReaderException(msg, bomEnc, xmlGuessEnc, xmlEnc);
+ }
+
+
+ /**
+ * Calculate the HTTP encoding.
+ *
+ * @param httpContentType The HTTP content type
+ * @param bomEnc BOM encoding
+ * @param xmlGuessEnc XML Guess encoding
+ * @param xmlEnc XML encoding
+ * @param lenient indicates if the charset encoding detection should be
+ * relaxed.
+ * @return the HTTP encoding
+ * @throws IOException thrown if there is a problem reading the stream.
+ */
+ String calculateHttpEncoding(String httpContentType,
+ String bomEnc, String xmlGuessEnc, String xmlEnc,
+ boolean lenient) throws IOException {
+
+ // Lenient and has XML encoding
+ if (lenient && xmlEnc != null) {
+ return xmlEnc;
+ }
+
+ // Determine mime/encoding content types from HTTP Content Type
+ String cTMime = getContentTypeMime(httpContentType);
+ String cTEnc = getContentTypeEncoding(httpContentType);
+ boolean appXml = isAppXml(cTMime);
+ boolean textXml = isTextXml(cTMime);
+
+ // Mime type NOT "application/xml" or "text/xml"
+ if (!appXml && !textXml) {
+ String msg = MessageFormat.format(HTTP_EX_3, cTMime, cTEnc, bomEnc, xmlGuessEnc, xmlEnc);
+ throw new XmlStreamReaderException(msg, cTMime, cTEnc, bomEnc, xmlGuessEnc, xmlEnc);
+ }
+
+ // No content type encoding
+ if (cTEnc == null) {
+ if (appXml) {
+ return calculateRawEncoding(bomEnc, xmlGuessEnc, xmlEnc);
+ } else {
+ return (defaultEncoding == null) ? US_ASCII : defaultEncoding;
+ }
+ }
+
+ // UTF-16BE or UTF-16LE content type encoding
+ if (cTEnc.equals(UTF_16BE) || cTEnc.equals(UTF_16LE)) {
+ if (bomEnc != null) {
+ String msg = MessageFormat.format(HTTP_EX_1, cTMime, cTEnc, bomEnc, xmlGuessEnc, xmlEnc);
+ throw new XmlStreamReaderException(msg, cTMime, cTEnc, bomEnc, xmlGuessEnc, xmlEnc);
+ }
+ return cTEnc;
+ }
+
+ // UTF-16 content type encoding
+ if (cTEnc.equals(UTF_16)) {
+ if (bomEnc != null && bomEnc.startsWith(UTF_16)) {
+ return bomEnc;
+ }
+ String msg = MessageFormat.format(HTTP_EX_2, cTMime, cTEnc, bomEnc, xmlGuessEnc, xmlEnc);
+ throw new XmlStreamReaderException(msg, cTMime, cTEnc, bomEnc, xmlGuessEnc, xmlEnc);
+ }
+
+ return cTEnc;
+ }
+
+ /**
+ * Returns MIME type or NULL if httpContentType is NULL.
+ *
+ * @param httpContentType the HTTP content type
+ * @return The mime content type
+ */
+ static String getContentTypeMime(String httpContentType) {
+ String mime = null;
+ if (httpContentType != null) {
+ int i = httpContentType.indexOf(";");
+ if (i >= 0) {
+ mime = httpContentType.substring(0, i);
+ } else {
+ mime = httpContentType;
+ }
+ mime = mime.trim();
+ }
+ return mime;
+ }
+
+ private static final Pattern CHARSET_PATTERN = Pattern
+ .compile("charset=[\"']?([.[^; \"']]*)[\"']?");
+
+ /**
+ * Returns charset parameter value, NULL if not present, NULL if
+ * httpContentType is NULL.
+ *
+ * @param httpContentType the HTTP content type
+ * @return The content type encoding
+ */
+ static String getContentTypeEncoding(String httpContentType) {
+ String encoding = null;
+ if (httpContentType != null) {
+ int i = httpContentType.indexOf(";");
+ if (i > -1) {
+ String postMime = httpContentType.substring(i + 1);
+ Matcher m = CHARSET_PATTERN.matcher(postMime);
+ encoding = (m.find()) ? m.group(1) : null;
+ encoding = (encoding != null) ? encoding.toUpperCase() : null;
+ }
+ }
+ return encoding;
+ }
+
+ public static final Pattern ENCODING_PATTERN = Pattern.compile(
+ "<\\?xml.*encoding[\\s]*=[\\s]*((?:\".[^\"]*\")|(?:'.[^']*'))",
+ Pattern.MULTILINE);
+
+ /**
+ * Returns the encoding declared in the , NULL if none.
+ *
+ * @param is InputStream to create the reader from.
+ * @param guessedEnc guessed encoding
+ * @return the encoding declared in the
+ * @throws IOException thrown if there is a problem reading the stream.
+ */
+ private static String getXmlProlog(InputStream is, String guessedEnc)
+ throws IOException {
+ String encoding = null;
+ if (guessedEnc != null) {
+ byte[] bytes = new byte[BUFFER_SIZE];
+ is.mark(BUFFER_SIZE);
+ int offset = 0;
+ int max = BUFFER_SIZE;
+ int c = is.read(bytes, offset, max);
+ int firstGT = -1;
+ String xmlProlog = null;
+ while (c != -1 && firstGT == -1 && offset < BUFFER_SIZE) {
+ offset += c;
+ max -= c;
+ c = is.read(bytes, offset, max);
+ xmlProlog = new String(bytes, 0, offset, guessedEnc);
+ firstGT = xmlProlog.indexOf('>');
+ }
+ if (firstGT == -1) {
+ if (c == -1) {
+ throw new IOException("Unexpected end of XML stream");
+ } else {
+ throw new IOException(
+ "XML prolog or ROOT element not found on first "
+ + offset + " bytes");
+ }
+ }
+ int bytesRead = offset;
+ if (bytesRead > 0) {
+ is.reset();
+ BufferedReader bReader = new BufferedReader(new StringReader(
+ xmlProlog.substring(0, firstGT + 1)));
+ StringBuilder prolog = new StringBuilder();
+ String line = bReader.readLine();
+ while (line != null) {
+ prolog.append(line);
+ line = bReader.readLine();
+ }
+ Matcher m = ENCODING_PATTERN.matcher(prolog);
+ if (m.find()) {
+ encoding = m.group(1).toUpperCase();
+ encoding = encoding.substring(1, encoding.length() - 1);
+ }
+ }
+ }
+ return encoding;
+ }
+
+ /**
+ * Indicates if the MIME type belongs to the APPLICATION XML family.
+ *
+ * @param mime The mime type
+ * @return true if the mime type belongs to the APPLICATION XML family,
+ * otherwise false
+ */
+ static boolean isAppXml(String mime) {
+ return mime != null &&
+ (mime.equals("application/xml") ||
+ mime.equals("application/xml-dtd") ||
+ mime.equals("application/xml-external-parsed-entity") ||
+ (mime.startsWith("application/") && mime.endsWith("+xml")));
+ }
+
+ /**
+ * Indicates if the MIME type belongs to the TEXT XML family.
+ *
+ * @param mime The mime type
+ * @return true if the mime type belongs to the TEXT XML family,
+ * otherwise false
+ */
+ static boolean isTextXml(String mime) {
+ return mime != null &&
+ (mime.equals("text/xml") ||
+ mime.equals("text/xml-external-parsed-entity") ||
+ (mime.startsWith("text/") && mime.endsWith("+xml")));
+ }
+
+ private static final String RAW_EX_1 =
+ "Invalid encoding, BOM [{0}] XML guess [{1}] XML prolog [{2}] encoding mismatch";
+
+ private static final String RAW_EX_2 =
+ "Invalid encoding, BOM [{0}] XML guess [{1}] XML prolog [{2}] unknown BOM";
+
+ private static final String HTTP_EX_1 =
+ "Invalid encoding, CT-MIME [{0}] CT-Enc [{1}] BOM [{2}] XML guess [{3}] XML prolog [{4}], BOM must be NULL";
+
+ private static final String HTTP_EX_2 =
+ "Invalid encoding, CT-MIME [{0}] CT-Enc [{1}] BOM [{2}] XML guess [{3}] XML prolog [{4}], encoding mismatch";
+
+ private static final String HTTP_EX_3 =
+ "Invalid encoding, CT-MIME [{0}] CT-Enc [{1}] BOM [{2}] XML guess [{3}] XML prolog [{4}], Invalid MIME";
+
+}
diff --git a/epublib-core/src/main/java/nl/siegmann/epublib/util/commons/io/XmlStreamReaderException.java b/epublib-core/src/main/java/nl/siegmann/epublib/util/commons/io/XmlStreamReaderException.java
new file mode 100644
index 00000000..1ff2505f
--- /dev/null
+++ b/epublib-core/src/main/java/nl/siegmann/epublib/util/commons/io/XmlStreamReaderException.java
@@ -0,0 +1,138 @@
+package nl.siegmann.epublib.util.commons.io;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.io.IOException;
+
+/**
+ * The XmlStreamReaderException is thrown by the XmlStreamReader constructors if
+ * the charset encoding can not be determined according to the XML 1.0
+ * specification and RFC 3023.
+ *
+ * See Issue #122 Infinite loop.
+ */
+ @Test(expected = ZipException.class)
+ public void testLoadResources_ZipInputStream_WithZeroLengthFile() throws IOException {
+ // given
+ ZipInputStream zipInputStream = new ZipInputStream(this.getClass().getResourceAsStream("/zero_length_file.epub"));
+
+ // when
+ ResourcesLoader.loadResources(zipInputStream, encoding);
+ }
+
+ /**
+ * Loads the Resources from a file that is not a valid zip, using ZipInputStream
+ * See Issue #122 Infinite loop.
+ */
+ @Test(expected = ZipException.class)
+ public void testLoadResources_ZipInputStream_WithInvalidFile() throws IOException {
+ // given
+ ZipInputStream zipInputStream = new ZipInputStream(this.getClass().getResourceAsStream("/not_a_zip.epub"));
+
+ // when
+ ResourcesLoader.loadResources(zipInputStream, encoding);
+ }
+
+ /**
+ * Loads the Resources from a ZipFile
+ */
+ @Test
+ public void testLoadResources_ZipFile() throws IOException {
+ // given
+ ZipFile zipFile = new ZipFile(testBookFilename);
+
+ // when
+ Resources resources = ResourcesLoader.loadResources(zipFile, encoding);
+
+ // then
+ verifyResources(resources);
+ }
+
+ /**
+ * Loads all Resources lazily from a ZipFile
+ */
+ @Test
+ public void testLoadResources_ZipFile_lazy_all() throws IOException {
+ // given
+ ZipFile zipFile = new ZipFile(testBookFilename);
+
+ // when
+ Resources resources = ResourcesLoader.loadResources(zipFile, encoding, Arrays.asList(MediatypeService.mediatypes));
+
+ // then
+ verifyResources(resources);
+ Assert.assertEquals(Resource.class, resources.getById("container").getClass());
+ Assert.assertEquals(LazyResource.class, resources.getById("book1").getClass());
+ }
+
+ /**
+ * Loads the Resources from a ZipFile, some of them lazily.
+ */
+ @Test
+ public void testLoadResources_ZipFile_partial_lazy() throws IOException {
+ // given
+ ZipFile zipFile = new ZipFile(testBookFilename);
+
+ // when
+ Resources resources = ResourcesLoader.loadResources(zipFile, encoding, Collections.singletonList(MediatypeService.CSS));
+
+ // then
+ verifyResources(resources);
+ Assert.assertEquals(Resource.class, resources.getById("container").getClass());
+ Assert.assertEquals(LazyResource.class, resources.getById("book1").getClass());
+ Assert.assertEquals(Resource.class, resources.getById("chapter1").getClass());
+ }
+
+ private void verifyResources(Resources resources) throws IOException {
+ Assert.assertNotNull(resources);
+ Assert.assertEquals(12, resources.getAll().size());
+ List