Merge pull request #909 from ag2s20150909/master
添加自定义epublib,支持epub v3解析目录。pull/913/head 3.21.032520
commit
820a3c4fad
@ -0,0 +1 @@ |
||||
/build |
@ -0,0 +1,40 @@ |
||||
plugins { |
||||
id 'com.android.library' |
||||
} |
||||
|
||||
android { |
||||
compileSdkVersion 30 |
||||
buildToolsVersion "30.0.3" |
||||
|
||||
defaultConfig { |
||||
minSdkVersion 21 |
||||
targetSdkVersion 30 |
||||
versionCode 1 |
||||
versionName "1.0" |
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" |
||||
consumerProguardFiles "consumer-rules.pro" |
||||
} |
||||
|
||||
buildTypes { |
||||
release { |
||||
minifyEnabled false |
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' |
||||
} |
||||
} |
||||
compileOptions { |
||||
sourceCompatibility JavaVersion.VERSION_1_8 |
||||
targetCompatibility JavaVersion.VERSION_1_8 |
||||
} |
||||
} |
||||
|
||||
dependencies { |
||||
|
||||
//因为安卓自带的XmlPullParserFactory |
||||
//me.ag2s.epublib.epub.EpubProcessorSupport |
||||
//修改createXmlSerializer方法可使用kxml2 |
||||
// https://mvnrepository.com/artifact/kxml2/kxml2 |
||||
//implementation 'kxml2:kxml2:2.3.0' |
||||
//去除了slf4j依赖 |
||||
//implementation 'org.slf4j:-android:1.7.25' |
||||
} |
@ -0,0 +1,21 @@ |
||||
# Add project specific ProGuard rules here. |
||||
# You can control the set of applied configuration files using the |
||||
# proguardFiles setting in build.gradle. |
||||
# |
||||
# For more details, see |
||||
# http://developer.android.com/guide/developing/tools/proguard.html |
||||
|
||||
# If your project uses WebView with JS, uncomment the following |
||||
# and specify the fully qualified class name to the JavaScript interface |
||||
# class: |
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview { |
||||
# public *; |
||||
#} |
||||
|
||||
# Uncomment this to preserve the line number information for |
||||
# debugging stack traces. |
||||
#-keepattributes SourceFile,LineNumberTable |
||||
|
||||
# If you keep the line number information, uncomment this to |
||||
# hide the original source file name. |
||||
#-renamesourcefileattribute SourceFile |
@ -0,0 +1,26 @@ |
||||
package me.ag2s.epublib; |
||||
|
||||
import android.content.Context; |
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry; |
||||
import androidx.test.ext.junit.runners.AndroidJUnit4; |
||||
|
||||
import org.junit.Test; |
||||
import org.junit.runner.RunWith; |
||||
|
||||
import static org.junit.Assert.*; |
||||
|
||||
/** |
||||
* Instrumented test, which will execute on an Android device. |
||||
* |
||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a> |
||||
*/ |
||||
@RunWith(AndroidJUnit4.class) |
||||
public class ExampleInstrumentedTest { |
||||
@Test |
||||
public void useAppContext() { |
||||
// Context of the app under test.
|
||||
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); |
||||
assertEquals("me.ag2s.epublib.test", appContext.getPackageName()); |
||||
} |
||||
} |
@ -0,0 +1,7 @@ |
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" |
||||
package="me.ag2s.epublib"> |
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> |
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> |
||||
|
||||
</manifest> |
@ -0,0 +1,139 @@ |
||||
/* -*-mode:java; c-basic-offset:2; -*- */ |
||||
/* |
||||
Copyright (c) 2000-2011 ymnk, JCraft,Inc. All rights reserved. |
||||
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions are met: |
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, |
||||
this list of conditions and the following disclaimer. |
||||
|
||||
2. 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. |
||||
|
||||
3. The names of the authors may not be used to endorse or promote products |
||||
derived from this software without specific prior written permission. |
||||
|
||||
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 JCRAFT, |
||||
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE 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. |
||||
*/ |
||||
/* |
||||
* This program is based on zlib-1.1.3, so all credit should go authors |
||||
* Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) |
||||
* and contributors of zlib. |
||||
*/ |
||||
|
||||
package com.jcraft.jzlib; |
||||
|
||||
final public class Adler32 implements Checksum { |
||||
|
||||
// largest prime smaller than 65536
|
||||
static final private int BASE=65521; |
||||
// NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1
|
||||
static final private int NMAX=5552; |
||||
|
||||
private long s1=1L; |
||||
private long s2=0L; |
||||
|
||||
public void reset(long init){ |
||||
s1=init&0xffff; |
||||
s2=(init>>16)&0xffff; |
||||
} |
||||
|
||||
public void reset(){ |
||||
s1=1L; |
||||
s2=0L; |
||||
} |
||||
|
||||
public long getValue(){ |
||||
return ((s2<<16)|s1); |
||||
} |
||||
|
||||
public void update(byte[] buf, int index, int len){ |
||||
|
||||
if(len==1){ |
||||
s1+=buf[index++]&0xff; s2+=s1; |
||||
s1%=BASE; |
||||
s2%=BASE; |
||||
return; |
||||
} |
||||
|
||||
int len1 = len/NMAX; |
||||
int len2 = len%NMAX; |
||||
while(len1-->0) { |
||||
int k=NMAX; |
||||
len-=k; |
||||
while(k-->0){ |
||||
s1+=buf[index++]&0xff; s2+=s1; |
||||
} |
||||
s1%=BASE; |
||||
s2%=BASE; |
||||
} |
||||
|
||||
int k=len2; |
||||
len-=k; |
||||
while(k-->0){ |
||||
s1+=buf[index++]&0xff; s2+=s1; |
||||
} |
||||
s1%=BASE; |
||||
s2%=BASE; |
||||
} |
||||
|
||||
public Adler32 copy(){ |
||||
Adler32 foo = new Adler32(); |
||||
foo.s1 = this.s1; |
||||
foo.s2 = this.s2; |
||||
return foo; |
||||
} |
||||
|
||||
// The following logic has come from zlib.1.2.
|
||||
static long combine(long adler1, long adler2, long len2){ |
||||
long BASEL = (long)BASE; |
||||
long sum1; |
||||
long sum2; |
||||
long rem; // unsigned int
|
||||
|
||||
rem = len2 % BASEL; |
||||
sum1 = adler1 & 0xffffL; |
||||
sum2 = rem * sum1; |
||||
sum2 %= BASEL; // MOD(sum2);
|
||||
sum1 += (adler2 & 0xffffL) + BASEL - 1; |
||||
sum2 += ((adler1 >> 16) & 0xffffL) + ((adler2 >> 16) & 0xffffL) + BASEL - rem; |
||||
if (sum1 >= BASEL) sum1 -= BASEL; |
||||
if (sum1 >= BASEL) sum1 -= BASEL; |
||||
if (sum2 >= (BASEL << 1)) sum2 -= (BASEL << 1); |
||||
if (sum2 >= BASEL) sum2 -= BASEL; |
||||
return sum1 | (sum2 << 16); |
||||
} |
||||
|
||||
/* |
||||
private java.util.zip.Adler32 adler=new java.util.zip.Adler32(); |
||||
public void update(byte[] buf, int index, int len){ |
||||
if(buf==null) {adler.reset();} |
||||
else{adler.update(buf, index, len);} |
||||
} |
||||
public void reset(){ |
||||
adler.reset(); |
||||
} |
||||
public void reset(long init){ |
||||
if(init==1L){ |
||||
adler.reset(); |
||||
} |
||||
else{ |
||||
System.err.println("unsupported operation"); |
||||
} |
||||
} |
||||
public long getValue(){ |
||||
return adler.getValue(); |
||||
} |
||||
*/ |
||||
} |
@ -0,0 +1,179 @@ |
||||
/* -*-mode:java; c-basic-offset:2; -*- */ |
||||
/* |
||||
Copyright (c) 2011 ymnk, JCraft,Inc. All rights reserved. |
||||
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions are met: |
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, |
||||
this list of conditions and the following disclaimer. |
||||
|
||||
2. 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. |
||||
|
||||
3. The names of the authors may not be used to endorse or promote products |
||||
derived from this software without specific prior written permission. |
||||
|
||||
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 JCRAFT, |
||||
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE 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. |
||||
*/ |
||||
/* |
||||
* This program is based on zlib-1.1.3, so all credit should go authors |
||||
* Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) |
||||
* and contributors of zlib. |
||||
*/ |
||||
|
||||
package com.jcraft.jzlib; |
||||
|
||||
final public class CRC32 implements Checksum { |
||||
|
||||
/* |
||||
* The following logic has come from RFC1952. |
||||
*/ |
||||
private int v = 0; |
||||
private static int[] crc_table = null; |
||||
static { |
||||
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; |
||||
} |
||||
} |
||||
|
||||
public void update (byte[] buf, int index, int len) { |
||||
int c = ~v; |
||||
while (--len >= 0) |
||||
c = crc_table[(c^buf[index++])&0xff]^(c >>> 8); |
||||
v = ~c; |
||||
} |
||||
|
||||
public void reset(){ |
||||
v = 0; |
||||
} |
||||
|
||||
public void reset(long vv){ |
||||
v = (int)(vv&0xffffffffL); |
||||
} |
||||
|
||||
public long getValue(){ |
||||
return (long)(v&0xffffffffL); |
||||
} |
||||
|
||||
// The following logic has come from zlib.1.2.
|
||||
private static final int GF2_DIM = 32; |
||||
static long combine(long crc1, long crc2, long len2){ |
||||
long row; |
||||
long[] even = new long[GF2_DIM]; |
||||
long[] odd = new long[GF2_DIM]; |
||||
|
||||
// degenerate case (also disallow negative lengths)
|
||||
if (len2 <= 0) |
||||
return crc1; |
||||
|
||||
// put operator for one zero bit in odd
|
||||
odd[0] = 0xedb88320L; // CRC-32 polynomial
|
||||
row = 1; |
||||
for (int n = 1; n < GF2_DIM; n++) { |
||||
odd[n] = row; |
||||
row <<= 1; |
||||
} |
||||
|
||||
// put operator for two zero bits in even
|
||||
gf2_matrix_square(even, odd); |
||||
|
||||
// put operator for four zero bits in odd
|
||||
gf2_matrix_square(odd, even); |
||||
|
||||
// apply len2 zeros to crc1 (first square will put the operator for one
|
||||
// zero byte, eight zero bits, in even)
|
||||
do { |
||||
// apply zeros operator for this bit of len2
|
||||
gf2_matrix_square(even, odd); |
||||
if ((len2 & 1)!=0) |
||||
crc1 = gf2_matrix_times(even, crc1); |
||||
len2 >>= 1; |
||||
|
||||
// if no more bits set, then done
|
||||
if (len2 == 0) |
||||
break; |
||||
|
||||
// another iteration of the loop with odd and even swapped
|
||||
gf2_matrix_square(odd, even); |
||||
if ((len2 & 1)!=0) |
||||
crc1 = gf2_matrix_times(odd, crc1); |
||||
len2 >>= 1; |
||||
|
||||
// if no more bits set, then done
|
||||
} while (len2 != 0); |
||||
|
||||
/* return combined crc */ |
||||
crc1 ^= crc2; |
||||
return crc1; |
||||
} |
||||
|
||||
private static long gf2_matrix_times(long[] mat, long vec){ |
||||
long sum = 0; |
||||
int index = 0; |
||||
while (vec!=0) { |
||||
if ((vec & 1)!=0) |
||||
sum ^= mat[index]; |
||||
vec >>= 1; |
||||
index++; |
||||
} |
||||
return sum; |
||||
} |
||||
|
||||
static final void gf2_matrix_square(long[] square, long[] mat) { |
||||
for (int n = 0; n < GF2_DIM; n++) |
||||
square[n] = gf2_matrix_times(mat, mat[n]); |
||||
} |
||||
|
||||
/* |
||||
private java.util.zip.CRC32 crc32 = new java.util.zip.CRC32(); |
||||
|
||||
public void update(byte[] buf, int index, int len){ |
||||
if(buf==null) {crc32.reset();} |
||||
else{crc32.update(buf, index, len);} |
||||
} |
||||
public void reset(){ |
||||
crc32.reset(); |
||||
} |
||||
public void reset(long init){ |
||||
if(init==0L){ |
||||
crc32.reset(); |
||||
} |
||||
else{ |
||||
System.err.println("unsupported operation"); |
||||
} |
||||
} |
||||
public long getValue(){ |
||||
return crc32.getValue(); |
||||
} |
||||
*/ |
||||
public CRC32 copy(){ |
||||
CRC32 foo = new CRC32(); |
||||
foo.v = this.v; |
||||
return foo; |
||||
} |
||||
|
||||
public static int[] getCRC32Table(){ |
||||
int[] tmp = new int[crc_table.length]; |
||||
System.arraycopy(crc_table, 0, tmp, 0, tmp.length); |
||||
return tmp; |
||||
} |
||||
} |
@ -0,0 +1,43 @@ |
||||
/* -*-mode:java; c-basic-offset:2; -*- */ |
||||
/* |
||||
Copyright (c) 2011 ymnk, JCraft,Inc. All rights reserved. |
||||
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions are met: |
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, |
||||
this list of conditions and the following disclaimer. |
||||
|
||||
2. 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. |
||||
|
||||
3. The names of the authors may not be used to endorse or promote products |
||||
derived from this software without specific prior written permission. |
||||
|
||||
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 JCRAFT, |
||||
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE 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. |
||||
*/ |
||||
/* |
||||
* This program is based on zlib-1.1.3, so all credit should go authors |
||||
* Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) |
||||
* and contributors of zlib. |
||||
*/ |
||||
|
||||
package com.jcraft.jzlib; |
||||
|
||||
interface Checksum { |
||||
void update(byte[] buf, int index, int len); |
||||
void reset(); |
||||
void reset(long init); |
||||
long getValue(); |
||||
Checksum copy(); |
||||
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,171 @@ |
||||
/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ |
||||
/* |
||||
Copyright (c) 2011 ymnk, JCraft,Inc. All rights reserved. |
||||
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions are met: |
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, |
||||
this list of conditions and the following disclaimer. |
||||
|
||||
2. 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. |
||||
|
||||
3. The names of the authors may not be used to endorse or promote products |
||||
derived from this software without specific prior written permission. |
||||
|
||||
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 JCRAFT, |
||||
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE 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. |
||||
*/ |
||||
/* |
||||
* This program is based on zlib-1.1.3, so all credit should go authors |
||||
* Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) |
||||
* and contributors of zlib. |
||||
*/ |
||||
|
||||
package com.jcraft.jzlib; |
||||
|
||||
final public class Deflater extends ZStream{ |
||||
|
||||
static final private int MAX_WBITS=15; // 32K LZ77 window
|
||||
static final private int DEF_WBITS=MAX_WBITS; |
||||
|
||||
static final private int Z_NO_FLUSH=0; |
||||
static final private int Z_PARTIAL_FLUSH=1; |
||||
static final private int Z_SYNC_FLUSH=2; |
||||
static final private int Z_FULL_FLUSH=3; |
||||
static final private int Z_FINISH=4; |
||||
|
||||
static final private int MAX_MEM_LEVEL=9; |
||||
|
||||
static final private int Z_OK=0; |
||||
static final private int Z_STREAM_END=1; |
||||
static final private int Z_NEED_DICT=2; |
||||
static final private int Z_ERRNO=-1; |
||||
static final private int Z_STREAM_ERROR=-2; |
||||
static final private int Z_DATA_ERROR=-3; |
||||
static final private int Z_MEM_ERROR=-4; |
||||
static final private int Z_BUF_ERROR=-5; |
||||
static final private int Z_VERSION_ERROR=-6; |
||||
|
||||
private boolean finished = false; |
||||
|
||||
public Deflater(){ |
||||
super(); |
||||
} |
||||
|
||||
public Deflater(int level) throws GZIPException { |
||||
this(level, MAX_WBITS); |
||||
} |
||||
|
||||
public Deflater(int level, boolean nowrap) throws GZIPException { |
||||
this(level, MAX_WBITS, nowrap); |
||||
} |
||||
|
||||
public Deflater(int level, int bits) throws GZIPException { |
||||
this(level, bits, false); |
||||
} |
||||
|
||||
public Deflater(int level, int bits, boolean nowrap) throws GZIPException { |
||||
super(); |
||||
int ret = init(level, bits, nowrap); |
||||
if(ret!=Z_OK) |
||||
throw new GZIPException(ret+": "+msg); |
||||
} |
||||
|
||||
public Deflater(int level, int bits, int memlevel, JZlib.WrapperType wrapperType) throws GZIPException { |
||||
super(); |
||||
int ret = init(level, bits, memlevel, wrapperType); |
||||
if(ret!=Z_OK) |
||||
throw new GZIPException(ret+": "+msg); |
||||
} |
||||
|
||||
public Deflater(int level, int bits, int memlevel) throws GZIPException { |
||||
super(); |
||||
int ret = init(level, bits, memlevel); |
||||
if(ret!=Z_OK) |
||||
throw new GZIPException(ret+": "+msg); |
||||
} |
||||
|
||||
public int init(int level){ |
||||
return init(level, MAX_WBITS); |
||||
} |
||||
public int init(int level, boolean nowrap){ |
||||
return init(level, MAX_WBITS, nowrap); |
||||
} |
||||
public int init(int level, int bits){ |
||||
return init(level, bits, false); |
||||
} |
||||
public int init(int level, int bits, int memlevel, JZlib.WrapperType wrapperType){ |
||||
if(bits < 9 || bits > 15){ |
||||
return Z_STREAM_ERROR; |
||||
} |
||||
if(wrapperType == JZlib.W_NONE) { |
||||
bits *= -1; |
||||
} |
||||
else if(wrapperType == JZlib.W_GZIP) { |
||||
bits += 16; |
||||
} |
||||
else if(wrapperType == JZlib.W_ANY) { |
||||
return Z_STREAM_ERROR; |
||||
} |
||||
else if(wrapperType == JZlib.W_ZLIB) { |
||||
} |
||||
return init(level, bits, memlevel); |
||||
} |
||||
public int init(int level, int bits, int memlevel){ |
||||
finished = false; |
||||
dstate=new Deflate(this); |
||||
return dstate.deflateInit(level, bits, memlevel); |
||||
} |
||||
public int init(int level, int bits, boolean nowrap){ |
||||
finished = false; |
||||
dstate=new Deflate(this); |
||||
return dstate.deflateInit(level, nowrap?-bits:bits); |
||||
} |
||||
|
||||
public int deflate(int flush){ |
||||
if(dstate==null){ |
||||
return Z_STREAM_ERROR; |
||||
} |
||||
int ret = dstate.deflate(flush); |
||||
if(ret == Z_STREAM_END) |
||||
finished = true; |
||||
return ret; |
||||
} |
||||
public int end(){ |
||||
finished = true; |
||||
if(dstate==null) return Z_STREAM_ERROR; |
||||
int ret=dstate.deflateEnd(); |
||||
dstate=null; |
||||
free(); |
||||
return ret; |
||||
} |
||||
public int params(int level, int strategy){ |
||||
if(dstate==null) return Z_STREAM_ERROR; |
||||
return dstate.deflateParams(level, strategy); |
||||
} |
||||
public int setDictionary (byte[] dictionary, int dictLength){ |
||||
if(dstate == null) |
||||
return Z_STREAM_ERROR; |
||||
return dstate.deflateSetDictionary(dictionary, dictLength); |
||||
} |
||||
|
||||
public boolean finished(){ |
||||
return finished; |
||||
} |
||||
|
||||
public int copy(Deflater src){ |
||||
this.finished = src.finished; |
||||
return Deflate.deflateCopy(this, src); |
||||
} |
||||
} |
@ -0,0 +1,181 @@ |
||||
/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ |
||||
/* |
||||
Copyright (c) 2011 ymnk, JCraft,Inc. All rights reserved. |
||||
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions are met: |
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, |
||||
this list of conditions and the following disclaimer. |
||||
|
||||
2. 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. |
||||
|
||||
3. The names of the authors may not be used to endorse or promote products |
||||
derived from this software without specific prior written permission. |
||||
|
||||
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 JCRAFT, |
||||
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, |
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, |
||||
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, |
||||
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
*/ |
||||
|
||||
package com.jcraft.jzlib; |
||||
import java.io.*; |
||||
|
||||
public class DeflaterOutputStream extends FilterOutputStream { |
||||
|
||||
protected final Deflater deflater; |
||||
|
||||
protected byte[] buffer; |
||||
|
||||
private boolean closed = false; |
||||
|
||||
private boolean syncFlush = false; |
||||
|
||||
private final byte[] buf1 = new byte[1]; |
||||
|
||||
protected boolean mydeflater = false; |
||||
|
||||
private boolean close_out = true; |
||||
|
||||
protected static final int DEFAULT_BUFSIZE = 512; |
||||
|
||||
public DeflaterOutputStream(OutputStream out) throws IOException { |
||||
this(out, |
||||
new Deflater(JZlib.Z_DEFAULT_COMPRESSION), |
||||
DEFAULT_BUFSIZE, true); |
||||
mydeflater = true; |
||||
} |
||||
|
||||
public DeflaterOutputStream(OutputStream out, Deflater def) throws IOException { |
||||
this(out, def, DEFAULT_BUFSIZE, true); |
||||
} |
||||
|
||||
public DeflaterOutputStream(OutputStream out, |
||||
Deflater deflater, |
||||
int size) throws IOException { |
||||
this(out, deflater, size, true); |
||||
} |
||||
public DeflaterOutputStream(OutputStream out, |
||||
Deflater deflater, |
||||
int size, |
||||
boolean close_out) throws IOException { |
||||
super(out); |
||||
if (out == null || deflater == null) { |
||||
throw new NullPointerException(); |
||||
} |
||||
else if (size <= 0) { |
||||
throw new IllegalArgumentException("buffer size must be greater than 0"); |
||||
} |
||||
this.deflater = deflater; |
||||
buffer = new byte[size]; |
||||
this.close_out = close_out; |
||||
} |
||||
|
||||
public void write(int b) throws IOException { |
||||
buf1[0] = (byte)(b & 0xff); |
||||
write(buf1, 0, 1); |
||||
} |
||||
|
||||
public void write(byte[] b, int off, int len) throws IOException { |
||||
if (deflater.finished()) { |
||||
throw new IOException("finished"); |
||||
} |
||||
else if (off<0 | len<0 | off+len>b.length) { |
||||
throw new IndexOutOfBoundsException(); |
||||
} |
||||
else if (len == 0) { |
||||
return; |
||||
} |
||||
else { |
||||
int flush = syncFlush ? JZlib.Z_SYNC_FLUSH : JZlib.Z_NO_FLUSH; |
||||
deflater.setInput(b, off, len, true); |
||||
while (deflater.avail_in>0) { |
||||
int err = deflate(flush); |
||||
if (err == JZlib.Z_STREAM_END) |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
|
||||
public void finish() throws IOException { |
||||
while (!deflater.finished()) { |
||||
deflate(JZlib.Z_FINISH); |
||||
} |
||||
} |
||||
|
||||
public void close() throws IOException { |
||||
if (!closed) { |
||||
finish(); |
||||
if (mydeflater){ |
||||
deflater.end(); |
||||
} |
||||
if(close_out) |
||||
out.close(); |
||||
closed = true; |
||||
} |
||||
} |
||||
|
||||
protected int deflate(int flush) throws IOException { |
||||
deflater.setOutput(buffer, 0, buffer.length); |
||||
int err = deflater.deflate(flush); |
||||
switch(err) { |
||||
case JZlib.Z_OK: |
||||
case JZlib.Z_STREAM_END: |
||||
break; |
||||
case JZlib.Z_BUF_ERROR: |
||||
if(deflater.avail_in<=0 && flush!=JZlib.Z_FINISH){ |
||||
// flush() without any data
|
||||
break; |
||||
} |
||||
default: |
||||
throw new IOException("failed to deflate"); |
||||
} |
||||
int len = deflater.next_out_index; |
||||
if (len > 0) { |
||||
out.write(buffer, 0, len); |
||||
} |
||||
return err; |
||||
} |
||||
|
||||
public void flush() throws IOException { |
||||
if (syncFlush && !deflater.finished()) { |
||||
while (true) { |
||||
int err = deflate(JZlib.Z_SYNC_FLUSH); |
||||
if (deflater.next_out_index < buffer.length) |
||||
break; |
||||
if (err == JZlib.Z_STREAM_END) |
||||
break; |
||||
} |
||||
} |
||||
out.flush(); |
||||
} |
||||
|
||||
public long getTotalIn() { |
||||
return deflater.getTotalIn(); |
||||
} |
||||
|
||||
public long getTotalOut() { |
||||
return deflater.getTotalOut(); |
||||
} |
||||
|
||||
public void setSyncFlush(boolean syncFlush){ |
||||
this.syncFlush = syncFlush; |
||||
} |
||||
|
||||
public boolean getSyncFlush(){ |
||||
return this.syncFlush; |
||||
} |
||||
|
||||
public Deflater getDeflater(){ |
||||
return deflater; |
||||
} |
||||
} |
@ -0,0 +1,44 @@ |
||||
/* -*-mode:java; c-basic-offset:2; -*- */ |
||||
/* |
||||
Copyright (c) 2011 ymnk, JCraft,Inc. All rights reserved. |
||||
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions are met: |
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, |
||||
this list of conditions and the following disclaimer. |
||||
|
||||
2. 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. |
||||
|
||||
3. The names of the authors may not be used to endorse or promote products |
||||
derived from this software without specific prior written permission. |
||||
|
||||
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 JCRAFT, |
||||
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE 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. |
||||
*/ |
||||
/* |
||||
* This program is based on zlib-1.1.3, so all credit should go authors |
||||
* Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) |
||||
* and contributors of zlib. |
||||
*/ |
||||
|
||||
package com.jcraft.jzlib; |
||||
|
||||
public class GZIPException extends java.io.IOException { |
||||
public GZIPException() { |
||||
super(); |
||||
} |
||||
public GZIPException(String s) { |
||||
super(s); |
||||
} |
||||
} |
@ -0,0 +1,195 @@ |
||||
/* -*-mode:java; c-basic-offset:2; -*- */ |
||||
/* |
||||
Copyright (c) 2011 ymnk, JCraft,Inc. All rights reserved. |
||||
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions are met: |
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, |
||||
this list of conditions and the following disclaimer. |
||||
|
||||
2. 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. |
||||
|
||||
3. The names of the authors may not be used to endorse or promote products |
||||
derived from this software without specific prior written permission. |
||||
|
||||
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 JCRAFT, |
||||
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE 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. |
||||
*/ |
||||
/* |
||||
* This program is based on zlib-1.1.3, so all credit should go authors |
||||
* Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) |
||||
* and contributors of zlib. |
||||
*/ |
||||
|
||||
package com.jcraft.jzlib; |
||||
|
||||
import java.io.UnsupportedEncodingException; |
||||
import java.nio.charset.StandardCharsets; |
||||
|
||||
/** |
||||
* @see "http://www.ietf.org/rfc/rfc1952.txt" |
||||
*/ |
||||
public class GZIPHeader implements Cloneable { |
||||
|
||||
public static final byte OS_MSDOS = (byte) 0x00; |
||||
public static final byte OS_AMIGA = (byte) 0x01; |
||||
public static final byte OS_VMS = (byte) 0x02; |
||||
public static final byte OS_UNIX = (byte) 0x03; |
||||
public static final byte OS_ATARI = (byte) 0x05; |
||||
public static final byte OS_OS2 = (byte) 0x06; |
||||
public static final byte OS_MACOS = (byte) 0x07; |
||||
public static final byte OS_TOPS20 = (byte) 0x0a; |
||||
public static final byte OS_WIN32 = (byte) 0x0b; |
||||
public static final byte OS_VMCMS = (byte) 0x04; |
||||
public static final byte OS_ZSYSTEM = (byte) 0x08; |
||||
public static final byte OS_CPM = (byte) 0x09; |
||||
public static final byte OS_QDOS = (byte) 0x0c; |
||||
public static final byte OS_RISCOS = (byte) 0x0d; |
||||
public static final byte OS_UNKNOWN = (byte) 0xff; |
||||
|
||||
boolean text = false; |
||||
private boolean fhcrc = false; |
||||
long time; |
||||
int xflags; |
||||
int os = 255; |
||||
byte[] extra; |
||||
byte[] name; |
||||
byte[] comment; |
||||
int hcrc; |
||||
long crc; |
||||
boolean done = false; |
||||
long mtime = 0; |
||||
|
||||
public void setModifiedTime(long mtime) { |
||||
this.mtime = mtime; |
||||
} |
||||
|
||||
public long getModifiedTime() { |
||||
return mtime; |
||||
} |
||||
|
||||
public void setOS(int os) { |
||||
if((0<=os && os <=13) || os==255) |
||||
this.os=os; |
||||
else |
||||
throw new IllegalArgumentException("os: "+os); |
||||
} |
||||
|
||||
public int getOS(){ |
||||
return os; |
||||
} |
||||
|
||||
public void setName(String name) { |
||||
this.name=name.getBytes(StandardCharsets.ISO_8859_1); |
||||
} |
||||
|
||||
public String getName(){ |
||||
if(name==null) return ""; |
||||
return new String(name, StandardCharsets.ISO_8859_1); |
||||
} |
||||
|
||||
public void setComment(String comment) { |
||||
this.comment=comment.getBytes(StandardCharsets.ISO_8859_1); |
||||
} |
||||
|
||||
public String getComment(){ |
||||
if(comment==null) return ""; |
||||
return new String(comment, StandardCharsets.ISO_8859_1); |
||||
} |
||||
|
||||
public void setCRC(long crc){ |
||||
this.crc = crc; |
||||
} |
||||
|
||||
public long getCRC(){ |
||||
return crc; |
||||
} |
||||
|
||||
void put(Deflate d){ |
||||
int flag = 0; |
||||
if(text){ |
||||
flag |= 1; // FTEXT
|
||||
} |
||||
if(fhcrc){ |
||||
flag |= 2; // FHCRC
|
||||
} |
||||
if(extra!=null){ |
||||
flag |= 4; // FEXTRA
|
||||
} |
||||
if(name!=null){ |
||||
flag |= 8; // FNAME
|
||||
} |
||||
if(comment!=null){ |
||||
flag |= 16; // FCOMMENT
|
||||
} |
||||
int xfl = 0; |
||||
if(d.level == JZlib.Z_BEST_SPEED){ |
||||
xfl |= 4; |
||||
} |
||||
else if (d.level == JZlib.Z_BEST_COMPRESSION){ |
||||
xfl |= 2; |
||||
} |
||||
|
||||
d.put_short((short)0x8b1f); // ID1 ID2
|
||||
d.put_byte((byte)8); // CM(Compression Method)
|
||||
d.put_byte((byte)flag); |
||||
d.put_byte((byte)mtime); |
||||
d.put_byte((byte)(mtime>>8)); |
||||
d.put_byte((byte)(mtime>>16)); |
||||
d.put_byte((byte)(mtime>>24)); |
||||
d.put_byte((byte)xfl); |
||||
d.put_byte((byte)os); |
||||
|
||||
if(extra!=null){ |
||||
d.put_byte((byte)extra.length); |
||||
d.put_byte((byte)(extra.length>>8)); |
||||
d.put_byte(extra, 0, extra.length); |
||||
} |
||||
|
||||
if(name!=null){ |
||||
d.put_byte(name, 0, name.length); |
||||
d.put_byte((byte)0); |
||||
} |
||||
|
||||
if(comment!=null){ |
||||
d.put_byte(comment, 0, comment.length); |
||||
d.put_byte((byte)0); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public Object clone() throws CloneNotSupportedException { |
||||
GZIPHeader gheader = (GZIPHeader)super.clone(); |
||||
byte[] tmp; |
||||
if(gheader.extra!=null){ |
||||
tmp=new byte[gheader.extra.length]; |
||||
System.arraycopy(gheader.extra, 0, tmp, 0, tmp.length); |
||||
gheader.extra = tmp; |
||||
} |
||||
|
||||
if(gheader.name!=null){ |
||||
tmp=new byte[gheader.name.length]; |
||||
System.arraycopy(gheader.name, 0, tmp, 0, tmp.length); |
||||
gheader.name = tmp; |
||||
} |
||||
|
||||
if(gheader.comment!=null){ |
||||
tmp=new byte[gheader.comment.length]; |
||||
System.arraycopy(gheader.comment, 0, tmp, 0, tmp.length); |
||||
gheader.comment = tmp; |
||||
} |
||||
|
||||
return gheader; |
||||
} |
||||
} |
@ -0,0 +1,145 @@ |
||||
/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ |
||||
/* |
||||
Copyright (c) 2011 ymnk, JCraft,Inc. All rights reserved. |
||||
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions are met: |
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, |
||||
this list of conditions and the following disclaimer. |
||||
|
||||
2. 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. |
||||
|
||||
3. The names of the authors may not be used to endorse or promote products |
||||
derived from this software without specific prior written permission. |
||||
|
||||
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 JCRAFT, |
||||
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, |
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, |
||||
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, |
||||
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
*/ |
||||
|
||||
package com.jcraft.jzlib; |
||||
import java.io.*; |
||||
|
||||
public class GZIPInputStream extends InflaterInputStream { |
||||
|
||||
public GZIPInputStream(InputStream in) throws IOException { |
||||
this(in, DEFAULT_BUFSIZE, true); |
||||
} |
||||
|
||||
public GZIPInputStream(InputStream in, |
||||
int size, |
||||
boolean close_in) throws IOException { |
||||
this(in, new Inflater(15+16), size, close_in); |
||||
myinflater = true; |
||||
} |
||||
|
||||
public GZIPInputStream(InputStream in, |
||||
Inflater inflater, |
||||
int size, |
||||
boolean close_in) throws IOException { |
||||
super(in, inflater, size, close_in); |
||||
} |
||||
|
||||
public long getModifiedtime() { |
||||
return inflater.istate.getGZIPHeader().getModifiedTime(); |
||||
} |
||||
|
||||
public int getOS() { |
||||
return inflater.istate.getGZIPHeader().getOS(); |
||||
} |
||||
|
||||
public String getName() { |
||||
return inflater.istate.getGZIPHeader().getName(); |
||||
} |
||||
|
||||
public String getComment() { |
||||
return inflater.istate.getGZIPHeader().getComment(); |
||||
} |
||||
|
||||
public long getCRC() throws GZIPException { |
||||
if(inflater.istate.mode != 12 /*DONE*/) |
||||
throw new GZIPException("checksum is not calculated yet."); |
||||
return inflater.istate.getGZIPHeader().getCRC(); |
||||
} |
||||
|
||||
public void readHeader() throws IOException { |
||||
|
||||
byte[] empty = "".getBytes(); |
||||
inflater.setOutput(empty, 0, 0); |
||||
inflater.setInput(empty, 0, 0, false); |
||||
|
||||
byte[] b = new byte[10]; |
||||
|
||||
int n = fill(b); |
||||
if(n!=10){ |
||||
if(n>0){ |
||||
inflater.setInput(b, 0, n, false); |
||||
//inflater.next_in_index = n;
|
||||
inflater.next_in_index = 0; |
||||
inflater.avail_in = n; |
||||
} |
||||
throw new IOException("no input"); |
||||
} |
||||
|
||||
inflater.setInput(b, 0, n, false); |
||||
|
||||
byte[] b1 = new byte[1]; |
||||
do{ |
||||
if(inflater.avail_in<=0){ |
||||
int i = in.read(b1); |
||||
if(i<=0) |
||||
throw new IOException("no input"); |
||||
inflater.setInput(b1, 0, 1, true); |
||||
} |
||||
|
||||
int err = inflater.inflate(JZlib.Z_NO_FLUSH); |
||||
|
||||
if(err!=0/*Z_OK*/){ |
||||
int len = 2048-inflater.next_in.length; |
||||
if(len>0){ |
||||
byte[] tmp = new byte[len]; |
||||
n = fill(tmp); |
||||
if(n>0){ |
||||
inflater.avail_in += inflater.next_in_index; |
||||
inflater.next_in_index = 0; |
||||
inflater.setInput(tmp, 0, n, true); |
||||
} |
||||
} |
||||
//inflater.next_in_index = inflater.next_in.length;
|
||||
inflater.avail_in += inflater.next_in_index; |
||||
inflater.next_in_index = 0; |
||||
throw new IOException(inflater.msg); |
||||
} |
||||
} |
||||
while(inflater.istate.inParsingHeader()); |
||||
} |
||||
|
||||
private int fill(byte[] buf) { |
||||
int len = buf.length; |
||||
int n = 0; |
||||
do{ |
||||
int i = -1; |
||||
try { |
||||
i = in.read(buf, n, buf.length - n); |
||||
} |
||||
catch(IOException e){ |
||||
} |
||||
if(i == -1){ |
||||
break; |
||||
} |
||||
n+=i; |
||||
} |
||||
while(n<len); |
||||
return n; |
||||
} |
||||
} |
@ -0,0 +1,90 @@ |
||||
/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ |
||||
/* |
||||
Copyright (c) 2011 ymnk, JCraft,Inc. All rights reserved. |
||||
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions are met: |
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, |
||||
this list of conditions and the following disclaimer. |
||||
|
||||
2. 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. |
||||
|
||||
3. The names of the authors may not be used to endorse or promote products |
||||
derived from this software without specific prior written permission. |
||||
|
||||
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 JCRAFT, |
||||
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, |
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, |
||||
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, |
||||
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
*/ |
||||
|
||||
package com.jcraft.jzlib; |
||||
import java.io.*; |
||||
|
||||
public class GZIPOutputStream extends DeflaterOutputStream { |
||||
|
||||
public GZIPOutputStream(OutputStream out) throws IOException { |
||||
this(out, DEFAULT_BUFSIZE); |
||||
} |
||||
|
||||
public GZIPOutputStream(OutputStream out, int size) throws IOException { |
||||
this(out, size, true); |
||||
} |
||||
|
||||
public GZIPOutputStream(OutputStream out, |
||||
int size, |
||||
boolean close_out) throws IOException { |
||||
this(out, |
||||
new Deflater(JZlib.Z_DEFAULT_COMPRESSION, 15+16), |
||||
size, close_out); |
||||
mydeflater=true; |
||||
} |
||||
|
||||
public GZIPOutputStream(OutputStream out, |
||||
Deflater deflater, |
||||
int size, |
||||
boolean close_out) throws IOException{ |
||||
super(out, deflater, size, close_out); |
||||
} |
||||
|
||||
|
||||
private void check() throws GZIPException { |
||||
if(deflater.dstate.status != 42 /*INIT_STATUS*/) |
||||
throw new GZIPException("header is already written."); |
||||
} |
||||
|
||||
public void setModifiedTime(long mtime) throws GZIPException { |
||||
check(); |
||||
deflater.dstate.getGZIPHeader().setModifiedTime(mtime); |
||||
} |
||||
|
||||
public void setOS(int os) throws GZIPException { |
||||
check(); |
||||
deflater.dstate.getGZIPHeader().setOS(os); |
||||
} |
||||
|
||||
public void setName(String name) throws GZIPException { |
||||
check(); |
||||
deflater.dstate.getGZIPHeader().setName(name); |
||||
} |
||||
|
||||
public void setComment(String comment) throws GZIPException { |
||||
check(); |
||||
deflater.dstate.getGZIPHeader().setComment(comment); |
||||
} |
||||
|
||||
public long getCRC() throws GZIPException { |
||||
if(deflater.dstate.status != 666 /*FINISH_STATE*/) |
||||
throw new GZIPException("checksum is not calculated yet."); |
||||
return deflater.dstate.getGZIPHeader().getCRC(); |
||||
} |
||||
} |
@ -0,0 +1,614 @@ |
||||
/* -*-mode:java; c-basic-offset:2; -*- */ |
||||
/* |
||||
Copyright (c) 2011 ymnk, JCraft,Inc. All rights reserved. |
||||
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions are met: |
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, |
||||
this list of conditions and the following disclaimer. |
||||
|
||||
2. 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. |
||||
|
||||
3. The names of the authors may not be used to endorse or promote products |
||||
derived from this software without specific prior written permission. |
||||
|
||||
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 JCRAFT, |
||||
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE 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. |
||||
*/ |
||||
/* |
||||
* This program is based on zlib-1.1.3, so all credit should go authors |
||||
* Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) |
||||
* and contributors of zlib. |
||||
*/ |
||||
|
||||
package com.jcraft.jzlib; |
||||
|
||||
final class InfBlocks{ |
||||
static final private int MANY=1440; |
||||
|
||||
// And'ing with mask[n] masks the lower n bits
|
||||
static final private int[] inflate_mask = { |
||||
0x00000000, 0x00000001, 0x00000003, 0x00000007, 0x0000000f, |
||||
0x0000001f, 0x0000003f, 0x0000007f, 0x000000ff, 0x000001ff, |
||||
0x000003ff, 0x000007ff, 0x00000fff, 0x00001fff, 0x00003fff, |
||||
0x00007fff, 0x0000ffff |
||||
}; |
||||
|
||||
// Table for deflate from PKZIP's appnote.txt.
|
||||
static final int[] border = { // Order of the bit length code lengths
|
||||
16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 |
||||
}; |
||||
|
||||
static final private int Z_OK=0; |
||||
static final private int Z_STREAM_END=1; |
||||
static final private int Z_NEED_DICT=2; |
||||
static final private int Z_ERRNO=-1; |
||||
static final private int Z_STREAM_ERROR=-2; |
||||
static final private int Z_DATA_ERROR=-3; |
||||
static final private int Z_MEM_ERROR=-4; |
||||
static final private int Z_BUF_ERROR=-5; |
||||
static final private int Z_VERSION_ERROR=-6; |
||||
|
||||
static final private int TYPE=0; // get type bits (3, including end bit)
|
||||
static final private int LENS=1; // get lengths for stored
|
||||
static final private int STORED=2;// processing stored block
|
||||
static final private int TABLE=3; // get table lengths
|
||||
static final private int BTREE=4; // get bit lengths tree for a dynamic block
|
||||
static final private int DTREE=5; // get length, distance trees for a dynamic block
|
||||
static final private int CODES=6; // processing fixed or dynamic block
|
||||
static final private int DRY=7; // output remaining window bytes
|
||||
static final private int DONE=8; // finished last block, done
|
||||
static final private int BAD=9; // ot a data error--stuck here
|
||||
|
||||
int mode; // current inflate_block mode
|
||||
|
||||
int left; // if STORED, bytes left to copy
|
||||
|
||||
int table; // table lengths (14 bits)
|
||||
int index; // index into blens (or border)
|
||||
int[] blens; // bit lengths of codes
|
||||
int[] bb=new int[1]; // bit length tree depth
|
||||
int[] tb=new int[1]; // bit length decoding tree
|
||||
|
||||
int[] bl=new int[1]; |
||||
int[] bd=new int[1]; |
||||
|
||||
int[][] tl=new int[1][]; |
||||
int[][] td=new int[1][]; |
||||
int[] tli=new int[1]; // tl_index
|
||||
int[] tdi=new int[1]; // td_index
|
||||
|
||||
private final InfCodes codes; // if CODES, current state
|
||||
|
||||
int last; // true if this block is the last block
|
||||
|
||||
// mode independent information
|
||||
int bitk; // bits in bit buffer
|
||||
int bitb; // bit buffer
|
||||
int[] hufts; // single malloc for tree space
|
||||
byte[] window; // sliding window
|
||||
int end; // one byte after sliding window
|
||||
int read; // window read pointer
|
||||
int write; // window write pointer
|
||||
private boolean check; |
||||
|
||||
private final InfTree inftree=new InfTree(); |
||||
|
||||
private final ZStream z; |
||||
|
||||
InfBlocks(ZStream z, int w){ |
||||
this.z=z; |
||||
this.codes=new InfCodes(this.z, this); |
||||
hufts=new int[MANY*3]; |
||||
window=new byte[w]; |
||||
end=w; |
||||
this.check = (z.istate.wrap==0) ? false : true; |
||||
mode = TYPE; |
||||
reset(); |
||||
} |
||||
|
||||
void reset(){ |
||||
if(mode==BTREE || mode==DTREE){ |
||||
} |
||||
if(mode==CODES){ |
||||
codes.free(z); |
||||
} |
||||
mode=TYPE; |
||||
bitk=0; |
||||
bitb=0; |
||||
read=write=0; |
||||
if(check){ |
||||
z.adler.reset(); |
||||
} |
||||
} |
||||
|
||||
int proc(int r){ |
||||
int t; // temporary storage
|
||||
int b; // bit buffer
|
||||
int k; // bits in bit buffer
|
||||
int p; // input data pointer
|
||||
int n; // bytes available there
|
||||
int q; // output window write pointer
|
||||
int m; // bytes to end of window or read pointer
|
||||
|
||||
// copy input/output information to locals (UPDATE macro restores)
|
||||
{p=z.next_in_index;n=z.avail_in;b=bitb;k=bitk;} |
||||
{q=write;m=(int)(q<read?read-q-1:end-q);} |
||||
|
||||
// process input based on current state
|
||||
while(true){ |
||||
switch (mode){ |
||||
case TYPE: |
||||
|
||||
while(k<(3)){ |
||||
if(n!=0){ |
||||
r=Z_OK; |
||||
} |
||||
else{ |
||||
bitb=b; bitk=k; |
||||
z.avail_in=n; |
||||
z.total_in+=p-z.next_in_index;z.next_in_index=p; |
||||
write=q; |
||||
return inflate_flush(r); |
||||
} |
||||
n--; |
||||
b|=(z.next_in[p++]&0xff)<<k; |
||||
k+=8; |
||||
} |
||||
t = (int)(b & 7); |
||||
last = t & 1; |
||||
|
||||
switch (t >>> 1){ |
||||
case 0: // stored
|
||||
{b>>>=(3);k-=(3);} |
||||
t = k & 7; // go to byte boundary
|
||||
|
||||
{b>>>=(t);k-=(t);} |
||||
mode = LENS; // get length of stored block
|
||||
break; |
||||
case 1: // fixed
|
||||
InfTree.inflate_trees_fixed(bl, bd, tl, td, z); |
||||
codes.init(bl[0], bd[0], tl[0], 0, td[0], 0); |
||||
|
||||
{b>>>=(3);k-=(3);} |
||||
|
||||
mode = CODES; |
||||
break; |
||||
case 2: // dynamic
|
||||
|
||||
{b>>>=(3);k-=(3);} |
||||
|
||||
mode = TABLE; |
||||
break; |
||||
case 3: // illegal
|
||||
|
||||
{b>>>=(3);k-=(3);} |
||||
mode = BAD; |
||||
z.msg = "invalid block type"; |
||||
r = Z_DATA_ERROR; |
||||
|
||||
bitb=b; bitk=k; |
||||
z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; |
||||
write=q; |
||||
return inflate_flush(r); |
||||
} |
||||
break; |
||||
case LENS: |
||||
|
||||
while(k<(32)){ |
||||
if(n!=0){ |
||||
r=Z_OK; |
||||
} |
||||
else{ |
||||
bitb=b; bitk=k; |
||||
z.avail_in=n; |
||||
z.total_in+=p-z.next_in_index;z.next_in_index=p; |
||||
write=q; |
||||
return inflate_flush(r); |
||||
} |
||||
n--; |
||||
b|=(z.next_in[p++]&0xff)<<k; |
||||
k+=8; |
||||
} |
||||
|
||||
if ((((~b) >>> 16) & 0xffff) != (b & 0xffff)){ |
||||
mode = BAD; |
||||
z.msg = "invalid stored block lengths"; |
||||
r = Z_DATA_ERROR; |
||||
|
||||
bitb=b; bitk=k; |
||||
z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; |
||||
write=q; |
||||
return inflate_flush(r); |
||||
} |
||||
left = (b & 0xffff); |
||||
b = k = 0; // dump bits
|
||||
mode = left!=0 ? STORED : (last!=0 ? DRY : TYPE); |
||||
break; |
||||
case STORED: |
||||
if (n == 0){ |
||||
bitb=b; bitk=k; |
||||
z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; |
||||
write=q; |
||||
return inflate_flush(r); |
||||
} |
||||
|
||||
if(m==0){ |
||||
if(q==end&&read!=0){ |
||||
q=0; m=(int)(q<read?read-q-1:end-q); |
||||
} |
||||
if(m==0){ |
||||
write=q; |
||||
r=inflate_flush(r); |
||||
q=write;m=(int)(q<read?read-q-1:end-q); |
||||
if(q==end&&read!=0){ |
||||
q=0; m=(int)(q<read?read-q-1:end-q); |
||||
} |
||||
if(m==0){ |
||||
bitb=b; bitk=k; |
||||
z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; |
||||
write=q; |
||||
return inflate_flush(r); |
||||
} |
||||
} |
||||
} |
||||
r=Z_OK; |
||||
|
||||
t = left; |
||||
if(t>n) t = n; |
||||
if(t>m) t = m; |
||||
System.arraycopy(z.next_in, p, window, q, t); |
||||
p += t; n -= t; |
||||
q += t; m -= t; |
||||
if ((left -= t) != 0) |
||||
break; |
||||
mode = last!=0 ? DRY : TYPE; |
||||
break; |
||||
case TABLE: |
||||
|
||||
while(k<(14)){ |
||||
if(n!=0){ |
||||
r=Z_OK; |
||||
} |
||||
else{ |
||||
bitb=b; bitk=k; |
||||
z.avail_in=n; |
||||
z.total_in+=p-z.next_in_index;z.next_in_index=p; |
||||
write=q; |
||||
return inflate_flush(r); |
||||
} |
||||
n--; |
||||
b|=(z.next_in[p++]&0xff)<<k; |
||||
k+=8; |
||||
} |
||||
|
||||
table = t = (b & 0x3fff); |
||||
if ((t & 0x1f) > 29 || ((t >> 5) & 0x1f) > 29) |
||||
{ |
||||
mode = BAD; |
||||
z.msg = "too many length or distance symbols"; |
||||
r = Z_DATA_ERROR; |
||||
|
||||
bitb=b; bitk=k; |
||||
z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; |
||||
write=q; |
||||
return inflate_flush(r); |
||||
} |
||||
t = 258 + (t & 0x1f) + ((t >> 5) & 0x1f); |
||||
if(blens==null || blens.length<t){ |
||||
blens=new int[t]; |
||||
} |
||||
else{ |
||||
for(int i=0; i<t; i++){blens[i]=0;} |
||||
} |
||||
|
||||
{b>>>=(14);k-=(14);} |
||||
|
||||
index = 0; |
||||
mode = BTREE; |
||||
case BTREE: |
||||
while (index < 4 + (table >>> 10)){ |
||||
while(k<(3)){ |
||||
if(n!=0){ |
||||
r=Z_OK; |
||||
} |
||||
else{ |
||||
bitb=b; bitk=k; |
||||
z.avail_in=n; |
||||
z.total_in+=p-z.next_in_index;z.next_in_index=p; |
||||
write=q; |
||||
return inflate_flush(r); |
||||
} |
||||
n--; |
||||
b|=(z.next_in[p++]&0xff)<<k; |
||||
k+=8; |
||||
} |
||||
|
||||
blens[border[index++]] = b&7; |
||||
|
||||
{b>>>=(3);k-=(3);} |
||||
} |
||||
|
||||
while(index < 19){ |
||||
blens[border[index++]] = 0; |
||||
} |
||||
|
||||
bb[0] = 7; |
||||
t = inftree.inflate_trees_bits(blens, bb, tb, hufts, z); |
||||
if (t != Z_OK){ |
||||
r = t; |
||||
if (r == Z_DATA_ERROR){ |
||||
blens=null; |
||||
mode = BAD; |
||||
} |
||||
|
||||
bitb=b; bitk=k; |
||||
z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; |
||||
write=q; |
||||
return inflate_flush(r); |
||||
} |
||||
|
||||
index = 0; |
||||
mode = DTREE; |
||||
case DTREE: |
||||
while (true){ |
||||
t = table; |
||||
if(!(index < 258 + (t & 0x1f) + ((t >> 5) & 0x1f))){ |
||||
break; |
||||
} |
||||
|
||||
int[] h; |
||||
int i, j, c; |
||||
|
||||
t = bb[0]; |
||||
|
||||
while(k<(t)){ |
||||
if(n!=0){ |
||||
r=Z_OK; |
||||
} |
||||
else{ |
||||
bitb=b; bitk=k; |
||||
z.avail_in=n; |
||||
z.total_in+=p-z.next_in_index;z.next_in_index=p; |
||||
write=q; |
||||
return inflate_flush(r); |
||||
} |
||||
n--; |
||||
b|=(z.next_in[p++]&0xff)<<k; |
||||
k+=8; |
||||
} |
||||
|
||||
if(tb[0]==-1){ |
||||
//System.err.println("null...");
|
||||
} |
||||
|
||||
t=hufts[(tb[0]+(b&inflate_mask[t]))*3+1]; |
||||
c=hufts[(tb[0]+(b&inflate_mask[t]))*3+2]; |
||||
|
||||
if (c < 16){ |
||||
b>>>=(t);k-=(t); |
||||
blens[index++] = c; |
||||
} |
||||
else { // c == 16..18
|
||||
i = c == 18 ? 7 : c - 14; |
||||
j = c == 18 ? 11 : 3; |
||||
|
||||
while(k<(t+i)){ |
||||
if(n!=0){ |
||||
r=Z_OK; |
||||
} |
||||
else{ |
||||
bitb=b; bitk=k; |
||||
z.avail_in=n; |
||||
z.total_in+=p-z.next_in_index;z.next_in_index=p; |
||||
write=q; |
||||
return inflate_flush(r); |
||||
} |
||||
n--; |
||||
b|=(z.next_in[p++]&0xff)<<k; |
||||
k+=8; |
||||
} |
||||
|
||||
b>>>=(t);k-=(t); |
||||
|
||||
j += (b & inflate_mask[i]); |
||||
|
||||
b>>>=(i);k-=(i); |
||||
|
||||
i = index; |
||||
t = table; |
||||
if (i + j > 258 + (t & 0x1f) + ((t >> 5) & 0x1f) || |
||||
(c == 16 && i < 1)){ |
||||
blens=null; |
||||
mode = BAD; |
||||
z.msg = "invalid bit length repeat"; |
||||
r = Z_DATA_ERROR; |
||||
|
||||
bitb=b; bitk=k; |
||||
z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; |
||||
write=q; |
||||
return inflate_flush(r); |
||||
} |
||||
|
||||
c = c == 16 ? blens[i-1] : 0; |
||||
do{ |
||||
blens[i++] = c; |
||||
} |
||||
while (--j!=0); |
||||
index = i; |
||||
} |
||||
} |
||||
|
||||
tb[0]=-1; |
||||
{ |
||||
bl[0] = 9; // must be <= 9 for lookahead assumptions
|
||||
bd[0] = 6; // must be <= 9 for lookahead assumptions
|
||||
t = table; |
||||
t = inftree.inflate_trees_dynamic(257 + (t & 0x1f), |
||||
1 + ((t >> 5) & 0x1f), |
||||
blens, bl, bd, tli, tdi, hufts, z); |
||||
|
||||
if (t != Z_OK){ |
||||
if (t == Z_DATA_ERROR){ |
||||
blens=null; |
||||
mode = BAD; |
||||
} |
||||
r = t; |
||||
|
||||
bitb=b; bitk=k; |
||||
z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; |
||||
write=q; |
||||
return inflate_flush(r); |
||||
} |
||||
codes.init(bl[0], bd[0], hufts, tli[0], hufts, tdi[0]); |
||||
} |
||||
mode = CODES; |
||||
case CODES: |
||||
bitb=b; bitk=k; |
||||
z.avail_in=n; z.total_in+=p-z.next_in_index;z.next_in_index=p; |
||||
write=q; |
||||
|
||||
if ((r = codes.proc(r)) != Z_STREAM_END){ |
||||
return inflate_flush(r); |
||||
} |
||||
r = Z_OK; |
||||
codes.free(z); |
||||
|
||||
p=z.next_in_index; n=z.avail_in;b=bitb;k=bitk; |
||||
q=write;m=(int)(q<read?read-q-1:end-q); |
||||
|
||||
if (last==0){ |
||||
mode = TYPE; |
||||
break; |
||||
} |
||||
mode = DRY; |
||||
case DRY: |
||||
write=q; |
||||
r=inflate_flush(r); |
||||
q=write; m=(int)(q<read?read-q-1:end-q); |
||||
if (read != write){ |
||||
bitb=b; bitk=k; |
||||
z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; |
||||
write=q; |
||||
return inflate_flush(r); |
||||
} |
||||
mode = DONE; |
||||
case DONE: |
||||
r = Z_STREAM_END; |
||||
|
||||
bitb=b; bitk=k; |
||||
z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; |
||||
write=q; |
||||
return inflate_flush(r); |
||||
case BAD: |
||||
r = Z_DATA_ERROR; |
||||
|
||||
bitb=b; bitk=k; |
||||
z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; |
||||
write=q; |
||||
return inflate_flush(r); |
||||
|
||||
default: |
||||
r = Z_STREAM_ERROR; |
||||
|
||||
bitb=b; bitk=k; |
||||
z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; |
||||
write=q; |
||||
return inflate_flush(r); |
||||
} |
||||
} |
||||
} |
||||
|
||||
void free(){ |
||||
reset(); |
||||
window=null; |
||||
hufts=null; |
||||
//ZFREE(z, s);
|
||||
} |
||||
|
||||
void set_dictionary(byte[] d, int start, int n){ |
||||
System.arraycopy(d, start, window, 0, n); |
||||
read = write = n; |
||||
} |
||||
|
||||
// Returns true if inflate is currently at the end of a block generated
|
||||
// by Z_SYNC_FLUSH or Z_FULL_FLUSH.
|
||||
int sync_point(){ |
||||
return mode == LENS ? 1 : 0; |
||||
} |
||||
|
||||
// copy as much as possible from the sliding window to the output area
|
||||
int inflate_flush(int r){ |
||||
int n; |
||||
int p; |
||||
int q; |
||||
|
||||
// local copies of source and destination pointers
|
||||
p = z.next_out_index; |
||||
q = read; |
||||
|
||||
// compute number of bytes to copy as far as end of window
|
||||
n = (int)((q <= write ? write : end) - q); |
||||
if(n > z.avail_out) n = z.avail_out; |
||||
if(n!=0 && r == Z_BUF_ERROR) r = Z_OK; |
||||
|
||||
// update counters
|
||||
z.avail_out -= n; |
||||
z.total_out += n; |
||||
|
||||
// update check information
|
||||
if(check && n>0){ |
||||
z.adler.update(window, q, n); |
||||
} |
||||
|
||||
// copy as far as end of window
|
||||
System.arraycopy(window, q, z.next_out, p, n); |
||||
p += n; |
||||
q += n; |
||||
|
||||
// see if more to copy at beginning of window
|
||||
if (q == end){ |
||||
// wrap pointers
|
||||
q = 0; |
||||
if (write == end) |
||||
write = 0; |
||||
|
||||
// compute bytes to copy
|
||||
n = write - q; |
||||
if (n > z.avail_out) n = z.avail_out; |
||||
if (n!=0 && r == Z_BUF_ERROR) r = Z_OK; |
||||
|
||||
// update counters
|
||||
z.avail_out -= n; |
||||
z.total_out += n; |
||||
|
||||
// update check information
|
||||
if(check && n>0){ |
||||
z.adler.update(window, q, n); |
||||
} |
||||
|
||||
// copy
|
||||
System.arraycopy(window, q, z.next_out, p, n); |
||||
p += n; |
||||
q += n; |
||||
} |
||||
|
||||
// update pointers
|
||||
z.next_out_index = p; |
||||
read = q; |
||||
|
||||
// done
|
||||
return r; |
||||
} |
||||
} |
@ -0,0 +1,610 @@ |
||||
/* -*-mode:java; c-basic-offset:2; -*- */ |
||||
/* |
||||
Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved. |
||||
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions are met: |
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, |
||||
this list of conditions and the following disclaimer. |
||||
|
||||
2. 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. |
||||
|
||||
3. The names of the authors may not be used to endorse or promote products |
||||
derived from this software without specific prior written permission. |
||||
|
||||
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 JCRAFT, |
||||
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE 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. |
||||
*/ |
||||
/* |
||||
* This program is based on zlib-1.1.3, so all credit should go authors |
||||
* Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) |
||||
* and contributors of zlib. |
||||
*/ |
||||
|
||||
package com.jcraft.jzlib; |
||||
|
||||
final class InfCodes{ |
||||
|
||||
static final private int[] inflate_mask = { |
||||
0x00000000, 0x00000001, 0x00000003, 0x00000007, 0x0000000f, |
||||
0x0000001f, 0x0000003f, 0x0000007f, 0x000000ff, 0x000001ff, |
||||
0x000003ff, 0x000007ff, 0x00000fff, 0x00001fff, 0x00003fff, |
||||
0x00007fff, 0x0000ffff |
||||
}; |
||||
|
||||
static final private int Z_OK=0; |
||||
static final private int Z_STREAM_END=1; |
||||
static final private int Z_NEED_DICT=2; |
||||
static final private int Z_ERRNO=-1; |
||||
static final private int Z_STREAM_ERROR=-2; |
||||
static final private int Z_DATA_ERROR=-3; |
||||
static final private int Z_MEM_ERROR=-4; |
||||
static final private int Z_BUF_ERROR=-5; |
||||
static final private int Z_VERSION_ERROR=-6; |
||||
|
||||
// waiting for "i:"=input,
|
||||
// "o:"=output,
|
||||
// "x:"=nothing
|
||||
static final private int START=0; // x: set up for LEN
|
||||
static final private int LEN=1; // i: get length/literal/eob next
|
||||
static final private int LENEXT=2; // i: getting length extra (have base)
|
||||
static final private int DIST=3; // i: get distance next
|
||||
static final private int DISTEXT=4;// i: getting distance extra
|
||||
static final private int COPY=5; // o: copying bytes in window, waiting for space
|
||||
static final private int LIT=6; // o: got literal, waiting for output space
|
||||
static final private int WASH=7; // o: got eob, possibly still output waiting
|
||||
static final private int END=8; // x: got eob and all data flushed
|
||||
static final private int BADCODE=9;// x: got error
|
||||
|
||||
int mode; // current inflate_codes mode
|
||||
|
||||
// mode dependent information
|
||||
int len; |
||||
|
||||
int[] tree; // pointer into tree
|
||||
int tree_index=0; |
||||
int need; // bits needed
|
||||
|
||||
int lit; |
||||
|
||||
// if EXT or COPY, where and how much
|
||||
int get; // bits to get for extra
|
||||
int dist; // distance back to copy from
|
||||
|
||||
byte lbits; // ltree bits decoded per branch
|
||||
byte dbits; // dtree bits decoder per branch
|
||||
int[] ltree; // literal/length/eob tree
|
||||
int ltree_index; // literal/length/eob tree
|
||||
int[] dtree; // distance tree
|
||||
int dtree_index; // distance tree
|
||||
|
||||
private final ZStream z; |
||||
private final InfBlocks s; |
||||
InfCodes(ZStream z, InfBlocks s){ |
||||
this.z=z; |
||||
this.s=s; |
||||
} |
||||
|
||||
void init(int bl, int bd, |
||||
int[] tl, int tl_index, |
||||
int[] td, int td_index){ |
||||
mode=START; |
||||
lbits=(byte)bl; |
||||
dbits=(byte)bd; |
||||
ltree=tl; |
||||
ltree_index=tl_index; |
||||
dtree = td; |
||||
dtree_index=td_index; |
||||
tree=null; |
||||
} |
||||
|
||||
int proc(int r){ |
||||
int j; // temporary storage
|
||||
int[] t; // temporary pointer
|
||||
int tindex; // temporary pointer
|
||||
int e; // extra bits or operation
|
||||
int b=0; // bit buffer
|
||||
int k=0; // bits in bit buffer
|
||||
int p=0; // input data pointer
|
||||
int n; // bytes available there
|
||||
int q; // output window write pointer
|
||||
int m; // bytes to end of window or read pointer
|
||||
int f; // pointer to copy strings from
|
||||
|
||||
// copy input/output information to locals (UPDATE macro restores)
|
||||
p=z.next_in_index;n=z.avail_in;b=s.bitb;k=s.bitk; |
||||
q=s.write;m=q<s.read?s.read-q-1:s.end-q; |
||||
|
||||
// process input and output based on current state
|
||||
while (true){ |
||||
switch (mode){ |
||||
// waiting for "i:"=input, "o:"=output, "x:"=nothing
|
||||
case START: // x: set up for LEN
|
||||
if (m >= 258 && n >= 10){ |
||||
|
||||
s.bitb=b;s.bitk=k; |
||||
z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; |
||||
s.write=q; |
||||
r = inflate_fast(lbits, dbits, |
||||
ltree, ltree_index, |
||||
dtree, dtree_index, |
||||
s, z); |
||||
|
||||
p=z.next_in_index;n=z.avail_in;b=s.bitb;k=s.bitk; |
||||
q=s.write;m=q<s.read?s.read-q-1:s.end-q; |
||||
|
||||
if (r != Z_OK){ |
||||
mode = r == Z_STREAM_END ? WASH : BADCODE; |
||||
break; |
||||
} |
||||
} |
||||
need = lbits; |
||||
tree = ltree; |
||||
tree_index=ltree_index; |
||||
|
||||
mode = LEN; |
||||
case LEN: // i: get length/literal/eob next
|
||||
j = need; |
||||
|
||||
while(k<(j)){ |
||||
if(n!=0)r=Z_OK; |
||||
else{ |
||||
|
||||
s.bitb=b;s.bitk=k; |
||||
z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; |
||||
s.write=q; |
||||
return s.inflate_flush(r); |
||||
} |
||||
n--; |
||||
b|=(z.next_in[p++]&0xff)<<k; |
||||
k+=8; |
||||
} |
||||
|
||||
tindex=(tree_index+(b&inflate_mask[j]))*3; |
||||
|
||||
b>>>=(tree[tindex+1]); |
||||
k-=(tree[tindex+1]); |
||||
|
||||
e=tree[tindex]; |
||||
|
||||
if(e == 0){ // literal
|
||||
lit = tree[tindex+2]; |
||||
mode = LIT; |
||||
break; |
||||
} |
||||
if((e & 16)!=0 ){ // length
|
||||
get = e & 15; |
||||
len = tree[tindex+2]; |
||||
mode = LENEXT; |
||||
break; |
||||
} |
||||
if ((e & 64) == 0){ // next table
|
||||
need = e; |
||||
tree_index = tindex/3+tree[tindex+2]; |
||||
break; |
||||
} |
||||
if ((e & 32)!=0){ // end of block
|
||||
mode = WASH; |
||||
break; |
||||
} |
||||
mode = BADCODE; // invalid code
|
||||
z.msg = "invalid literal/length code"; |
||||
r = Z_DATA_ERROR; |
||||
|
||||
s.bitb=b;s.bitk=k; |
||||
z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; |
||||
s.write=q; |
||||
return s.inflate_flush(r); |
||||
|
||||
case LENEXT: // i: getting length extra (have base)
|
||||
j = get; |
||||
|
||||
while(k<(j)){ |
||||
if(n!=0)r=Z_OK; |
||||
else{ |
||||
|
||||
s.bitb=b;s.bitk=k; |
||||
z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; |
||||
s.write=q; |
||||
return s.inflate_flush(r); |
||||
} |
||||
n--; b|=(z.next_in[p++]&0xff)<<k; |
||||
k+=8; |
||||
} |
||||
|
||||
len += (b & inflate_mask[j]); |
||||
|
||||
b>>=j; |
||||
k-=j; |
||||
|
||||
need = dbits; |
||||
tree = dtree; |
||||
tree_index=dtree_index; |
||||
mode = DIST; |
||||
case DIST: // i: get distance next
|
||||
j = need; |
||||
|
||||
while(k<(j)){ |
||||
if(n!=0)r=Z_OK; |
||||
else{ |
||||
|
||||
s.bitb=b;s.bitk=k; |
||||
z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; |
||||
s.write=q; |
||||
return s.inflate_flush(r); |
||||
} |
||||
n--; b|=(z.next_in[p++]&0xff)<<k; |
||||
k+=8; |
||||
} |
||||
|
||||
tindex=(tree_index+(b & inflate_mask[j]))*3; |
||||
|
||||
b>>=tree[tindex+1]; |
||||
k-=tree[tindex+1]; |
||||
|
||||
e = (tree[tindex]); |
||||
if((e & 16)!=0){ // distance
|
||||
get = e & 15; |
||||
dist = tree[tindex+2]; |
||||
mode = DISTEXT; |
||||
break; |
||||
} |
||||
if ((e & 64) == 0){ // next table
|
||||
need = e; |
||||
tree_index = tindex/3 + tree[tindex+2]; |
||||
break; |
||||
} |
||||
mode = BADCODE; // invalid code
|
||||
z.msg = "invalid distance code"; |
||||
r = Z_DATA_ERROR; |
||||
|
||||
s.bitb=b;s.bitk=k; |
||||
z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; |
||||
s.write=q; |
||||
return s.inflate_flush(r); |
||||
|
||||
case DISTEXT: // i: getting distance extra
|
||||
j = get; |
||||
|
||||
while(k<(j)){ |
||||
if(n!=0)r=Z_OK; |
||||
else{ |
||||
|
||||
s.bitb=b;s.bitk=k; |
||||
z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; |
||||
s.write=q; |
||||
return s.inflate_flush(r); |
||||
} |
||||
n--; b|=(z.next_in[p++]&0xff)<<k; |
||||
k+=8; |
||||
} |
||||
|
||||
dist += (b & inflate_mask[j]); |
||||
|
||||
b>>=j; |
||||
k-=j; |
||||
|
||||
mode = COPY; |
||||
case COPY: // o: copying bytes in window, waiting for space
|
||||
f = q - dist; |
||||
while(f < 0){ // modulo window size-"while" instead
|
||||
f += s.end; // of "if" handles invalid distances
|
||||
} |
||||
while (len!=0){ |
||||
|
||||
if(m==0){ |
||||
if(q==s.end&&s.read!=0){q=0;m=q<s.read?s.read-q-1:s.end-q;} |
||||
if(m==0){ |
||||
s.write=q; r=s.inflate_flush(r); |
||||
q=s.write;m=q<s.read?s.read-q-1:s.end-q; |
||||
|
||||
if(q==s.end&&s.read!=0){q=0;m=q<s.read?s.read-q-1:s.end-q;} |
||||
|
||||
if(m==0){ |
||||
s.bitb=b;s.bitk=k; |
||||
z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; |
||||
s.write=q; |
||||
return s.inflate_flush(r); |
||||
} |
||||
} |
||||
} |
||||
|
||||
s.window[q++]=s.window[f++]; m--; |
||||
|
||||
if (f == s.end) |
||||
f = 0; |
||||
len--; |
||||
} |
||||
mode = START; |
||||
break; |
||||
case LIT: // o: got literal, waiting for output space
|
||||
if(m==0){ |
||||
if(q==s.end&&s.read!=0){q=0;m=q<s.read?s.read-q-1:s.end-q;} |
||||
if(m==0){ |
||||
s.write=q; r=s.inflate_flush(r); |
||||
q=s.write;m=q<s.read?s.read-q-1:s.end-q; |
||||
|
||||
if(q==s.end&&s.read!=0){q=0;m=q<s.read?s.read-q-1:s.end-q;} |
||||
if(m==0){ |
||||
s.bitb=b;s.bitk=k; |
||||
z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; |
||||
s.write=q; |
||||
return s.inflate_flush(r); |
||||
} |
||||
} |
||||
} |
||||
r=Z_OK; |
||||
|
||||
s.window[q++]=(byte)lit; m--; |
||||
|
||||
mode = START; |
||||
break; |
||||
case WASH: // o: got eob, possibly more output
|
||||
if (k > 7){ // return unused byte, if any
|
||||
k -= 8; |
||||
n++; |
||||
p--; // can always return one
|
||||
} |
||||
|
||||
s.write=q; r=s.inflate_flush(r); |
||||
q=s.write;m=q<s.read?s.read-q-1:s.end-q; |
||||
|
||||
if (s.read != s.write){ |
||||
s.bitb=b;s.bitk=k; |
||||
z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; |
||||
s.write=q; |
||||
return s.inflate_flush(r); |
||||
} |
||||
mode = END; |
||||
case END: |
||||
r = Z_STREAM_END; |
||||
s.bitb=b;s.bitk=k; |
||||
z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; |
||||
s.write=q; |
||||
return s.inflate_flush(r); |
||||
|
||||
case BADCODE: // x: got error
|
||||
|
||||
r = Z_DATA_ERROR; |
||||
|
||||
s.bitb=b;s.bitk=k; |
||||
z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; |
||||
s.write=q; |
||||
return s.inflate_flush(r); |
||||
|
||||
default: |
||||
r = Z_STREAM_ERROR; |
||||
|
||||
s.bitb=b;s.bitk=k; |
||||
z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; |
||||
s.write=q; |
||||
return s.inflate_flush(r); |
||||
} |
||||
} |
||||
} |
||||
|
||||
void free(ZStream z){ |
||||
// ZFREE(z, c);
|
||||
} |
||||
|
||||
// Called with number of bytes left to write in window at least 258
|
||||
// (the maximum string length) and number of input bytes available
|
||||
// at least ten. The ten bytes are six bytes for the longest length/
|
||||
// distance pair plus four bytes for overloading the bit buffer.
|
||||
|
||||
int inflate_fast(int bl, int bd, |
||||
int[] tl, int tl_index, |
||||
int[] td, int td_index, |
||||
InfBlocks s, ZStream z){ |
||||
int t; // temporary pointer
|
||||
int[] tp; // temporary pointer
|
||||
int tp_index; // temporary pointer
|
||||
int e; // extra bits or operation
|
||||
int b; // bit buffer
|
||||
int k; // bits in bit buffer
|
||||
int p; // input data pointer
|
||||
int n; // bytes available there
|
||||
int q; // output window write pointer
|
||||
int m; // bytes to end of window or read pointer
|
||||
int ml; // mask for literal/length tree
|
||||
int md; // mask for distance tree
|
||||
int c; // bytes to copy
|
||||
int d; // distance back to copy from
|
||||
int r; // copy source pointer
|
||||
|
||||
int tp_index_t_3; // (tp_index+t)*3
|
||||
|
||||
// load input, output, bit values
|
||||
p=z.next_in_index;n=z.avail_in;b=s.bitb;k=s.bitk; |
||||
q=s.write;m=q<s.read?s.read-q-1:s.end-q; |
||||
|
||||
// initialize masks
|
||||
ml = inflate_mask[bl]; |
||||
md = inflate_mask[bd]; |
||||
|
||||
// do until not enough input or output space for fast loop
|
||||
do { // assume called with m >= 258 && n >= 10
|
||||
// get literal/length code
|
||||
while(k<(20)){ // max bits for literal/length code
|
||||
n--; |
||||
b|=(z.next_in[p++]&0xff)<<k;k+=8; |
||||
} |
||||
|
||||
t= b&ml; |
||||
tp=tl; |
||||
tp_index=tl_index; |
||||
tp_index_t_3=(tp_index+t)*3; |
||||
if ((e = tp[tp_index_t_3]) == 0){ |
||||
b>>=(tp[tp_index_t_3+1]); k-=(tp[tp_index_t_3+1]); |
||||
|
||||
s.window[q++] = (byte)tp[tp_index_t_3+2]; |
||||
m--; |
||||
continue; |
||||
} |
||||
do { |
||||
|
||||
b>>=(tp[tp_index_t_3+1]); k-=(tp[tp_index_t_3+1]); |
||||
|
||||
if((e&16)!=0){ |
||||
e &= 15; |
||||
c = tp[tp_index_t_3+2] + ((int)b & inflate_mask[e]); |
||||
|
||||
b>>=e; k-=e; |
||||
|
||||
// decode distance base of block to copy
|
||||
while(k<(15)){ // max bits for distance code
|
||||
n--; |
||||
b|=(z.next_in[p++]&0xff)<<k;k+=8; |
||||
} |
||||
|
||||
t= b&md; |
||||
tp=td; |
||||
tp_index=td_index; |
||||
tp_index_t_3=(tp_index+t)*3; |
||||
e = tp[tp_index_t_3]; |
||||
|
||||
do { |
||||
|
||||
b>>=(tp[tp_index_t_3+1]); k-=(tp[tp_index_t_3+1]); |
||||
|
||||
if((e&16)!=0){ |
||||
// get extra bits to add to distance base
|
||||
e &= 15; |
||||
while(k<(e)){ // get extra bits (up to 13)
|
||||
n--; |
||||
b|=(z.next_in[p++]&0xff)<<k;k+=8; |
||||
} |
||||
|
||||
d = tp[tp_index_t_3+2] + (b&inflate_mask[e]); |
||||
|
||||
b>>=(e); k-=(e); |
||||
|
||||
// do the copy
|
||||
m -= c; |
||||
if (q >= d){ // offset before dest
|
||||
// just copy
|
||||
r=q-d; |
||||
if(q-r>0 && 2>(q-r)){ |
||||
s.window[q++]=s.window[r++]; // minimum count is three,
|
||||
s.window[q++]=s.window[r++]; // so unroll loop a little
|
||||
c-=2; |
||||
} |
||||
else{ |
||||
System.arraycopy(s.window, r, s.window, q, 2); |
||||
q+=2; r+=2; c-=2; |
||||
} |
||||
} |
||||
else{ // else offset after destination
|
||||
r=q-d; |
||||
do{ |
||||
r+=s.end; // force pointer in window
|
||||
}while(r<0); // covers invalid distances
|
||||
e=s.end-r; |
||||
if(c>e){ // if source crosses,
|
||||
c-=e; // wrapped copy
|
||||
if(q-r>0 && e>(q-r)){ |
||||
do{s.window[q++] = s.window[r++];} |
||||
while(--e!=0); |
||||
} |
||||
else{ |
||||
System.arraycopy(s.window, r, s.window, q, e); |
||||
q+=e; r+=e; e=0; |
||||
} |
||||
r = 0; // copy rest from start of window
|
||||
} |
||||
|
||||
} |
||||
|
||||
// copy all or what's left
|
||||
if(q-r>0 && c>(q-r)){ |
||||
do{s.window[q++] = s.window[r++];} |
||||
while(--c!=0); |
||||
} |
||||
else{ |
||||
System.arraycopy(s.window, r, s.window, q, c); |
||||
q+=c; r+=c; c=0; |
||||
} |
||||
break; |
||||
} |
||||
else if((e&64)==0){ |
||||
t+=tp[tp_index_t_3+2]; |
||||
t+=(b&inflate_mask[e]); |
||||
tp_index_t_3=(tp_index+t)*3; |
||||
e=tp[tp_index_t_3]; |
||||
} |
||||
else{ |
||||
z.msg = "invalid distance code"; |
||||
|
||||
c=z.avail_in-n;c=(k>>3)<c?k>>3:c;n+=c;p-=c;k-=c<<3; |
||||
|
||||
s.bitb=b;s.bitk=k; |
||||
z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; |
||||
s.write=q; |
||||
|
||||
return Z_DATA_ERROR; |
||||
} |
||||
} |
||||
while(true); |
||||
break; |
||||
} |
||||
|
||||
if((e&64)==0){ |
||||
t+=tp[tp_index_t_3+2]; |
||||
t+=(b&inflate_mask[e]); |
||||
tp_index_t_3=(tp_index+t)*3; |
||||
if((e=tp[tp_index_t_3])==0){ |
||||
|
||||
b>>=(tp[tp_index_t_3+1]); k-=(tp[tp_index_t_3+1]); |
||||
|
||||
s.window[q++]=(byte)tp[tp_index_t_3+2]; |
||||
m--; |
||||
break; |
||||
} |
||||
} |
||||
else if((e&32)!=0){ |
||||
|
||||
c=z.avail_in-n;c=(k>>3)<c?k>>3:c;n+=c;p-=c;k-=c<<3; |
||||
|
||||
s.bitb=b;s.bitk=k; |
||||
z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; |
||||
s.write=q; |
||||
|
||||
return Z_STREAM_END; |
||||
} |
||||
else{ |
||||
z.msg="invalid literal/length code"; |
||||
|
||||
c=z.avail_in-n;c=(k>>3)<c?k>>3:c;n+=c;p-=c;k-=c<<3; |
||||
|
||||
s.bitb=b;s.bitk=k; |
||||
z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; |
||||
s.write=q; |
||||
|
||||
return Z_DATA_ERROR; |
||||
} |
||||
} |
||||
while(true); |
||||
} |
||||
while(m>=258 && n>= 10); |
||||
|
||||
// not enough input or output--restore pointers and return
|
||||
c=z.avail_in-n;c=(k>>3)<c?k>>3:c;n+=c;p-=c;k-=c<<3; |
||||
|
||||
s.bitb=b;s.bitk=k; |
||||
z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; |
||||
s.write=q; |
||||
|
||||
return Z_OK; |
||||
} |
||||
} |
@ -0,0 +1,518 @@ |
||||
/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ |
||||
/* |
||||
Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved. |
||||
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions are met: |
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, |
||||
this list of conditions and the following disclaimer. |
||||
|
||||
2. 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. |
||||
|
||||
3. The names of the authors may not be used to endorse or promote products |
||||
derived from this software without specific prior written permission. |
||||
|
||||
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 JCRAFT, |
||||
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE 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. |
||||
*/ |
||||
/* |
||||
* This program is based on zlib-1.1.3, so all credit should go authors |
||||
* Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) |
||||
* and contributors of zlib. |
||||
*/ |
||||
|
||||
package com.jcraft.jzlib; |
||||
|
||||
final class InfTree{ |
||||
|
||||
static final private int MANY=1440; |
||||
|
||||
static final private int Z_OK=0; |
||||
static final private int Z_STREAM_END=1; |
||||
static final private int Z_NEED_DICT=2; |
||||
static final private int Z_ERRNO=-1; |
||||
static final private int Z_STREAM_ERROR=-2; |
||||
static final private int Z_DATA_ERROR=-3; |
||||
static final private int Z_MEM_ERROR=-4; |
||||
static final private int Z_BUF_ERROR=-5; |
||||
static final private int Z_VERSION_ERROR=-6; |
||||
|
||||
static final int fixed_bl = 9; |
||||
static final int fixed_bd = 5; |
||||
|
||||
static final int[] fixed_tl = { |
||||
96,7,256, 0,8,80, 0,8,16, 84,8,115, |
||||
82,7,31, 0,8,112, 0,8,48, 0,9,192, |
||||
80,7,10, 0,8,96, 0,8,32, 0,9,160, |
||||
0,8,0, 0,8,128, 0,8,64, 0,9,224, |
||||
80,7,6, 0,8,88, 0,8,24, 0,9,144, |
||||
83,7,59, 0,8,120, 0,8,56, 0,9,208, |
||||
81,7,17, 0,8,104, 0,8,40, 0,9,176, |
||||
0,8,8, 0,8,136, 0,8,72, 0,9,240, |
||||
80,7,4, 0,8,84, 0,8,20, 85,8,227, |
||||
83,7,43, 0,8,116, 0,8,52, 0,9,200, |
||||
81,7,13, 0,8,100, 0,8,36, 0,9,168, |
||||
0,8,4, 0,8,132, 0,8,68, 0,9,232, |
||||
80,7,8, 0,8,92, 0,8,28, 0,9,152, |
||||
84,7,83, 0,8,124, 0,8,60, 0,9,216, |
||||
82,7,23, 0,8,108, 0,8,44, 0,9,184, |
||||
0,8,12, 0,8,140, 0,8,76, 0,9,248, |
||||
80,7,3, 0,8,82, 0,8,18, 85,8,163, |
||||
83,7,35, 0,8,114, 0,8,50, 0,9,196, |
||||
81,7,11, 0,8,98, 0,8,34, 0,9,164, |
||||
0,8,2, 0,8,130, 0,8,66, 0,9,228, |
||||
80,7,7, 0,8,90, 0,8,26, 0,9,148, |
||||
84,7,67, 0,8,122, 0,8,58, 0,9,212, |
||||
82,7,19, 0,8,106, 0,8,42, 0,9,180, |
||||
0,8,10, 0,8,138, 0,8,74, 0,9,244, |
||||
80,7,5, 0,8,86, 0,8,22, 192,8,0, |
||||
83,7,51, 0,8,118, 0,8,54, 0,9,204, |
||||
81,7,15, 0,8,102, 0,8,38, 0,9,172, |
||||
0,8,6, 0,8,134, 0,8,70, 0,9,236, |
||||
80,7,9, 0,8,94, 0,8,30, 0,9,156, |
||||
84,7,99, 0,8,126, 0,8,62, 0,9,220, |
||||
82,7,27, 0,8,110, 0,8,46, 0,9,188, |
||||
0,8,14, 0,8,142, 0,8,78, 0,9,252, |
||||
96,7,256, 0,8,81, 0,8,17, 85,8,131, |
||||
82,7,31, 0,8,113, 0,8,49, 0,9,194, |
||||
80,7,10, 0,8,97, 0,8,33, 0,9,162, |
||||
0,8,1, 0,8,129, 0,8,65, 0,9,226, |
||||
80,7,6, 0,8,89, 0,8,25, 0,9,146, |
||||
83,7,59, 0,8,121, 0,8,57, 0,9,210, |
||||
81,7,17, 0,8,105, 0,8,41, 0,9,178, |
||||
0,8,9, 0,8,137, 0,8,73, 0,9,242, |
||||
80,7,4, 0,8,85, 0,8,21, 80,8,258, |
||||
83,7,43, 0,8,117, 0,8,53, 0,9,202, |
||||
81,7,13, 0,8,101, 0,8,37, 0,9,170, |
||||
0,8,5, 0,8,133, 0,8,69, 0,9,234, |
||||
80,7,8, 0,8,93, 0,8,29, 0,9,154, |
||||
84,7,83, 0,8,125, 0,8,61, 0,9,218, |
||||
82,7,23, 0,8,109, 0,8,45, 0,9,186, |
||||
0,8,13, 0,8,141, 0,8,77, 0,9,250, |
||||
80,7,3, 0,8,83, 0,8,19, 85,8,195, |
||||
83,7,35, 0,8,115, 0,8,51, 0,9,198, |
||||
81,7,11, 0,8,99, 0,8,35, 0,9,166, |
||||
0,8,3, 0,8,131, 0,8,67, 0,9,230, |
||||
80,7,7, 0,8,91, 0,8,27, 0,9,150, |
||||
84,7,67, 0,8,123, 0,8,59, 0,9,214, |
||||
82,7,19, 0,8,107, 0,8,43, 0,9,182, |
||||
0,8,11, 0,8,139, 0,8,75, 0,9,246, |
||||
80,7,5, 0,8,87, 0,8,23, 192,8,0, |
||||
83,7,51, 0,8,119, 0,8,55, 0,9,206, |
||||
81,7,15, 0,8,103, 0,8,39, 0,9,174, |
||||
0,8,7, 0,8,135, 0,8,71, 0,9,238, |
||||
80,7,9, 0,8,95, 0,8,31, 0,9,158, |
||||
84,7,99, 0,8,127, 0,8,63, 0,9,222, |
||||
82,7,27, 0,8,111, 0,8,47, 0,9,190, |
||||
0,8,15, 0,8,143, 0,8,79, 0,9,254, |
||||
96,7,256, 0,8,80, 0,8,16, 84,8,115, |
||||
82,7,31, 0,8,112, 0,8,48, 0,9,193, |
||||
|
||||
80,7,10, 0,8,96, 0,8,32, 0,9,161, |
||||
0,8,0, 0,8,128, 0,8,64, 0,9,225, |
||||
80,7,6, 0,8,88, 0,8,24, 0,9,145, |
||||
83,7,59, 0,8,120, 0,8,56, 0,9,209, |
||||
81,7,17, 0,8,104, 0,8,40, 0,9,177, |
||||
0,8,8, 0,8,136, 0,8,72, 0,9,241, |
||||
80,7,4, 0,8,84, 0,8,20, 85,8,227, |
||||
83,7,43, 0,8,116, 0,8,52, 0,9,201, |
||||
81,7,13, 0,8,100, 0,8,36, 0,9,169, |
||||
0,8,4, 0,8,132, 0,8,68, 0,9,233, |
||||
80,7,8, 0,8,92, 0,8,28, 0,9,153, |
||||
84,7,83, 0,8,124, 0,8,60, 0,9,217, |
||||
82,7,23, 0,8,108, 0,8,44, 0,9,185, |
||||
0,8,12, 0,8,140, 0,8,76, 0,9,249, |
||||
80,7,3, 0,8,82, 0,8,18, 85,8,163, |
||||
83,7,35, 0,8,114, 0,8,50, 0,9,197, |
||||
81,7,11, 0,8,98, 0,8,34, 0,9,165, |
||||
0,8,2, 0,8,130, 0,8,66, 0,9,229, |
||||
80,7,7, 0,8,90, 0,8,26, 0,9,149, |
||||
84,7,67, 0,8,122, 0,8,58, 0,9,213, |
||||
82,7,19, 0,8,106, 0,8,42, 0,9,181, |
||||
0,8,10, 0,8,138, 0,8,74, 0,9,245, |
||||
80,7,5, 0,8,86, 0,8,22, 192,8,0, |
||||
83,7,51, 0,8,118, 0,8,54, 0,9,205, |
||||
81,7,15, 0,8,102, 0,8,38, 0,9,173, |
||||
0,8,6, 0,8,134, 0,8,70, 0,9,237, |
||||
80,7,9, 0,8,94, 0,8,30, 0,9,157, |
||||
84,7,99, 0,8,126, 0,8,62, 0,9,221, |
||||
82,7,27, 0,8,110, 0,8,46, 0,9,189, |
||||
0,8,14, 0,8,142, 0,8,78, 0,9,253, |
||||
96,7,256, 0,8,81, 0,8,17, 85,8,131, |
||||
82,7,31, 0,8,113, 0,8,49, 0,9,195, |
||||
80,7,10, 0,8,97, 0,8,33, 0,9,163, |
||||
0,8,1, 0,8,129, 0,8,65, 0,9,227, |
||||
80,7,6, 0,8,89, 0,8,25, 0,9,147, |
||||
83,7,59, 0,8,121, 0,8,57, 0,9,211, |
||||
81,7,17, 0,8,105, 0,8,41, 0,9,179, |
||||
0,8,9, 0,8,137, 0,8,73, 0,9,243, |
||||
80,7,4, 0,8,85, 0,8,21, 80,8,258, |
||||
83,7,43, 0,8,117, 0,8,53, 0,9,203, |
||||
81,7,13, 0,8,101, 0,8,37, 0,9,171, |
||||
0,8,5, 0,8,133, 0,8,69, 0,9,235, |
||||
80,7,8, 0,8,93, 0,8,29, 0,9,155, |
||||
84,7,83, 0,8,125, 0,8,61, 0,9,219, |
||||
82,7,23, 0,8,109, 0,8,45, 0,9,187, |
||||
0,8,13, 0,8,141, 0,8,77, 0,9,251, |
||||
80,7,3, 0,8,83, 0,8,19, 85,8,195, |
||||
83,7,35, 0,8,115, 0,8,51, 0,9,199, |
||||
81,7,11, 0,8,99, 0,8,35, 0,9,167, |
||||
0,8,3, 0,8,131, 0,8,67, 0,9,231, |
||||
80,7,7, 0,8,91, 0,8,27, 0,9,151, |
||||
84,7,67, 0,8,123, 0,8,59, 0,9,215, |
||||
82,7,19, 0,8,107, 0,8,43, 0,9,183, |
||||
0,8,11, 0,8,139, 0,8,75, 0,9,247, |
||||
80,7,5, 0,8,87, 0,8,23, 192,8,0, |
||||
83,7,51, 0,8,119, 0,8,55, 0,9,207, |
||||
81,7,15, 0,8,103, 0,8,39, 0,9,175, |
||||
0,8,7, 0,8,135, 0,8,71, 0,9,239, |
||||
80,7,9, 0,8,95, 0,8,31, 0,9,159, |
||||
84,7,99, 0,8,127, 0,8,63, 0,9,223, |
||||
82,7,27, 0,8,111, 0,8,47, 0,9,191, |
||||
0,8,15, 0,8,143, 0,8,79, 0,9,255 |
||||
}; |
||||
static final int[] fixed_td = { |
||||
80,5,1, 87,5,257, 83,5,17, 91,5,4097, |
||||
81,5,5, 89,5,1025, 85,5,65, 93,5,16385, |
||||
80,5,3, 88,5,513, 84,5,33, 92,5,8193, |
||||
82,5,9, 90,5,2049, 86,5,129, 192,5,24577, |
||||
80,5,2, 87,5,385, 83,5,25, 91,5,6145, |
||||
81,5,7, 89,5,1537, 85,5,97, 93,5,24577, |
||||
80,5,4, 88,5,769, 84,5,49, 92,5,12289, |
||||
82,5,13, 90,5,3073, 86,5,193, 192,5,24577 |
||||
}; |
||||
|
||||
// Tables for deflate from PKZIP's appnote.txt.
|
||||
static final int[] cplens = { // Copy lengths for literal codes 257..285
|
||||
3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, |
||||
35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0 |
||||
}; |
||||
|
||||
// see note #13 above about 258
|
||||
static final int[] cplext = { // Extra bits for literal codes 257..285
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, |
||||
3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 112, 112 // 112==invalid
|
||||
}; |
||||
|
||||
static final int[] cpdist = { // Copy offsets for distance codes 0..29
|
||||
1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, |
||||
257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, |
||||
8193, 12289, 16385, 24577 |
||||
}; |
||||
|
||||
static final int[] cpdext = { // Extra bits for distance codes
|
||||
0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, |
||||
7, 7, 8, 8, 9, 9, 10, 10, 11, 11, |
||||
12, 12, 13, 13}; |
||||
|
||||
// If BMAX needs to be larger than 16, then h and x[] should be uLong.
|
||||
static final int BMAX=15; // maximum bit length of any code
|
||||
|
||||
int[] hn = null; // hufts used in space
|
||||
int[] v = null; // work area for huft_build
|
||||
int[] c = null; // bit length count table
|
||||
int[] r = null; // table entry for structure assignment
|
||||
int[] u = null; // table stack
|
||||
int[] x = null; // bit offsets, then code stack
|
||||
|
||||
private int huft_build(int[] b, // code lengths in bits (all assumed <= BMAX)
|
||||
int bindex, |
||||
int n, // number of codes (assumed <= 288)
|
||||
int s, // number of simple-valued codes (0..s-1)
|
||||
int[] d, // list of base values for non-simple codes
|
||||
int[] e, // list of extra bits for non-simple codes
|
||||
int[] t, // result: starting table
|
||||
int[] m, // maximum lookup bits, returns actual
|
||||
int[] hp,// space for trees
|
||||
int[] hn,// hufts used in space
|
||||
int[] v // working area: values in order of bit length
|
||||
){ |
||||
// Given a list of code lengths and a maximum table size, make a set of
|
||||
// tables to decode that set of codes. Return Z_OK on success, Z_BUF_ERROR
|
||||
// if the given code set is incomplete (the tables are still built in this
|
||||
// case), Z_DATA_ERROR if the input is invalid (an over-subscribed set of
|
||||
// lengths), or Z_MEM_ERROR if not enough memory.
|
||||
|
||||
int a; // counter for codes of length k
|
||||
int f; // i repeats in table every f entries
|
||||
int g; // maximum code length
|
||||
int h; // table level
|
||||
int i; // counter, current code
|
||||
int j; // counter
|
||||
int k; // number of bits in current code
|
||||
int l; // bits per table (returned in m)
|
||||
int mask; // (1 << w) - 1, to avoid cc -O bug on HP
|
||||
int p; // pointer into c[], b[], or v[]
|
||||
int q; // points to current table
|
||||
int w; // bits before this table == (l * h)
|
||||
int xp; // pointer into x
|
||||
int y; // number of dummy codes added
|
||||
int z; // number of entries in current table
|
||||
|
||||
// Generate counts for each bit length
|
||||
|
||||
p = 0; i = n; |
||||
do { |
||||
c[b[bindex+p]]++; p++; i--; // assume all entries <= BMAX
|
||||
}while(i!=0); |
||||
|
||||
if(c[0] == n){ // null input--all zero length codes
|
||||
t[0] = -1; |
||||
m[0] = 0; |
||||
return Z_OK; |
||||
} |
||||
|
||||
// Find minimum and maximum length, bound *m by those
|
||||
l = m[0]; |
||||
for (j = 1; j <= BMAX; j++) |
||||
if(c[j]!=0) break; |
||||
k = j; // minimum code length
|
||||
if(l < j){ |
||||
l = j; |
||||
} |
||||
for (i = BMAX; i!=0; i--){ |
||||
if(c[i]!=0) break; |
||||
} |
||||
g = i; // maximum code length
|
||||
if(l > i){ |
||||
l = i; |
||||
} |
||||
m[0] = l; |
||||
|
||||
// Adjust last length count to fill out codes, if needed
|
||||
for (y = 1 << j; j < i; j++, y <<= 1){ |
||||
if ((y -= c[j]) < 0){ |
||||
return Z_DATA_ERROR; |
||||
} |
||||
} |
||||
if ((y -= c[i]) < 0){ |
||||
return Z_DATA_ERROR; |
||||
} |
||||
c[i] += y; |
||||
|
||||
// Generate starting offsets into the value table for each length
|
||||
x[1] = j = 0; |
||||
p = 1; xp = 2; |
||||
while (--i!=0) { // note that i == g from above
|
||||
x[xp] = (j += c[p]); |
||||
xp++; |
||||
p++; |
||||
} |
||||
|
||||
// Make a table of values in order of bit lengths
|
||||
i = 0; p = 0; |
||||
do { |
||||
if ((j = b[bindex+p]) != 0){ |
||||
v[x[j]++] = i; |
||||
} |
||||
p++; |
||||
} |
||||
while (++i < n); |
||||
n = x[g]; // set n to length of v
|
||||
|
||||
// Generate the Huffman codes and for each, make the table entries
|
||||
x[0] = i = 0; // first Huffman code is zero
|
||||
p = 0; // grab values in bit order
|
||||
h = -1; // no tables yet--level -1
|
||||
w = -l; // bits decoded == (l * h)
|
||||
u[0] = 0; // just to keep compilers happy
|
||||
q = 0; // ditto
|
||||
z = 0; // ditto
|
||||
|
||||
// go through the bit lengths (k already is bits in shortest code)
|
||||
for (; k <= g; k++){ |
||||
a = c[k]; |
||||
while (a--!=0){ |
||||
// here i is the Huffman code of length k bits for value *p
|
||||
// make tables up to required level
|
||||
while (k > w + l){ |
||||
h++; |
||||
w += l; // previous table always l bits
|
||||
// compute minimum size table less than or equal to l bits
|
||||
z = g - w; |
||||
z = (z > l) ? l : z; // table size upper limit
|
||||
if((f=1<<(j=k-w))>a+1){ // try a k-w bit table
|
||||
// too few codes for k-w bit table
|
||||
f -= a + 1; // deduct codes from patterns left
|
||||
xp = k; |
||||
if(j < z){ |
||||
while (++j < z){ // try smaller tables up to z bits
|
||||
if((f <<= 1) <= c[++xp]) |
||||
break; // enough codes to use up j bits
|
||||
f -= c[xp]; // else deduct codes from patterns
|
||||
} |
||||
} |
||||
} |
||||
z = 1 << j; // table entries for j-bit table
|
||||
|
||||
// allocate new table
|
||||
if (hn[0] + z > MANY){ // (note: doesn't matter for fixed)
|
||||
return Z_DATA_ERROR; // overflow of MANY
|
||||
} |
||||
u[h] = q = /*hp+*/ hn[0]; // DEBUG
|
||||
hn[0] += z; |
||||
|
||||
// connect to last table, if there is one
|
||||
if(h!=0){ |
||||
x[h]=i; // save pattern for backing up
|
||||
r[0]=(byte)j; // bits in this table
|
||||
r[1]=(byte)l; // bits to dump before this table
|
||||
j=i>>>(w - l); |
||||
r[2] = (int)(q - u[h-1] - j); // offset to this table
|
||||
System.arraycopy(r, 0, hp, (u[h-1]+j)*3, 3); // connect to last table
|
||||
} |
||||
else{ |
||||
t[0] = q; // first table is returned result
|
||||
} |
||||
} |
||||
|
||||
// set up table entry in r
|
||||
r[1] = (byte)(k - w); |
||||
if (p >= n){ |
||||
r[0] = 128 + 64; // out of values--invalid code
|
||||
} |
||||
else if (v[p] < s){ |
||||
r[0] = (byte)(v[p] < 256 ? 0 : 32 + 64); // 256 is end-of-block
|
||||
r[2] = v[p++]; // simple code is just the value
|
||||
} |
||||
else{ |
||||
r[0]=(byte)(e[v[p]-s]+16+64); // non-simple--look up in lists
|
||||
r[2]=d[v[p++] - s]; |
||||
} |
||||
|
||||
// fill code-like entries with r
|
||||
f=1<<(k-w); |
||||
for (j=i>>>w;j<z;j+=f){ |
||||
System.arraycopy(r, 0, hp, (q+j)*3, 3); |
||||
} |
||||
|
||||
// backwards increment the k-bit code i
|
||||
for (j = 1 << (k - 1); (i & j)!=0; j >>>= 1){ |
||||
i ^= j; |
||||
} |
||||
i ^= j; |
||||
|
||||
// backup over finished tables
|
||||
mask = (1 << w) - 1; // needed on HP, cc -O bug
|
||||
while ((i & mask) != x[h]){ |
||||
h--; // don't need to update q
|
||||
w -= l; |
||||
mask = (1 << w) - 1; |
||||
} |
||||
} |
||||
} |
||||
// Return Z_BUF_ERROR if we were given an incomplete table
|
||||
return y != 0 && g != 1 ? Z_BUF_ERROR : Z_OK; |
||||
} |
||||
|
||||
int inflate_trees_bits(int[] c, // 19 code lengths
|
||||
int[] bb, // bits tree desired/actual depth
|
||||
int[] tb, // bits tree result
|
||||
int[] hp, // space for trees
|
||||
ZStream z // for messages
|
||||
){ |
||||
int result; |
||||
initWorkArea(19); |
||||
hn[0]=0; |
||||
result = huft_build(c, 0, 19, 19, null, null, tb, bb, hp, hn, v); |
||||
|
||||
if(result == Z_DATA_ERROR){ |
||||
z.msg = "oversubscribed dynamic bit lengths tree"; |
||||
} |
||||
else if(result == Z_BUF_ERROR || bb[0] == 0){ |
||||
z.msg = "incomplete dynamic bit lengths tree"; |
||||
result = Z_DATA_ERROR; |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
int inflate_trees_dynamic(int nl, // number of literal/length codes
|
||||
int nd, // number of distance codes
|
||||
int[] c, // that many (total) code lengths
|
||||
int[] bl, // literal desired/actual bit depth
|
||||
int[] bd, // distance desired/actual bit depth
|
||||
int[] tl, // literal/length tree result
|
||||
int[] td, // distance tree result
|
||||
int[] hp, // space for trees
|
||||
ZStream z // for messages
|
||||
){ |
||||
int result; |
||||
|
||||
// build literal/length tree
|
||||
initWorkArea(288); |
||||
hn[0]=0; |
||||
result = huft_build(c, 0, nl, 257, cplens, cplext, tl, bl, hp, hn, v); |
||||
if (result != Z_OK || bl[0] == 0){ |
||||
if(result == Z_DATA_ERROR){ |
||||
z.msg = "oversubscribed literal/length tree"; |
||||
} |
||||
else if (result != Z_MEM_ERROR){ |
||||
z.msg = "incomplete literal/length tree"; |
||||
result = Z_DATA_ERROR; |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
// build distance tree
|
||||
initWorkArea(288); |
||||
result = huft_build(c, nl, nd, 0, cpdist, cpdext, td, bd, hp, hn, v); |
||||
|
||||
if (result != Z_OK || (bd[0] == 0 && nl > 257)){ |
||||
if (result == Z_DATA_ERROR){ |
||||
z.msg = "oversubscribed distance tree"; |
||||
} |
||||
else if (result == Z_BUF_ERROR) { |
||||
z.msg = "incomplete distance tree"; |
||||
result = Z_DATA_ERROR; |
||||
} |
||||
else if (result != Z_MEM_ERROR){ |
||||
z.msg = "empty distance tree with lengths"; |
||||
result = Z_DATA_ERROR; |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
return Z_OK; |
||||
} |
||||
|
||||
static int inflate_trees_fixed(int[] bl, //literal desired/actual bit depth
|
||||
int[] bd, //distance desired/actual bit depth
|
||||
int[][] tl,//literal/length tree result
|
||||
int[][] td,//distance tree result
|
||||
ZStream z //for memory allocation
|
||||
){ |
||||
bl[0]=fixed_bl; |
||||
bd[0]=fixed_bd; |
||||
tl[0]=fixed_tl; |
||||
td[0]=fixed_td; |
||||
return Z_OK; |
||||
} |
||||
|
||||
private void initWorkArea(int vsize){ |
||||
if(hn==null){ |
||||
hn=new int[1]; |
||||
v=new int[vsize]; |
||||
c=new int[BMAX+1]; |
||||
r=new int[3]; |
||||
u=new int[BMAX]; |
||||
x=new int[BMAX+1]; |
||||
} |
||||
if(v.length<vsize){ v=new int[vsize]; } |
||||
for(int i=0; i<vsize; i++){v[i]=0;} |
||||
for(int i=0; i<BMAX+1; i++){c[i]=0;} |
||||
for(int i=0; i<3; i++){r[i]=0;} |
||||
System.arraycopy(c, 0, u, 0, BMAX); |
||||
System.arraycopy(c, 0, x, 0, BMAX+1); |
||||
} |
||||
} |
@ -0,0 +1,751 @@ |
||||
/* -*-mode:java; c-basic-offset:2; -*- */ |
||||
/* |
||||
Copyright (c) 2000-2011 ymnk, JCraft,Inc. All rights reserved. |
||||
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions are met: |
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, |
||||
this list of conditions and the following disclaimer. |
||||
|
||||
2. 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. |
||||
|
||||
3. The names of the authors may not be used to endorse or promote products |
||||
derived from this software without specific prior written permission. |
||||
|
||||
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 JCRAFT, |
||||
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE 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. |
||||
*/ |
||||
/* |
||||
* This program is based on zlib-1.1.3, so all credit should go authors |
||||
* Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) |
||||
* and contributors of zlib. |
||||
*/ |
||||
|
||||
package com.jcraft.jzlib; |
||||
|
||||
final class Inflate{ |
||||
|
||||
static final private int MAX_WBITS=15; // 32K LZ77 window
|
||||
|
||||
// preset dictionary flag in zlib header
|
||||
static final private int PRESET_DICT=0x20; |
||||
|
||||
static final int Z_NO_FLUSH=0; |
||||
static final int Z_PARTIAL_FLUSH=1; |
||||
static final int Z_SYNC_FLUSH=2; |
||||
static final int Z_FULL_FLUSH=3; |
||||
static final int Z_FINISH=4; |
||||
|
||||
static final private int Z_DEFLATED=8; |
||||
|
||||
static final private int Z_OK=0; |
||||
static final private int Z_STREAM_END=1; |
||||
static final private int Z_NEED_DICT=2; |
||||
static final private int Z_ERRNO=-1; |
||||
static final private int Z_STREAM_ERROR=-2; |
||||
static final private int Z_DATA_ERROR=-3; |
||||
static final private int Z_MEM_ERROR=-4; |
||||
static final private int Z_BUF_ERROR=-5; |
||||
static final private int Z_VERSION_ERROR=-6; |
||||
|
||||
static final private int METHOD=0; // waiting for method byte
|
||||
static final private int FLAG=1; // waiting for flag byte
|
||||
static final private int DICT4=2; // four dictionary check bytes to go
|
||||
static final private int DICT3=3; // three dictionary check bytes to go
|
||||
static final private int DICT2=4; // two dictionary check bytes to go
|
||||
static final private int DICT1=5; // one dictionary check byte to go
|
||||
static final private int DICT0=6; // waiting for inflateSetDictionary
|
||||
static final private int BLOCKS=7; // decompressing blocks
|
||||
static final private int CHECK4=8; // four check bytes to go
|
||||
static final private int CHECK3=9; // three check bytes to go
|
||||
static final private int CHECK2=10; // two check bytes to go
|
||||
static final private int CHECK1=11; // one check byte to go
|
||||
static final private int DONE=12; // finished check, done
|
||||
static final private int BAD=13; // got an error--stay here
|
||||
|
||||
static final private int HEAD=14; |
||||
static final private int LENGTH=15; |
||||
static final private int TIME=16; |
||||
static final private int OS=17; |
||||
static final private int EXLEN=18; |
||||
static final private int EXTRA=19; |
||||
static final private int NAME=20; |
||||
static final private int COMMENT=21; |
||||
static final private int HCRC=22; |
||||
static final private int FLAGS=23; |
||||
|
||||
static final int INFLATE_ANY=0x40000000; |
||||
|
||||
int mode; // current inflate mode
|
||||
|
||||
// mode dependent information
|
||||
int method; // if FLAGS, method byte
|
||||
|
||||
// if CHECK, check values to compare
|
||||
long was = -1; // computed check value
|
||||
long need; // stream check value
|
||||
|
||||
// if BAD, inflateSync's marker bytes count
|
||||
int marker; |
||||
|
||||
// mode independent information
|
||||
int wrap; // flag for no wrapper
|
||||
// 0: no wrapper
|
||||
// 1: zlib header
|
||||
// 2: gzip header
|
||||
// 4: auto detection
|
||||
|
||||
int wbits; // log2(window size) (8..15, defaults to 15)
|
||||
|
||||
InfBlocks blocks; // current inflate_blocks state
|
||||
|
||||
private final ZStream z; |
||||
|
||||
private int flags; |
||||
|
||||
private int need_bytes = -1; |
||||
private byte[] crcbuf=new byte[4]; |
||||
|
||||
GZIPHeader gheader = null; |
||||
|
||||
int inflateReset(){ |
||||
if(z == null) return Z_STREAM_ERROR; |
||||
|
||||
z.total_in = z.total_out = 0; |
||||
z.msg = null; |
||||
this.mode = HEAD; |
||||
this.need_bytes = -1; |
||||
this.blocks.reset(); |
||||
return Z_OK; |
||||
} |
||||
|
||||
int inflateEnd(){ |
||||
if(blocks != null){ |
||||
blocks.free(); |
||||
} |
||||
return Z_OK; |
||||
} |
||||
|
||||
Inflate(ZStream z){ |
||||
this.z=z; |
||||
} |
||||
|
||||
int inflateInit(int w){ |
||||
z.msg = null; |
||||
blocks = null; |
||||
|
||||
// handle undocumented wrap option (no zlib header or check)
|
||||
wrap = 0; |
||||
if(w < 0){ |
||||
w = - w; |
||||
} |
||||
else if((w&INFLATE_ANY) != 0){ |
||||
wrap = 4; |
||||
w &= ~INFLATE_ANY; |
||||
if(w < 48) |
||||
w &= 15; |
||||
} |
||||
else if((w & ~31) != 0) { // for example, DEF_WBITS + 32
|
||||
wrap = 4; // zlib and gzip wrapped data should be accepted.
|
||||
w &= 15; |
||||
} |
||||
else { |
||||
wrap = (w >> 4) + 1; |
||||
if(w < 48) |
||||
w &= 15; |
||||
} |
||||
|
||||
if(w<8 ||w>15){ |
||||
inflateEnd(); |
||||
return Z_STREAM_ERROR; |
||||
} |
||||
if(blocks != null && wbits != w){ |
||||
blocks.free(); |
||||
blocks=null; |
||||
} |
||||
|
||||
// set window size
|
||||
wbits=w; |
||||
|
||||
this.blocks=new InfBlocks(z, 1<<w); |
||||
|
||||
// reset state
|
||||
inflateReset(); |
||||
|
||||
return Z_OK; |
||||
} |
||||
|
||||
int inflate(int f){ |
||||
int hold = 0; |
||||
|
||||
int r; |
||||
int b; |
||||
|
||||
if(z == null || z.next_in == null){ |
||||
if(f == Z_FINISH && this.mode==HEAD) |
||||
return Z_OK; |
||||
return Z_STREAM_ERROR; |
||||
} |
||||
|
||||
f = f == Z_FINISH ? Z_BUF_ERROR : Z_OK; |
||||
r = Z_BUF_ERROR; |
||||
while (true){ |
||||
|
||||
switch (this.mode){ |
||||
case HEAD: |
||||
if(wrap==0){ |
||||
this.mode = BLOCKS; |
||||
break; |
||||
} |
||||
|
||||
try { r=readBytes(2, r, f); } |
||||
catch(Return e){ return e.r; } |
||||
|
||||
if((wrap == 4 || (wrap&2)!=0) && |
||||
this.need == 0x8b1fL) { // gzip header
|
||||
if(wrap == 4){ |
||||
wrap = 2; |
||||
} |
||||
z.adler=new CRC32(); |
||||
checksum(2, this.need); |
||||
|
||||
if(gheader==null) |
||||
gheader=new GZIPHeader(); |
||||
|
||||
this.mode = FLAGS; |
||||
break; |
||||
} |
||||
|
||||
if((wrap&2) != 0){ |
||||
this.mode = BAD; |
||||
z.msg = "incorrect header check"; |
||||
break; |
||||
} |
||||
|
||||
flags = 0; |
||||
|
||||
this.method = ((int)this.need)&0xff; |
||||
b=((int)(this.need>>8))&0xff; |
||||
|
||||
if(((wrap&1)==0 || // check if zlib header allowed
|
||||
(((this.method << 8)+b) % 31)!=0) && |
||||
(this.method&0xf)!=Z_DEFLATED){ |
||||
if(wrap == 4){ |
||||
z.next_in_index -= 2; |
||||
z.avail_in += 2; |
||||
z.total_in -= 2; |
||||
wrap = 0; |
||||
this.mode = BLOCKS; |
||||
break; |
||||
} |
||||
this.mode = BAD; |
||||
z.msg = "incorrect header check"; |
||||
// since zlib 1.2, it is allowted to inflateSync for this case.
|
||||
/* |
||||
this.marker = 5; // can't try inflateSync
|
||||
*/ |
||||
break; |
||||
} |
||||
|
||||
if((this.method&0xf)!=Z_DEFLATED){ |
||||
this.mode = BAD; |
||||
z.msg="unknown compression method"; |
||||
// since zlib 1.2, it is allowted to inflateSync for this case.
|
||||
/* |
||||
this.marker = 5; // can't try inflateSync
|
||||
*/ |
||||
break; |
||||
} |
||||
|
||||
if(wrap == 4){ |
||||
wrap = 1; |
||||
} |
||||
|
||||
if((this.method>>4)+8>this.wbits){ |
||||
this.mode = BAD; |
||||
z.msg="invalid window size"; |
||||
// since zlib 1.2, it is allowted to inflateSync for this case.
|
||||
/* |
||||
this.marker = 5; // can't try inflateSync
|
||||
*/ |
||||
break; |
||||
} |
||||
|
||||
z.adler=new Adler32(); |
||||
|
||||
if((b&PRESET_DICT)==0){ |
||||
this.mode = BLOCKS; |
||||
break; |
||||
} |
||||
this.mode = DICT4; |
||||
case DICT4: |
||||
|
||||
if(z.avail_in==0)return r;r=f; |
||||
|
||||
z.avail_in--; z.total_in++; |
||||
this.need=((z.next_in[z.next_in_index++]&0xff)<<24)&0xff000000L; |
||||
this.mode=DICT3; |
||||
case DICT3: |
||||
|
||||
if(z.avail_in==0)return r;r=f; |
||||
|
||||
z.avail_in--; z.total_in++; |
||||
this.need+=((z.next_in[z.next_in_index++]&0xff)<<16)&0xff0000L; |
||||
this.mode=DICT2; |
||||
case DICT2: |
||||
|
||||
if(z.avail_in==0)return r;r=f; |
||||
|
||||
z.avail_in--; z.total_in++; |
||||
this.need+=((z.next_in[z.next_in_index++]&0xff)<<8)&0xff00L; |
||||
this.mode=DICT1; |
||||
case DICT1: |
||||
|
||||
if(z.avail_in==0)return r;r=f; |
||||
|
||||
z.avail_in--; z.total_in++; |
||||
this.need += (z.next_in[z.next_in_index++]&0xffL); |
||||
z.adler.reset(this.need); |
||||
this.mode = DICT0; |
||||
return Z_NEED_DICT; |
||||
case DICT0: |
||||
this.mode = BAD; |
||||
z.msg = "need dictionary"; |
||||
this.marker = 0; // can try inflateSync
|
||||
return Z_STREAM_ERROR; |
||||
case BLOCKS: |
||||
r = this.blocks.proc(r); |
||||
if(r == Z_DATA_ERROR){ |
||||
this.mode = BAD; |
||||
this.marker = 0; // can try inflateSync
|
||||
break; |
||||
} |
||||
if(r == Z_OK){ |
||||
r = f; |
||||
} |
||||
if(r != Z_STREAM_END){ |
||||
return r; |
||||
} |
||||
r = f; |
||||
this.was=z.adler.getValue(); |
||||
this.blocks.reset(); |
||||
if(this.wrap==0){ |
||||
this.mode=DONE; |
||||
break; |
||||
} |
||||
this.mode=CHECK4; |
||||
case CHECK4: |
||||
|
||||
if(z.avail_in==0)return r;r=f; |
||||
|
||||
z.avail_in--; z.total_in++; |
||||
this.need=((z.next_in[z.next_in_index++]&0xff)<<24)&0xff000000L; |
||||
this.mode=CHECK3; |
||||
case CHECK3: |
||||
|
||||
if(z.avail_in==0)return r;r=f; |
||||
|
||||
z.avail_in--; z.total_in++; |
||||
this.need+=((z.next_in[z.next_in_index++]&0xff)<<16)&0xff0000L; |
||||
this.mode = CHECK2; |
||||
case CHECK2: |
||||
|
||||
if(z.avail_in==0)return r;r=f; |
||||
|
||||
z.avail_in--; z.total_in++; |
||||
this.need+=((z.next_in[z.next_in_index++]&0xff)<<8)&0xff00L; |
||||
this.mode = CHECK1; |
||||
case CHECK1: |
||||
|
||||
if(z.avail_in==0)return r;r=f; |
||||
|
||||
z.avail_in--; z.total_in++; |
||||
this.need+=(z.next_in[z.next_in_index++]&0xffL); |
||||
|
||||
if(flags!=0){ // gzip
|
||||
this.need = ((this.need&0xff000000)>>24 | |
||||
(this.need&0x00ff0000)>>8 | |
||||
(this.need&0x0000ff00)<<8 | |
||||
(this.need&0x0000ffff)<<24)&0xffffffffL; |
||||
} |
||||
|
||||
if(((int)(this.was)) != ((int)(this.need))){ |
||||
z.msg = "incorrect data check"; |
||||
// chack is delayed
|
||||
/* |
||||
this.mode = BAD; |
||||
this.marker = 5; // can't try inflateSync
|
||||
break; |
||||
*/ |
||||
} |
||||
else if(flags!=0 && gheader!=null){ |
||||
gheader.crc = this.need; |
||||
} |
||||
|
||||
this.mode = LENGTH; |
||||
case LENGTH: |
||||
if (wrap!=0 && flags!=0) { |
||||
|
||||
try { r=readBytes(4, r, f); } |
||||
catch(Return e){ return e.r; } |
||||
|
||||
if(z.msg!=null && z.msg.equals("incorrect data check")){ |
||||
this.mode = BAD; |
||||
this.marker = 5; // can't try inflateSync
|
||||
break; |
||||
} |
||||
|
||||
if (this.need != (z.total_out & 0xffffffffL)) { |
||||
z.msg = "incorrect length check"; |
||||
this.mode = BAD; |
||||
break; |
||||
} |
||||
z.msg = null; |
||||
} |
||||
else { |
||||
if(z.msg!=null && z.msg.equals("incorrect data check")){ |
||||
this.mode = BAD; |
||||
this.marker = 5; // can't try inflateSync
|
||||
break; |
||||
} |
||||
} |
||||
|
||||
this.mode = DONE; |
||||
case DONE: |
||||
return Z_STREAM_END; |
||||
case BAD: |
||||
return Z_DATA_ERROR; |
||||
|
||||
case FLAGS: |
||||
|
||||
try { r=readBytes(2, r, f); } |
||||
catch(Return e){ return e.r; } |
||||
|
||||
flags = ((int)this.need)&0xffff; |
||||
|
||||
if ((flags & 0xff) != Z_DEFLATED) { |
||||
z.msg = "unknown compression method"; |
||||
this.mode = BAD; |
||||
break; |
||||
} |
||||
if ((flags & 0xe000)!=0) { |
||||
z.msg = "unknown header flags set"; |
||||
this.mode = BAD; |
||||
break; |
||||
} |
||||
|
||||
if ((flags & 0x0200)!=0){ |
||||
checksum(2, this.need); |
||||
} |
||||
|
||||
this.mode = TIME; |
||||
|
||||
case TIME: |
||||
try { r=readBytes(4, r, f); } |
||||
catch(Return e){ return e.r; } |
||||
if(gheader!=null) |
||||
gheader.time = this.need; |
||||
if ((flags & 0x0200)!=0){ |
||||
checksum(4, this.need); |
||||
} |
||||
this.mode = OS; |
||||
case OS: |
||||
try { r=readBytes(2, r, f); } |
||||
catch(Return e){ return e.r; } |
||||
if(gheader!=null){ |
||||
gheader.xflags = ((int)this.need)&0xff; |
||||
gheader.os = (((int)this.need)>>8)&0xff; |
||||
} |
||||
if ((flags & 0x0200)!=0){ |
||||
checksum(2, this.need); |
||||
} |
||||
this.mode = EXLEN; |
||||
case EXLEN: |
||||
if ((flags & 0x0400)!=0) { |
||||
try { r=readBytes(2, r, f); } |
||||
catch(Return e){ return e.r; } |
||||
if(gheader!=null){ |
||||
gheader.extra = new byte[((int)this.need)&0xffff]; |
||||
} |
||||
if ((flags & 0x0200)!=0){ |
||||
checksum(2, this.need); |
||||
} |
||||
} |
||||
else if(gheader!=null){ |
||||
gheader.extra=null; |
||||
} |
||||
this.mode = EXTRA; |
||||
|
||||
case EXTRA: |
||||
if ((flags & 0x0400)!=0) { |
||||
try { |
||||
r=readBytes(r, f); |
||||
if(gheader!=null){ |
||||
byte[] foo = tmp_string.toByteArray(); |
||||
tmp_string=null; |
||||
if(foo.length == gheader.extra.length){ |
||||
System.arraycopy(foo, 0, gheader.extra, 0, foo.length); |
||||
} |
||||
else{ |
||||
z.msg = "bad extra field length"; |
||||
this.mode = BAD; |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
catch(Return e){ return e.r; } |
||||
} |
||||
else if(gheader!=null){ |
||||
gheader.extra=null; |
||||
} |
||||
this.mode = NAME; |
||||
case NAME: |
||||
if ((flags & 0x0800)!=0) { |
||||
try { |
||||
r=readString(r, f); |
||||
if(gheader!=null){ |
||||
gheader.name=tmp_string.toByteArray(); |
||||
} |
||||
tmp_string=null; |
||||
} |
||||
catch(Return e){ return e.r; } |
||||
} |
||||
else if(gheader!=null){ |
||||
gheader.name=null; |
||||
} |
||||
this.mode = COMMENT; |
||||
case COMMENT: |
||||
if ((flags & 0x1000)!=0) { |
||||
try { |
||||
r=readString(r, f); |
||||
if(gheader!=null){ |
||||
gheader.comment=tmp_string.toByteArray(); |
||||
} |
||||
tmp_string=null; |
||||
} |
||||
catch(Return e){ return e.r; } |
||||
} |
||||
else if(gheader!=null){ |
||||
gheader.comment=null; |
||||
} |
||||
this.mode = HCRC; |
||||
case HCRC: |
||||
if ((flags & 0x0200)!=0) { |
||||
try { r=readBytes(2, r, f); } |
||||
catch(Return e){ return e.r; } |
||||
if(gheader!=null){ |
||||
gheader.hcrc=(int)(this.need&0xffff); |
||||
} |
||||
if(this.need != (z.adler.getValue()&0xffffL)){ |
||||
this.mode = BAD; |
||||
z.msg = "header crc mismatch"; |
||||
this.marker = 5; // can't try inflateSync
|
||||
break; |
||||
} |
||||
} |
||||
z.adler = new CRC32(); |
||||
|
||||
this.mode = BLOCKS; |
||||
break; |
||||
default: |
||||
return Z_STREAM_ERROR; |
||||
} |
||||
} |
||||
} |
||||
|
||||
int inflateSetDictionary(byte[] dictionary, int dictLength){ |
||||
if(z==null || (this.mode != DICT0 && this.wrap != 0)){ |
||||
return Z_STREAM_ERROR; |
||||
} |
||||
|
||||
int index=0; |
||||
int length = dictLength; |
||||
|
||||
if(this.mode==DICT0){ |
||||
long adler_need=z.adler.getValue(); |
||||
z.adler.reset(); |
||||
z.adler.update(dictionary, 0, dictLength); |
||||
if(z.adler.getValue()!=adler_need){ |
||||
return Z_DATA_ERROR; |
||||
} |
||||
} |
||||
|
||||
z.adler.reset(); |
||||
|
||||
if(length >= (1<<this.wbits)){ |
||||
length = (1<<this.wbits)-1; |
||||
index=dictLength - length; |
||||
} |
||||
this.blocks.set_dictionary(dictionary, index, length); |
||||
this.mode = BLOCKS; |
||||
return Z_OK; |
||||
} |
||||
|
||||
static private byte[] mark = {(byte)0, (byte)0, (byte)0xff, (byte)0xff}; |
||||
|
||||
int inflateSync(){ |
||||
int n; // number of bytes to look at
|
||||
int p; // pointer to bytes
|
||||
int m; // number of marker bytes found in a row
|
||||
long r, w; // temporaries to save total_in and total_out
|
||||
|
||||
// set up
|
||||
if(z == null) |
||||
return Z_STREAM_ERROR; |
||||
if(this.mode != BAD){ |
||||
this.mode = BAD; |
||||
this.marker = 0; |
||||
} |
||||
if((n=z.avail_in)==0) |
||||
return Z_BUF_ERROR; |
||||
|
||||
p=z.next_in_index; |
||||
m=this.marker; |
||||
// search
|
||||
while (n!=0 && m < 4){ |
||||
if(z.next_in[p] == mark[m]){ |
||||
m++; |
||||
} |
||||
else if(z.next_in[p]!=0){ |
||||
m = 0; |
||||
} |
||||
else{ |
||||
m = 4 - m; |
||||
} |
||||
p++; n--; |
||||
} |
||||
|
||||
// restore
|
||||
z.total_in += p-z.next_in_index; |
||||
z.next_in_index = p; |
||||
z.avail_in = n; |
||||
this.marker = m; |
||||
|
||||
// return no joy or set up to restart on a new block
|
||||
if(m != 4){ |
||||
return Z_DATA_ERROR; |
||||
} |
||||
r=z.total_in; w=z.total_out; |
||||
inflateReset(); |
||||
z.total_in=r; z.total_out = w; |
||||
this.mode = BLOCKS; |
||||
|
||||
return Z_OK; |
||||
} |
||||
|
||||
// Returns true if inflate is currently at the end of a block generated
|
||||
// by Z_SYNC_FLUSH or Z_FULL_FLUSH. This function is used by one PPP
|
||||
// implementation to provide an additional safety check. PPP uses Z_SYNC_FLUSH
|
||||
// but removes the length bytes of the resulting empty stored block. When
|
||||
// decompressing, PPP checks that at the end of input packet, inflate is
|
||||
// waiting for these length bytes.
|
||||
int inflateSyncPoint(){ |
||||
if(z == null || this.blocks == null) |
||||
return Z_STREAM_ERROR; |
||||
return this.blocks.sync_point(); |
||||
} |
||||
|
||||
private int readBytes(int n, int r, int f) throws Return{ |
||||
if(need_bytes == -1){ |
||||
need_bytes=n; |
||||
this.need=0; |
||||
} |
||||
while(need_bytes>0){ |
||||
if(z.avail_in==0){ throw new Return(r); }; r=f; |
||||
z.avail_in--; z.total_in++; |
||||
this.need = this.need | |
||||
((z.next_in[z.next_in_index++]&0xff)<<((n-need_bytes)*8)); |
||||
need_bytes--; |
||||
} |
||||
if(n==2){ |
||||
this.need&=0xffffL; |
||||
} |
||||
else if(n==4) { |
||||
this.need&=0xffffffffL; |
||||
} |
||||
need_bytes=-1; |
||||
return r; |
||||
} |
||||
class Return extends Exception{ |
||||
int r; |
||||
Return(int r){this.r=r; } |
||||
} |
||||
|
||||
private java.io.ByteArrayOutputStream tmp_string = null; |
||||
private int readString(int r, int f) throws Return{ |
||||
if(tmp_string == null){ |
||||
tmp_string=new java.io.ByteArrayOutputStream(); |
||||
} |
||||
int b=0; |
||||
do { |
||||
if(z.avail_in==0){ throw new Return(r); }; r=f; |
||||
z.avail_in--; z.total_in++; |
||||
b = z.next_in[z.next_in_index]; |
||||
if(b!=0) tmp_string.write(z.next_in, z.next_in_index, 1); |
||||
z.adler.update(z.next_in, z.next_in_index, 1); |
||||
z.next_in_index++; |
||||
}while(b!=0); |
||||
return r; |
||||
} |
||||
|
||||
private int readBytes(int r, int f) throws Return{ |
||||
if(tmp_string == null){ |
||||
tmp_string=new java.io.ByteArrayOutputStream(); |
||||
} |
||||
int b=0; |
||||
while(this.need>0){ |
||||
if(z.avail_in==0){ throw new Return(r); }; r=f; |
||||
z.avail_in--; z.total_in++; |
||||
b = z.next_in[z.next_in_index]; |
||||
tmp_string.write(z.next_in, z.next_in_index, 1); |
||||
z.adler.update(z.next_in, z.next_in_index, 1); |
||||
z.next_in_index++; |
||||
this.need--; |
||||
} |
||||
return r; |
||||
} |
||||
|
||||
private void checksum(int n, long v){ |
||||
for(int i=0; i<n; i++){ |
||||
crcbuf[i]=(byte)(v&0xff); |
||||
v>>=8; |
||||
} |
||||
z.adler.update(crcbuf, 0, n); |
||||
} |
||||
|
||||
public GZIPHeader getGZIPHeader(){ |
||||
return gheader; |
||||
} |
||||
|
||||
boolean inParsingHeader(){ |
||||
switch(mode){ |
||||
case HEAD: |
||||
case DICT4: |
||||
case DICT3: |
||||
case DICT2: |
||||
case DICT1: |
||||
case FLAGS: |
||||
case TIME: |
||||
case OS: |
||||
case EXLEN: |
||||
case EXTRA: |
||||
case NAME: |
||||
case COMMENT: |
||||
case HCRC: |
||||
return true; |
||||
default: |
||||
return false; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,168 @@ |
||||
/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ |
||||
/* |
||||
Copyright (c) 2011 ymnk, JCraft,Inc. All rights reserved. |
||||
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions are met: |
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, |
||||
this list of conditions and the following disclaimer. |
||||
|
||||
2. 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. |
||||
|
||||
3. The names of the authors may not be used to endorse or promote products |
||||
derived from this software without specific prior written permission. |
||||
|
||||
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 JCRAFT, |
||||
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE 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. |
||||
*/ |
||||
/* |
||||
* This program is based on zlib-1.1.3, so all credit should go authors |
||||
* Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) |
||||
* and contributors of zlib. |
||||
*/ |
||||
|
||||
package com.jcraft.jzlib; |
||||
|
||||
final public class Inflater extends ZStream{ |
||||
|
||||
static final private int MAX_WBITS=15; // 32K LZ77 window
|
||||
static final private int DEF_WBITS=MAX_WBITS; |
||||
|
||||
static final private int Z_NO_FLUSH=0; |
||||
static final private int Z_PARTIAL_FLUSH=1; |
||||
static final private int Z_SYNC_FLUSH=2; |
||||
static final private int Z_FULL_FLUSH=3; |
||||
static final private int Z_FINISH=4; |
||||
|
||||
static final private int MAX_MEM_LEVEL=9; |
||||
|
||||
static final private int Z_OK=0; |
||||
static final private int Z_STREAM_END=1; |
||||
static final private int Z_NEED_DICT=2; |
||||
static final private int Z_ERRNO=-1; |
||||
static final private int Z_STREAM_ERROR=-2; |
||||
static final private int Z_DATA_ERROR=-3; |
||||
static final private int Z_MEM_ERROR=-4; |
||||
static final private int Z_BUF_ERROR=-5; |
||||
static final private int Z_VERSION_ERROR=-6; |
||||
|
||||
public Inflater() { |
||||
super(); |
||||
init(); |
||||
} |
||||
|
||||
public Inflater(JZlib.WrapperType wrapperType) throws GZIPException { |
||||
this(DEF_WBITS, wrapperType); |
||||
} |
||||
|
||||
public Inflater(int w, JZlib.WrapperType wrapperType) throws GZIPException { |
||||
super(); |
||||
int ret = init(w, wrapperType); |
||||
if(ret!=Z_OK) |
||||
throw new GZIPException(ret+": "+msg); |
||||
} |
||||
|
||||
public Inflater(int w) throws GZIPException { |
||||
this(w, false); |
||||
} |
||||
|
||||
public Inflater(boolean nowrap) throws GZIPException { |
||||
this(DEF_WBITS, nowrap); |
||||
} |
||||
|
||||
public Inflater(int w, boolean nowrap) throws GZIPException { |
||||
super(); |
||||
int ret = init(w, nowrap); |
||||
if(ret!=Z_OK) |
||||
throw new GZIPException(ret+": "+msg); |
||||
} |
||||
|
||||
private boolean finished = false; |
||||
|
||||
public int init(){ |
||||
return init(DEF_WBITS); |
||||
} |
||||
|
||||
public int init(JZlib.WrapperType wrapperType){ |
||||
return init(DEF_WBITS, wrapperType); |
||||
} |
||||
|
||||
public int init(int w, JZlib.WrapperType wrapperType) { |
||||
boolean nowrap = false; |
||||
if(wrapperType == JZlib.W_NONE){ |
||||
nowrap = true; |
||||
} |
||||
else if(wrapperType == JZlib.W_GZIP) { |
||||
w += 16; |
||||
} |
||||
else if(wrapperType == JZlib.W_ANY) { |
||||
w |= Inflate.INFLATE_ANY; |
||||
} |
||||
else if(wrapperType == JZlib.W_ZLIB) { |
||||
} |
||||
return init(w, nowrap); |
||||
} |
||||
|
||||
public int init(boolean nowrap){ |
||||
return init(DEF_WBITS, nowrap); |
||||
} |
||||
|
||||
public int init(int w){ |
||||
return init(w, false); |
||||
} |
||||
|
||||
public int init(int w, boolean nowrap){ |
||||
finished = false; |
||||
istate=new Inflate(this); |
||||
return istate.inflateInit(nowrap?-w:w); |
||||
} |
||||
|
||||
public int inflate(int f){ |
||||
if(istate==null) return Z_STREAM_ERROR; |
||||
int ret = istate.inflate(f); |
||||
if(ret == Z_STREAM_END) |
||||
finished = true; |
||||
return ret; |
||||
} |
||||
|
||||
public int end(){ |
||||
finished = true; |
||||
if(istate==null) return Z_STREAM_ERROR; |
||||
int ret=istate.inflateEnd(); |
||||
// istate = null;
|
||||
return ret; |
||||
} |
||||
|
||||
public int sync(){ |
||||
if(istate == null) |
||||
return Z_STREAM_ERROR; |
||||
return istate.inflateSync(); |
||||
} |
||||
|
||||
public int syncPoint(){ |
||||
if(istate == null) |
||||
return Z_STREAM_ERROR; |
||||
return istate.inflateSyncPoint(); |
||||
} |
||||
|
||||
public int setDictionary(byte[] dictionary, int dictLength){ |
||||
if(istate == null) |
||||
return Z_STREAM_ERROR; |
||||
return istate.inflateSetDictionary(dictionary, dictLength); |
||||
} |
||||
|
||||
public boolean finished(){ |
||||
return istate.mode==12 /*DONE*/; |
||||
} |
||||
} |
@ -0,0 +1,247 @@ |
||||
/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ |
||||
/* |
||||
Copyright (c) 2011 ymnk, JCraft,Inc. All rights reserved. |
||||
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions are met: |
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, |
||||
this list of conditions and the following disclaimer. |
||||
|
||||
2. 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. |
||||
|
||||
3. The names of the authors may not be used to endorse or promote products |
||||
derived from this software without specific prior written permission. |
||||
|
||||
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 JCRAFT, |
||||
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, |
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, |
||||
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, |
||||
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
*/ |
||||
|
||||
package com.jcraft.jzlib; |
||||
import java.io.*; |
||||
|
||||
public class InflaterInputStream extends FilterInputStream { |
||||
protected final Inflater inflater; |
||||
protected byte[] buf; |
||||
|
||||
private boolean closed = false; |
||||
|
||||
private boolean eof = false; |
||||
|
||||
private boolean close_in = true; |
||||
|
||||
protected static final int DEFAULT_BUFSIZE = 512; |
||||
|
||||
public InflaterInputStream(InputStream in) throws IOException { |
||||
this(in, false); |
||||
} |
||||
|
||||
public InflaterInputStream(InputStream in, boolean nowrap) throws IOException { |
||||
this(in, new Inflater(nowrap)); |
||||
myinflater = true; |
||||
} |
||||
|
||||
public InflaterInputStream(InputStream in, Inflater inflater) throws IOException { |
||||
this(in, inflater, DEFAULT_BUFSIZE); |
||||
} |
||||
|
||||
public InflaterInputStream(InputStream in, |
||||
Inflater inflater, int size) throws IOException { |
||||
this(in, inflater, size, true); |
||||
} |
||||
|
||||
public InflaterInputStream(InputStream in, |
||||
Inflater inflater, |
||||
int size, boolean close_in) throws IOException { |
||||
super(in); |
||||
if (in == null || inflater == null) { |
||||
throw new NullPointerException(); |
||||
} |
||||
else if (size <= 0) { |
||||
throw new IllegalArgumentException("buffer size must be greater than 0"); |
||||
} |
||||
this.inflater = inflater; |
||||
buf = new byte[size]; |
||||
this.close_in = close_in; |
||||
} |
||||
|
||||
protected boolean myinflater = false; |
||||
|
||||
private byte[] byte1 = new byte[1]; |
||||
|
||||
public int read() throws IOException { |
||||
if (closed) { throw new IOException("Stream closed"); } |
||||
return read(byte1, 0, 1) == -1 ? -1 : byte1[0] & 0xff; |
||||
} |
||||
|
||||
public int read(byte[] b, int off, int len) throws IOException { |
||||
if (closed) { throw new IOException("Stream closed"); } |
||||
if (b == null) { |
||||
throw new NullPointerException(); |
||||
} |
||||
else if (off < 0 || len < 0 || len > b.length - off) { |
||||
throw new IndexOutOfBoundsException(); |
||||
} |
||||
else if (len == 0) { |
||||
return 0; |
||||
} |
||||
else if (eof) { |
||||
return -1; |
||||
} |
||||
|
||||
int n = 0; |
||||
inflater.setOutput(b, off, len); |
||||
while(!eof) { |
||||
if(inflater.avail_in==0) |
||||
fill(); |
||||
int err = inflater.inflate(JZlib.Z_NO_FLUSH); |
||||
n += inflater.next_out_index - off; |
||||
off = inflater.next_out_index; |
||||
switch(err) { |
||||
case JZlib.Z_DATA_ERROR: |
||||
throw new IOException(inflater.msg); |
||||
case JZlib.Z_STREAM_END: |
||||
case JZlib.Z_NEED_DICT: |
||||
eof = true; |
||||
if(err == JZlib.Z_NEED_DICT) |
||||
return -1; |
||||
break; |
||||
default: |
||||
} |
||||
if(inflater.avail_out==0) |
||||
break; |
||||
} |
||||
return n; |
||||
} |
||||
|
||||
public int available() throws IOException { |
||||
if (closed) { throw new IOException("Stream closed"); } |
||||
if (eof) { |
||||
return 0; |
||||
} |
||||
else { |
||||
return 1; |
||||
} |
||||
} |
||||
|
||||
private byte[] b = new byte[512]; |
||||
|
||||
public long skip(long n) throws IOException { |
||||
if (n < 0) { |
||||
throw new IllegalArgumentException("negative skip length"); |
||||
} |
||||
|
||||
if (closed) { throw new IOException("Stream closed"); } |
||||
|
||||
int max = (int)Math.min(n, Integer.MAX_VALUE); |
||||
int total = 0; |
||||
while (total < max) { |
||||
int len = max - total; |
||||
if (len > b.length) { |
||||
len = b.length; |
||||
} |
||||
len = read(b, 0, len); |
||||
if (len == -1) { |
||||
eof = true; |
||||
break; |
||||
} |
||||
total += len; |
||||
} |
||||
return total; |
||||
} |
||||
|
||||
public void close() throws IOException { |
||||
if (!closed) { |
||||
if (myinflater) |
||||
inflater.end(); |
||||
if(close_in) |
||||
in.close(); |
||||
closed = true; |
||||
} |
||||
} |
||||
|
||||
protected void fill() throws IOException { |
||||
if (closed) { throw new IOException("Stream closed"); } |
||||
int len = in.read(buf, 0, buf.length); |
||||
if (len == -1) { |
||||
if(inflater.istate.wrap == 0 && |
||||
!inflater.finished()){ |
||||
buf[0]=0; |
||||
len=1; |
||||
} |
||||
else if(inflater.istate.was != -1){ // in reading trailer
|
||||
throw new IOException("footer is not found"); |
||||
} |
||||
else{ |
||||
throw new EOFException("Unexpected end of ZLIB input stream"); |
||||
} |
||||
} |
||||
inflater.setInput(buf, 0, len, true); |
||||
} |
||||
|
||||
public boolean markSupported() { |
||||
return false; |
||||
} |
||||
|
||||
public synchronized void mark(int readlimit) { |
||||
} |
||||
|
||||
public synchronized void reset() throws IOException { |
||||
throw new IOException("mark/reset not supported"); |
||||
} |
||||
|
||||
public long getTotalIn() { |
||||
return inflater.getTotalIn(); |
||||
} |
||||
|
||||
public long getTotalOut() { |
||||
return inflater.getTotalOut(); |
||||
} |
||||
|
||||
public byte[] getAvailIn() { |
||||
if(inflater.avail_in<=0) |
||||
return null; |
||||
byte[] tmp = new byte[inflater.avail_in]; |
||||
System.arraycopy(inflater.next_in, inflater.next_in_index, |
||||
tmp, 0, inflater.avail_in); |
||||
return tmp; |
||||
} |
||||
|
||||
public void readHeader() throws IOException { |
||||
|
||||
byte[] empty = "".getBytes(); |
||||
inflater.setInput(empty, 0, 0, false); |
||||
inflater.setOutput(empty, 0, 0); |
||||
|
||||
int err = inflater.inflate(JZlib.Z_NO_FLUSH); |
||||
if(!inflater.istate.inParsingHeader()){ |
||||
return; |
||||
} |
||||
|
||||
byte[] b1 = new byte[1]; |
||||
do{ |
||||
int i = in.read(b1); |
||||
if(i<=0) |
||||
throw new IOException("no input"); |
||||
inflater.setInput(b1); |
||||
err = inflater.inflate(JZlib.Z_NO_FLUSH); |
||||
if(err!=0/*Z_OK*/) |
||||
throw new IOException(inflater.msg); |
||||
} |
||||
while(inflater.istate.inParsingHeader()); |
||||
} |
||||
|
||||
public Inflater getInflater(){ |
||||
return inflater; |
||||
} |
||||
} |
@ -0,0 +1,92 @@ |
||||
/* -*-mode:java; c-basic-offset:2; -*- */ |
||||
/* |
||||
Copyright (c) 2011 ymnk, JCraft,Inc. All rights reserved. |
||||
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions are met: |
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, |
||||
this list of conditions and the following disclaimer. |
||||
|
||||
2. 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. |
||||
|
||||
3. The names of the authors may not be used to endorse or promote products |
||||
derived from this software without specific prior written permission. |
||||
|
||||
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 JCRAFT, |
||||
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE 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. |
||||
*/ |
||||
/* |
||||
* This program is based on zlib-1.1.3, so all credit should go authors |
||||
* Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) |
||||
* and contributors of zlib. |
||||
*/ |
||||
|
||||
package com.jcraft.jzlib; |
||||
|
||||
final public class JZlib{ |
||||
private static final String version="1.1.0"; |
||||
public static String version(){return version;} |
||||
|
||||
static final public int MAX_WBITS=15; // 32K LZ77 window
|
||||
static final public int DEF_WBITS=MAX_WBITS; |
||||
|
||||
public enum WrapperType { |
||||
NONE, ZLIB, GZIP, ANY |
||||
} |
||||
|
||||
public static final WrapperType W_NONE = WrapperType.NONE; |
||||
public static final WrapperType W_ZLIB = WrapperType.ZLIB; |
||||
public static final WrapperType W_GZIP = WrapperType.GZIP; |
||||
public static final WrapperType W_ANY = WrapperType.ANY; |
||||
|
||||
// compression levels
|
||||
static final public int Z_NO_COMPRESSION=0; |
||||
static final public int Z_BEST_SPEED=1; |
||||
static final public int Z_BEST_COMPRESSION=9; |
||||
static final public int Z_DEFAULT_COMPRESSION=(-1); |
||||
|
||||
// compression strategy
|
||||
static final public int Z_FILTERED=1; |
||||
static final public int Z_HUFFMAN_ONLY=2; |
||||
static final public int Z_DEFAULT_STRATEGY=0; |
||||
|
||||
static final public int Z_NO_FLUSH=0; |
||||
static final public int Z_PARTIAL_FLUSH=1; |
||||
static final public int Z_SYNC_FLUSH=2; |
||||
static final public int Z_FULL_FLUSH=3; |
||||
static final public int Z_FINISH=4; |
||||
|
||||
static final public int Z_OK=0; |
||||
static final public int Z_STREAM_END=1; |
||||
static final public int Z_NEED_DICT=2; |
||||
static final public int Z_ERRNO=-1; |
||||
static final public int Z_STREAM_ERROR=-2; |
||||
static final public int Z_DATA_ERROR=-3; |
||||
static final public int Z_MEM_ERROR=-4; |
||||
static final public int Z_BUF_ERROR=-5; |
||||
static final public int Z_VERSION_ERROR=-6; |
||||
|
||||
// The three kinds of block type
|
||||
static final public byte Z_BINARY = 0; |
||||
static final public byte Z_ASCII = 1; |
||||
static final public byte Z_UNKNOWN = 2; |
||||
|
||||
public static long adler32_combine(long adler1, long adler2, long len2){ |
||||
return Adler32.combine(adler1, adler2, len2); |
||||
} |
||||
|
||||
public static long crc32_combine(long crc1, long crc2, long len2){ |
||||
return CRC32.combine(crc1, crc2, len2); |
||||
} |
||||
} |
@ -0,0 +1,148 @@ |
||||
/* -*-mode:java; c-basic-offset:2; -*- */ |
||||
/* |
||||
Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved. |
||||
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions are met: |
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, |
||||
this list of conditions and the following disclaimer. |
||||
|
||||
2. 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. |
||||
|
||||
3. The names of the authors may not be used to endorse or promote products |
||||
derived from this software without specific prior written permission. |
||||
|
||||
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 JCRAFT, |
||||
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE 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. |
||||
*/ |
||||
/* |
||||
* This program is based on zlib-1.1.3, so all credit should go authors |
||||
* Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) |
||||
* and contributors of zlib. |
||||
*/ |
||||
|
||||
package com.jcraft.jzlib; |
||||
|
||||
final class StaticTree{ |
||||
static final private int MAX_BITS=15; |
||||
|
||||
static final private int BL_CODES=19; |
||||
static final private int D_CODES=30; |
||||
static final private int LITERALS=256; |
||||
static final private int LENGTH_CODES=29; |
||||
static final private int L_CODES=(LITERALS+1+LENGTH_CODES); |
||||
|
||||
// Bit length codes must not exceed MAX_BL_BITS bits
|
||||
static final int MAX_BL_BITS=7; |
||||
|
||||
static final short[] static_ltree = { |
||||
12, 8, 140, 8, 76, 8, 204, 8, 44, 8, |
||||
172, 8, 108, 8, 236, 8, 28, 8, 156, 8, |
||||
92, 8, 220, 8, 60, 8, 188, 8, 124, 8, |
||||
252, 8, 2, 8, 130, 8, 66, 8, 194, 8, |
||||
34, 8, 162, 8, 98, 8, 226, 8, 18, 8, |
||||
146, 8, 82, 8, 210, 8, 50, 8, 178, 8, |
||||
114, 8, 242, 8, 10, 8, 138, 8, 74, 8, |
||||
202, 8, 42, 8, 170, 8, 106, 8, 234, 8, |
||||
26, 8, 154, 8, 90, 8, 218, 8, 58, 8, |
||||
186, 8, 122, 8, 250, 8, 6, 8, 134, 8, |
||||
70, 8, 198, 8, 38, 8, 166, 8, 102, 8, |
||||
230, 8, 22, 8, 150, 8, 86, 8, 214, 8, |
||||
54, 8, 182, 8, 118, 8, 246, 8, 14, 8, |
||||
142, 8, 78, 8, 206, 8, 46, 8, 174, 8, |
||||
110, 8, 238, 8, 30, 8, 158, 8, 94, 8, |
||||
222, 8, 62, 8, 190, 8, 126, 8, 254, 8, |
||||
1, 8, 129, 8, 65, 8, 193, 8, 33, 8, |
||||
161, 8, 97, 8, 225, 8, 17, 8, 145, 8, |
||||
81, 8, 209, 8, 49, 8, 177, 8, 113, 8, |
||||
241, 8, 9, 8, 137, 8, 73, 8, 201, 8, |
||||
41, 8, 169, 8, 105, 8, 233, 8, 25, 8, |
||||
153, 8, 89, 8, 217, 8, 57, 8, 185, 8, |
||||
121, 8, 249, 8, 5, 8, 133, 8, 69, 8, |
||||
197, 8, 37, 8, 165, 8, 101, 8, 229, 8, |
||||
21, 8, 149, 8, 85, 8, 213, 8, 53, 8, |
||||
181, 8, 117, 8, 245, 8, 13, 8, 141, 8, |
||||
77, 8, 205, 8, 45, 8, 173, 8, 109, 8, |
||||
237, 8, 29, 8, 157, 8, 93, 8, 221, 8, |
||||
61, 8, 189, 8, 125, 8, 253, 8, 19, 9, |
||||
275, 9, 147, 9, 403, 9, 83, 9, 339, 9, |
||||
211, 9, 467, 9, 51, 9, 307, 9, 179, 9, |
||||
435, 9, 115, 9, 371, 9, 243, 9, 499, 9, |
||||
11, 9, 267, 9, 139, 9, 395, 9, 75, 9, |
||||
331, 9, 203, 9, 459, 9, 43, 9, 299, 9, |
||||
171, 9, 427, 9, 107, 9, 363, 9, 235, 9, |
||||
491, 9, 27, 9, 283, 9, 155, 9, 411, 9, |
||||
91, 9, 347, 9, 219, 9, 475, 9, 59, 9, |
||||
315, 9, 187, 9, 443, 9, 123, 9, 379, 9, |
||||
251, 9, 507, 9, 7, 9, 263, 9, 135, 9, |
||||
391, 9, 71, 9, 327, 9, 199, 9, 455, 9, |
||||
39, 9, 295, 9, 167, 9, 423, 9, 103, 9, |
||||
359, 9, 231, 9, 487, 9, 23, 9, 279, 9, |
||||
151, 9, 407, 9, 87, 9, 343, 9, 215, 9, |
||||
471, 9, 55, 9, 311, 9, 183, 9, 439, 9, |
||||
119, 9, 375, 9, 247, 9, 503, 9, 15, 9, |
||||
271, 9, 143, 9, 399, 9, 79, 9, 335, 9, |
||||
207, 9, 463, 9, 47, 9, 303, 9, 175, 9, |
||||
431, 9, 111, 9, 367, 9, 239, 9, 495, 9, |
||||
31, 9, 287, 9, 159, 9, 415, 9, 95, 9, |
||||
351, 9, 223, 9, 479, 9, 63, 9, 319, 9, |
||||
191, 9, 447, 9, 127, 9, 383, 9, 255, 9, |
||||
511, 9, 0, 7, 64, 7, 32, 7, 96, 7, |
||||
16, 7, 80, 7, 48, 7, 112, 7, 8, 7, |
||||
72, 7, 40, 7, 104, 7, 24, 7, 88, 7, |
||||
56, 7, 120, 7, 4, 7, 68, 7, 36, 7, |
||||
100, 7, 20, 7, 84, 7, 52, 7, 116, 7, |
||||
3, 8, 131, 8, 67, 8, 195, 8, 35, 8, |
||||
163, 8, 99, 8, 227, 8 |
||||
}; |
||||
|
||||
static final short[] static_dtree = { |
||||
0, 5, 16, 5, 8, 5, 24, 5, 4, 5, |
||||
20, 5, 12, 5, 28, 5, 2, 5, 18, 5, |
||||
10, 5, 26, 5, 6, 5, 22, 5, 14, 5, |
||||
30, 5, 1, 5, 17, 5, 9, 5, 25, 5, |
||||
5, 5, 21, 5, 13, 5, 29, 5, 3, 5, |
||||
19, 5, 11, 5, 27, 5, 7, 5, 23, 5 |
||||
}; |
||||
|
||||
static StaticTree static_l_desc = |
||||
new StaticTree(static_ltree, Tree.extra_lbits, |
||||
LITERALS+1, L_CODES, MAX_BITS); |
||||
|
||||
static StaticTree static_d_desc = |
||||
new StaticTree(static_dtree, Tree.extra_dbits, |
||||
0, D_CODES, MAX_BITS); |
||||
|
||||
static StaticTree static_bl_desc = |
||||
new StaticTree(null, Tree.extra_blbits, |
||||
0, BL_CODES, MAX_BL_BITS); |
||||
|
||||
short[] static_tree; // static tree or null
|
||||
int[] extra_bits; // extra bits for each code or null
|
||||
int extra_base; // base index for extra_bits
|
||||
int elems; // max number of elements in the tree
|
||||
int max_length; // max bit length for the codes
|
||||
|
||||
private StaticTree(short[] static_tree, |
||||
int[] extra_bits, |
||||
int extra_base, |
||||
int elems, |
||||
int max_length){ |
||||
this.static_tree=static_tree; |
||||
this.extra_bits=extra_bits; |
||||
this.extra_base=extra_base; |
||||
this.elems=elems; |
||||
this.max_length=max_length; |
||||
} |
||||
} |
@ -0,0 +1,367 @@ |
||||
/* -*-mode:java; c-basic-offset:2; -*- */ |
||||
/* |
||||
Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved. |
||||
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions are met: |
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, |
||||
this list of conditions and the following disclaimer. |
||||
|
||||
2. 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. |
||||
|
||||
3. The names of the authors may not be used to endorse or promote products |
||||
derived from this software without specific prior written permission. |
||||
|
||||
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 JCRAFT, |
||||
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE 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. |
||||
*/ |
||||
/* |
||||
* This program is based on zlib-1.1.3, so all credit should go authors |
||||
* Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) |
||||
* and contributors of zlib. |
||||
*/ |
||||
|
||||
package com.jcraft.jzlib; |
||||
|
||||
final class Tree{ |
||||
static final private int MAX_BITS=15; |
||||
static final private int BL_CODES=19; |
||||
static final private int D_CODES=30; |
||||
static final private int LITERALS=256; |
||||
static final private int LENGTH_CODES=29; |
||||
static final private int L_CODES=(LITERALS+1+LENGTH_CODES); |
||||
static final private int HEAP_SIZE=(2*L_CODES+1); |
||||
|
||||
// Bit length codes must not exceed MAX_BL_BITS bits
|
||||
static final int MAX_BL_BITS=7; |
||||
|
||||
// end of block literal code
|
||||
static final int END_BLOCK=256; |
||||
|
||||
// repeat previous bit length 3-6 times (2 bits of repeat count)
|
||||
static final int REP_3_6=16; |
||||
|
||||
// repeat a zero length 3-10 times (3 bits of repeat count)
|
||||
static final int REPZ_3_10=17; |
||||
|
||||
// repeat a zero length 11-138 times (7 bits of repeat count)
|
||||
static final int REPZ_11_138=18; |
||||
|
||||
// extra bits for each length code
|
||||
static final int[] extra_lbits={ |
||||
0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0 |
||||
}; |
||||
|
||||
// extra bits for each distance code
|
||||
static final int[] extra_dbits={ |
||||
0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13 |
||||
}; |
||||
|
||||
// extra bits for each bit length code
|
||||
static final int[] extra_blbits={ |
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,7 |
||||
}; |
||||
|
||||
static final byte[] bl_order={ |
||||
16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15}; |
||||
|
||||
|
||||
// The lengths of the bit length codes are sent in order of decreasing
|
||||
// probability, to avoid transmitting the lengths for unused bit
|
||||
// length codes.
|
||||
|
||||
static final int Buf_size=8*2; |
||||
|
||||
// see definition of array dist_code below
|
||||
static final int DIST_CODE_LEN=512; |
||||
|
||||
static final byte[] _dist_code = { |
||||
0, 1, 2, 3, 4, 4, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, |
||||
8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, |
||||
10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, |
||||
11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, |
||||
12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, |
||||
13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, |
||||
13, 13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, |
||||
14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, |
||||
14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, |
||||
14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 15, 15, |
||||
15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, |
||||
15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, |
||||
15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 16, 17, |
||||
18, 18, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, 22, 22, |
||||
23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, |
||||
24, 24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, |
||||
26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, |
||||
26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, |
||||
27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, |
||||
27, 27, 27, 27, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, |
||||
28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, |
||||
28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, |
||||
28, 28, 28, 28, 28, 28, 28, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, |
||||
29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, |
||||
29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, |
||||
29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29 |
||||
}; |
||||
|
||||
static final byte[] _length_code={ |
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 12, 12, |
||||
13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16, 16, 16, |
||||
17, 17, 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 19, 19, 19, 19, |
||||
19, 19, 19, 19, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, |
||||
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, |
||||
22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23, 23, 23, 23, 23, 23, 23, 23, |
||||
23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, |
||||
24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, |
||||
25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, |
||||
25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26, 26, 26, 26, |
||||
26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, |
||||
26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, |
||||
27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 28 |
||||
}; |
||||
|
||||
static final int[] base_length = { |
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 20, 24, 28, 32, 40, 48, 56, |
||||
64, 80, 96, 112, 128, 160, 192, 224, 0 |
||||
}; |
||||
|
||||
static final int[] base_dist = { |
||||
0, 1, 2, 3, 4, 6, 8, 12, 16, 24, |
||||
32, 48, 64, 96, 128, 192, 256, 384, 512, 768, |
||||
1024, 1536, 2048, 3072, 4096, 6144, 8192, 12288, 16384, 24576 |
||||
}; |
||||
|
||||
// Mapping from a distance to a distance code. dist is the distance - 1 and
|
||||
// must not have side effects. _dist_code[256] and _dist_code[257] are never
|
||||
// used.
|
||||
static int d_code(int dist){ |
||||
return ((dist) < 256 ? _dist_code[dist] : _dist_code[256+((dist)>>>7)]); |
||||
} |
||||
|
||||
short[] dyn_tree; // the dynamic tree
|
||||
int max_code; // largest code with non zero frequency
|
||||
StaticTree stat_desc; // the corresponding static tree
|
||||
|
||||
// Compute the optimal bit lengths for a tree and update the total bit length
|
||||
// for the current block.
|
||||
// IN assertion: the fields freq and dad are set, heap[heap_max] and
|
||||
// above are the tree nodes sorted by increasing frequency.
|
||||
// OUT assertions: the field len is set to the optimal bit length, the
|
||||
// array bl_count contains the frequencies for each bit length.
|
||||
// The length opt_len is updated; static_len is also updated if stree is
|
||||
// not null.
|
||||
void gen_bitlen(Deflate s){ |
||||
short[] tree = dyn_tree; |
||||
short[] stree = stat_desc.static_tree; |
||||
int[] extra = stat_desc.extra_bits; |
||||
int base = stat_desc.extra_base; |
||||
int max_length = stat_desc.max_length; |
||||
int h; // heap index
|
||||
int n, m; // iterate over the tree elements
|
||||
int bits; // bit length
|
||||
int xbits; // extra bits
|
||||
short f; // frequency
|
||||
int overflow = 0; // number of elements with bit length too large
|
||||
|
||||
for (bits = 0; bits <= MAX_BITS; bits++) s.bl_count[bits] = 0; |
||||
|
||||
// In a first pass, compute the optimal bit lengths (which may
|
||||
// overflow in the case of the bit length tree).
|
||||
tree[s.heap[s.heap_max]*2+1] = 0; // root of the heap
|
||||
|
||||
for(h=s.heap_max+1; h<HEAP_SIZE; h++){ |
||||
n = s.heap[h]; |
||||
bits = tree[tree[n*2+1]*2+1] + 1; |
||||
if (bits > max_length){ bits = max_length; overflow++; } |
||||
tree[n*2+1] = (short)bits; |
||||
// We overwrite tree[n*2+1] which is no longer needed
|
||||
|
||||
if (n > max_code) continue; // not a leaf node
|
||||
|
||||
s.bl_count[bits]++; |
||||
xbits = 0; |
||||
if (n >= base) xbits = extra[n-base]; |
||||
f = tree[n*2]; |
||||
s.opt_len += f * (bits + xbits); |
||||
if (stree!=null) s.static_len += f * (stree[n*2+1] + xbits); |
||||
} |
||||
if (overflow == 0) return; |
||||
|
||||
// This happens for example on obj2 and pic of the Calgary corpus
|
||||
// Find the first bit length which could increase:
|
||||
do { |
||||
bits = max_length-1; |
||||
while(s.bl_count[bits]==0) bits--; |
||||
s.bl_count[bits]--; // move one leaf down the tree
|
||||
s.bl_count[bits+1]+=2; // move one overflow item as its brother
|
||||
s.bl_count[max_length]--; |
||||
// The brother of the overflow item also moves one step up,
|
||||
// but this does not affect bl_count[max_length]
|
||||
overflow -= 2; |
||||
} |
||||
while (overflow > 0); |
||||
|
||||
for (bits = max_length; bits != 0; bits--) { |
||||
n = s.bl_count[bits]; |
||||
while (n != 0) { |
||||
m = s.heap[--h]; |
||||
if (m > max_code) continue; |
||||
if (tree[m*2+1] != bits) { |
||||
s.opt_len += ((long)bits - (long)tree[m*2+1])*(long)tree[m*2]; |
||||
tree[m*2+1] = (short)bits; |
||||
} |
||||
n--; |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Construct one Huffman tree and assigns the code bit strings and lengths.
|
||||
// Update the total bit length for the current block.
|
||||
// IN assertion: the field freq is set for all tree elements.
|
||||
// OUT assertions: the fields len and code are set to the optimal bit length
|
||||
// and corresponding code. The length opt_len is updated; static_len is
|
||||
// also updated if stree is not null. The field max_code is set.
|
||||
void build_tree(Deflate s){ |
||||
short[] tree=dyn_tree; |
||||
short[] stree=stat_desc.static_tree; |
||||
int elems=stat_desc.elems; |
||||
int n, m; // iterate over heap elements
|
||||
int max_code=-1; // largest code with non zero frequency
|
||||
int node; // new node being created
|
||||
|
||||
// Construct the initial heap, with least frequent element in
|
||||
// heap[1]. The sons of heap[n] are heap[2*n] and heap[2*n+1].
|
||||
// heap[0] is not used.
|
||||
s.heap_len = 0; |
||||
s.heap_max = HEAP_SIZE; |
||||
|
||||
for(n=0; n<elems; n++) { |
||||
if(tree[n*2] != 0) { |
||||
s.heap[++s.heap_len] = max_code = n; |
||||
s.depth[n] = 0; |
||||
} |
||||
else{ |
||||
tree[n*2+1] = 0; |
||||
} |
||||
} |
||||
|
||||
// The pkzip format requires that at least one distance code exists,
|
||||
// and that at least one bit should be sent even if there is only one
|
||||
// possible code. So to avoid special checks later on we force at least
|
||||
// two codes of non zero frequency.
|
||||
while (s.heap_len < 2) { |
||||
node = s.heap[++s.heap_len] = (max_code < 2 ? ++max_code : 0); |
||||
tree[node*2] = 1; |
||||
s.depth[node] = 0; |
||||
s.opt_len--; if (stree!=null) s.static_len -= stree[node*2+1]; |
||||
// node is 0 or 1 so it does not have extra bits
|
||||
} |
||||
this.max_code = max_code; |
||||
|
||||
// The elements heap[heap_len/2+1 .. heap_len] are leaves of the tree,
|
||||
// establish sub-heaps of increasing lengths:
|
||||
|
||||
for(n=s.heap_len/2;n>=1; n--) |
||||
s.pqdownheap(tree, n); |
||||
|
||||
// Construct the Huffman tree by repeatedly combining the least two
|
||||
// frequent nodes.
|
||||
|
||||
node=elems; // next internal node of the tree
|
||||
do{ |
||||
// n = node of least frequency
|
||||
n=s.heap[1]; |
||||
s.heap[1]=s.heap[s.heap_len--]; |
||||
s.pqdownheap(tree, 1); |
||||
m=s.heap[1]; // m = node of next least frequency
|
||||
|
||||
s.heap[--s.heap_max] = n; // keep the nodes sorted by frequency
|
||||
s.heap[--s.heap_max] = m; |
||||
|
||||
// Create a new node father of n and m
|
||||
tree[node*2] = (short)(tree[n*2] + tree[m*2]); |
||||
s.depth[node] = (byte)(Math.max(s.depth[n],s.depth[m])+1); |
||||
tree[n*2+1] = tree[m*2+1] = (short)node; |
||||
|
||||
// and insert the new node in the heap
|
||||
s.heap[1] = node++; |
||||
s.pqdownheap(tree, 1); |
||||
} |
||||
while(s.heap_len>=2); |
||||
|
||||
s.heap[--s.heap_max] = s.heap[1]; |
||||
|
||||
// At this point, the fields freq and dad are set. We can now
|
||||
// generate the bit lengths.
|
||||
|
||||
gen_bitlen(s); |
||||
|
||||
// The field len is now set, we can generate the bit codes
|
||||
gen_codes(tree, max_code, s.bl_count, s.next_code); |
||||
} |
||||
|
||||
// Generate the codes for a given tree and bit counts (which need not be
|
||||
// optimal).
|
||||
// IN assertion: the array bl_count contains the bit length statistics for
|
||||
// the given tree and the field len is set for all tree elements.
|
||||
// OUT assertion: the field code is set for all tree elements of non
|
||||
// zero code length.
|
||||
private final static void gen_codes( |
||||
short[] tree, // the tree to decorate
|
||||
int max_code, // largest code with non zero frequency
|
||||
short[] bl_count, // number of codes at each bit length
|
||||
short[] next_code){ |
||||
short code = 0; // running code value
|
||||
int bits; // bit index
|
||||
int n; // code index
|
||||
|
||||
// The distribution counts are first used to generate the code values
|
||||
// without bit reversal.
|
||||
next_code[0]=0; |
||||
for (bits = 1; bits <= MAX_BITS; bits++) { |
||||
next_code[bits] = code = (short)((code + bl_count[bits-1]) << 1); |
||||
} |
||||
|
||||
// Check that the bit counts in bl_count are consistent. The last code
|
||||
// must be all ones.
|
||||
//Assert (code + bl_count[MAX_BITS]-1 == (1<<MAX_BITS)-1,
|
||||
// "inconsistent bit counts");
|
||||
//Tracev((stderr,"\ngen_codes: max_code %d ", max_code));
|
||||
|
||||
for (n = 0; n <= max_code; n++) { |
||||
int len = tree[n*2+1]; |
||||
if (len == 0) continue; |
||||
// Now reverse the bits
|
||||
tree[n*2] = (short)(bi_reverse(next_code[len]++, len)); |
||||
} |
||||
} |
||||
|
||||
// Reverse the first len bits of a code, using straightforward code (a faster
|
||||
// method would use a table)
|
||||
// IN assertion: 1 <= len <= 15
|
||||
private final static int bi_reverse( |
||||
int code, // the value to invert
|
||||
int len // its bit length
|
||||
){ |
||||
int res = 0; |
||||
do{ |
||||
res|=code&1; |
||||
code>>>=1; |
||||
res<<=1; |
||||
} |
||||
while(--len>0); |
||||
return res>>>1; |
||||
} |
||||
} |
||||
|
@ -0,0 +1,126 @@ |
||||
/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ |
||||
/* |
||||
Copyright (c) 2011 ymnk, JCraft,Inc. All rights reserved. |
||||
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions are met: |
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, |
||||
this list of conditions and the following disclaimer. |
||||
|
||||
2. 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. |
||||
|
||||
3. The names of the authors may not be used to endorse or promote products |
||||
derived from this software without specific prior written permission. |
||||
|
||||
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 JCRAFT, |
||||
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, |
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, |
||||
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, |
||||
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
*/ |
||||
|
||||
package com.jcraft.jzlib; |
||||
import java.io.*; |
||||
|
||||
/** |
||||
* ZInputStream |
||||
* |
||||
* @deprecated use DeflaterOutputStream or InflaterInputStream |
||||
*/ |
||||
@Deprecated |
||||
public class ZInputStream extends FilterInputStream { |
||||
|
||||
protected int flush=JZlib.Z_NO_FLUSH; |
||||
protected boolean compress; |
||||
protected InputStream in=null; |
||||
|
||||
protected Deflater deflater; |
||||
protected InflaterInputStream iis; |
||||
|
||||
public ZInputStream(InputStream in) throws IOException { |
||||
this(in, false); |
||||
} |
||||
public ZInputStream(InputStream in, boolean nowrap) throws IOException { |
||||
super(in); |
||||
iis = new InflaterInputStream(in, nowrap); |
||||
compress=false; |
||||
} |
||||
|
||||
public ZInputStream(InputStream in, int level) throws IOException { |
||||
super(in); |
||||
this.in=in; |
||||
deflater = new Deflater(); |
||||
deflater.init(level); |
||||
compress=true; |
||||
} |
||||
|
||||
private byte[] buf1 = new byte[1]; |
||||
public int read() throws IOException { |
||||
if(read(buf1, 0, 1)==-1) return -1; |
||||
return(buf1[0]&0xFF); |
||||
} |
||||
|
||||
private byte[] buf = new byte[512]; |
||||
|
||||
public int read(byte[] b, int off, int len) throws IOException { |
||||
if(compress){ |
||||
deflater.setOutput(b, off, len); |
||||
while(true){ |
||||
int datalen = in.read(buf, 0, buf.length); |
||||
if(datalen == -1) return -1; |
||||
deflater.setInput(buf, 0, datalen, true); |
||||
int err = deflater.deflate(flush); |
||||
if(deflater.next_out_index>0) |
||||
return deflater.next_out_index; |
||||
if(err == JZlib.Z_STREAM_END) |
||||
return 0; |
||||
if(err == JZlib.Z_STREAM_ERROR || |
||||
err == JZlib.Z_DATA_ERROR){ |
||||
throw new ZStreamException("deflating: "+deflater.msg); |
||||
} |
||||
} |
||||
} |
||||
else{ |
||||
return iis.read(b, off, len); |
||||
} |
||||
} |
||||
|
||||
public long skip(long n) throws IOException { |
||||
int len=512; |
||||
if(n<len) |
||||
len=(int)n; |
||||
byte[] tmp=new byte[len]; |
||||
return((long)read(tmp)); |
||||
} |
||||
|
||||
public int getFlushMode() { |
||||
return flush; |
||||
} |
||||
|
||||
public void setFlushMode(int flush) { |
||||
this.flush=flush; |
||||
} |
||||
|
||||
public long getTotalIn() { |
||||
if(compress) return deflater.total_in; |
||||
else return iis.getTotalIn(); |
||||
} |
||||
|
||||
public long getTotalOut() { |
||||
if(compress) return deflater.total_out; |
||||
else return iis.getTotalOut(); |
||||
} |
||||
|
||||
public void close() throws IOException{ |
||||
if(compress) deflater.end(); |
||||
else iis.close(); |
||||
} |
||||
} |
@ -0,0 +1,159 @@ |
||||
/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ |
||||
/* |
||||
Copyright (c) 2011 ymnk, JCraft,Inc. All rights reserved. |
||||
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions are met: |
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, |
||||
this list of conditions and the following disclaimer. |
||||
|
||||
2. 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. |
||||
|
||||
3. The names of the authors may not be used to endorse or promote products |
||||
derived from this software without specific prior written permission. |
||||
|
||||
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 JCRAFT, |
||||
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, |
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, |
||||
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, |
||||
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
*/ |
||||
|
||||
package com.jcraft.jzlib; |
||||
import java.io.*; |
||||
|
||||
/** |
||||
* ZOutputStream |
||||
* |
||||
* @deprecated use DeflaterOutputStream or InflaterInputStream |
||||
*/ |
||||
@Deprecated |
||||
public class ZOutputStream extends FilterOutputStream { |
||||
|
||||
protected int bufsize=512; |
||||
protected int flush=JZlib.Z_NO_FLUSH; |
||||
protected byte[] buf=new byte[bufsize]; |
||||
protected boolean compress; |
||||
|
||||
protected OutputStream out; |
||||
private boolean end=false; |
||||
|
||||
private DeflaterOutputStream dos; |
||||
private Inflater inflater; |
||||
|
||||
public ZOutputStream(OutputStream out) throws IOException { |
||||
super(out); |
||||
this.out=out; |
||||
inflater = new Inflater(); |
||||
inflater.init(); |
||||
compress=false; |
||||
} |
||||
|
||||
public ZOutputStream(OutputStream out, int level) throws IOException { |
||||
this(out, level, false); |
||||
} |
||||
|
||||
public ZOutputStream(OutputStream out, int level, boolean nowrap) throws IOException { |
||||
super(out); |
||||
this.out=out; |
||||
Deflater deflater = new Deflater(level, nowrap); |
||||
dos = new DeflaterOutputStream(out, deflater); |
||||
compress=true; |
||||
} |
||||
|
||||
private byte[] buf1 = new byte[1]; |
||||
public void write(int b) throws IOException { |
||||
buf1[0]=(byte)b; |
||||
write(buf1, 0, 1); |
||||
} |
||||
|
||||
public void write(byte b[], int off, int len) throws IOException { |
||||
if(len==0) return; |
||||
if(compress){ |
||||
dos.write(b, off, len); |
||||
} |
||||
else { |
||||
inflater.setInput(b, off, len, true); |
||||
int err = JZlib.Z_OK; |
||||
while(inflater.avail_in>0){ |
||||
inflater.setOutput(buf, 0, buf.length); |
||||
err = inflater.inflate(flush); |
||||
if(inflater.next_out_index>0) |
||||
out.write(buf, 0, inflater.next_out_index); |
||||
if(err != JZlib.Z_OK) |
||||
break; |
||||
} |
||||
if(err != JZlib.Z_OK) |
||||
throw new ZStreamException("inflating: "+inflater.msg); |
||||
return; |
||||
} |
||||
} |
||||
|
||||
public int getFlushMode() { |
||||
return flush; |
||||
} |
||||
|
||||
public void setFlushMode(int flush) { |
||||
this.flush=flush; |
||||
} |
||||
|
||||
public void finish() throws IOException { |
||||
int err; |
||||
if(compress){ |
||||
int tmp = flush; |
||||
int flush = JZlib.Z_FINISH; |
||||
try{ |
||||
write("".getBytes(), 0, 0); |
||||
} |
||||
finally { flush = tmp; } |
||||
} |
||||
else{ |
||||
dos.finish(); |
||||
} |
||||
flush(); |
||||
} |
||||
public synchronized void end() { |
||||
if(end) return; |
||||
if(compress){ |
||||
try { dos.finish(); } catch(Exception e){} |
||||
} |
||||
else{ |
||||
inflater.end(); |
||||
} |
||||
end=true; |
||||
} |
||||
public void close() throws IOException { |
||||
try{ |
||||
try{finish();} |
||||
catch (IOException ignored) {} |
||||
} |
||||
finally{ |
||||
end(); |
||||
out.close(); |
||||
out=null; |
||||
} |
||||
} |
||||
|
||||
public long getTotalIn() { |
||||
if(compress) return dos.getTotalIn(); |
||||
else return inflater.total_in; |
||||
} |
||||
|
||||
public long getTotalOut() { |
||||
if(compress) return dos.getTotalOut(); |
||||
else return inflater.total_out; |
||||
} |
||||
|
||||
public void flush() throws IOException { |
||||
out.flush(); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,377 @@ |
||||
/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ |
||||
/* |
||||
Copyright (c) 2000-2011 ymnk, JCraft,Inc. All rights reserved. |
||||
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions are met: |
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, |
||||
this list of conditions and the following disclaimer. |
||||
|
||||
2. 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. |
||||
|
||||
3. The names of the authors may not be used to endorse or promote products |
||||
derived from this software without specific prior written permission. |
||||
|
||||
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 JCRAFT, |
||||
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE 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. |
||||
*/ |
||||
/* |
||||
* This program is based on zlib-1.1.3, so all credit should go authors |
||||
* Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) |
||||
* and contributors of zlib. |
||||
*/ |
||||
|
||||
package com.jcraft.jzlib; |
||||
|
||||
/** |
||||
* ZStream |
||||
* |
||||
* @deprecated Not for public use in the future. |
||||
*/ |
||||
@Deprecated |
||||
public class ZStream{ |
||||
|
||||
static final private int MAX_WBITS=15; // 32K LZ77 window
|
||||
static final private int DEF_WBITS=MAX_WBITS; |
||||
|
||||
static final private int Z_NO_FLUSH=0; |
||||
static final private int Z_PARTIAL_FLUSH=1; |
||||
static final private int Z_SYNC_FLUSH=2; |
||||
static final private int Z_FULL_FLUSH=3; |
||||
static final private int Z_FINISH=4; |
||||
|
||||
static final private int MAX_MEM_LEVEL=9; |
||||
|
||||
static final private int Z_OK=0; |
||||
static final private int Z_STREAM_END=1; |
||||
static final private int Z_NEED_DICT=2; |
||||
static final private int Z_ERRNO=-1; |
||||
static final private int Z_STREAM_ERROR=-2; |
||||
static final private int Z_DATA_ERROR=-3; |
||||
static final private int Z_MEM_ERROR=-4; |
||||
static final private int Z_BUF_ERROR=-5; |
||||
static final private int Z_VERSION_ERROR=-6; |
||||
|
||||
public byte[] next_in; // next input byte
|
||||
public int next_in_index; |
||||
public int avail_in; // number of bytes available at next_in
|
||||
public long total_in; // total nb of input bytes read so far
|
||||
|
||||
public byte[] next_out; // next output byte should be put there
|
||||
public int next_out_index; |
||||
public int avail_out; // remaining free space at next_out
|
||||
public long total_out; // total nb of bytes output so far
|
||||
|
||||
public String msg; |
||||
|
||||
Deflate dstate; |
||||
Inflate istate; |
||||
|
||||
int data_type; // best guess about the data type: ascii or binary
|
||||
|
||||
Checksum adler; |
||||
|
||||
public ZStream(){ |
||||
this(new Adler32()); |
||||
} |
||||
|
||||
public ZStream(Checksum adler){ |
||||
this.adler=adler; |
||||
} |
||||
|
||||
public int inflateInit(){ |
||||
return inflateInit(DEF_WBITS); |
||||
} |
||||
public int inflateInit(boolean nowrap){ |
||||
return inflateInit(DEF_WBITS, nowrap); |
||||
} |
||||
public int inflateInit(int w){ |
||||
return inflateInit(w, false); |
||||
} |
||||
public int inflateInit(JZlib.WrapperType wrapperType) { |
||||
return inflateInit(DEF_WBITS, wrapperType); |
||||
} |
||||
public int inflateInit(int w, JZlib.WrapperType wrapperType) { |
||||
boolean nowrap = false; |
||||
if(wrapperType == JZlib.W_NONE){ |
||||
nowrap = true; |
||||
} |
||||
else if(wrapperType == JZlib.W_GZIP) { |
||||
w += 16; |
||||
} |
||||
else if(wrapperType == JZlib.W_ANY) { |
||||
w |= Inflate.INFLATE_ANY; |
||||
} |
||||
else if(wrapperType == JZlib.W_ZLIB) { |
||||
} |
||||
return inflateInit(w, nowrap); |
||||
} |
||||
public int inflateInit(int w, boolean nowrap){ |
||||
istate=new Inflate(this); |
||||
return istate.inflateInit(nowrap?-w:w); |
||||
} |
||||
|
||||
public int inflate(int f){ |
||||
if(istate==null) return Z_STREAM_ERROR; |
||||
return istate.inflate(f); |
||||
} |
||||
public int inflateEnd(){ |
||||
if(istate==null) return Z_STREAM_ERROR; |
||||
int ret=istate.inflateEnd(); |
||||
// istate = null;
|
||||
return ret; |
||||
} |
||||
public int inflateSync(){ |
||||
if(istate == null) |
||||
return Z_STREAM_ERROR; |
||||
return istate.inflateSync(); |
||||
} |
||||
public int inflateSyncPoint(){ |
||||
if(istate == null) |
||||
return Z_STREAM_ERROR; |
||||
return istate.inflateSyncPoint(); |
||||
} |
||||
public int inflateSetDictionary(byte[] dictionary, int dictLength){ |
||||
if(istate == null) |
||||
return Z_STREAM_ERROR; |
||||
return istate.inflateSetDictionary(dictionary, dictLength); |
||||
} |
||||
public boolean inflateFinished(){ |
||||
return istate.mode==12 /*DONE*/; |
||||
} |
||||
|
||||
public int deflateInit(int level){ |
||||
return deflateInit(level, MAX_WBITS); |
||||
} |
||||
public int deflateInit(int level, boolean nowrap){ |
||||
return deflateInit(level, MAX_WBITS, nowrap); |
||||
} |
||||
public int deflateInit(int level, int bits){ |
||||
return deflateInit(level, bits, false); |
||||
} |
||||
public int deflateInit(int level, int bits, int memlevel, JZlib.WrapperType wrapperType){ |
||||
if(bits < 9 || bits > 15){ |
||||
return Z_STREAM_ERROR; |
||||
} |
||||
if(wrapperType == JZlib.W_NONE) { |
||||
bits *= -1; |
||||
} |
||||
else if(wrapperType == JZlib.W_GZIP) { |
||||
bits += 16; |
||||
} |
||||
else if(wrapperType == JZlib.W_ANY) { |
||||
return Z_STREAM_ERROR; |
||||
} |
||||
else if(wrapperType == JZlib.W_ZLIB) { |
||||
} |
||||
return this.deflateInit(level, bits, memlevel); |
||||
} |
||||
public int deflateInit(int level, int bits, int memlevel){ |
||||
dstate=new Deflate(this); |
||||
return dstate.deflateInit(level, bits, memlevel); |
||||
} |
||||
public int deflateInit(int level, int bits, boolean nowrap){ |
||||
dstate=new Deflate(this); |
||||
return dstate.deflateInit(level, nowrap?-bits:bits); |
||||
} |
||||
public int deflate(int flush){ |
||||
if(dstate==null){ |
||||
return Z_STREAM_ERROR; |
||||
} |
||||
return dstate.deflate(flush); |
||||
} |
||||
public int deflateEnd(){ |
||||
if(dstate==null) return Z_STREAM_ERROR; |
||||
int ret=dstate.deflateEnd(); |
||||
dstate=null; |
||||
return ret; |
||||
} |
||||
public int deflateParams(int level, int strategy){ |
||||
if(dstate==null) return Z_STREAM_ERROR; |
||||
return dstate.deflateParams(level, strategy); |
||||
} |
||||
public int deflateSetDictionary (byte[] dictionary, int dictLength){ |
||||
if(dstate == null) |
||||
return Z_STREAM_ERROR; |
||||
return dstate.deflateSetDictionary(dictionary, dictLength); |
||||
} |
||||
|
||||
// Flush as much pending output as possible. All deflate() output goes
|
||||
// through this function so some applications may wish to modify it
|
||||
// to avoid allocating a large strm->next_out buffer and copying into it.
|
||||
// (See also read_buf()).
|
||||
void flush_pending(){ |
||||
int len=dstate.pending; |
||||
|
||||
if(len>avail_out) len=avail_out; |
||||
if(len==0) return; |
||||
|
||||
if(dstate.pending_buf.length<=dstate.pending_out || |
||||
next_out.length<=next_out_index || |
||||
dstate.pending_buf.length<(dstate.pending_out+len) || |
||||
next_out.length<(next_out_index+len)){ |
||||
//System.out.println(dstate.pending_buf.length+", "+dstate.pending_out+
|
||||
// ", "+next_out.length+", "+next_out_index+", "+len);
|
||||
//System.out.println("avail_out="+avail_out);
|
||||
} |
||||
|
||||
System.arraycopy(dstate.pending_buf, dstate.pending_out, |
||||
next_out, next_out_index, len); |
||||
|
||||
next_out_index+=len; |
||||
dstate.pending_out+=len; |
||||
total_out+=len; |
||||
avail_out-=len; |
||||
dstate.pending-=len; |
||||
if(dstate.pending==0){ |
||||
dstate.pending_out=0; |
||||
} |
||||
} |
||||
|
||||
// Read a new buffer from the current input stream, update the adler32
|
||||
// and total number of bytes read. All deflate() input goes through
|
||||
// this function so some applications may wish to modify it to avoid
|
||||
// allocating a large strm->next_in buffer and copying from it.
|
||||
// (See also flush_pending()).
|
||||
int read_buf(byte[] buf, int start, int size) { |
||||
int len=avail_in; |
||||
|
||||
if(len>size) len=size; |
||||
if(len==0) return 0; |
||||
|
||||
avail_in-=len; |
||||
|
||||
if(dstate.wrap!=0) { |
||||
adler.update(next_in, next_in_index, len); |
||||
} |
||||
System.arraycopy(next_in, next_in_index, buf, start, len); |
||||
next_in_index += len; |
||||
total_in += len; |
||||
return len; |
||||
} |
||||
|
||||
public long getAdler(){ |
||||
return adler.getValue(); |
||||
} |
||||
|
||||
public void free(){ |
||||
next_in=null; |
||||
next_out=null; |
||||
msg=null; |
||||
} |
||||
|
||||
public void setOutput(byte[] buf){ |
||||
setOutput(buf, 0, buf.length); |
||||
} |
||||
|
||||
public void setOutput(byte[] buf, int off, int len){ |
||||
next_out = buf; |
||||
next_out_index = off; |
||||
avail_out = len; |
||||
} |
||||
|
||||
public void setInput(byte[] buf){ |
||||
setInput(buf, 0, buf.length, false); |
||||
} |
||||
|
||||
public void setInput(byte[] buf, boolean append){ |
||||
setInput(buf, 0, buf.length, append); |
||||
} |
||||
|
||||
public void setInput(byte[] buf, int off, int len, boolean append){ |
||||
if(len<=0 && append && next_in!=null) return; |
||||
|
||||
if(avail_in>0 && append){ |
||||
byte[] tmp = new byte[avail_in+len]; |
||||
System.arraycopy(next_in, next_in_index, tmp, 0, avail_in); |
||||
System.arraycopy(buf, off, tmp, avail_in, len); |
||||
next_in=tmp; |
||||
next_in_index=0; |
||||
avail_in+=len; |
||||
} |
||||
else{ |
||||
next_in=buf; |
||||
next_in_index=off; |
||||
avail_in=len; |
||||
} |
||||
} |
||||
|
||||
public byte[] getNextIn(){ |
||||
return next_in; |
||||
} |
||||
|
||||
public void setNextIn(byte[] next_in){ |
||||
this.next_in = next_in; |
||||
} |
||||
|
||||
public int getNextInIndex(){ |
||||
return next_in_index; |
||||
} |
||||
|
||||
public void setNextInIndex(int next_in_index){ |
||||
this.next_in_index = next_in_index; |
||||
} |
||||
|
||||
public int getAvailIn(){ |
||||
return avail_in; |
||||
} |
||||
|
||||
public void setAvailIn(int avail_in){ |
||||
this.avail_in = avail_in; |
||||
} |
||||
|
||||
public byte[] getNextOut(){ |
||||
return next_out; |
||||
} |
||||
|
||||
public void setNextOut(byte[] next_out){ |
||||
this.next_out = next_out; |
||||
} |
||||
|
||||
public int getNextOutIndex(){ |
||||
return next_out_index; |
||||
} |
||||
|
||||
public void setNextOutIndex(int next_out_index){ |
||||
this.next_out_index = next_out_index; |
||||
} |
||||
|
||||
public int getAvailOut(){ |
||||
return avail_out; |
||||
|
||||
} |
||||
|
||||
public void setAvailOut(int avail_out){ |
||||
this.avail_out = avail_out; |
||||
} |
||||
|
||||
public long getTotalOut(){ |
||||
return total_out; |
||||
} |
||||
|
||||
public long getTotalIn(){ |
||||
return total_in; |
||||
} |
||||
|
||||
public String getMessage(){ |
||||
return msg; |
||||
} |
||||
|
||||
/** |
||||
* Those methods are expected to be override by Inflater and Deflater. |
||||
* In the future, they will become abstract methods. |
||||
*/ |
||||
public int end(){ return Z_OK; } |
||||
public boolean finished(){ return false; } |
||||
} |
@ -0,0 +1,44 @@ |
||||
/* -*-mode:java; c-basic-offset:2; -*- */ |
||||
/* |
||||
Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved. |
||||
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions are met: |
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, |
||||
this list of conditions and the following disclaimer. |
||||
|
||||
2. 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. |
||||
|
||||
3. The names of the authors may not be used to endorse or promote products |
||||
derived from this software without specific prior written permission. |
||||
|
||||
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 JCRAFT, |
||||
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE 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. |
||||
*/ |
||||
/* |
||||
* This program is based on zlib-1.1.3, so all credit should go authors |
||||
* Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) |
||||
* and contributors of zlib. |
||||
*/ |
||||
|
||||
package com.jcraft.jzlib; |
||||
|
||||
public class ZStreamException extends java.io.IOException { |
||||
public ZStreamException() { |
||||
super(); |
||||
} |
||||
public ZStreamException(String s) { |
||||
super(s); |
||||
} |
||||
} |
@ -0,0 +1,12 @@ |
||||
package me.ag2s.epublib; |
||||
|
||||
|
||||
public interface Constants { |
||||
|
||||
String CHARACTER_ENCODING = "UTF-8"; |
||||
String DOCTYPE_XHTML = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">"; |
||||
String NAMESPACE_XHTML = "http://www.w3.org/1999/xhtml"; |
||||
String EPUB4J_GENERATOR_NAME = "EpubLib"; |
||||
char FRAGMENT_SEPARATOR_CHAR = '#'; |
||||
String DEFAULT_TOC_ID = "toc"; |
||||
} |
@ -0,0 +1,155 @@ |
||||
package me.ag2s.epublib.browsersupport; |
||||
|
||||
import me.ag2s.epublib.domain.Book; |
||||
import me.ag2s.epublib.domain.Resource; |
||||
import me.ag2s.epublib.util.StringUtil; |
||||
import java.util.EventObject; |
||||
|
||||
/** |
||||
* Used to tell NavigationEventListener just what kind of navigation action |
||||
* the user just did. |
||||
* |
||||
* @author paul |
||||
* |
||||
*/ |
||||
public class NavigationEvent extends EventObject { |
||||
|
||||
private static final long serialVersionUID = -6346750144308952762L; |
||||
|
||||
private Resource oldResource; |
||||
private int oldSpinePos; |
||||
private Navigator navigator; |
||||
private Book oldBook; |
||||
private int oldSectionPos; |
||||
private String oldFragmentId; |
||||
|
||||
public NavigationEvent(Object source) { |
||||
super(source); |
||||
} |
||||
|
||||
public NavigationEvent(Object source, Navigator navigator) { |
||||
super(source); |
||||
this.navigator = navigator; |
||||
this.oldBook = navigator.getBook(); |
||||
this.oldFragmentId = navigator.getCurrentFragmentId(); |
||||
this.oldSectionPos = navigator.getCurrentSectionPos(); |
||||
this.oldResource = navigator.getCurrentResource(); |
||||
this.oldSpinePos = navigator.getCurrentSpinePos(); |
||||
} |
||||
|
||||
/** |
||||
* The previous position within the section. |
||||
* |
||||
* @return The previous position within the section. |
||||
*/ |
||||
public int getOldSectionPos() { |
||||
return oldSectionPos; |
||||
} |
||||
|
||||
public Navigator getNavigator() { |
||||
return navigator; |
||||
} |
||||
|
||||
public String getOldFragmentId() { |
||||
return oldFragmentId; |
||||
} |
||||
|
||||
// package
|
||||
void setOldFragmentId(String oldFragmentId) { |
||||
this.oldFragmentId = oldFragmentId; |
||||
} |
||||
|
||||
public Book getOldBook() { |
||||
return oldBook; |
||||
} |
||||
|
||||
// package
|
||||
void setOldPagePos(int oldPagePos) { |
||||
this.oldSectionPos = oldPagePos; |
||||
} |
||||
|
||||
public int getCurrentSectionPos() { |
||||
return navigator.getCurrentSectionPos(); |
||||
} |
||||
|
||||
public int getOldSpinePos() { |
||||
return oldSpinePos; |
||||
} |
||||
|
||||
public int getCurrentSpinePos() { |
||||
return navigator.getCurrentSpinePos(); |
||||
} |
||||
|
||||
public String getCurrentFragmentId() { |
||||
return navigator.getCurrentFragmentId(); |
||||
} |
||||
|
||||
public boolean isBookChanged() { |
||||
if (oldBook == null) { |
||||
return true; |
||||
} |
||||
return oldBook != navigator.getBook(); |
||||
} |
||||
|
||||
public boolean isSpinePosChanged() { |
||||
return getOldSpinePos() != getCurrentSpinePos(); |
||||
} |
||||
|
||||
public boolean isFragmentChanged() { |
||||
return StringUtil.equals(getOldFragmentId(), getCurrentFragmentId()); |
||||
} |
||||
|
||||
public Resource getOldResource() { |
||||
return oldResource; |
||||
} |
||||
|
||||
public Resource getCurrentResource() { |
||||
return navigator.getCurrentResource(); |
||||
} |
||||
|
||||
public void setOldResource(Resource oldResource) { |
||||
this.oldResource = oldResource; |
||||
} |
||||
|
||||
|
||||
public void setOldSpinePos(int oldSpinePos) { |
||||
this.oldSpinePos = oldSpinePos; |
||||
} |
||||
|
||||
|
||||
public void setNavigator(Navigator navigator) { |
||||
this.navigator = navigator; |
||||
} |
||||
|
||||
|
||||
public void setOldBook(Book oldBook) { |
||||
this.oldBook = oldBook; |
||||
} |
||||
|
||||
public Book getCurrentBook() { |
||||
return getNavigator().getBook(); |
||||
} |
||||
|
||||
public boolean isResourceChanged() { |
||||
return oldResource != getCurrentResource(); |
||||
} |
||||
|
||||
public String toString() { |
||||
return StringUtil.toString( |
||||
"oldSectionPos", oldSectionPos, |
||||
"oldResource", oldResource, |
||||
"oldBook", oldBook, |
||||
"oldFragmentId", oldFragmentId, |
||||
"oldSpinePos", oldSpinePos, |
||||
"currentPagePos", getCurrentSectionPos(), |
||||
"currentResource", getCurrentResource(), |
||||
"currentBook", getCurrentBook(), |
||||
"currentFragmentId", getCurrentFragmentId(), |
||||
"currentSpinePos", getCurrentSpinePos() |
||||
); |
||||
} |
||||
|
||||
public boolean isSectionPosChanged() { |
||||
return oldSectionPos != getCurrentSectionPos(); |
||||
} |
||||
} |
@ -0,0 +1,18 @@ |
||||
package me.ag2s.epublib.browsersupport; |
||||
|
||||
/** |
||||
* Implemented by classes that want to be notified if the user moves to |
||||
* another location in the book. |
||||
* |
||||
* @author paul |
||||
* |
||||
*/ |
||||
public interface NavigationEventListener { |
||||
|
||||
/** |
||||
* Called whenever the user navigates to another position in the book. |
||||
* |
||||
* @param navigationEvent |
||||
*/ |
||||
void navigationPerformed(NavigationEvent navigationEvent); |
||||
} |
@ -0,0 +1,207 @@ |
||||
package me.ag2s.epublib.browsersupport; |
||||
|
||||
import me.ag2s.epublib.domain.Book; |
||||
import me.ag2s.epublib.domain.Resource; |
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
/** |
||||
* A history of the user's locations with the epub. |
||||
* |
||||
* @author paul.siegmann |
||||
* |
||||
*/ |
||||
public class NavigationHistory implements NavigationEventListener { |
||||
|
||||
public static final int DEFAULT_MAX_HISTORY_SIZE = 1000; |
||||
private static final long DEFAULT_HISTORY_WAIT_TIME = 1000; |
||||
|
||||
private static class Location { |
||||
|
||||
private String href; |
||||
|
||||
public Location(String href) { |
||||
super(); |
||||
this.href = href; |
||||
} |
||||
|
||||
@SuppressWarnings("unused") |
||||
public void setHref(String href) { |
||||
this.href = href; |
||||
} |
||||
|
||||
public String getHref() { |
||||
return href; |
||||
} |
||||
} |
||||
|
||||
private long lastUpdateTime = 0; |
||||
private List<Location> locations = new ArrayList<Location>(); |
||||
private Navigator navigator; |
||||
private int currentPos = -1; |
||||
private int currentSize = 0; |
||||
private int maxHistorySize = DEFAULT_MAX_HISTORY_SIZE; |
||||
private long historyWaitTime = DEFAULT_HISTORY_WAIT_TIME; |
||||
|
||||
public NavigationHistory(Navigator navigator) { |
||||
this.navigator = navigator; |
||||
navigator.addNavigationEventListener(this); |
||||
initBook(navigator.getBook()); |
||||
} |
||||
|
||||
public int getCurrentPos() { |
||||
return currentPos; |
||||
} |
||||
|
||||
|
||||
public int getCurrentSize() { |
||||
return currentSize; |
||||
} |
||||
|
||||
public void initBook(Book book) { |
||||
if (book == null) { |
||||
return; |
||||
} |
||||
locations = new ArrayList<Location>(); |
||||
currentPos = -1; |
||||
currentSize = 0; |
||||
if (navigator.getCurrentResource() != null) { |
||||
addLocation(navigator.getCurrentResource().getHref()); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* If the time between a navigation event is less than the historyWaitTime |
||||
* then the new location is not added to the history. |
||||
* |
||||
* When a user is rapidly viewing many pages using the slider we do not |
||||
* want all of them to be added to the history. |
||||
* |
||||
* @return the time we wait before adding the page to the history |
||||
*/ |
||||
public long getHistoryWaitTime() { |
||||
return historyWaitTime; |
||||
} |
||||
|
||||
public void setHistoryWaitTime(long historyWaitTime) { |
||||
this.historyWaitTime = historyWaitTime; |
||||
} |
||||
|
||||
public void addLocation(Resource resource) { |
||||
if (resource == null) { |
||||
return; |
||||
} |
||||
addLocation(resource.getHref()); |
||||
} |
||||
|
||||
/** |
||||
* Adds the location after the current position. |
||||
* If the currentposition is not the end of the list then the elements |
||||
* between the current element and the end of the list will be discarded. |
||||
* |
||||
* Does nothing if the new location matches the current location. |
||||
* <br/> |
||||
* If this nr of locations becomes larger then the historySize then the |
||||
* first item(s) will be removed. |
||||
* |
||||
* @param location |
||||
*/ |
||||
public void addLocation(Location location) { |
||||
// do nothing if the new location matches the current location
|
||||
if (!(locations.isEmpty()) && |
||||
location.getHref().equals(locations.get(currentPos).getHref())) { |
||||
return; |
||||
} |
||||
currentPos++; |
||||
if (currentPos != currentSize) { |
||||
locations.set(currentPos, location); |
||||
} else { |
||||
locations.add(location); |
||||
checkHistorySize(); |
||||
} |
||||
currentSize = currentPos + 1; |
||||
} |
||||
|
||||
/** |
||||
* Removes all elements that are too much for the maxHistorySize |
||||
* out of the history. |
||||
*/ |
||||
private void checkHistorySize() { |
||||
while (locations.size() > maxHistorySize) { |
||||
locations.remove(0); |
||||
currentSize--; |
||||
currentPos--; |
||||
} |
||||
} |
||||
|
||||
public void addLocation(String href) { |
||||
addLocation(new Location(href)); |
||||
} |
||||
|
||||
private String getLocationHref(int pos) { |
||||
if (pos < 0 || pos >= locations.size()) { |
||||
return null; |
||||
} |
||||
return locations.get(currentPos).getHref(); |
||||
} |
||||
|
||||
/** |
||||
* Moves the current positions delta positions. |
||||
* |
||||
* move(-1) to go one position back in history.<br/> |
||||
* move(1) to go one position forward.<br/> |
||||
* |
||||
* @param delta |
||||
* |
||||
* @return Whether we actually moved. If the requested value is illegal |
||||
* it will return false, true otherwise. |
||||
*/ |
||||
public boolean move(int delta) { |
||||
if (((currentPos + delta) < 0) |
||||
|| ((currentPos + delta) >= currentSize)) { |
||||
return false; |
||||
} |
||||
currentPos += delta; |
||||
navigator.gotoResource(getLocationHref(currentPos), this); |
||||
return true; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* If this is not the source of the navigationEvent then the addLocation |
||||
* will be called with the href of the currentResource in the navigationEvent. |
||||
*/ |
||||
@Override |
||||
public void navigationPerformed(NavigationEvent navigationEvent) { |
||||
if (this == navigationEvent.getSource()) { |
||||
return; |
||||
} |
||||
if (navigationEvent.getCurrentResource() == null) { |
||||
return; |
||||
} |
||||
|
||||
if ((System.currentTimeMillis() - this.lastUpdateTime) > historyWaitTime) { |
||||
// if the user scrolled rapidly through the pages then the last page
|
||||
// will not be added to the history. We fix that here:
|
||||
addLocation(navigationEvent.getOldResource()); |
||||
|
||||
addLocation(navigationEvent.getCurrentResource().getHref()); |
||||
} |
||||
lastUpdateTime = System.currentTimeMillis(); |
||||
} |
||||
|
||||
public String getCurrentHref() { |
||||
if (currentPos < 0 || currentPos >= locations.size()) { |
||||
return null; |
||||
} |
||||
return locations.get(currentPos).getHref(); |
||||
} |
||||
|
||||
public void setMaxHistorySize(int maxHistorySize) { |
||||
this.maxHistorySize = maxHistorySize; |
||||
} |
||||
|
||||
public int getMaxHistorySize() { |
||||
return maxHistorySize; |
||||
} |
||||
} |
@ -0,0 +1,219 @@ |
||||
package me.ag2s.epublib.browsersupport; |
||||
|
||||
import me.ag2s.epublib.domain.Book; |
||||
import me.ag2s.epublib.domain.Resource; |
||||
import java.io.Serializable; |
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
/** |
||||
* A helper class for epub browser applications. |
||||
* |
||||
* It helps moving from one resource to the other, from one resource |
||||
* to the other and keeping other elements of the application up-to-date |
||||
* by calling the NavigationEventListeners. |
||||
* |
||||
* @author paul |
||||
*/ |
||||
public class Navigator implements Serializable { |
||||
|
||||
private static final long serialVersionUID = 1076126986424925474L; |
||||
private Book book; |
||||
private int currentSpinePos; |
||||
private Resource currentResource; |
||||
private int currentPagePos; |
||||
private String currentFragmentId; |
||||
|
||||
private List<NavigationEventListener> eventListeners = new ArrayList<>(); |
||||
|
||||
public Navigator() { |
||||
this(null); |
||||
} |
||||
|
||||
public Navigator(Book book) { |
||||
this.book = book; |
||||
this.currentSpinePos = 0; |
||||
if (book != null) { |
||||
this.currentResource = book.getCoverPage(); |
||||
} |
||||
this.currentPagePos = 0; |
||||
} |
||||
|
||||
private synchronized void handleEventListeners( |
||||
NavigationEvent navigationEvent) { |
||||
for (int i = 0; i < eventListeners.size(); i++) { |
||||
NavigationEventListener navigationEventListener = eventListeners.get(i); |
||||
navigationEventListener.navigationPerformed(navigationEvent); |
||||
} |
||||
} |
||||
|
||||
public boolean addNavigationEventListener( |
||||
NavigationEventListener navigationEventListener) { |
||||
return this.eventListeners.add(navigationEventListener); |
||||
} |
||||
|
||||
public boolean removeNavigationEventListener( |
||||
NavigationEventListener navigationEventListener) { |
||||
return this.eventListeners.remove(navigationEventListener); |
||||
} |
||||
|
||||
public int gotoFirstSpineSection(Object source) { |
||||
return gotoSpineSection(0, source); |
||||
} |
||||
|
||||
public int gotoPreviousSpineSection(Object source) { |
||||
return gotoPreviousSpineSection(0, source); |
||||
} |
||||
|
||||
public int gotoPreviousSpineSection(int pagePos, Object source) { |
||||
if (currentSpinePos < 0) { |
||||
return gotoSpineSection(0, pagePos, source); |
||||
} else { |
||||
return gotoSpineSection(currentSpinePos - 1, pagePos, source); |
||||
} |
||||
} |
||||
|
||||
public boolean hasNextSpineSection() { |
||||
return (currentSpinePos < (book.getSpine().size() - 1)); |
||||
} |
||||
|
||||
public boolean hasPreviousSpineSection() { |
||||
return (currentSpinePos > 0); |
||||
} |
||||
|
||||
public int gotoNextSpineSection(Object source) { |
||||
if (currentSpinePos < 0) { |
||||
return gotoSpineSection(0, source); |
||||
} else { |
||||
return gotoSpineSection(currentSpinePos + 1, source); |
||||
} |
||||
} |
||||
|
||||
public int gotoResource(String resourceHref, Object source) { |
||||
Resource resource = book.getResources().getByHref(resourceHref); |
||||
return gotoResource(resource, source); |
||||
} |
||||
|
||||
|
||||
public int gotoResource(Resource resource, Object source) { |
||||
return gotoResource(resource, 0, null, source); |
||||
} |
||||
|
||||
public int gotoResource(Resource resource, String fragmentId, Object source) { |
||||
return gotoResource(resource, 0, fragmentId, source); |
||||
} |
||||
|
||||
public int gotoResource(Resource resource, int pagePos, Object source) { |
||||
return gotoResource(resource, pagePos, null, source); |
||||
} |
||||
|
||||
public int gotoResource(Resource resource, int pagePos, String fragmentId, |
||||
Object source) { |
||||
if (resource == null) { |
||||
return -1; |
||||
} |
||||
NavigationEvent navigationEvent = new NavigationEvent(source, this); |
||||
this.currentResource = resource; |
||||
this.currentSpinePos = book.getSpine().getResourceIndex(currentResource); |
||||
this.currentPagePos = pagePos; |
||||
this.currentFragmentId = fragmentId; |
||||
handleEventListeners(navigationEvent); |
||||
|
||||
return currentSpinePos; |
||||
} |
||||
|
||||
public int gotoResourceId(String resourceId, Object source) { |
||||
return gotoSpineSection(book.getSpine().findFirstResourceById(resourceId), |
||||
source); |
||||
} |
||||
|
||||
public int gotoSpineSection(int newSpinePos, Object source) { |
||||
return gotoSpineSection(newSpinePos, 0, source); |
||||
} |
||||
|
||||
/** |
||||
* Go to a specific section. |
||||
* Illegal spine positions are silently ignored. |
||||
* |
||||
* @param newSpinePos |
||||
* @param source |
||||
* @return The current position within the spine |
||||
*/ |
||||
public int gotoSpineSection(int newSpinePos, int newPagePos, Object source) { |
||||
if (newSpinePos == currentSpinePos) { |
||||
return currentSpinePos; |
||||
} |
||||
if (newSpinePos < 0 || newSpinePos >= book.getSpine().size()) { |
||||
return currentSpinePos; |
||||
} |
||||
NavigationEvent navigationEvent = new NavigationEvent(source, this); |
||||
currentSpinePos = newSpinePos; |
||||
currentPagePos = newPagePos; |
||||
currentResource = book.getSpine().getResource(currentSpinePos); |
||||
handleEventListeners(navigationEvent); |
||||
return currentSpinePos; |
||||
} |
||||
|
||||
public int gotoLastSpineSection(Object source) { |
||||
return gotoSpineSection(book.getSpine().size() - 1, source); |
||||
} |
||||
|
||||
public void gotoBook(Book book, Object source) { |
||||
NavigationEvent navigationEvent = new NavigationEvent(source, this); |
||||
this.book = book; |
||||
this.currentFragmentId = null; |
||||
this.currentPagePos = 0; |
||||
this.currentResource = null; |
||||
this.currentSpinePos = book.getSpine().getResourceIndex(currentResource); |
||||
handleEventListeners(navigationEvent); |
||||
} |
||||
|
||||
/** |
||||
* The current position within the spine. |
||||
* |
||||
* @return something < 0 if the current position is not within the spine. |
||||
*/ |
||||
public int getCurrentSpinePos() { |
||||
return currentSpinePos; |
||||
} |
||||
|
||||
public Resource getCurrentResource() { |
||||
return currentResource; |
||||
} |
||||
|
||||
/** |
||||
* Sets the current index and resource without calling the eventlisteners. |
||||
* |
||||
* If you want the eventListeners called use gotoSection(index); |
||||
* |
||||
* @param currentIndex |
||||
*/ |
||||
public void setCurrentSpinePos(int currentIndex) { |
||||
this.currentSpinePos = currentIndex; |
||||
this.currentResource = book.getSpine().getResource(currentIndex); |
||||
} |
||||
|
||||
public Book getBook() { |
||||
return book; |
||||
} |
||||
|
||||
/** |
||||
* Sets the current index and resource without calling the eventlisteners. |
||||
* |
||||
* If you want the eventListeners called use gotoSection(index); |
||||
* |
||||
*/ |
||||
public int setCurrentResource(Resource currentResource) { |
||||
this.currentSpinePos = book.getSpine().getResourceIndex(currentResource); |
||||
this.currentResource = currentResource; |
||||
return currentSpinePos; |
||||
} |
||||
|
||||
public String getCurrentFragmentId() { |
||||
return currentFragmentId; |
||||
} |
||||
|
||||
public int getCurrentSectionPos() { |
||||
return currentPagePos; |
||||
} |
||||
} |
@ -0,0 +1,7 @@ |
||||
/** |
||||
* Provides classes that help make an epub reader application. |
||||
* |
||||
* These classes have no dependencies on graphic toolkits, they're purely |
||||
* to help with the browsing/navigation logic. |
||||
*/ |
||||
package me.ag2s.epublib.browsersupport; |
@ -0,0 +1,79 @@ |
||||
package me.ag2s.epublib.domain; |
||||
|
||||
import me.ag2s.epublib.util.StringUtil; |
||||
import java.io.Serializable; |
||||
|
||||
/** |
||||
* Represents one of the authors of the book |
||||
* |
||||
* @author paul |
||||
* |
||||
*/ |
||||
public class Author implements Serializable { |
||||
|
||||
private static final long serialVersionUID = 6663408501416574200L; |
||||
|
||||
private String firstname; |
||||
private String lastname; |
||||
private Relator relator = Relator.AUTHOR; |
||||
|
||||
public Author(String singleName) { |
||||
this("", singleName); |
||||
} |
||||
|
||||
public Author(String firstname, String lastname) { |
||||
this.firstname = firstname; |
||||
this.lastname = lastname; |
||||
} |
||||
|
||||
public String getFirstname() { |
||||
return firstname; |
||||
} |
||||
|
||||
public void setFirstname(String firstname) { |
||||
this.firstname = firstname; |
||||
} |
||||
|
||||
public String getLastname() { |
||||
return lastname; |
||||
} |
||||
|
||||
public void setLastname(String lastname) { |
||||
this.lastname = lastname; |
||||
} |
||||
|
||||
public String toString() { |
||||
return lastname + ", " + firstname; |
||||
} |
||||
|
||||
public int hashCode() { |
||||
return StringUtil.hashCode(firstname, lastname); |
||||
} |
||||
|
||||
public boolean equals(Object authorObject) { |
||||
if (!(authorObject instanceof Author)) { |
||||
return false; |
||||
} |
||||
Author other = (Author) authorObject; |
||||
return StringUtil.equals(firstname, other.firstname) |
||||
&& StringUtil.equals(lastname, other.lastname); |
||||
} |
||||
|
||||
public Relator setRole(String code) { |
||||
Relator result = Relator.byCode(code); |
||||
if (result == null) { |
||||
result = Relator.AUTHOR; |
||||
} |
||||
this.relator = result; |
||||
return result; |
||||
} |
||||
|
||||
public Relator getRelator() { |
||||
return relator; |
||||
} |
||||
|
||||
|
||||
public void setRelator(Relator relator) { |
||||
this.relator = relator; |
||||
} |
||||
} |
@ -0,0 +1,316 @@ |
||||
package me.ag2s.epublib.domain; |
||||
|
||||
import java.io.Serializable; |
||||
import java.util.ArrayList; |
||||
import java.util.LinkedHashMap; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
|
||||
/** |
||||
* Representation of a Book. |
||||
* |
||||
* All resources of a Book (html, css, xml, fonts, images) are represented |
||||
* as Resources. See getResources() for access to these.<br/> |
||||
* A Book as 3 indexes into these Resources, as per the epub specification.<br/> |
||||
* <dl> |
||||
* <dt>Spine</dt> |
||||
* <dd>these are the Resources to be shown when a user reads the book from |
||||
* start to finish.</dd> |
||||
* <dt>Table of Contents<dt> |
||||
* <dd>The table of contents. Table of Contents references may be in a |
||||
* different order and contain different Resources than the spine, and often do. |
||||
* <dt>Guide</dt> |
||||
* <dd>The Guide has references to a set of special Resources like the |
||||
* cover page, the Glossary, the copyright page, etc. |
||||
* </dl> |
||||
* <p/> |
||||
* The complication is that these 3 indexes may and usually do point to |
||||
* different pages. |
||||
* A chapter may be split up in 2 pieces to fit it in to memory. Then the |
||||
* spine will contain both pieces, but the Table of Contents only the first. |
||||
* |
||||
* The Content page may be in the Table of Contents, the Guide, but not |
||||
* in the Spine. |
||||
* Etc. |
||||
* <p/> |
||||
* |
||||
* Please see the illustration at: doc/schema.svg |
||||
* |
||||
* @author paul |
||||
* @author jake |
||||
*/ |
||||
public class Book implements Serializable { |
||||
|
||||
private static final long serialVersionUID = 2068355170895770100L; |
||||
|
||||
private Resources resources = new Resources(); |
||||
private Metadata metadata = new Metadata(); |
||||
private Spine spine = new Spine(); |
||||
private TableOfContents tableOfContents = new TableOfContents(); |
||||
private Guide guide = new Guide(); |
||||
private Resource opfResource; |
||||
private Resource ncxResource; |
||||
private Resource coverImage; |
||||
|
||||
|
||||
|
||||
private String version; |
||||
|
||||
public String getVersion() { |
||||
return version; |
||||
} |
||||
|
||||
public void setVersion(String version) { |
||||
this.version = version; |
||||
} |
||||
|
||||
public TOCReference addSection( |
||||
TOCReference parentSection, String sectionTitle, Resource resource) { |
||||
return addSection(parentSection, sectionTitle, resource, null); |
||||
} |
||||
|
||||
/** |
||||
* Adds the resource to the table of contents of the book as a child |
||||
* section of the given parentSection |
||||
* |
||||
* @param parentSection |
||||
* @param sectionTitle |
||||
* @param resource |
||||
* @param fragmentId |
||||
* @return The table of contents |
||||
*/ |
||||
public TOCReference addSection( |
||||
TOCReference parentSection, String sectionTitle, Resource resource, |
||||
String fragmentId) { |
||||
getResources().add(resource); |
||||
if (spine.findFirstResourceById(resource.getId()) < 0) { |
||||
spine.addSpineReference(new SpineReference(resource)); |
||||
} |
||||
return parentSection.addChildSection( |
||||
new TOCReference(sectionTitle, resource, fragmentId)); |
||||
} |
||||
|
||||
public TOCReference addSection(String title, Resource resource) { |
||||
return addSection(title, resource, null); |
||||
} |
||||
|
||||
/** |
||||
* Adds a resource to the book's set of resources, table of contents and |
||||
* if there is no resource with the id in the spine also adds it to the spine. |
||||
* |
||||
* @param title |
||||
* @param resource |
||||
* @param fragmentId |
||||
* @return The table of contents |
||||
*/ |
||||
public TOCReference addSection( |
||||
String title, Resource resource, String fragmentId) { |
||||
getResources().add(resource); |
||||
TOCReference tocReference = tableOfContents |
||||
.addTOCReference(new TOCReference(title, resource, fragmentId)); |
||||
if (spine.findFirstResourceById(resource.getId()) < 0) { |
||||
spine.addSpineReference(new SpineReference(resource)); |
||||
} |
||||
return tocReference; |
||||
} |
||||
|
||||
public void generateSpineFromTableOfContents() { |
||||
Spine spine = new Spine(tableOfContents); |
||||
|
||||
// in case the tocResource was already found and assigned
|
||||
spine.setTocResource(this.spine.getTocResource()); |
||||
|
||||
this.spine = spine; |
||||
} |
||||
|
||||
/** |
||||
* The Book's metadata (titles, authors, etc) |
||||
* |
||||
* @return The Book's metadata (titles, authors, etc) |
||||
*/ |
||||
public Metadata getMetadata() { |
||||
return metadata; |
||||
} |
||||
|
||||
public void setMetadata(Metadata metadata) { |
||||
this.metadata = metadata; |
||||
} |
||||
|
||||
|
||||
public void setResources(Resources resources) { |
||||
this.resources = resources; |
||||
} |
||||
|
||||
|
||||
public Resource addResource(Resource resource) { |
||||
return resources.add(resource); |
||||
} |
||||
|
||||
/** |
||||
* The collection of all images, chapters, sections, xhtml files, |
||||
* stylesheets, etc that make up the book. |
||||
* |
||||
* @return The collection of all images, chapters, sections, xhtml files, |
||||
* stylesheets, etc that make up the book. |
||||
*/ |
||||
public Resources getResources() { |
||||
return resources; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* The sections of the book that should be shown if a user reads the book |
||||
* from start to finish. |
||||
* |
||||
* @return The Spine |
||||
*/ |
||||
public Spine getSpine() { |
||||
return spine; |
||||
} |
||||
|
||||
|
||||
public void setSpine(Spine spine) { |
||||
this.spine = spine; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* The Table of Contents of the book. |
||||
* |
||||
* @return The Table of Contents of the book. |
||||
*/ |
||||
public TableOfContents getTableOfContents() { |
||||
return tableOfContents; |
||||
} |
||||
|
||||
|
||||
public void setTableOfContents(TableOfContents tableOfContents) { |
||||
this.tableOfContents = tableOfContents; |
||||
} |
||||
|
||||
/** |
||||
* The book's cover page as a Resource. |
||||
* An XHTML document containing a link to the cover image. |
||||
* |
||||
* @return The book's cover page as a Resource |
||||
*/ |
||||
public Resource getCoverPage() { |
||||
Resource coverPage = guide.getCoverPage(); |
||||
if (coverPage == null) { |
||||
coverPage = spine.getResource(0); |
||||
} |
||||
return coverPage; |
||||
} |
||||
|
||||
|
||||
public void setCoverPage(Resource coverPage) { |
||||
if (coverPage == null) { |
||||
return; |
||||
} |
||||
if (!resources.containsByHref(coverPage.getHref())) { |
||||
resources.add(coverPage); |
||||
} |
||||
guide.setCoverPage(coverPage); |
||||
} |
||||
|
||||
/** |
||||
* Gets the first non-blank title from the book's metadata. |
||||
* |
||||
* @return the first non-blank title from the book's metadata. |
||||
*/ |
||||
public String getTitle() { |
||||
return getMetadata().getFirstTitle(); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* The book's cover image. |
||||
* |
||||
* @return The book's cover image. |
||||
*/ |
||||
public Resource getCoverImage() { |
||||
return coverImage; |
||||
} |
||||
|
||||
public void setCoverImage(Resource coverImage) { |
||||
if (coverImage == null) { |
||||
return; |
||||
} |
||||
if (!resources.containsByHref(coverImage.getHref())) { |
||||
resources.add(coverImage); |
||||
} |
||||
this.coverImage = coverImage; |
||||
} |
||||
|
||||
/** |
||||
* The guide; contains references to special sections of the book like |
||||
* colophon, glossary, etc. |
||||
* |
||||
* @return The guide; contains references to special sections of the book |
||||
* like colophon, glossary, etc. |
||||
*/ |
||||
public Guide getGuide() { |
||||
return guide; |
||||
} |
||||
|
||||
/** |
||||
* All Resources of the Book that can be reached via the Spine, the |
||||
* TableOfContents or the Guide. |
||||
* <p/> |
||||
* Consists of a list of "reachable" resources: |
||||
* <ul> |
||||
* <li>The coverpage</li> |
||||
* <li>The resources of the Spine that are not already in the result</li> |
||||
* <li>The resources of the Table of Contents that are not already in the |
||||
* result</li> |
||||
* <li>The resources of the Guide that are not already in the result</li> |
||||
* </ul> |
||||
* To get all html files that make up the epub file use |
||||
* {@link #getResources()} |
||||
* @return All Resources of the Book that can be reached via the Spine, |
||||
* the TableOfContents or the Guide. |
||||
*/ |
||||
public List<Resource> getContents() { |
||||
Map<String, Resource> result = new LinkedHashMap<String, Resource>(); |
||||
addToContentsResult(getCoverPage(), result); |
||||
|
||||
for (SpineReference spineReference : getSpine().getSpineReferences()) { |
||||
addToContentsResult(spineReference.getResource(), result); |
||||
} |
||||
|
||||
for (Resource resource : getTableOfContents().getAllUniqueResources()) { |
||||
addToContentsResult(resource, result); |
||||
} |
||||
|
||||
for (GuideReference guideReference : getGuide().getReferences()) { |
||||
addToContentsResult(guideReference.getResource(), result); |
||||
} |
||||
|
||||
return new ArrayList<Resource>(result.values()); |
||||
} |
||||
|
||||
private static void addToContentsResult(Resource resource, |
||||
Map<String, Resource> allReachableResources) { |
||||
if (resource != null && (!allReachableResources |
||||
.containsKey(resource.getHref()))) { |
||||
allReachableResources.put(resource.getHref(), resource); |
||||
} |
||||
} |
||||
|
||||
public Resource getOpfResource() { |
||||
return opfResource; |
||||
} |
||||
|
||||
public void setOpfResource(Resource opfResource) { |
||||
this.opfResource = opfResource; |
||||
} |
||||
|
||||
public void setNcxResource(Resource ncxResource) { |
||||
this.ncxResource = ncxResource; |
||||
} |
||||
|
||||
public Resource getNcxResource() { |
||||
return ncxResource; |
||||
} |
||||
} |
||||
|
@ -0,0 +1,104 @@ |
||||
package me.ag2s.epublib.domain; |
||||
|
||||
import me.ag2s.epublib.epub.PackageDocumentBase; |
||||
import java.io.Serializable; |
||||
import java.text.SimpleDateFormat; |
||||
import java.util.Locale; |
||||
|
||||
/** |
||||
* A Date used by the book's metadata. |
||||
* |
||||
* Examples: creation-date, modification-date, etc |
||||
* |
||||
* @author paul |
||||
* |
||||
*/ |
||||
public class Date implements Serializable { |
||||
|
||||
private static final long serialVersionUID = 7533866830395120136L; |
||||
|
||||
public enum Event { |
||||
PUBLICATION("publication"), |
||||
MODIFICATION("modification"), |
||||
CREATION("creation"); |
||||
|
||||
private final String value; |
||||
|
||||
Event(String v) { |
||||
value = v; |
||||
} |
||||
|
||||
public static Event fromValue(String v) { |
||||
for (Event c : Event.values()) { |
||||
if (c.value.equals(v)) { |
||||
return c; |
||||
} |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
public String toString() { |
||||
return value; |
||||
} |
||||
} |
||||
|
||||
|
||||
private Event event; |
||||
private String dateString; |
||||
|
||||
public Date(java.util.Date date) { |
||||
this(date, (Event) null); |
||||
} |
||||
|
||||
public Date(String dateString) { |
||||
this(dateString, (Event) null); |
||||
} |
||||
|
||||
public Date(java.util.Date date, Event event) { |
||||
this((new SimpleDateFormat(PackageDocumentBase.dateFormat, Locale.US)).format(date), |
||||
event); |
||||
} |
||||
|
||||
public Date(String dateString, Event event) { |
||||
this.dateString = dateString; |
||||
this.event = event; |
||||
} |
||||
|
||||
public Date(java.util.Date date, String event) { |
||||
this((new SimpleDateFormat(PackageDocumentBase.dateFormat,Locale.US)).format(date), |
||||
event); |
||||
} |
||||
|
||||
public Date(String dateString, String event) { |
||||
this(checkDate(dateString), Event.fromValue(event)); |
||||
this.dateString = dateString; |
||||
} |
||||
|
||||
private static String checkDate(String dateString) { |
||||
if (dateString == null) { |
||||
throw new IllegalArgumentException( |
||||
"Cannot create a date from a blank string"); |
||||
} |
||||
return dateString; |
||||
} |
||||
|
||||
public String getValue() { |
||||
return dateString; |
||||
} |
||||
|
||||
public Event getEvent() { |
||||
return event; |
||||
} |
||||
|
||||
public void setEvent(Event event) { |
||||
this.event = event; |
||||
} |
||||
|
||||
public String toString() { |
||||
if (event == null) { |
||||
return dateString; |
||||
} |
||||
return "" + event + ":" + dateString; |
||||
} |
||||
} |
||||
|
@ -0,0 +1,33 @@ |
||||
package me.ag2s.epublib.domain; |
||||
|
||||
import java.io.IOException; |
||||
import java.io.InputStream; |
||||
import java.util.zip.ZipEntry; |
||||
import java.util.zip.ZipFile; |
||||
|
||||
/** |
||||
* @author jake |
||||
*/ |
||||
public class EpubResourceProvider implements LazyResourceProvider { |
||||
|
||||
private final String epubFilename; |
||||
|
||||
/** |
||||
* @param epubFilename the file name for the epub we're created from. |
||||
*/ |
||||
public EpubResourceProvider(String epubFilename) { |
||||
this.epubFilename = epubFilename; |
||||
} |
||||
|
||||
@Override |
||||
public InputStream getResourceStream(String href) throws IOException { |
||||
ZipFile zipFile = new ZipFile(epubFilename); |
||||
ZipEntry zipEntry = zipFile.getEntry(href); |
||||
if (zipEntry == null) { |
||||
zipFile.close(); |
||||
throw new IllegalStateException( |
||||
"Cannot find entry " + href + " in epub file " + epubFilename); |
||||
} |
||||
return new ResourceInputStream(zipFile.getInputStream(zipEntry), zipFile); |
||||
} |
||||
} |
@ -0,0 +1,128 @@ |
||||
package me.ag2s.epublib.domain; |
||||
|
||||
import java.io.Serializable; |
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
/** |
||||
* The guide is a selection of special pages of the book. |
||||
* Examples of these are the cover, list of illustrations, etc. |
||||
* |
||||
* It is an optional part of an epub, and support for the various types |
||||
* of references varies by reader. |
||||
* |
||||
* The only part of this that is heavily used is the cover page. |
||||
* |
||||
* @author paul |
||||
* |
||||
*/ |
||||
public class Guide implements Serializable { |
||||
|
||||
/** |
||||
* |
||||
*/ |
||||
private static final long serialVersionUID = -6256645339915751189L; |
||||
|
||||
public static final String DEFAULT_COVER_TITLE = GuideReference.COVER; |
||||
|
||||
private List<GuideReference> references = new ArrayList<GuideReference>(); |
||||
private static final int COVERPAGE_NOT_FOUND = -1; |
||||
private static final int COVERPAGE_UNITIALIZED = -2; |
||||
|
||||
private int coverPageIndex = -1; |
||||
|
||||
public List<GuideReference> getReferences() { |
||||
return references; |
||||
} |
||||
|
||||
public void setReferences(List<GuideReference> references) { |
||||
this.references = references; |
||||
uncheckCoverPage(); |
||||
} |
||||
|
||||
private void uncheckCoverPage() { |
||||
coverPageIndex = COVERPAGE_UNITIALIZED; |
||||
} |
||||
|
||||
public GuideReference getCoverReference() { |
||||
checkCoverPage(); |
||||
if (coverPageIndex >= 0) { |
||||
return references.get(coverPageIndex); |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
public int setCoverReference(GuideReference guideReference) { |
||||
if (coverPageIndex >= 0) { |
||||
references.set(coverPageIndex, guideReference); |
||||
} else { |
||||
references.add(0, guideReference); |
||||
coverPageIndex = 0; |
||||
} |
||||
return coverPageIndex; |
||||
} |
||||
|
||||
private void checkCoverPage() { |
||||
if (coverPageIndex == COVERPAGE_UNITIALIZED) { |
||||
initCoverPage(); |
||||
} |
||||
} |
||||
|
||||
|
||||
private void initCoverPage() { |
||||
int result = COVERPAGE_NOT_FOUND; |
||||
for (int i = 0; i < references.size(); i++) { |
||||
GuideReference guideReference = references.get(i); |
||||
if (guideReference.getType().equals(GuideReference.COVER)) { |
||||
result = i; |
||||
break; |
||||
} |
||||
} |
||||
coverPageIndex = result; |
||||
} |
||||
|
||||
/** |
||||
* The coverpage of the book. |
||||
* |
||||
* @return The coverpage of the book. |
||||
*/ |
||||
public Resource getCoverPage() { |
||||
GuideReference guideReference = getCoverReference(); |
||||
if (guideReference == null) { |
||||
return null; |
||||
} |
||||
return guideReference.getResource(); |
||||
} |
||||
|
||||
public void setCoverPage(Resource coverPage) { |
||||
GuideReference coverpageGuideReference = new GuideReference(coverPage, |
||||
GuideReference.COVER, DEFAULT_COVER_TITLE); |
||||
setCoverReference(coverpageGuideReference); |
||||
} |
||||
|
||||
|
||||
public ResourceReference addReference(GuideReference reference) { |
||||
this.references.add(reference); |
||||
uncheckCoverPage(); |
||||
return reference; |
||||
} |
||||
|
||||
/** |
||||
* A list of all GuideReferences that have the given |
||||
* referenceTypeName (ignoring case). |
||||
* |
||||
* @param referenceTypeName |
||||
* @return A list of all GuideReferences that have the given |
||||
* referenceTypeName (ignoring case). |
||||
*/ |
||||
public List<GuideReference> getGuideReferencesByType( |
||||
String referenceTypeName) { |
||||
List<GuideReference> result = new ArrayList<GuideReference>(); |
||||
for (GuideReference guideReference : references) { |
||||
if (referenceTypeName.equalsIgnoreCase(guideReference.getType())) { |
||||
result.add(guideReference); |
||||
} |
||||
} |
||||
return result; |
||||
} |
||||
} |
@ -0,0 +1,102 @@ |
||||
package me.ag2s.epublib.domain; |
||||
|
||||
import me.ag2s.epublib.util.StringUtil; |
||||
import java.io.Serializable; |
||||
|
||||
|
||||
/** |
||||
* These are references to elements of the book's guide. |
||||
* |
||||
* @see Guide |
||||
* |
||||
* @author paul |
||||
* |
||||
*/ |
||||
public class GuideReference extends TitledResourceReference |
||||
implements Serializable { |
||||
|
||||
private static final long serialVersionUID = -316179702440631834L; |
||||
|
||||
/** |
||||
* the book cover(s), jacket information, etc. |
||||
*/ |
||||
public static String COVER = "cover"; |
||||
|
||||
/** |
||||
* human-readable page with title, author, publisher, and other metadata |
||||
*/ |
||||
public static String TITLE_PAGE = "title-page"; |
||||
|
||||
/** |
||||
* Human-readable table of contents. |
||||
* Not to be confused the epub file table of contents |
||||
* |
||||
*/ |
||||
public static String TOC = "toc"; |
||||
|
||||
/** |
||||
* back-of-book style index |
||||
*/ |
||||
public static String INDEX = "index"; |
||||
public static String GLOSSARY = "glossary"; |
||||
public static String ACKNOWLEDGEMENTS = "acknowledgements"; |
||||
public static String BIBLIOGRAPHY = "bibliography"; |
||||
public static String COLOPHON = "colophon"; |
||||
public static String COPYRIGHT_PAGE = "copyright-page"; |
||||
public static String DEDICATION = "dedication"; |
||||
|
||||
/** |
||||
* an epigraph is a phrase, quotation, or poem that is set at the |
||||
* beginning of a document or component. |
||||
* |
||||
* source: http://en.wikipedia.org/wiki/Epigraph_%28literature%29
|
||||
*/ |
||||
public static String EPIGRAPH = "epigraph"; |
||||
|
||||
public static String FOREWORD = "foreword"; |
||||
|
||||
/** |
||||
* list of illustrations |
||||
*/ |
||||
public static String LOI = "loi"; |
||||
|
||||
/** |
||||
* list of tables |
||||
*/ |
||||
public static String LOT = "lot"; |
||||
public static String NOTES = "notes"; |
||||
public static String PREFACE = "preface"; |
||||
|
||||
/** |
||||
* A page of content (e.g. "Chapter 1") |
||||
*/ |
||||
public static String TEXT = "text"; |
||||
|
||||
private String type; |
||||
|
||||
public GuideReference(Resource resource) { |
||||
this(resource, null); |
||||
} |
||||
|
||||
public GuideReference(Resource resource, String title) { |
||||
super(resource, title); |
||||
} |
||||
|
||||
public GuideReference(Resource resource, String type, String title) { |
||||
this(resource, type, title, null); |
||||
} |
||||
|
||||
public GuideReference(Resource resource, String type, String title, |
||||
String fragmentId) { |
||||
super(resource, title, fragmentId); |
||||
this.type = StringUtil.isNotBlank(type) ? type.toLowerCase() : null; |
||||
} |
||||
|
||||
public String getType() { |
||||
return type; |
||||
} |
||||
|
||||
public void setType(String type) { |
||||
this.type = type; |
||||
} |
||||
} |
@ -0,0 +1,129 @@ |
||||
package me.ag2s.epublib.domain; |
||||
|
||||
import me.ag2s.epublib.util.StringUtil; |
||||
import java.io.Serializable; |
||||
import java.util.List; |
||||
import java.util.UUID; |
||||
|
||||
/** |
||||
* A Book's identifier. |
||||
* |
||||
* Defaults to a random UUID and scheme "UUID" |
||||
* |
||||
* @author paul |
||||
*/ |
||||
public class Identifier implements Serializable { |
||||
|
||||
private static final long serialVersionUID = 955949951416391810L; |
||||
|
||||
public interface Scheme { |
||||
|
||||
String UUID = "UUID"; |
||||
String ISBN = "ISBN"; |
||||
String URL = "URL"; |
||||
String URI = "URI"; |
||||
} |
||||
|
||||
private boolean bookId = false; |
||||
private String scheme; |
||||
private String value; |
||||
|
||||
/** |
||||
* Creates an Identifier with as value a random UUID and scheme "UUID" |
||||
*/ |
||||
public Identifier() { |
||||
this(Scheme.UUID, UUID.randomUUID().toString()); |
||||
} |
||||
|
||||
|
||||
public Identifier(String scheme, String value) { |
||||
this.scheme = scheme; |
||||
this.value = value; |
||||
} |
||||
|
||||
/** |
||||
* The first identifier for which the bookId is true is made the |
||||
* bookId identifier. |
||||
* |
||||
* If no identifier has bookId == true then the first bookId identifier |
||||
* is written as the primary. |
||||
* |
||||
* @param identifiers |
||||
* @return The first identifier for which the bookId is true is made |
||||
* the bookId identifier. |
||||
*/ |
||||
public static Identifier getBookIdIdentifier(List<Identifier> identifiers) { |
||||
if (identifiers == null || identifiers.isEmpty()) { |
||||
return null; |
||||
} |
||||
|
||||
Identifier result = null; |
||||
for (Identifier identifier : identifiers) { |
||||
if (identifier.isBookId()) { |
||||
result = identifier; |
||||
break; |
||||
} |
||||
} |
||||
|
||||
if (result == null) { |
||||
result = identifiers.get(0); |
||||
} |
||||
|
||||
return result; |
||||
} |
||||
|
||||
public String getScheme() { |
||||
return scheme; |
||||
} |
||||
|
||||
public void setScheme(String scheme) { |
||||
this.scheme = scheme; |
||||
} |
||||
|
||||
public String getValue() { |
||||
return value; |
||||
} |
||||
|
||||
public void setValue(String value) { |
||||
this.value = value; |
||||
} |
||||
|
||||
|
||||
public void setBookId(boolean bookId) { |
||||
this.bookId = bookId; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* This bookId property allows the book creator to add multiple ids and |
||||
* tell the epubwriter which one to write out as the bookId. |
||||
* |
||||
* The Dublin Core metadata spec allows multiple identifiers for a Book. |
||||
* The epub spec requires exactly one identifier to be marked as the book id. |
||||
* |
||||
* @return whether this is the unique book id. |
||||
*/ |
||||
public boolean isBookId() { |
||||
return bookId; |
||||
} |
||||
|
||||
public int hashCode() { |
||||
return StringUtil.defaultIfNull(scheme).hashCode() ^ StringUtil |
||||
.defaultIfNull(value).hashCode(); |
||||
} |
||||
|
||||
public boolean equals(Object otherIdentifier) { |
||||
if (!(otherIdentifier instanceof Identifier)) { |
||||
return false; |
||||
} |
||||
return StringUtil.equals(scheme, ((Identifier) otherIdentifier).scheme) |
||||
&& StringUtil.equals(value, ((Identifier) otherIdentifier).value); |
||||
} |
||||
|
||||
public String toString() { |
||||
if (StringUtil.isBlank(scheme)) { |
||||
return "" + value; |
||||
} |
||||
return "" + scheme + ":" + value; |
||||
} |
||||
} |
@ -0,0 +1,135 @@ |
||||
package me.ag2s.epublib.domain; |
||||
|
||||
import android.util.Log; |
||||
|
||||
import me.ag2s.epublib.util.IOUtil; |
||||
//import io.documentnode.minilog.Logger;
|
||||
import java.io.ByteArrayInputStream; |
||||
import java.io.IOException; |
||||
import java.io.InputStream; |
||||
|
||||
/** |
||||
* A Resource that loads its data only on-demand from a EPUB book file. |
||||
* This way larger books can fit into memory and can be opened faster. |
||||
*/ |
||||
public class LazyResource extends Resource { |
||||
|
||||
private static final long serialVersionUID = 5089400472352002866L; |
||||
private static String TAG= LazyResource.class.getName(); |
||||
|
||||
private final LazyResourceProvider resourceProvider; |
||||
private final long cachedSize; |
||||
|
||||
/** |
||||
* Creates a lazy resource, when the size is unknown. |
||||
* |
||||
* @param resourceProvider The resource provider loads data on demand. |
||||
* @param href The resource's href within the epub. |
||||
*/ |
||||
public LazyResource(LazyResourceProvider resourceProvider, String href) { |
||||
this(resourceProvider, -1, href); |
||||
} |
||||
|
||||
/** |
||||
* Creates a Lazy resource, by not actually loading the data for this entry. |
||||
* |
||||
* The data will be loaded on the first call to getData() |
||||
* |
||||
* @param resourceProvider The resource provider loads data on demand. |
||||
* @param size The size of this resource. |
||||
* @param href The resource's href within the epub. |
||||
*/ |
||||
public LazyResource( |
||||
LazyResourceProvider resourceProvider, long size, String href) { |
||||
super(null, null, href, MediaTypes.determineMediaType(href)); |
||||
this.resourceProvider = resourceProvider; |
||||
this.cachedSize = size; |
||||
} |
||||
|
||||
/** |
||||
* Gets the contents of the Resource as an InputStream. |
||||
* |
||||
* @return The contents of the Resource. |
||||
* |
||||
* @throws IOException |
||||
*/ |
||||
public InputStream getInputStream() throws IOException { |
||||
if (isInitialized()) { |
||||
return new ByteArrayInputStream(getData()); |
||||
} else { |
||||
return resourceProvider.getResourceStream(this.originalHref); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Initializes the resource by loading its data into memory. |
||||
* |
||||
* @throws IOException |
||||
*/ |
||||
public void initialize() throws IOException { |
||||
getData(); |
||||
} |
||||
|
||||
/** |
||||
* The contents of the resource as a byte[] |
||||
* |
||||
* If this resource was lazy-loaded and the data was not yet loaded, |
||||
* it will be loaded into memory at this point. |
||||
* This included opening the zip file, so expect a first load to be slow. |
||||
* |
||||
* @return The contents of the resource |
||||
*/ |
||||
public byte[] getData() throws IOException { |
||||
|
||||
if (data == null) { |
||||
|
||||
Log.d(TAG, "Initializing lazy resource: " + this.getHref()); |
||||
|
||||
InputStream in = resourceProvider.getResourceStream(this.originalHref); |
||||
byte[] readData = IOUtil.toByteArray(in, (int) this.cachedSize); |
||||
if (readData == null) { |
||||
throw new IOException( |
||||
"Could not load the contents of resource: " + this.getHref()); |
||||
} else { |
||||
this.data = readData; |
||||
} |
||||
|
||||
in.close(); |
||||
} |
||||
|
||||
return data; |
||||
} |
||||
|
||||
/** |
||||
* Tells this resource to release its cached data. |
||||
* |
||||
* If this resource was not lazy-loaded, this is a no-op. |
||||
*/ |
||||
public void close() { |
||||
if (this.resourceProvider != null) { |
||||
this.data = null; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Returns if the data for this resource has been loaded into memory. |
||||
* |
||||
* @return true if data was loaded. |
||||
*/ |
||||
public boolean isInitialized() { |
||||
return data != null; |
||||
} |
||||
|
||||
/** |
||||
* Returns the size of this resource in bytes. |
||||
* |
||||
* @return the size. |
||||
*/ |
||||
public long getSize() { |
||||
if (data != null) { |
||||
return data.length; |
||||
} |
||||
|
||||
return cachedSize; |
||||
} |
||||
} |
@ -0,0 +1,12 @@ |
||||
package me.ag2s.epublib.domain; |
||||
|
||||
import java.io.IOException; |
||||
import java.io.InputStream; |
||||
|
||||
/** |
||||
* @author jake |
||||
*/ |
||||
public interface LazyResourceProvider { |
||||
|
||||
InputStream getResourceStream(String href) throws IOException; |
||||
} |
@ -0,0 +1,21 @@ |
||||
package me.ag2s.epublib.domain; |
||||
|
||||
public enum ManifestItemProperties implements ManifestProperties { |
||||
COVER_IMAGE("cover-image"), |
||||
MATHML("mathml"), |
||||
NAV("nav"), |
||||
REMOTE_RESOURCES("remote-resources"), |
||||
SCRIPTED("scripted"), |
||||
SVG("svg"), |
||||
SWITCH("switch"); |
||||
|
||||
private String name; |
||||
|
||||
ManifestItemProperties(String name) { |
||||
this.name = name; |
||||
} |
||||
|
||||
public String getName() { |
||||
return name; |
||||
} |
||||
} |
@ -0,0 +1,16 @@ |
||||
package me.ag2s.epublib.domain; |
||||
|
||||
public enum ManifestItemRefProperties implements ManifestProperties { |
||||
PAGE_SPREAD_LEFT("page-spread-left"), |
||||
PAGE_SPREAD_RIGHT("page-spread-right"); |
||||
|
||||
private String name; |
||||
|
||||
ManifestItemRefProperties(String name) { |
||||
this.name = name; |
||||
} |
||||
|
||||
public String getName() { |
||||
return name; |
||||
} |
||||
} |
@ -0,0 +1,6 @@ |
||||
package me.ag2s.epublib.domain; |
||||
|
||||
public interface ManifestProperties { |
||||
|
||||
String getName(); |
||||
} |
@ -0,0 +1,73 @@ |
||||
package me.ag2s.epublib.domain; |
||||
|
||||
import java.io.Serializable; |
||||
import java.util.Arrays; |
||||
import java.util.Collection; |
||||
|
||||
/** |
||||
* MediaType is used to tell the type of content a resource is. |
||||
* |
||||
* Examples of mediatypes are image/gif, text/css and application/xhtml+xml |
||||
* |
||||
* All allowed mediaTypes are maintained bye the MediaTypeService. |
||||
* |
||||
* @see MediaTypes |
||||
* |
||||
* @author paul |
||||
*/ |
||||
public class MediaType implements Serializable { |
||||
|
||||
private static final long serialVersionUID = -7256091153727506788L; |
||||
private String name; |
||||
private String defaultExtension; |
||||
private Collection<String> extensions; |
||||
|
||||
public MediaType(String name, String defaultExtension) { |
||||
this(name, defaultExtension, new String[]{defaultExtension}); |
||||
} |
||||
|
||||
public MediaType(String name, String defaultExtension, |
||||
String[] extensions) { |
||||
this(name, defaultExtension, Arrays.asList(extensions)); |
||||
} |
||||
|
||||
public int hashCode() { |
||||
if (name == null) { |
||||
return 0; |
||||
} |
||||
return name.hashCode(); |
||||
} |
||||
|
||||
public MediaType(String name, String defaultExtension, |
||||
Collection<String> extensions) { |
||||
super(); |
||||
this.name = name; |
||||
this.defaultExtension = defaultExtension; |
||||
this.extensions = extensions; |
||||
} |
||||
|
||||
public String getName() { |
||||
return name; |
||||
} |
||||
|
||||
|
||||
public String getDefaultExtension() { |
||||
return defaultExtension; |
||||
} |
||||
|
||||
|
||||
public Collection<String> getExtensions() { |
||||
return extensions; |
||||
} |
||||
|
||||
public boolean equals(Object otherMediaType) { |
||||
if (!(otherMediaType instanceof MediaType)) { |
||||
return false; |
||||
} |
||||
return name.equals(((MediaType) otherMediaType).getName()); |
||||
} |
||||
|
||||
public String toString() { |
||||
return name; |
||||
} |
||||
} |
@ -0,0 +1,95 @@ |
||||
package me.ag2s.epublib.domain; |
||||
|
||||
import me.ag2s.epublib.domain.MediaType; |
||||
import me.ag2s.epublib.util.StringUtil; |
||||
import java.util.HashMap; |
||||
import java.util.Map; |
||||
|
||||
|
||||
/** |
||||
* Manages mediatypes that are used by epubs |
||||
* |
||||
* @author paul |
||||
*/ |
||||
public class MediaTypes { |
||||
|
||||
public static final MediaType XHTML = new MediaType("application/xhtml+xml", |
||||
".xhtml", new String[]{".htm", ".html", ".xhtml"}); |
||||
public static final MediaType EPUB = new MediaType("application/epub+zip", |
||||
".epub"); |
||||
public static final MediaType NCX = new MediaType("application/x-dtbncx+xml", |
||||
".ncx"); |
||||
|
||||
public static final MediaType JAVASCRIPT = new MediaType("text/javascript", |
||||
".js"); |
||||
public static final MediaType CSS = new MediaType("text/css", ".css"); |
||||
|
||||
// images
|
||||
public static final MediaType JPG = new MediaType("image/jpeg", ".jpg", |
||||
new String[]{".jpg", ".jpeg"}); |
||||
public static final MediaType PNG = new MediaType("image/png", ".png"); |
||||
public static final MediaType GIF = new MediaType("image/gif", ".gif"); |
||||
|
||||
public static final MediaType SVG = new MediaType("image/svg+xml", ".svg"); |
||||
|
||||
// fonts
|
||||
public static final MediaType TTF = new MediaType( |
||||
"application/x-truetype-font", ".ttf"); |
||||
public static final MediaType OPENTYPE = new MediaType( |
||||
"application/vnd.ms-opentype", ".otf"); |
||||
public static final MediaType WOFF = new MediaType("application/font-woff", |
||||
".woff"); |
||||
|
||||
// audio
|
||||
public static final MediaType MP3 = new MediaType("audio/mpeg", ".mp3"); |
||||
public static final MediaType OGG = new MediaType("audio/ogg", ".ogg"); |
||||
|
||||
// video
|
||||
public static final MediaType MP4 = new MediaType("video/mp4", ".mp4"); |
||||
|
||||
public static final MediaType SMIL = new MediaType("application/smil+xml", |
||||
".smil"); |
||||
public static final MediaType XPGT = new MediaType( |
||||
"application/adobe-page-template+xml", ".xpgt"); |
||||
public static final MediaType PLS = new MediaType("application/pls+xml", |
||||
".pls"); |
||||
|
||||
public static MediaType[] mediaTypes = new MediaType[]{ |
||||
XHTML, EPUB, JPG, PNG, GIF, CSS, SVG, TTF, NCX, XPGT, OPENTYPE, WOFF, |
||||
SMIL, PLS, JAVASCRIPT, MP3, MP4, OGG |
||||
}; |
||||
|
||||
public static Map<String, MediaType> mediaTypesByName = new HashMap<String, MediaType>(); |
||||
|
||||
static { |
||||
for (int i = 0; i < mediaTypes.length; i++) { |
||||
mediaTypesByName.put(mediaTypes[i].getName(), mediaTypes[i]); |
||||
} |
||||
} |
||||
|
||||
public static boolean isBitmapImage(MediaType mediaType) { |
||||
return mediaType == JPG || mediaType == PNG || mediaType == GIF; |
||||
} |
||||
|
||||
/** |
||||
* Gets the MediaType based on the file extension. |
||||
* Null of no matching extension found. |
||||
* |
||||
* @param filename |
||||
* @return the MediaType based on the file extension. |
||||
*/ |
||||
public static MediaType determineMediaType(String filename) { |
||||
for (MediaType mediaType : mediaTypesByName.values()) { |
||||
for (String extension : mediaType.getExtensions()) { |
||||
if (StringUtil.endsWithIgnoreCase(filename, extension)) { |
||||
return mediaType; |
||||
} |
||||
} |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
public static MediaType getMediaTypeByName(String mediaTypeName) { |
||||
return mediaTypesByName.get(mediaTypeName); |
||||
} |
||||
} |
@ -0,0 +1,230 @@ |
||||
package me.ag2s.epublib.domain; |
||||
|
||||
import me.ag2s.epublib.util.StringUtil; |
||||
import java.io.Serializable; |
||||
import java.util.ArrayList; |
||||
import java.util.HashMap; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import javax.xml.namespace.QName; |
||||
|
||||
/** |
||||
* A Book's collection of Metadata. |
||||
* In the future it should contain all Dublin Core attributes, for now |
||||
* it contains a set of often-used ones. |
||||
* |
||||
* @author paul |
||||
*/ |
||||
public class Metadata implements Serializable { |
||||
|
||||
private static final long serialVersionUID = -2437262888962149444L; |
||||
|
||||
public static final String DEFAULT_LANGUAGE = "en"; |
||||
|
||||
private boolean autoGeneratedId = true; |
||||
private List<Author> authors = new ArrayList<Author>(); |
||||
private List<Author> contributors = new ArrayList<Author>(); |
||||
private List<Date> dates = new ArrayList<Date>(); |
||||
private String language = DEFAULT_LANGUAGE; |
||||
private Map<QName, String> otherProperties = new HashMap<QName, String>(); |
||||
private List<String> rights = new ArrayList<String>(); |
||||
private List<String> titles = new ArrayList<String>(); |
||||
private List<Identifier> identifiers = new ArrayList<Identifier>(); |
||||
private List<String> subjects = new ArrayList<String>(); |
||||
private String format = MediaTypes.EPUB.getName(); |
||||
private List<String> types = new ArrayList<String>(); |
||||
private List<String> descriptions = new ArrayList<String>(); |
||||
private List<String> publishers = new ArrayList<String>(); |
||||
private Map<String, String> metaAttributes = new HashMap<String, String>(); |
||||
|
||||
public Metadata() { |
||||
identifiers.add(new Identifier()); |
||||
autoGeneratedId = true; |
||||
} |
||||
|
||||
public boolean isAutoGeneratedId() { |
||||
return autoGeneratedId; |
||||
} |
||||
|
||||
/** |
||||
* Metadata properties not hard-coded like the author, title, etc. |
||||
* |
||||
* @return Metadata properties not hard-coded like the author, title, etc. |
||||
*/ |
||||
public Map<QName, String> getOtherProperties() { |
||||
return otherProperties; |
||||
} |
||||
|
||||
public void setOtherProperties(Map<QName, String> otherProperties) { |
||||
this.otherProperties = otherProperties; |
||||
} |
||||
|
||||
public Date addDate(Date date) { |
||||
this.dates.add(date); |
||||
return date; |
||||
} |
||||
|
||||
public List<Date> getDates() { |
||||
return dates; |
||||
} |
||||
|
||||
public void setDates(List<Date> dates) { |
||||
this.dates = dates; |
||||
} |
||||
|
||||
public Author addAuthor(Author author) { |
||||
authors.add(author); |
||||
return author; |
||||
} |
||||
|
||||
public List<Author> getAuthors() { |
||||
return authors; |
||||
} |
||||
|
||||
public void setAuthors(List<Author> authors) { |
||||
this.authors = authors; |
||||
} |
||||
|
||||
public Author addContributor(Author contributor) { |
||||
contributors.add(contributor); |
||||
return contributor; |
||||
} |
||||
|
||||
public List<Author> getContributors() { |
||||
return contributors; |
||||
} |
||||
|
||||
public void setContributors(List<Author> contributors) { |
||||
this.contributors = contributors; |
||||
} |
||||
|
||||
public String getLanguage() { |
||||
return language; |
||||
} |
||||
|
||||
public void setLanguage(String language) { |
||||
this.language = language; |
||||
} |
||||
|
||||
public List<String> getSubjects() { |
||||
return subjects; |
||||
} |
||||
|
||||
public void setSubjects(List<String> subjects) { |
||||
this.subjects = subjects; |
||||
} |
||||
|
||||
public void setRights(List<String> rights) { |
||||
this.rights = rights; |
||||
} |
||||
|
||||
public List<String> getRights() { |
||||
return rights; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Gets the first non-blank title of the book. |
||||
* Will return "" if no title found. |
||||
* |
||||
* @return the first non-blank title of the book. |
||||
*/ |
||||
public String getFirstTitle() { |
||||
if (titles == null || titles.isEmpty()) { |
||||
return ""; |
||||
} |
||||
for (String title : titles) { |
||||
if (StringUtil.isNotBlank(title)) { |
||||
return title; |
||||
} |
||||
} |
||||
return ""; |
||||
} |
||||
|
||||
public String addTitle(String title) { |
||||
this.titles.add(title); |
||||
return title; |
||||
} |
||||
|
||||
public void setTitles(List<String> titles) { |
||||
this.titles = titles; |
||||
} |
||||
|
||||
public List<String> getTitles() { |
||||
return titles; |
||||
} |
||||
|
||||
public String addPublisher(String publisher) { |
||||
this.publishers.add(publisher); |
||||
return publisher; |
||||
} |
||||
|
||||
public void setPublishers(List<String> publishers) { |
||||
this.publishers = publishers; |
||||
} |
||||
|
||||
public List<String> getPublishers() { |
||||
return publishers; |
||||
} |
||||
|
||||
public String addDescription(String description) { |
||||
this.descriptions.add(description); |
||||
return description; |
||||
} |
||||
|
||||
public void setDescriptions(List<String> descriptions) { |
||||
this.descriptions = descriptions; |
||||
} |
||||
|
||||
public List<String> getDescriptions() { |
||||
return descriptions; |
||||
} |
||||
|
||||
public Identifier addIdentifier(Identifier identifier) { |
||||
if (autoGeneratedId && (!(identifiers.isEmpty()))) { |
||||
identifiers.set(0, identifier); |
||||
} else { |
||||
identifiers.add(identifier); |
||||
} |
||||
autoGeneratedId = false; |
||||
return identifier; |
||||
} |
||||
|
||||
public void setIdentifiers(List<Identifier> identifiers) { |
||||
this.identifiers = identifiers; |
||||
autoGeneratedId = false; |
||||
} |
||||
|
||||
public List<Identifier> getIdentifiers() { |
||||
return identifiers; |
||||
} |
||||
|
||||
public void setFormat(String format) { |
||||
this.format = format; |
||||
} |
||||
|
||||
public String getFormat() { |
||||
return format; |
||||
} |
||||
|
||||
public String addType(String type) { |
||||
this.types.add(type); |
||||
return type; |
||||
} |
||||
|
||||
public List<String> getTypes() { |
||||
return types; |
||||
} |
||||
|
||||
public void setTypes(List<String> types) { |
||||
this.types = types; |
||||
} |
||||
|
||||
public String getMetaAttribute(String name) { |
||||
return metaAttributes.get(name); |
||||
} |
||||
|
||||
public void setMetaAttributes(Map<String, String> metaAttributes) { |
||||
this.metaAttributes = metaAttributes; |
||||
} |
||||
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,327 @@ |
||||
package me.ag2s.epublib.domain; |
||||
|
||||
import me.ag2s.epublib.Constants; |
||||
import me.ag2s.epublib.util.IOUtil; |
||||
import me.ag2s.epublib.util.StringUtil; |
||||
import me.ag2s.epublib.util.commons.io.XmlStreamReader; |
||||
import java.io.ByteArrayInputStream; |
||||
import java.io.IOException; |
||||
import java.io.InputStream; |
||||
import java.io.Reader; |
||||
import java.io.Serializable; |
||||
|
||||
/** |
||||
* Represents a resource that is part of the epub. |
||||
* A resource can be a html file, image, xml, etc. |
||||
* |
||||
* @author paul |
||||
* |
||||
*/ |
||||
public class Resource implements Serializable { |
||||
|
||||
private static final long serialVersionUID = 1043946707835004037L; |
||||
private String id; |
||||
private String title; |
||||
private String href; |
||||
|
||||
|
||||
|
||||
private String properties; |
||||
protected String originalHref; |
||||
private MediaType mediaType; |
||||
private String inputEncoding = Constants.CHARACTER_ENCODING; |
||||
protected byte[] data; |
||||
|
||||
/** |
||||
* Creates an empty Resource with the given href. |
||||
* |
||||
* Assumes that if the data is of a text type (html/css/etc) then the encoding will be UTF-8 |
||||
* |
||||
* @param href The location of the resource within the epub. Example: "chapter1.html". |
||||
*/ |
||||
public Resource(String href) { |
||||
this(null, new byte[0], href, MediaTypes.determineMediaType(href)); |
||||
} |
||||
|
||||
/** |
||||
* Creates a Resource with the given data and MediaType. |
||||
* The href will be automatically generated. |
||||
* |
||||
* Assumes that if the data is of a text type (html/css/etc) then the encoding will be UTF-8 |
||||
* |
||||
* @param data The Resource's contents |
||||
* @param mediaType The MediaType of the Resource |
||||
*/ |
||||
public Resource(byte[] data, MediaType mediaType) { |
||||
this(null, data, null, mediaType); |
||||
} |
||||
|
||||
/** |
||||
* Creates a resource with the given data at the specified href. |
||||
* The MediaType will be determined based on the href extension. |
||||
* |
||||
* Assumes that if the data is of a text type (html/css/etc) then the encoding will be UTF-8 |
||||
* |
||||
* @see MediaTypes#determineMediaType(String) |
||||
* |
||||
* @param data The Resource's contents |
||||
* @param href The location of the resource within the epub. Example: "chapter1.html". |
||||
*/ |
||||
public Resource(byte[] data, String href) { |
||||
this(null, data, href, MediaTypes.determineMediaType(href), |
||||
Constants.CHARACTER_ENCODING); |
||||
} |
||||
|
||||
/** |
||||
* Creates a resource with the data from the given Reader at the specified href. |
||||
* The MediaType will be determined based on the href extension. |
||||
* |
||||
* @see MediaTypes#determineMediaType(String) |
||||
* |
||||
* @param in The Resource's contents |
||||
* @param href The location of the resource within the epub. Example: "cover.jpg". |
||||
*/ |
||||
public Resource(Reader in, String href) throws IOException { |
||||
this(null, IOUtil.toByteArray(in, Constants.CHARACTER_ENCODING), href, |
||||
MediaTypes.determineMediaType(href), |
||||
Constants.CHARACTER_ENCODING); |
||||
} |
||||
|
||||
/** |
||||
* Creates a resource with the data from the given InputStream at the specified href. |
||||
* The MediaType will be determined based on the href extension. |
||||
* |
||||
* @see MediaTypes#determineMediaType(String) |
||||
* |
||||
* Assumes that if the data is of a text type (html/css/etc) then the encoding will be UTF-8 |
||||
* |
||||
* It is recommended to us the {@link #Resource(Reader, String)} method for creating textual |
||||
* (html/css/etc) resources to prevent encoding problems. |
||||
* Use this method only for binary Resources like images, fonts, etc. |
||||
* |
||||
* |
||||
* @param in The Resource's contents |
||||
* @param href The location of the resource within the epub. Example: "cover.jpg". |
||||
*/ |
||||
public Resource(InputStream in, String href) throws IOException { |
||||
this(null, IOUtil.toByteArray(in), href, |
||||
MediaTypes.determineMediaType(href)); |
||||
} |
||||
|
||||
/** |
||||
* Creates a resource with the given id, data, mediatype at the specified href. |
||||
* Assumes that if the data is of a text type (html/css/etc) then the encoding will be UTF-8 |
||||
* |
||||
* @param id The id of the Resource. Internal use only. Will be auto-generated if it has a null-value. |
||||
* @param data The Resource's contents |
||||
* @param href The location of the resource within the epub. Example: "chapter1.html". |
||||
* @param mediaType The resources MediaType |
||||
*/ |
||||
public Resource(String id, byte[] data, String href, MediaType mediaType) { |
||||
this(id, data, href, mediaType, Constants.CHARACTER_ENCODING); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Creates a resource with the given id, data, mediatype at the specified href. |
||||
* If the data is of a text type (html/css/etc) then it will use the given inputEncoding. |
||||
* |
||||
* @param id The id of the Resource. Internal use only. Will be auto-generated if it has a null-value. |
||||
* @param data The Resource's contents |
||||
* @param href The location of the resource within the epub. Example: "chapter1.html". |
||||
* @param mediaType The resources MediaType |
||||
* @param inputEncoding If the data is of a text type (html/css/etc) then it will use the given inputEncoding. |
||||
*/ |
||||
public Resource(String id, byte[] data, String href, MediaType mediaType, |
||||
String inputEncoding) { |
||||
this.id = id; |
||||
this.href = href; |
||||
this.originalHref = href; |
||||
this.mediaType = mediaType; |
||||
this.inputEncoding = inputEncoding; |
||||
this.data = data; |
||||
} |
||||
|
||||
/** |
||||
* Gets the contents of the Resource as an InputStream. |
||||
* |
||||
* @return The contents of the Resource. |
||||
* |
||||
* @throws IOException |
||||
*/ |
||||
public InputStream getInputStream() throws IOException { |
||||
return new ByteArrayInputStream(getData()); |
||||
} |
||||
|
||||
/** |
||||
* The contents of the resource as a byte[] |
||||
* |
||||
* @return The contents of the resource |
||||
*/ |
||||
public byte[] getData() throws IOException { |
||||
return data; |
||||
} |
||||
|
||||
/** |
||||
* Tells this resource to release its cached data. |
||||
* |
||||
* If this resource was not lazy-loaded, this is a no-op. |
||||
*/ |
||||
public void close() { |
||||
} |
||||
|
||||
/** |
||||
* Sets the data of the Resource. |
||||
* If the data is a of a different type then the original data then make sure to change the MediaType. |
||||
* |
||||
* @param data |
||||
*/ |
||||
public void setData(byte[] data) { |
||||
this.data = data; |
||||
} |
||||
|
||||
/** |
||||
* Returns the size of this resource in bytes. |
||||
* |
||||
* @return the size. |
||||
*/ |
||||
public long getSize() { |
||||
return data.length; |
||||
} |
||||
|
||||
/** |
||||
* If the title is found by scanning the underlying html document then it is cached here. |
||||
* |
||||
* @return the title |
||||
*/ |
||||
public String getTitle() { |
||||
return title; |
||||
} |
||||
|
||||
/** |
||||
* Sets the Resource's id: Make sure it is unique and a valid identifier. |
||||
* |
||||
* @param id |
||||
*/ |
||||
public void setId(String id) { |
||||
this.id = id; |
||||
} |
||||
|
||||
/** |
||||
* The resources Id. |
||||
* |
||||
* Must be both unique within all the resources of this book and a valid identifier. |
||||
* @return The resources Id. |
||||
*/ |
||||
public String getId() { |
||||
return id; |
||||
} |
||||
|
||||
/** |
||||
* The location of the resource within the contents folder of the epub file. |
||||
* |
||||
* Example:<br/> |
||||
* images/cover.jpg<br/> |
||||
* content/chapter1.xhtml<br/> |
||||
* |
||||
* @return The location of the resource within the contents folder of the epub file. |
||||
*/ |
||||
public String getHref() { |
||||
return href; |
||||
} |
||||
|
||||
/** |
||||
* Sets the Resource's href. |
||||
* |
||||
* @param href |
||||
*/ |
||||
public void setHref(String href) { |
||||
this.href = href; |
||||
} |
||||
|
||||
/** |
||||
* The character encoding of the resource. |
||||
* Is allowed to be null for non-text resources like images. |
||||
* |
||||
* @return The character encoding of the resource. |
||||
*/ |
||||
public String getInputEncoding() { |
||||
return inputEncoding; |
||||
} |
||||
|
||||
/** |
||||
* Sets the Resource's input character encoding. |
||||
* |
||||
* @param encoding |
||||
*/ |
||||
public void setInputEncoding(String encoding) { |
||||
this.inputEncoding = encoding; |
||||
} |
||||
|
||||
/** |
||||
* Gets the contents of the Resource as Reader. |
||||
* |
||||
* Does all sorts of smart things (courtesy of apache commons io XMLStreamREader) to handle encodings, byte order markers, etc. |
||||
* |
||||
* @return the contents of the Resource as Reader. |
||||
* @throws IOException |
||||
*/ |
||||
public Reader getReader() throws IOException { |
||||
return new XmlStreamReader(new ByteArrayInputStream(getData()), |
||||
getInputEncoding()); |
||||
} |
||||
|
||||
/** |
||||
* Gets the hashCode of the Resource's href. |
||||
* |
||||
*/ |
||||
public int hashCode() { |
||||
return href.hashCode(); |
||||
} |
||||
|
||||
/** |
||||
* Checks to see of the given resourceObject is a resource and whether its href is equal to this one. |
||||
* |
||||
* @return whether the given resourceObject is a resource and whether its href is equal to this one. |
||||
*/ |
||||
public boolean equals(Object resourceObject) { |
||||
if (!(resourceObject instanceof Resource)) { |
||||
return false; |
||||
} |
||||
return href.equals(((Resource) resourceObject).getHref()); |
||||
} |
||||
|
||||
/** |
||||
* This resource's mediaType. |
||||
* |
||||
* @return This resource's mediaType. |
||||
*/ |
||||
public MediaType getMediaType() { |
||||
return mediaType; |
||||
} |
||||
|
||||
public void setMediaType(MediaType mediaType) { |
||||
this.mediaType = mediaType; |
||||
} |
||||
|
||||
public void setTitle(String title) { |
||||
this.title = title; |
||||
} |
||||
|
||||
public String getProperties() { |
||||
return properties; |
||||
} |
||||
|
||||
public void setProperties(String properties) { |
||||
this.properties = properties; |
||||
} |
||||
|
||||
public String toString() { |
||||
return StringUtil.toString("id", id, |
||||
"title", title, |
||||
"encoding", inputEncoding, |
||||
"mediaType", mediaType, |
||||
"href", href, |
||||
"size", (data == null ? 0 : data.length)); |
||||
} |
||||
} |
@ -0,0 +1,36 @@ |
||||
package me.ag2s.epublib.domain; |
||||
|
||||
import java.io.FilterInputStream; |
||||
import java.io.IOException; |
||||
import java.io.InputStream; |
||||
import java.util.zip.ZipFile; |
||||
|
||||
/** |
||||
* A wrapper class for closing a ZipFile object when the InputStream derived |
||||
* from it is closed. |
||||
* |
||||
* @author ttopalov |
||||
*/ |
||||
public class ResourceInputStream extends FilterInputStream { |
||||
|
||||
private final ZipFile zipFile; |
||||
|
||||
/** |
||||
* Constructor. |
||||
* |
||||
* @param in |
||||
* The InputStream object. |
||||
* @param zipFile |
||||
* The ZipFile object. |
||||
*/ |
||||
public ResourceInputStream(InputStream in, ZipFile zipFile) { |
||||
super(in); |
||||
this.zipFile = zipFile; |
||||
} |
||||
|
||||
@Override |
||||
public void close() throws IOException { |
||||
super.close(); |
||||
zipFile.close(); |
||||
} |
||||
} |
@ -0,0 +1,43 @@ |
||||
package me.ag2s.epublib.domain; |
||||
|
||||
import java.io.Serializable; |
||||
|
||||
public class ResourceReference implements Serializable { |
||||
|
||||
private static final long serialVersionUID = 2596967243557743048L; |
||||
|
||||
protected Resource resource; |
||||
|
||||
public ResourceReference(Resource resource) { |
||||
this.resource = resource; |
||||
} |
||||
|
||||
|
||||
public Resource getResource() { |
||||
return resource; |
||||
} |
||||
|
||||
/** |
||||
* Besides setting the resource it also sets the fragmentId to null. |
||||
* |
||||
* @param resource |
||||
*/ |
||||
public void setResource(Resource resource) { |
||||
this.resource = resource; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* The id of the reference referred to. |
||||
* |
||||
* null of the reference is null or has a null id itself. |
||||
* |
||||
* @return The id of the reference referred to. |
||||
*/ |
||||
public String getResourceId() { |
||||
if (resource != null) { |
||||
return resource.getId(); |
||||
} |
||||
return null; |
||||
} |
||||
} |
@ -0,0 +1,387 @@ |
||||
package me.ag2s.epublib.domain; |
||||
|
||||
import me.ag2s.epublib.Constants; |
||||
import me.ag2s.epublib.util.StringUtil; |
||||
import java.io.Serializable; |
||||
import java.util.ArrayList; |
||||
import java.util.Arrays; |
||||
import java.util.Collection; |
||||
import java.util.HashMap; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
|
||||
/** |
||||
* All the resources that make up the book. |
||||
* XHTML files, images and epub xml documents must be here. |
||||
* |
||||
* @author paul |
||||
* |
||||
*/ |
||||
public class Resources implements Serializable { |
||||
|
||||
private static final long serialVersionUID = 2450876953383871451L; |
||||
private static final String IMAGE_PREFIX = "image_"; |
||||
private static final String ITEM_PREFIX = "item_"; |
||||
private int lastId = 1; |
||||
|
||||
private Map<String, Resource> resources = new HashMap<>(); |
||||
|
||||
/** |
||||
* Adds a resource to the resources. |
||||
* |
||||
* Fixes the resources id and href if necessary. |
||||
* |
||||
* @param resource |
||||
* @return the newly added resource |
||||
*/ |
||||
public Resource add(Resource resource) { |
||||
fixResourceHref(resource); |
||||
fixResourceId(resource); |
||||
this.resources.put(resource.getHref(), resource); |
||||
return resource; |
||||
} |
||||
|
||||
/** |
||||
* Checks the id of the given resource and changes to a unique identifier if it isn't one already. |
||||
* |
||||
* @param resource |
||||
*/ |
||||
public void fixResourceId(Resource resource) { |
||||
String resourceId = resource.getId(); |
||||
|
||||
// first try and create a unique id based on the resource's href
|
||||
if (StringUtil.isBlank(resource.getId())) { |
||||
resourceId = StringUtil.substringBeforeLast(resource.getHref(), '.'); |
||||
resourceId = StringUtil.substringAfterLast(resourceId, '/'); |
||||
} |
||||
|
||||
resourceId = makeValidId(resourceId, resource); |
||||
|
||||
// check if the id is unique. if not: create one from scratch
|
||||
if (StringUtil.isBlank(resourceId) || containsId(resourceId)) { |
||||
resourceId = createUniqueResourceId(resource); |
||||
} |
||||
resource.setId(resourceId); |
||||
} |
||||
|
||||
/** |
||||
* Check if the id is a valid identifier. if not: prepend with valid identifier |
||||
* |
||||
* @param resource |
||||
* @return a valid id |
||||
*/ |
||||
private String makeValidId(String resourceId, Resource resource) { |
||||
if (StringUtil.isNotBlank(resourceId) && !Character |
||||
.isJavaIdentifierStart(resourceId.charAt(0))) { |
||||
resourceId = getResourceItemPrefix(resource) + resourceId; |
||||
} |
||||
return resourceId; |
||||
} |
||||
|
||||
private String getResourceItemPrefix(Resource resource) { |
||||
String result; |
||||
if (MediaTypes.isBitmapImage(resource.getMediaType())) { |
||||
result = IMAGE_PREFIX; |
||||
} else { |
||||
result = ITEM_PREFIX; |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
/** |
||||
* Creates a new resource id that is guaranteed to be unique for this set of Resources |
||||
* |
||||
* @param resource |
||||
* @return a new resource id that is guaranteed to be unique for this set of Resources |
||||
*/ |
||||
private String createUniqueResourceId(Resource resource) { |
||||
int counter = lastId; |
||||
if (counter == Integer.MAX_VALUE) { |
||||
if (resources.size() == Integer.MAX_VALUE) { |
||||
throw new IllegalArgumentException( |
||||
"Resources contains " + Integer.MAX_VALUE |
||||
+ " elements: no new elements can be added"); |
||||
} else { |
||||
counter = 1; |
||||
} |
||||
} |
||||
String prefix = getResourceItemPrefix(resource); |
||||
String result = prefix + counter; |
||||
while (containsId(result)) { |
||||
result = prefix + (++counter); |
||||
} |
||||
lastId = counter; |
||||
return result; |
||||
} |
||||
|
||||
/** |
||||
* Whether the map of resources already contains a resource with the given id. |
||||
* |
||||
* @param id |
||||
* @return Whether the map of resources already contains a resource with the given id. |
||||
*/ |
||||
public boolean containsId(String id) { |
||||
if (StringUtil.isBlank(id)) { |
||||
return false; |
||||
} |
||||
for (Resource resource : resources.values()) { |
||||
if (id.equals(resource.getId())) { |
||||
return true; |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
/** |
||||
* Gets the resource with the given id. |
||||
* |
||||
* @param id |
||||
* @return null if not found |
||||
*/ |
||||
public Resource getById(String id) { |
||||
if (StringUtil.isBlank(id)) { |
||||
return null; |
||||
} |
||||
for (Resource resource : resources.values()) { |
||||
if (id.equals(resource.getId())) { |
||||
return resource; |
||||
} |
||||
} |
||||
return null; |
||||
} |
||||
public Resource getByProperties(String properties) { |
||||
if (StringUtil.isBlank(properties)) { |
||||
return null; |
||||
} |
||||
for (Resource resource : resources.values()) { |
||||
if (properties.equals(resource.getProperties())) { |
||||
return resource; |
||||
} |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
/** |
||||
* Remove the resource with the given href. |
||||
* |
||||
* @param href |
||||
* @return the removed resource, null if not found |
||||
*/ |
||||
public Resource remove(String href) { |
||||
return resources.remove(href); |
||||
} |
||||
|
||||
private void fixResourceHref(Resource resource) { |
||||
if (StringUtil.isNotBlank(resource.getHref()) |
||||
&& !resources.containsKey(resource.getHref())) { |
||||
return; |
||||
} |
||||
if (StringUtil.isBlank(resource.getHref())) { |
||||
if (resource.getMediaType() == null) { |
||||
throw new IllegalArgumentException( |
||||
"Resource must have either a MediaType or a href"); |
||||
} |
||||
int i = 1; |
||||
String href = createHref(resource.getMediaType(), i); |
||||
while (resources.containsKey(href)) { |
||||
href = createHref(resource.getMediaType(), (++i)); |
||||
} |
||||
resource.setHref(href); |
||||
} |
||||
} |
||||
|
||||
private String createHref(MediaType mediaType, int counter) { |
||||
if (MediaTypes.isBitmapImage(mediaType)) { |
||||
return "image_" + counter + mediaType.getDefaultExtension(); |
||||
} else { |
||||
return "item_" + counter + mediaType.getDefaultExtension(); |
||||
} |
||||
} |
||||
|
||||
|
||||
public boolean isEmpty() { |
||||
return resources.isEmpty(); |
||||
} |
||||
|
||||
/** |
||||
* The number of resources |
||||
* @return The number of resources |
||||
*/ |
||||
public int size() { |
||||
return resources.size(); |
||||
} |
||||
|
||||
/** |
||||
* The resources that make up this book. |
||||
* Resources can be xhtml pages, images, xml documents, etc. |
||||
* |
||||
* @return The resources that make up this book. |
||||
*/ |
||||
public Map<String, Resource> getResourceMap() { |
||||
return resources; |
||||
} |
||||
|
||||
public Collection<Resource> getAll() { |
||||
return resources.values(); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Whether there exists a resource with the given href |
||||
* @param href |
||||
* @return Whether there exists a resource with the given href |
||||
*/ |
||||
public boolean containsByHref(String href) { |
||||
if (StringUtil.isBlank(href)) { |
||||
return false; |
||||
} |
||||
return resources.containsKey( |
||||
StringUtil.substringBefore(href, Constants.FRAGMENT_SEPARATOR_CHAR)); |
||||
} |
||||
|
||||
/** |
||||
* Sets the collection of Resources to the given collection of resources |
||||
* |
||||
* @param resources |
||||
*/ |
||||
public void set(Collection<Resource> resources) { |
||||
this.resources.clear(); |
||||
addAll(resources); |
||||
} |
||||
|
||||
/** |
||||
* Adds all resources from the given Collection of resources to the existing collection. |
||||
* |
||||
* @param resources |
||||
*/ |
||||
public void addAll(Collection<Resource> resources) { |
||||
for (Resource resource : resources) { |
||||
fixResourceHref(resource); |
||||
this.resources.put(resource.getHref(), resource); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Sets the collection of Resources to the given collection of resources |
||||
* |
||||
* @param resources A map with as keys the resources href and as values the Resources |
||||
*/ |
||||
public void set(Map<String, Resource> resources) { |
||||
this.resources = new HashMap<String, Resource>(resources); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* First tries to find a resource with as id the given idOrHref, if that |
||||
* fails it tries to find one with the idOrHref as href. |
||||
* |
||||
* @param idOrHref |
||||
* @return the found Resource |
||||
*/ |
||||
public Resource getByIdOrHref(String idOrHref) { |
||||
Resource resource = getById(idOrHref); |
||||
if (resource == null) { |
||||
resource = getByHref(idOrHref); |
||||
} |
||||
return resource; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Gets the resource with the given href. |
||||
* If the given href contains a fragmentId then that fragment id will be ignored. |
||||
* |
||||
* @param href |
||||
* @return null if not found. |
||||
*/ |
||||
public Resource getByHref(String href) { |
||||
if (StringUtil.isBlank(href)) { |
||||
return null; |
||||
} |
||||
href = StringUtil.substringBefore(href, Constants.FRAGMENT_SEPARATOR_CHAR); |
||||
Resource result = resources.get(href); |
||||
return result; |
||||
} |
||||
|
||||
/** |
||||
* Gets the first resource (random order) with the give mediatype. |
||||
* |
||||
* Useful for looking up the table of contents as it's supposed to be the only resource with NCX mediatype. |
||||
* |
||||
* @param mediaType |
||||
* @return the first resource (random order) with the give mediatype. |
||||
*/ |
||||
public Resource findFirstResourceByMediaType(MediaType mediaType) { |
||||
return findFirstResourceByMediaType(resources.values(), mediaType); |
||||
} |
||||
|
||||
/** |
||||
* Gets the first resource (random order) with the give mediatype. |
||||
* |
||||
* Useful for looking up the table of contents as it's supposed to be the only resource with NCX mediatype. |
||||
* |
||||
* @param mediaType |
||||
* @return the first resource (random order) with the give mediatype. |
||||
*/ |
||||
public static Resource findFirstResourceByMediaType( |
||||
Collection<Resource> resources, MediaType mediaType) { |
||||
for (Resource resource : resources) { |
||||
if (resource.getMediaType() == mediaType) { |
||||
return resource; |
||||
} |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
/** |
||||
* All resources that have the given MediaType. |
||||
* |
||||
* @param mediaType |
||||
* @return All resources that have the given MediaType. |
||||
*/ |
||||
public List<Resource> getResourcesByMediaType(MediaType mediaType) { |
||||
List<Resource> result = new ArrayList<Resource>(); |
||||
if (mediaType == null) { |
||||
return result; |
||||
} |
||||
for (Resource resource : getAll()) { |
||||
if (resource.getMediaType() == mediaType) { |
||||
result.add(resource); |
||||
} |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
/** |
||||
* All Resources that match any of the given list of MediaTypes |
||||
* |
||||
* @param mediaTypes |
||||
* @return All Resources that match any of the given list of MediaTypes |
||||
*/ |
||||
public List<Resource> getResourcesByMediaTypes(MediaType[] mediaTypes) { |
||||
List<Resource> result = new ArrayList<Resource>(); |
||||
if (mediaTypes == null) { |
||||
return result; |
||||
} |
||||
|
||||
// this is the fastest way of doing this according to
|
||||
// http://stackoverflow.com/questions/1128723/in-java-how-can-i-test-if-an-array-contains-a-certain-value
|
||||
List<MediaType> mediaTypesList = Arrays.asList(mediaTypes); |
||||
for (Resource resource : getAll()) { |
||||
if (mediaTypesList.contains(resource.getMediaType())) { |
||||
result.add(resource); |
||||
} |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* All resource hrefs |
||||
* |
||||
* @return all resource hrefs |
||||
*/ |
||||
public Collection<String> getAllHrefs() { |
||||
return resources.keySet(); |
||||
} |
||||
} |
@ -0,0 +1,190 @@ |
||||
package me.ag2s.epublib.domain; |
||||
|
||||
import me.ag2s.epublib.util.StringUtil; |
||||
import java.io.Serializable; |
||||
import java.util.ArrayList; |
||||
import java.util.Collection; |
||||
import java.util.List; |
||||
|
||||
/** |
||||
* The spine sections are the sections of the book in the order in which the book should be read. |
||||
* |
||||
* This contrasts with the Table of Contents sections which is an index into the Book's sections. |
||||
* |
||||
* @see TableOfContents |
||||
* |
||||
* @author paul |
||||
*/ |
||||
public class Spine implements Serializable { |
||||
|
||||
private static final long serialVersionUID = 3878483958947357246L; |
||||
private Resource tocResource; |
||||
private List<SpineReference> spineReferences; |
||||
|
||||
public Spine() { |
||||
this(new ArrayList<SpineReference>()); |
||||
} |
||||
|
||||
/** |
||||
* Creates a spine out of all the resources in the table of contents. |
||||
* |
||||
* @param tableOfContents |
||||
*/ |
||||
public Spine(TableOfContents tableOfContents) { |
||||
this.spineReferences = createSpineReferences( |
||||
tableOfContents.getAllUniqueResources()); |
||||
} |
||||
|
||||
public Spine(List<SpineReference> spineReferences) { |
||||
this.spineReferences = spineReferences; |
||||
} |
||||
|
||||
public static List<SpineReference> createSpineReferences( |
||||
Collection<Resource> resources) { |
||||
List<SpineReference> result = new ArrayList<SpineReference>( |
||||
resources.size()); |
||||
for (Resource resource : resources) { |
||||
result.add(new SpineReference(resource)); |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
public List<SpineReference> getSpineReferences() { |
||||
return spineReferences; |
||||
} |
||||
|
||||
public void setSpineReferences(List<SpineReference> spineReferences) { |
||||
this.spineReferences = spineReferences; |
||||
} |
||||
|
||||
/** |
||||
* Gets the resource at the given index. |
||||
* Null if not found. |
||||
* |
||||
* @param index |
||||
* @return the resource at the given index. |
||||
*/ |
||||
public Resource getResource(int index) { |
||||
if (index < 0 || index >= spineReferences.size()) { |
||||
return null; |
||||
} |
||||
return spineReferences.get(index).getResource(); |
||||
} |
||||
|
||||
/** |
||||
* Finds the first resource that has the given resourceId. |
||||
* |
||||
* Null if not found. |
||||
* |
||||
* @param resourceId |
||||
* @return the first resource that has the given resourceId. |
||||
*/ |
||||
public int findFirstResourceById(String resourceId) { |
||||
if (StringUtil.isBlank(resourceId)) { |
||||
return -1; |
||||
} |
||||
|
||||
for (int i = 0; i < spineReferences.size(); i++) { |
||||
SpineReference spineReference = spineReferences.get(i); |
||||
if (resourceId.equals(spineReference.getResourceId())) { |
||||
return i; |
||||
} |
||||
} |
||||
return -1; |
||||
} |
||||
|
||||
/** |
||||
* Adds the given spineReference to the spine references and returns it. |
||||
* |
||||
* @param spineReference |
||||
* @return the given spineReference |
||||
*/ |
||||
public SpineReference addSpineReference(SpineReference spineReference) { |
||||
if (spineReferences == null) { |
||||
this.spineReferences = new ArrayList<SpineReference>(); |
||||
} |
||||
spineReferences.add(spineReference); |
||||
return spineReference; |
||||
} |
||||
|
||||
/** |
||||
* Adds the given resource to the spine references and returns it. |
||||
* |
||||
* @return the given spineReference |
||||
*/ |
||||
public SpineReference addResource(Resource resource) { |
||||
return addSpineReference(new SpineReference(resource)); |
||||
} |
||||
|
||||
/** |
||||
* The number of elements in the spine. |
||||
* |
||||
* @return The number of elements in the spine. |
||||
*/ |
||||
public int size() { |
||||
return spineReferences.size(); |
||||
} |
||||
|
||||
/** |
||||
* As per the epub file format the spine officially maintains a reference to the Table of Contents. |
||||
* The epubwriter will look for it here first, followed by some clever tricks to find it elsewhere if not found. |
||||
* Put it here to be sure of the expected behaviours. |
||||
* |
||||
* @param tocResource |
||||
*/ |
||||
public void setTocResource(Resource tocResource) { |
||||
this.tocResource = tocResource; |
||||
} |
||||
|
||||
/** |
||||
* The resource containing the XML for the tableOfContents. |
||||
* When saving an epub file this resource needs to be in this place. |
||||
* |
||||
* @return The resource containing the XML for the tableOfContents. |
||||
*/ |
||||
public Resource getTocResource() { |
||||
return tocResource; |
||||
} |
||||
|
||||
/** |
||||
* The position within the spine of the given resource. |
||||
* |
||||
* @param currentResource |
||||
* @return something < 0 if not found. |
||||
* |
||||
*/ |
||||
public int getResourceIndex(Resource currentResource) { |
||||
if (currentResource == null) { |
||||
return -1; |
||||
} |
||||
return getResourceIndex(currentResource.getHref()); |
||||
} |
||||
|
||||
/** |
||||
* The first position within the spine of a resource with the given href. |
||||
* |
||||
* @return something < 0 if not found. |
||||
* |
||||
*/ |
||||
public int getResourceIndex(String resourceHref) { |
||||
int result = -1; |
||||
if (StringUtil.isBlank(resourceHref)) { |
||||
return result; |
||||
} |
||||
for (int i = 0; i < spineReferences.size(); i++) { |
||||
if (resourceHref.equals(spineReferences.get(i).getResource().getHref())) { |
||||
result = i; |
||||
break; |
||||
} |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
/** |
||||
* Whether the spine has any references |
||||
* @return Whether the spine has any references |
||||
*/ |
||||
public boolean isEmpty() { |
||||
return spineReferences.isEmpty(); |
||||
} |
||||
} |
@ -0,0 +1,52 @@ |
||||
package me.ag2s.epublib.domain; |
||||
|
||||
import java.io.Serializable; |
||||
|
||||
|
||||
/** |
||||
* A Section of a book. |
||||
* Represents both an item in the package document and a item in the index. |
||||
* |
||||
* @author paul |
||||
*/ |
||||
public class SpineReference extends ResourceReference implements Serializable { |
||||
|
||||
private static final long serialVersionUID = -7921609197351510248L; |
||||
private boolean linear = true; |
||||
|
||||
public SpineReference(Resource resource) { |
||||
this(resource, true); |
||||
} |
||||
|
||||
|
||||
public SpineReference(Resource resource, boolean linear) { |
||||
super(resource); |
||||
this.linear = linear; |
||||
} |
||||
|
||||
/** |
||||
* Linear denotes whether the section is Primary or Auxiliary. |
||||
* Usually the cover page has linear set to false and all the other sections |
||||
* have it set to true. |
||||
* |
||||
* It's an optional property that readers may also ignore. |
||||
* |
||||
* <blockquote>primary 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.)</blockquote> |
||||
* @see <a href="http://www.idpf.org/epub/20/spec/OPF_2.0.1_draft.htm#Section2.4">OPF Spine specification</a> |
||||
* |
||||
* @return whether the section is Primary or Auxiliary. |
||||
*/ |
||||
public boolean isLinear() { |
||||
return linear; |
||||
} |
||||
|
||||
public void setLinear(boolean linear) { |
||||
this.linear = linear; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,59 @@ |
||||
package me.ag2s.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 TableOfContents |
||||
* |
||||
* @author paul |
||||
*/ |
||||
public class TOCReference extends TitledResourceReference |
||||
implements Serializable { |
||||
|
||||
private static final long serialVersionUID = 5787958246077042456L; |
||||
private List<TOCReference> children; |
||||
private static final Comparator<TOCReference> COMPARATOR_BY_TITLE_IGNORE_CASE = |
||||
(tocReference1, tocReference2) -> |
||||
String.CASE_INSENSITIVE_ORDER |
||||
.compare(tocReference1.getTitle(), tocReference2.getTitle()); |
||||
|
||||
public TOCReference() { |
||||
this(null, null, null); |
||||
} |
||||
|
||||
public TOCReference(String name, Resource resource) { |
||||
this(name, resource, null); |
||||
} |
||||
|
||||
public TOCReference(String name, Resource resource, String fragmentId) { |
||||
this(name, resource, fragmentId, new ArrayList<>()); |
||||
} |
||||
|
||||
public TOCReference(String title, Resource resource, String fragmentId, |
||||
List<TOCReference> children) { |
||||
super(resource, title, fragmentId); |
||||
this.children = children; |
||||
} |
||||
|
||||
public static Comparator<TOCReference> getComparatorByTitleIgnoreCase() { |
||||
return COMPARATOR_BY_TITLE_IGNORE_CASE; |
||||
} |
||||
|
||||
public List<TOCReference> getChildren() { |
||||
return children; |
||||
} |
||||
|
||||
public TOCReference addChildSection(TOCReference childSection) { |
||||
this.children.add(childSection); |
||||
return childSection; |
||||
} |
||||
|
||||
public void setChildren(List<TOCReference> children) { |
||||
this.children = children; |
||||
} |
||||
} |
@ -0,0 +1,259 @@ |
||||
package me.ag2s.epublib.domain; |
||||
|
||||
import java.io.Serializable; |
||||
import java.util.ArrayList; |
||||
import java.util.Collection; |
||||
import java.util.HashSet; |
||||
import java.util.List; |
||||
import java.util.Set; |
||||
|
||||
/** |
||||
* The table of contents of the book. |
||||
* The TableOfContents is a tree structure at the root it is a list of TOCReferences, each if which may have as children another list of TOCReferences. |
||||
* |
||||
* The table of contents is used by epub as a quick index to chapters and sections within chapters. |
||||
* It may contain duplicate entries, may decide to point not to certain chapters, etc. |
||||
* |
||||
* See the spine for the complete list of sections in the order in which they should be read. |
||||
* |
||||
* @see Spine |
||||
* |
||||
* @author paul |
||||
*/ |
||||
public class TableOfContents implements Serializable { |
||||
|
||||
private static final long serialVersionUID = -3147391239966275152L; |
||||
|
||||
public static final String DEFAULT_PATH_SEPARATOR = "/"; |
||||
|
||||
private List<TOCReference> tocReferences; |
||||
|
||||
public TableOfContents() { |
||||
this(new ArrayList<TOCReference>()); |
||||
} |
||||
|
||||
public TableOfContents(List<TOCReference> tocReferences) { |
||||
this.tocReferences = tocReferences; |
||||
} |
||||
|
||||
public List<TOCReference> getTocReferences() { |
||||
return tocReferences; |
||||
} |
||||
|
||||
public void setTocReferences(List<TOCReference> tocReferences) { |
||||
this.tocReferences = tocReferences; |
||||
} |
||||
|
||||
/** |
||||
* Calls addTOCReferenceAtLocation after splitting the path using the DEFAULT_PATH_SEPARATOR. |
||||
* @return the new TOCReference |
||||
*/ |
||||
public TOCReference addSection(Resource resource, String path) { |
||||
return addSection(resource, path, DEFAULT_PATH_SEPARATOR); |
||||
} |
||||
|
||||
/** |
||||
* Calls addTOCReferenceAtLocation after splitting the path using the given pathSeparator. |
||||
* |
||||
* @param resource |
||||
* @param path |
||||
* @param pathSeparator |
||||
* @return the new TOCReference |
||||
*/ |
||||
public TOCReference addSection(Resource resource, String path, |
||||
String pathSeparator) { |
||||
String[] pathElements = path.split(pathSeparator); |
||||
return addSection(resource, pathElements); |
||||
} |
||||
|
||||
/** |
||||
* Finds the first TOCReference in the given list that has the same title as the given Title. |
||||
* |
||||
* @param title |
||||
* @param tocReferences |
||||
* @return null if not found. |
||||
*/ |
||||
private static TOCReference findTocReferenceByTitle(String title, |
||||
List<TOCReference> tocReferences) { |
||||
for (TOCReference tocReference : tocReferences) { |
||||
if (title.equals(tocReference.getTitle())) { |
||||
return tocReference; |
||||
} |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
/** |
||||
* Adds the given Resources to the TableOfContents at the location specified by the pathElements. |
||||
* |
||||
* Example: |
||||
* Calling this method with a Resource and new String[] {"chapter1", "paragraph1"} will result in the following: |
||||
* <ul> |
||||
* <li>a TOCReference with the title "chapter1" at the root level.<br/> |
||||
* If this TOCReference did not yet exist it will have been created and does not point to any resource</li> |
||||
* <li>A TOCReference that has the title "paragraph1". This TOCReference will be the child of TOCReference "chapter1" and |
||||
* will point to the given Resource</li> |
||||
* </ul> |
||||
* |
||||
* @param resource |
||||
* @param pathElements |
||||
* @return the new TOCReference |
||||
*/ |
||||
public TOCReference addSection(Resource resource, String[] pathElements) { |
||||
if (pathElements == null || pathElements.length == 0) { |
||||
return null; |
||||
} |
||||
TOCReference result = null; |
||||
List<TOCReference> currentTocReferences = this.tocReferences; |
||||
for (int i = 0; i < pathElements.length; i++) { |
||||
String currentTitle = pathElements[i]; |
||||
result = findTocReferenceByTitle(currentTitle, currentTocReferences); |
||||
if (result == null) { |
||||
result = new TOCReference(currentTitle, null); |
||||
currentTocReferences.add(result); |
||||
} |
||||
currentTocReferences = result.getChildren(); |
||||
} |
||||
result.setResource(resource); |
||||
return result; |
||||
} |
||||
|
||||
/** |
||||
* Adds the given Resources to the TableOfContents at the location specified by the pathElements. |
||||
* |
||||
* Example: |
||||
* Calling this method with a Resource and new int[] {0, 0} will result in the following: |
||||
* <ul> |
||||
* <li>a TOCReference at the root level.<br/> |
||||
* If this TOCReference did not yet exist it will have been created with a title of "" and does not point to any resource</li> |
||||
* <li>A TOCReference that points to the given resource and is a child of the previously created TOCReference.<br/> |
||||
* If this TOCReference didn't exist yet it will be created and have a title of ""</li> |
||||
* </ul> |
||||
* |
||||
* @param resource |
||||
* @param pathElements |
||||
* @return the new TOCReference |
||||
*/ |
||||
public TOCReference addSection(Resource resource, int[] pathElements, |
||||
String sectionTitlePrefix, String sectionNumberSeparator) { |
||||
if (pathElements == null || pathElements.length == 0) { |
||||
return null; |
||||
} |
||||
TOCReference result = null; |
||||
List<TOCReference> currentTocReferences = this.tocReferences; |
||||
for (int i = 0; i < pathElements.length; i++) { |
||||
int currentIndex = pathElements[i]; |
||||
if (currentIndex > 0 && currentIndex < (currentTocReferences.size() |
||||
- 1)) { |
||||
result = currentTocReferences.get(currentIndex); |
||||
} else { |
||||
result = null; |
||||
} |
||||
if (result == null) { |
||||
paddTOCReferences(currentTocReferences, pathElements, i, |
||||
sectionTitlePrefix, sectionNumberSeparator); |
||||
result = currentTocReferences.get(currentIndex); |
||||
} |
||||
currentTocReferences = result.getChildren(); |
||||
} |
||||
result.setResource(resource); |
||||
return result; |
||||
} |
||||
|
||||
private void paddTOCReferences(List<TOCReference> currentTocReferences, |
||||
int[] pathElements, int pathPos, String sectionPrefix, |
||||
String sectionNumberSeparator) { |
||||
for (int i = currentTocReferences.size(); i <= pathElements[pathPos]; i++) { |
||||
String sectionTitle = createSectionTitle(pathElements, pathPos, i, |
||||
sectionPrefix, |
||||
sectionNumberSeparator); |
||||
currentTocReferences.add(new TOCReference(sectionTitle, null)); |
||||
} |
||||
} |
||||
|
||||
private String createSectionTitle(int[] pathElements, int pathPos, |
||||
int lastPos, |
||||
String sectionPrefix, String sectionNumberSeparator) { |
||||
StringBuilder title = new StringBuilder(sectionPrefix); |
||||
for (int i = 0; i < pathPos; i++) { |
||||
if (i > 0) { |
||||
title.append(sectionNumberSeparator); |
||||
} |
||||
title.append(pathElements[i] + 1); |
||||
} |
||||
if (pathPos > 0) { |
||||
title.append(sectionNumberSeparator); |
||||
} |
||||
title.append(lastPos + 1); |
||||
return title.toString(); |
||||
} |
||||
|
||||
public TOCReference addTOCReference(TOCReference tocReference) { |
||||
if (tocReferences == null) { |
||||
tocReferences = new ArrayList<>(); |
||||
} |
||||
tocReferences.add(tocReference); |
||||
return tocReference; |
||||
} |
||||
|
||||
/** |
||||
* All unique references (unique by href) in the order in which they are referenced to in the table of contents. |
||||
* |
||||
* @return All unique references (unique by href) in the order in which they are referenced to in the table of contents. |
||||
*/ |
||||
public List<Resource> getAllUniqueResources() { |
||||
Set<String> uniqueHrefs = new HashSet<>(); |
||||
List<Resource> result = new ArrayList<>(); |
||||
getAllUniqueResources(uniqueHrefs, result, tocReferences); |
||||
return result; |
||||
} |
||||
|
||||
private static void getAllUniqueResources(Set<String> uniqueHrefs, |
||||
List<Resource> result, List<TOCReference> tocReferences) { |
||||
for (TOCReference tocReference : tocReferences) { |
||||
Resource resource = tocReference.getResource(); |
||||
if (resource != null && !uniqueHrefs.contains(resource.getHref())) { |
||||
uniqueHrefs.add(resource.getHref()); |
||||
result.add(resource); |
||||
} |
||||
getAllUniqueResources(uniqueHrefs, result, tocReference.getChildren()); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* The total number of references in this table of contents. |
||||
* |
||||
* @return The total number of references in this table of contents. |
||||
*/ |
||||
public int size() { |
||||
return getTotalSize(tocReferences); |
||||
} |
||||
|
||||
private static int getTotalSize(Collection<TOCReference> tocReferences) { |
||||
int result = tocReferences.size(); |
||||
for (TOCReference tocReference : tocReferences) { |
||||
result += getTotalSize(tocReference.getChildren()); |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
/** |
||||
* The maximum depth of the reference tree |
||||
* @return The maximum depth of the reference tree |
||||
*/ |
||||
public int calculateDepth() { |
||||
return calculateDepth(tocReferences, 0); |
||||
} |
||||
|
||||
private int calculateDepth(List<TOCReference> tocReferences, |
||||
int currentDepth) { |
||||
int maxChildDepth = 0; |
||||
for (TOCReference tocReference : tocReferences) { |
||||
int childDepth = calculateDepth(tocReference.getChildren(), 1); |
||||
if (childDepth > maxChildDepth) { |
||||
maxChildDepth = childDepth; |
||||
} |
||||
} |
||||
return currentDepth + maxChildDepth; |
||||
} |
||||
} |
@ -0,0 +1,78 @@ |
||||
package me.ag2s.epublib.domain; |
||||
|
||||
import me.ag2s.epublib.Constants; |
||||
import me.ag2s.epublib.util.StringUtil; |
||||
import java.io.Serializable; |
||||
|
||||
public class TitledResourceReference extends ResourceReference |
||||
implements Serializable { |
||||
|
||||
private static final long serialVersionUID = 3918155020095190080L; |
||||
private String fragmentId; |
||||
private String title; |
||||
|
||||
public TitledResourceReference(Resource resource) { |
||||
this(resource, null); |
||||
} |
||||
|
||||
public TitledResourceReference(Resource resource, String title) { |
||||
this(resource, title, null); |
||||
} |
||||
|
||||
public TitledResourceReference(Resource resource, String title, |
||||
String fragmentId) { |
||||
super(resource); |
||||
this.title = title; |
||||
this.fragmentId = fragmentId; |
||||
} |
||||
|
||||
public String getFragmentId() { |
||||
return fragmentId; |
||||
} |
||||
|
||||
public void setFragmentId(String fragmentId) { |
||||
this.fragmentId = fragmentId; |
||||
} |
||||
|
||||
public String getTitle() { |
||||
return title; |
||||
} |
||||
|
||||
public void setTitle(String title) { |
||||
this.title = title; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* If the fragmentId is blank it returns the resource href, otherwise |
||||
* it returns the resource href + '#' + the fragmentId. |
||||
* |
||||
* @return If the fragmentId is blank it returns the resource href, |
||||
* otherwise it returns the resource href + '#' + the fragmentId. |
||||
*/ |
||||
public String getCompleteHref() { |
||||
if (StringUtil.isBlank(fragmentId)) { |
||||
return resource.getHref(); |
||||
} else { |
||||
return resource.getHref() + Constants.FRAGMENT_SEPARATOR_CHAR |
||||
+ fragmentId; |
||||
} |
||||
} |
||||
@Override |
||||
public Resource getResource() { |
||||
resource.setTitle(title); |
||||
return resource; |
||||
} |
||||
|
||||
public void setResource(Resource resource, String fragmentId) { |
||||
super.setResource(resource); |
||||
this.fragmentId = fragmentId; |
||||
} |
||||
|
||||
/** |
||||
* Sets the resource to the given resource and sets the fragmentId to null. |
||||
*/ |
||||
public void setResource(Resource resource) { |
||||
setResource(resource, null); |
||||
} |
||||
} |
@ -0,0 +1,20 @@ |
||||
package me.ag2s.epublib.epub; |
||||
|
||||
import me.ag2s.epublib.domain.Book; |
||||
|
||||
/** |
||||
* Post-processes a book. |
||||
* |
||||
* Can be used to clean up a book after reading or before writing. |
||||
* |
||||
* @author paul |
||||
*/ |
||||
public interface BookProcessor { |
||||
|
||||
/** |
||||
* A BookProcessor that returns the input book unchanged. |
||||
*/ |
||||
BookProcessor IDENTITY_BOOKPROCESSOR = book -> book; |
||||
|
||||
Book processBook(Book book); |
||||
} |
@ -0,0 +1,72 @@ |
||||
package me.ag2s.epublib.epub; |
||||
|
||||
import android.util.Log; |
||||
|
||||
import me.ag2s.epublib.domain.Book; |
||||
//import io.documentnode.minilog.Logger;
|
||||
import java.util.ArrayList; |
||||
import java.util.Collection; |
||||
import java.util.List; |
||||
|
||||
/** |
||||
* A book processor that combines several other bookprocessors |
||||
* |
||||
* Fixes coverpage/coverimage. |
||||
* Cleans up the XHTML. |
||||
* |
||||
* @author paul.siegmann |
||||
*/ |
||||
public class BookProcessorPipeline implements BookProcessor { |
||||
|
||||
private static String TAG= BookProcessorPipeline.class.getName(); |
||||
private List<BookProcessor> bookProcessors; |
||||
|
||||
public BookProcessorPipeline() { |
||||
this(null); |
||||
} |
||||
|
||||
public BookProcessorPipeline(List<BookProcessor> bookProcessingPipeline) { |
||||
this.bookProcessors = bookProcessingPipeline; |
||||
} |
||||
|
||||
@Override |
||||
public Book processBook(Book book) { |
||||
if (bookProcessors == null) { |
||||
return book; |
||||
} |
||||
for (BookProcessor bookProcessor : bookProcessors) { |
||||
try { |
||||
book = bookProcessor.processBook(book); |
||||
} catch (Exception e) { |
||||
Log.e(TAG,e.getMessage(), e); |
||||
} |
||||
} |
||||
return book; |
||||
} |
||||
|
||||
public void addBookProcessor(BookProcessor bookProcessor) { |
||||
if (this.bookProcessors == null) { |
||||
bookProcessors = new ArrayList<BookProcessor>(); |
||||
} |
||||
this.bookProcessors.add(bookProcessor); |
||||
} |
||||
|
||||
public void addBookProcessors(Collection<BookProcessor> bookProcessors) { |
||||
if (this.bookProcessors == null) { |
||||
this.bookProcessors = new ArrayList<BookProcessor>(); |
||||
} |
||||
this.bookProcessors.addAll(bookProcessors); |
||||
} |
||||
|
||||
|
||||
public List<BookProcessor> getBookProcessors() { |
||||
return bookProcessors; |
||||
} |
||||
|
||||
|
||||
public void setBookProcessingPipeline( |
||||
List<BookProcessor> bookProcessingPipeline) { |
||||
this.bookProcessors = bookProcessingPipeline; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,134 @@ |
||||
package me.ag2s.epublib.epub; |
||||
|
||||
import me.ag2s.epublib.util.StringUtil; |
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
import org.w3c.dom.Document; |
||||
import org.w3c.dom.Element; |
||||
import org.w3c.dom.Node; |
||||
import org.w3c.dom.NodeList; |
||||
import org.w3c.dom.Text; |
||||
|
||||
/** |
||||
* Utility methods for working with the DOM. |
||||
* |
||||
* @author paul |
||||
* |
||||
*/ |
||||
// package
|
||||
class DOMUtil { |
||||
|
||||
/** |
||||
* First tries to get the attribute value by doing an getAttributeNS on the element, if that gets an empty element it does a getAttribute without namespace. |
||||
* |
||||
* @param element |
||||
* @param namespace |
||||
* @param attribute |
||||
* @return |
||||
*/ |
||||
public static String getAttribute(Element element, String namespace, |
||||
String attribute) { |
||||
String result = element.getAttributeNS(namespace, attribute); |
||||
if (StringUtil.isEmpty(result)) { |
||||
result = element.getAttribute(attribute); |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
/** |
||||
* Gets all descendant elements of the given parentElement with the given namespace and tagname and returns their text child as a list of String. |
||||
* |
||||
* @param parentElement |
||||
* @param namespace |
||||
* @param tagname |
||||
* @return |
||||
*/ |
||||
public static List<String> getElementsTextChild(Element parentElement, |
||||
String namespace, String tagname) { |
||||
NodeList elements = parentElement |
||||
.getElementsByTagNameNS(namespace, tagname); |
||||
List<String> result = new ArrayList<String>(elements.getLength()); |
||||
for (int i = 0; i < elements.getLength(); i++) { |
||||
result.add(getTextChildrenContent((Element) elements.item(i))); |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
/** |
||||
* Finds in the current document the first element with the given namespace and elementName and with the given findAttributeName and findAttributeValue. |
||||
* It then returns the value of the given resultAttributeName. |
||||
* |
||||
* @param document |
||||
* @param namespace |
||||
* @param elementName |
||||
* @param findAttributeName |
||||
* @param findAttributeValue |
||||
* @param resultAttributeName |
||||
* @return |
||||
*/ |
||||
public static String getFindAttributeValue(Document document, |
||||
String namespace, String elementName, String findAttributeName, |
||||
String findAttributeValue, String resultAttributeName) { |
||||
NodeList metaTags = document.getElementsByTagNameNS(namespace, elementName); |
||||
for (int i = 0; i < metaTags.getLength(); i++) { |
||||
Element metaElement = (Element) metaTags.item(i); |
||||
if (findAttributeValue |
||||
.equalsIgnoreCase(metaElement.getAttribute(findAttributeName)) |
||||
&& StringUtil |
||||
.isNotBlank(metaElement.getAttribute(resultAttributeName))) { |
||||
return metaElement.getAttribute(resultAttributeName); |
||||
} |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
/** |
||||
* Gets the first element that is a child of the parentElement and has the given namespace and tagName |
||||
* |
||||
* @param parentElement |
||||
* @param namespace |
||||
* @param tagName |
||||
* @return |
||||
*/ |
||||
public static Element getFirstElementByTagNameNS(Element parentElement, |
||||
String namespace, String tagName) { |
||||
NodeList nodes = parentElement.getElementsByTagNameNS(namespace, tagName); |
||||
if (nodes.getLength() != 0) { |
||||
return (Element) nodes.item(0); |
||||
} |
||||
nodes= parentElement.getElementsByTagName(tagName); |
||||
if (nodes.getLength()==0){ |
||||
return null; |
||||
} |
||||
return (Element) nodes.item(0); |
||||
} |
||||
|
||||
/** |
||||
* The contents of all Text nodes that are children of the given parentElement. |
||||
* The result is trim()-ed. |
||||
* |
||||
* The reason for this more complicated procedure instead of just returning the data of the firstChild is that |
||||
* when the text is Chinese characters then on Android each Characater is represented in the DOM as |
||||
* an individual Text node. |
||||
* |
||||
* @param parentElement |
||||
* @return |
||||
*/ |
||||
public static String getTextChildrenContent(Element parentElement) { |
||||
if (parentElement == null) { |
||||
return null; |
||||
} |
||||
StringBuilder result = new StringBuilder(); |
||||
NodeList childNodes = parentElement.getChildNodes(); |
||||
for (int i = 0; i < childNodes.getLength(); i++) { |
||||
Node node = childNodes.item(i); |
||||
if ((node == null) || |
||||
(node.getNodeType() != Node.TEXT_NODE)) { |
||||
continue; |
||||
} |
||||
result.append(((Text) node).getData()); |
||||
} |
||||
return result.toString().trim(); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,138 @@ |
||||
package me.ag2s.epublib.epub; |
||||
|
||||
import android.util.Log; |
||||
|
||||
import me.ag2s.epublib.Constants; |
||||
//import io.documentnode.minilog.Logger;
|
||||
import java.io.IOException; |
||||
import java.io.InputStream; |
||||
import java.io.OutputStream; |
||||
import java.io.OutputStreamWriter; |
||||
import java.io.UnsupportedEncodingException; |
||||
import java.io.Writer; |
||||
import java.net.URL; |
||||
import javax.xml.parsers.DocumentBuilder; |
||||
import javax.xml.parsers.DocumentBuilderFactory; |
||||
import javax.xml.parsers.ParserConfigurationException; |
||||
//import org.kxml2.io.KXmlSerializer;
|
||||
import org.xml.sax.EntityResolver; |
||||
import org.xml.sax.InputSource; |
||||
import org.xml.sax.SAXException; |
||||
import org.xmlpull.v1.XmlPullParserFactory; |
||||
import org.xmlpull.v1.XmlSerializer; |
||||
|
||||
/** |
||||
* Various low-level support methods for reading/writing epubs. |
||||
* |
||||
* @author paul.siegmann |
||||
* |
||||
*/ |
||||
public class EpubProcessorSupport { |
||||
|
||||
private static String TAG= EpubProcessorSupport.class.getName(); |
||||
|
||||
protected static DocumentBuilderFactory documentBuilderFactory; |
||||
|
||||
static { |
||||
init(); |
||||
} |
||||
|
||||
static class EntityResolverImpl implements EntityResolver { |
||||
|
||||
private String previousLocation; |
||||
|
||||
@Override |
||||
public InputSource resolveEntity(String publicId, String systemId) |
||||
throws SAXException, IOException { |
||||
String resourcePath; |
||||
if (systemId.startsWith("http:")) { |
||||
URL url = new URL(systemId); |
||||
resourcePath = "dtd/" + url.getHost() + url.getPath(); |
||||
previousLocation = resourcePath |
||||
.substring(0, resourcePath.lastIndexOf('/')); |
||||
} else { |
||||
resourcePath = |
||||
previousLocation + systemId.substring(systemId.lastIndexOf('/')); |
||||
} |
||||
|
||||
if (this.getClass().getClassLoader().getResource(resourcePath) == null) { |
||||
throw new RuntimeException( |
||||
"remote resource is not cached : [" + systemId |
||||
+ "] cannot continue"); |
||||
} |
||||
|
||||
InputStream in = EpubProcessorSupport.class.getClassLoader() |
||||
.getResourceAsStream(resourcePath); |
||||
return new InputSource(in); |
||||
} |
||||
} |
||||
|
||||
|
||||
private static void init() { |
||||
EpubProcessorSupport.documentBuilderFactory = DocumentBuilderFactory |
||||
.newInstance(); |
||||
documentBuilderFactory.setNamespaceAware(true); |
||||
documentBuilderFactory.setValidating(false); |
||||
} |
||||
|
||||
public static XmlSerializer createXmlSerializer(OutputStream out) |
||||
throws UnsupportedEncodingException { |
||||
return createXmlSerializer( |
||||
new OutputStreamWriter(out, Constants.CHARACTER_ENCODING)); |
||||
} |
||||
|
||||
public static XmlSerializer createXmlSerializer(Writer out) { |
||||
XmlSerializer result = null; |
||||
try { |
||||
/* |
||||
* Disable XmlPullParserFactory here before it doesn't work when |
||||
* building native image using GraalVM |
||||
*/ |
||||
XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); |
||||
factory.setValidating(true); |
||||
result = factory.newSerializer(); |
||||
|
||||
//result = new KXmlSerializer();
|
||||
result.setFeature( |
||||
"http://xmlpull.org/v1/doc/features.html#indent-output", true); |
||||
result.setOutput(out); |
||||
} catch (Exception e) { |
||||
Log.e(TAG, |
||||
"When creating XmlSerializer: " + e.getClass().getName() + ": " + e |
||||
.getMessage()); |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
/** |
||||
* Gets an EntityResolver that loads dtd's and such from the epub4j classpath. |
||||
* In order to enable the loading of relative urls the given EntityResolver contains the previousLocation. |
||||
* Because of a new EntityResolver is created every time this method is called. |
||||
* Fortunately the EntityResolver created uses up very little memory per instance. |
||||
* |
||||
* @return an EntityResolver that loads dtd's and such from the epub4j classpath. |
||||
*/ |
||||
public static EntityResolver getEntityResolver() { |
||||
return new EntityResolverImpl(); |
||||
} |
||||
|
||||
public DocumentBuilderFactory getDocumentBuilderFactory() { |
||||
return documentBuilderFactory; |
||||
} |
||||
|
||||
/** |
||||
* Creates a DocumentBuilder that looks up dtd's and schema's from epub4j's classpath. |
||||
* |
||||
* @return a DocumentBuilder that looks up dtd's and schema's from epub4j's classpath. |
||||
*/ |
||||
public static DocumentBuilder createDocumentBuilder() { |
||||
DocumentBuilder result = null; |
||||
try { |
||||
result = documentBuilderFactory.newDocumentBuilder(); |
||||
result.setEntityResolver(getEntityResolver()); |
||||
} catch (ParserConfigurationException e) { |
||||
Log.e(TAG,e.getMessage()); |
||||
} |
||||
return result; |
||||
} |
||||
} |
@ -0,0 +1,169 @@ |
||||
package me.ag2s.epublib.epub; |
||||
|
||||
import android.util.Log; |
||||
|
||||
import me.ag2s.epublib.Constants; |
||||
import me.ag2s.epublib.domain.Book; |
||||
import me.ag2s.epublib.domain.MediaType; |
||||
import me.ag2s.epublib.domain.Resource; |
||||
import me.ag2s.epublib.domain.Resources; |
||||
import me.ag2s.epublib.domain.MediaTypes; |
||||
import me.ag2s.epublib.util.ResourceUtil; |
||||
import me.ag2s.epublib.util.StringUtil; |
||||
//import io.documentnode.minilog.Logger;
|
||||
import java.io.IOException; |
||||
import java.io.InputStream; |
||||
import java.util.Arrays; |
||||
import java.util.List; |
||||
import java.util.zip.ZipFile; |
||||
import java.util.zip.ZipInputStream; |
||||
|
||||
import org.w3c.dom.Document; |
||||
import org.w3c.dom.Element; |
||||
|
||||
/** |
||||
* Reads an epub file. |
||||
* |
||||
* @author paul |
||||
* |
||||
*/ |
||||
public class EpubReader { |
||||
|
||||
private static String TAG= EpubReader.class.getName(); |
||||
private BookProcessor bookProcessor = BookProcessor.IDENTITY_BOOKPROCESSOR; |
||||
|
||||
public Book readEpub(InputStream in) throws IOException { |
||||
return readEpub(in, Constants.CHARACTER_ENCODING); |
||||
} |
||||
|
||||
public Book readEpub(ZipInputStream in) throws IOException { |
||||
return readEpub(in, Constants.CHARACTER_ENCODING); |
||||
} |
||||
|
||||
public Book readEpub(ZipFile zipfile) throws IOException { |
||||
return readEpub(zipfile, Constants.CHARACTER_ENCODING); |
||||
} |
||||
|
||||
/** |
||||
* Read epub from inputstream |
||||
* |
||||
* @param in the inputstream from which to read the epub |
||||
* @param encoding the encoding to use for the html files within the epub |
||||
* @return the Book as read from the inputstream |
||||
* @throws IOException |
||||
*/ |
||||
public Book readEpub(InputStream in, String encoding) throws IOException { |
||||
return readEpub(new ZipInputStream(in), encoding); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Reads this EPUB without loading any resources into memory. |
||||
* |
||||
* @param zipFile the file to load |
||||
* @param encoding the encoding for XHTML files |
||||
* |
||||
* @return this Book without loading all resources into memory. |
||||
* @throws IOException |
||||
*/ |
||||
public Book readEpubLazy(ZipFile zipFile, String encoding) |
||||
throws IOException { |
||||
return readEpubLazy(zipFile, encoding, |
||||
Arrays.asList(MediaTypes.mediaTypes)); |
||||
} |
||||
|
||||
public Book readEpub(ZipInputStream in, String encoding) throws IOException { |
||||
return readEpub(ResourcesLoader.loadResources(in, encoding)); |
||||
} |
||||
|
||||
public Book readEpub(ZipFile in, String encoding) throws IOException { |
||||
return readEpub(ResourcesLoader.loadResources(in, encoding)); |
||||
} |
||||
|
||||
/** |
||||
* Reads this EPUB without loading all resources into memory. |
||||
* |
||||
* @param zipFile the file to load |
||||
* @param encoding the encoding for XHTML files |
||||
* @param lazyLoadedTypes a list of the MediaType to load lazily |
||||
* @return this Book without loading all resources into memory. |
||||
* @throws IOException |
||||
*/ |
||||
public Book readEpubLazy(ZipFile zipFile, String encoding, |
||||
List<MediaType> lazyLoadedTypes) throws IOException { |
||||
Resources resources = ResourcesLoader |
||||
.loadResources(zipFile, encoding, lazyLoadedTypes); |
||||
return readEpub(resources); |
||||
} |
||||
|
||||
public Book readEpub(Resources resources) throws IOException { |
||||
return readEpub(resources, new Book()); |
||||
} |
||||
|
||||
public Book readEpub(Resources resources, Book result) throws IOException { |
||||
if (result == null) { |
||||
result = new Book(); |
||||
} |
||||
handleMimeType(result, resources); |
||||
String packageResourceHref = getPackageResourceHref(resources); |
||||
Resource packageResource = processPackageResource(packageResourceHref, |
||||
result, resources); |
||||
result.setOpfResource(packageResource); |
||||
Resource ncxResource = processNcxResource(packageResource, result); |
||||
result.setNcxResource(ncxResource); |
||||
result = postProcessBook(result); |
||||
return result; |
||||
} |
||||
|
||||
|
||||
private Book postProcessBook(Book book) { |
||||
if (bookProcessor != null) { |
||||
book = bookProcessor.processBook(book); |
||||
} |
||||
return book; |
||||
} |
||||
|
||||
private Resource processNcxResource(Resource packageResource, Book book) { |
||||
Log.d(TAG,"OPF:getHref()"+packageResource.getHref()); |
||||
|
||||
return NCXDocument.read(book, this); |
||||
} |
||||
|
||||
private Resource processPackageResource(String packageResourceHref, Book book, |
||||
Resources resources) { |
||||
Resource packageResource = resources.remove(packageResourceHref); |
||||
try { |
||||
PackageDocumentReader.read(packageResource, this, book, resources); |
||||
} catch (Exception e) { |
||||
Log.e(TAG,e.getMessage(), e); |
||||
} |
||||
return packageResource; |
||||
} |
||||
|
||||
private String getPackageResourceHref(Resources resources) { |
||||
String defaultResult = "OEBPS/content.opf"; |
||||
String result = defaultResult; |
||||
|
||||
Resource containerResource = resources.remove("META-INF/container.xml"); |
||||
if (containerResource == null) { |
||||
return result; |
||||
} |
||||
try { |
||||
Document document = ResourceUtil.getAsDocument(containerResource); |
||||
Element rootFileElement = (Element) ((Element) document |
||||
.getDocumentElement().getElementsByTagName("rootfiles").item(0)) |
||||
.getElementsByTagName("rootfile").item(0); |
||||
result = rootFileElement.getAttribute("full-path"); |
||||
} catch (Exception e) { |
||||
Log.e(TAG,e.getMessage(), e); |
||||
} |
||||
if (StringUtil.isBlank(result)) { |
||||
result = defaultResult; |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
private void handleMimeType(Book result, Resources resources) { |
||||
resources.remove("mimetype"); |
||||
} |
||||
} |
@ -0,0 +1,184 @@ |
||||
package me.ag2s.epublib.epub; |
||||
|
||||
import android.util.Log; |
||||
|
||||
import me.ag2s.epublib.domain.Book; |
||||
import me.ag2s.epublib.domain.MediaTypes; |
||||
import me.ag2s.epublib.domain.Resource; |
||||
import me.ag2s.epublib.util.IOUtil; |
||||
//import io.documentnode.minilog.Logger;
|
||||
import java.io.IOException; |
||||
import java.io.InputStream; |
||||
import java.io.OutputStream; |
||||
import java.io.OutputStreamWriter; |
||||
import java.io.Writer; |
||||
import java.util.zip.CRC32; |
||||
import java.util.zip.ZipEntry; |
||||
import java.util.zip.ZipOutputStream; |
||||
import org.xmlpull.v1.XmlSerializer; |
||||
|
||||
/** |
||||
* Generates an epub file. Not thread-safe, single use object. |
||||
* |
||||
* @author paul |
||||
*/ |
||||
public class EpubWriter { |
||||
|
||||
private static String TAG= EpubWriter.class.getName(); |
||||
|
||||
// package
|
||||
static final String EMPTY_NAMESPACE_PREFIX = ""; |
||||
|
||||
private BookProcessor bookProcessor = BookProcessor.IDENTITY_BOOKPROCESSOR; |
||||
|
||||
public EpubWriter() { |
||||
this(BookProcessor.IDENTITY_BOOKPROCESSOR); |
||||
} |
||||
|
||||
|
||||
public EpubWriter(BookProcessor bookProcessor) { |
||||
this.bookProcessor = bookProcessor; |
||||
} |
||||
|
||||
|
||||
public void write(Book book, OutputStream out) throws IOException { |
||||
book = processBook(book); |
||||
ZipOutputStream resultStream = new ZipOutputStream(out); |
||||
writeMimeType(resultStream); |
||||
writeContainer(resultStream); |
||||
initTOCResource(book); |
||||
writeResources(book, resultStream); |
||||
writePackageDocument(book, resultStream); |
||||
resultStream.close(); |
||||
} |
||||
|
||||
private Book processBook(Book book) { |
||||
if (bookProcessor != null) { |
||||
book = bookProcessor.processBook(book); |
||||
} |
||||
return book; |
||||
} |
||||
|
||||
private void initTOCResource(Book book) { |
||||
Resource tocResource; |
||||
try { |
||||
tocResource = NCXDocument.createNCXResource(book); |
||||
Resource currentTocResource = book.getSpine().getTocResource(); |
||||
if (currentTocResource != null) { |
||||
book.getResources().remove(currentTocResource.getHref()); |
||||
} |
||||
book.getSpine().setTocResource(tocResource); |
||||
book.getResources().add(tocResource); |
||||
} catch (Exception ex) { |
||||
Log.e(TAG, |
||||
"Error writing table of contents: " |
||||
+ ex.getClass().getName() + ": " + ex.getMessage(), ex); |
||||
} |
||||
} |
||||
|
||||
|
||||
private void writeResources(Book book, ZipOutputStream resultStream) |
||||
throws IOException { |
||||
for (Resource resource : book.getResources().getAll()) { |
||||
writeResource(resource, resultStream); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Writes the resource to the resultStream. |
||||
* |
||||
* @param resource |
||||
* @param resultStream |
||||
* @throws IOException |
||||
*/ |
||||
private void writeResource(Resource resource, ZipOutputStream resultStream) |
||||
throws IOException { |
||||
if (resource == null) { |
||||
return; |
||||
} |
||||
try { |
||||
resultStream.putNextEntry(new ZipEntry("OEBPS/" + resource.getHref())); |
||||
InputStream inputStream = resource.getInputStream(); |
||||
IOUtil.copy(inputStream, resultStream); |
||||
inputStream.close(); |
||||
} catch (Exception e) { |
||||
Log.e(TAG,e.getMessage(), e); |
||||
} |
||||
} |
||||
|
||||
|
||||
private void writePackageDocument(Book book, ZipOutputStream resultStream) |
||||
throws IOException { |
||||
resultStream.putNextEntry(new ZipEntry("OEBPS/content.opf")); |
||||
XmlSerializer xmlSerializer = EpubProcessorSupport |
||||
.createXmlSerializer(resultStream); |
||||
PackageDocumentWriter.write(this, xmlSerializer, book); |
||||
xmlSerializer.flush(); |
||||
// String resultAsString = result.toString();
|
||||
// resultStream.write(resultAsString.getBytes(Constants.ENCODING));
|
||||
} |
||||
|
||||
/** |
||||
* Writes the META-INF/container.xml file. |
||||
* |
||||
* @param resultStream |
||||
* @throws IOException |
||||
*/ |
||||
private void writeContainer(ZipOutputStream resultStream) throws IOException { |
||||
resultStream.putNextEntry(new ZipEntry("META-INF/container.xml")); |
||||
Writer out = new OutputStreamWriter(resultStream); |
||||
out.write("<?xml version=\"1.0\"?>\n"); |
||||
out.write( |
||||
"<container version=\"1.0\" xmlns=\"urn:oasis:names:tc:opendocument:xmlns:container\">\n"); |
||||
out.write("\t<rootfiles>\n"); |
||||
out.write( |
||||
"\t\t<rootfile full-path=\"OEBPS/content.opf\" media-type=\"application/oebps-package+xml\"/>\n"); |
||||
out.write("\t</rootfiles>\n"); |
||||
out.write("</container>"); |
||||
out.flush(); |
||||
} |
||||
|
||||
/** |
||||
* Stores the mimetype as an uncompressed file in the ZipOutputStream. |
||||
* |
||||
* @param resultStream |
||||
* @throws IOException |
||||
*/ |
||||
private void writeMimeType(ZipOutputStream resultStream) throws IOException { |
||||
ZipEntry mimetypeZipEntry = new ZipEntry("mimetype"); |
||||
mimetypeZipEntry.setMethod(ZipEntry.STORED); |
||||
byte[] mimetypeBytes = MediaTypes.EPUB.getName().getBytes(); |
||||
mimetypeZipEntry.setSize(mimetypeBytes.length); |
||||
mimetypeZipEntry.setCrc(calculateCrc(mimetypeBytes)); |
||||
resultStream.putNextEntry(mimetypeZipEntry); |
||||
resultStream.write(mimetypeBytes); |
||||
} |
||||
|
||||
private long calculateCrc(byte[] data) { |
||||
CRC32 crc = new CRC32(); |
||||
crc.update(data); |
||||
return crc.getValue(); |
||||
} |
||||
|
||||
String getNcxId() { |
||||
return "ncx"; |
||||
} |
||||
|
||||
String getNcxHref() { |
||||
return "toc.ncx"; |
||||
} |
||||
|
||||
String getNcxMediaType() { |
||||
return MediaTypes.NCX.getName(); |
||||
} |
||||
|
||||
public BookProcessor getBookProcessor() { |
||||
return bookProcessor; |
||||
} |
||||
|
||||
|
||||
public void setBookProcessor(BookProcessor bookProcessor) { |
||||
this.bookProcessor = bookProcessor; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,9 @@ |
||||
package me.ag2s.epublib.epub; |
||||
|
||||
import me.ag2s.epublib.domain.Resource; |
||||
import java.io.OutputStream; |
||||
|
||||
public interface HtmlProcessor { |
||||
|
||||
void processHtmlResource(Resource resource, OutputStream out); |
||||
} |
@ -0,0 +1,5 @@ |
||||
package me.ag2s.epublib.epub; |
||||
|
||||
public class Main { |
||||
|
||||
} |
@ -0,0 +1,297 @@ |
||||
package me.ag2s.epublib.epub; |
||||
|
||||
import android.util.Log; |
||||
|
||||
import me.ag2s.epublib.Constants; |
||||
import me.ag2s.epublib.domain.Author; |
||||
import me.ag2s.epublib.domain.Book; |
||||
import me.ag2s.epublib.domain.Identifier; |
||||
import me.ag2s.epublib.domain.MediaTypes; |
||||
import me.ag2s.epublib.domain.Resource; |
||||
import me.ag2s.epublib.domain.TOCReference; |
||||
import me.ag2s.epublib.domain.TableOfContents; |
||||
import me.ag2s.epublib.util.ResourceUtil; |
||||
import me.ag2s.epublib.util.StringUtil; |
||||
//import io.documentnode.minilog.Logger;
|
||||
import java.io.ByteArrayOutputStream; |
||||
import java.io.IOException; |
||||
import java.io.UnsupportedEncodingException; |
||||
import java.net.URLDecoder; |
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
import java.util.zip.ZipEntry; |
||||
import java.util.zip.ZipOutputStream; |
||||
//import javax.xml.stream.FactoryConfigurationError;
|
||||
import org.w3c.dom.Document; |
||||
import org.w3c.dom.Element; |
||||
import org.w3c.dom.Node; |
||||
import org.w3c.dom.NodeList; |
||||
import org.xmlpull.v1.XmlSerializer; |
||||
|
||||
/** |
||||
* Writes the ncx document as defined by namespace http://www.daisy.org/z3986/2005/ncx/
|
||||
* |
||||
* @author paul |
||||
*/ |
||||
public class NCXDocument { |
||||
|
||||
public static final String NAMESPACE_NCX = "http://www.daisy.org/z3986/2005/ncx/"; |
||||
public static final String PREFIX_NCX = "ncx"; |
||||
public static final String NCX_ITEM_ID = "ncx"; |
||||
public static final String DEFAULT_NCX_HREF = "toc.ncx"; |
||||
public static final String PREFIX_DTB = "dtb"; |
||||
|
||||
private static String TAG = NCXDocument.class.getName(); |
||||
|
||||
private interface NCXTags { |
||||
|
||||
String ncx = "ncx"; |
||||
String meta = "meta"; |
||||
String navPoint = "navPoint"; |
||||
String navMap = "navMap"; |
||||
String navLabel = "navLabel"; |
||||
String content = "content"; |
||||
String text = "text"; |
||||
String docTitle = "docTitle"; |
||||
String docAuthor = "docAuthor"; |
||||
String head = "head"; |
||||
} |
||||
|
||||
private interface NCXAttributes { |
||||
|
||||
String src = "src"; |
||||
String name = "name"; |
||||
String content = "content"; |
||||
String id = "id"; |
||||
String playOrder = "playOrder"; |
||||
String clazz = "class"; |
||||
String version = "version"; |
||||
} |
||||
|
||||
private interface NCXAttributeValues { |
||||
|
||||
String chapter = "chapter"; |
||||
String version = "2005-1"; |
||||
|
||||
} |
||||
|
||||
public static Resource read(Book book, EpubReader epubReader) { |
||||
Log.d(TAG, book.getVersion()); |
||||
String version = book.getVersion(); |
||||
if (version.startsWith("2.")) { |
||||
return NCXDocumentV2.read(book, epubReader); |
||||
} else if (version.startsWith("3.")) { |
||||
return NCXDocumentV3.read(book, epubReader); |
||||
} else { |
||||
return NCXDocumentV2.read(book, epubReader); |
||||
} |
||||
|
||||
} |
||||
|
||||
private static List<TOCReference> readTOCReferences(NodeList navpoints, |
||||
Book book) { |
||||
Log.d(TAG, book.getVersion()); |
||||
String version = book.getVersion(); |
||||
if (version.startsWith("2.")) { |
||||
return NCXDocumentV2.readTOCReferences(navpoints,book); |
||||
} else if (version.startsWith("3.")) { |
||||
return NCXDocumentV3.readTOCReferences(navpoints,book); |
||||
} else { |
||||
return NCXDocumentV2.readTOCReferences(navpoints,book); |
||||
} |
||||
|
||||
} |
||||
|
||||
static TOCReference readTOCReference(Element navpointElement, Book book) { |
||||
Log.d(TAG, book.getVersion()); |
||||
String version = book.getVersion(); |
||||
if (version.startsWith("2.")) { |
||||
return NCXDocumentV2.readTOCReference(navpointElement,book); |
||||
} else if (version.startsWith("3.")) { |
||||
return NCXDocumentV3.readTOCReference(navpointElement,book); |
||||
} else { |
||||
return NCXDocumentV2.readTOCReference(navpointElement,book); |
||||
} |
||||
|
||||
} |
||||
|
||||
private static String readNavReference(Element navpointElement) { |
||||
Element contentElement = DOMUtil |
||||
.getFirstElementByTagNameNS(navpointElement, NAMESPACE_NCX, |
||||
NCXTags.content); |
||||
String result = DOMUtil |
||||
.getAttribute(contentElement, NAMESPACE_NCX, NCXAttributes.src); |
||||
try { |
||||
result = URLDecoder.decode(result, Constants.CHARACTER_ENCODING); |
||||
} catch (UnsupportedEncodingException e) { |
||||
Log.e(TAG, e.getMessage()); |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
private static String readNavLabel(Element navpointElement) { |
||||
Element navLabel = DOMUtil |
||||
.getFirstElementByTagNameNS(navpointElement, NAMESPACE_NCX, |
||||
NCXTags.navLabel); |
||||
return DOMUtil.getTextChildrenContent(DOMUtil |
||||
.getFirstElementByTagNameNS(navLabel, NAMESPACE_NCX, NCXTags.text)); |
||||
} |
||||
|
||||
|
||||
public static void write(EpubWriter epubWriter, Book book, |
||||
ZipOutputStream resultStream) throws IOException { |
||||
resultStream |
||||
.putNextEntry(new ZipEntry(book.getSpine().getTocResource().getHref())); |
||||
XmlSerializer out = EpubProcessorSupport.createXmlSerializer(resultStream); |
||||
write(out, book); |
||||
out.flush(); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Generates a resource containing an xml document containing the table of contents of the book in ncx format. |
||||
* |
||||
* @param xmlSerializer the serializer used |
||||
* @param book the book to serialize |
||||
* @throws IOException |
||||
* @throws IllegalStateException |
||||
* @throws IllegalArgumentException |
||||
* @1throws FactoryConfigurationError |
||||
*/ |
||||
public static void write(XmlSerializer xmlSerializer, Book book) |
||||
throws IllegalArgumentException, IllegalStateException, IOException { |
||||
write(xmlSerializer, book.getMetadata().getIdentifiers(), book.getTitle(), |
||||
book.getMetadata().getAuthors(), book.getTableOfContents()); |
||||
} |
||||
|
||||
public static Resource createNCXResource(Book book) |
||||
throws IllegalArgumentException, IllegalStateException, IOException { |
||||
return createNCXResource(book.getMetadata().getIdentifiers(), |
||||
book.getTitle(), book.getMetadata().getAuthors(), |
||||
book.getTableOfContents()); |
||||
} |
||||
|
||||
public static Resource createNCXResource(List<Identifier> identifiers, |
||||
String title, List<Author> authors, TableOfContents tableOfContents) |
||||
throws IllegalArgumentException, IllegalStateException, IOException { |
||||
ByteArrayOutputStream data = new ByteArrayOutputStream(); |
||||
XmlSerializer out = EpubProcessorSupport.createXmlSerializer(data); |
||||
write(out, identifiers, title, authors, tableOfContents); |
||||
Resource resource = new Resource(NCX_ITEM_ID, data.toByteArray(), |
||||
DEFAULT_NCX_HREF, MediaTypes.NCX); |
||||
return resource; |
||||
} |
||||
|
||||
public static void write(XmlSerializer serializer, |
||||
List<Identifier> identifiers, String title, List<Author> authors, |
||||
TableOfContents tableOfContents) |
||||
throws IllegalArgumentException, IllegalStateException, IOException { |
||||
serializer.startDocument(Constants.CHARACTER_ENCODING, false); |
||||
serializer.setPrefix(EpubWriter.EMPTY_NAMESPACE_PREFIX, NAMESPACE_NCX); |
||||
serializer.startTag(NAMESPACE_NCX, NCXTags.ncx); |
||||
// serializer.writeNamespace("ncx", NAMESPACE_NCX);
|
||||
// serializer.attribute("xmlns", NAMESPACE_NCX);
|
||||
serializer |
||||
.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, NCXAttributes.version, |
||||
NCXAttributeValues.version); |
||||
serializer.startTag(NAMESPACE_NCX, NCXTags.head); |
||||
|
||||
for (Identifier identifier : identifiers) { |
||||
writeMetaElement(identifier.getScheme(), identifier.getValue(), |
||||
serializer); |
||||
} |
||||
|
||||
writeMetaElement("generator", Constants.EPUB4J_GENERATOR_NAME, serializer); |
||||
writeMetaElement("depth", String.valueOf(tableOfContents.calculateDepth()), |
||||
serializer); |
||||
writeMetaElement("totalPageCount", "0", serializer); |
||||
writeMetaElement("maxPageNumber", "0", serializer); |
||||
|
||||
serializer.endTag(NAMESPACE_NCX, "head"); |
||||
|
||||
serializer.startTag(NAMESPACE_NCX, NCXTags.docTitle); |
||||
serializer.startTag(NAMESPACE_NCX, NCXTags.text); |
||||
// write the first title
|
||||
serializer.text(StringUtil.defaultIfNull(title)); |
||||
serializer.endTag(NAMESPACE_NCX, NCXTags.text); |
||||
serializer.endTag(NAMESPACE_NCX, NCXTags.docTitle); |
||||
|
||||
for (Author author : authors) { |
||||
serializer.startTag(NAMESPACE_NCX, NCXTags.docAuthor); |
||||
serializer.startTag(NAMESPACE_NCX, NCXTags.text); |
||||
serializer.text(author.getLastname() + ", " + author.getFirstname()); |
||||
serializer.endTag(NAMESPACE_NCX, NCXTags.text); |
||||
serializer.endTag(NAMESPACE_NCX, NCXTags.docAuthor); |
||||
} |
||||
|
||||
serializer.startTag(NAMESPACE_NCX, NCXTags.navMap); |
||||
writeNavPoints(tableOfContents.getTocReferences(), 1, serializer); |
||||
serializer.endTag(NAMESPACE_NCX, NCXTags.navMap); |
||||
|
||||
serializer.endTag(NAMESPACE_NCX, "ncx"); |
||||
serializer.endDocument(); |
||||
} |
||||
|
||||
|
||||
private static void writeMetaElement(String dtbName, String content, |
||||
XmlSerializer serializer) |
||||
throws IllegalArgumentException, IllegalStateException, IOException { |
||||
serializer.startTag(NAMESPACE_NCX, NCXTags.meta); |
||||
serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, NCXAttributes.name, |
||||
PREFIX_DTB + ":" + dtbName); |
||||
serializer |
||||
.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, NCXAttributes.content, |
||||
content); |
||||
serializer.endTag(NAMESPACE_NCX, NCXTags.meta); |
||||
} |
||||
|
||||
private static int writeNavPoints(List<TOCReference> tocReferences, |
||||
int playOrder, |
||||
XmlSerializer serializer) |
||||
throws IllegalArgumentException, IllegalStateException, IOException { |
||||
for (TOCReference tocReference : tocReferences) { |
||||
if (tocReference.getResource() == null) { |
||||
playOrder = writeNavPoints(tocReference.getChildren(), playOrder, |
||||
serializer); |
||||
continue; |
||||
} |
||||
writeNavPointStart(tocReference, playOrder, serializer); |
||||
playOrder++; |
||||
if (!tocReference.getChildren().isEmpty()) { |
||||
playOrder = writeNavPoints(tocReference.getChildren(), playOrder, |
||||
serializer); |
||||
} |
||||
writeNavPointEnd(tocReference, serializer); |
||||
} |
||||
return playOrder; |
||||
} |
||||
|
||||
|
||||
private static void writeNavPointStart(TOCReference tocReference, |
||||
int playOrder, XmlSerializer serializer) |
||||
throws IllegalArgumentException, IllegalStateException, IOException { |
||||
serializer.startTag(NAMESPACE_NCX, NCXTags.navPoint); |
||||
serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, NCXAttributes.id, |
||||
"navPoint-" + playOrder); |
||||
serializer |
||||
.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, NCXAttributes.playOrder, |
||||
String.valueOf(playOrder)); |
||||
serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, NCXAttributes.clazz, |
||||
NCXAttributeValues.chapter); |
||||
serializer.startTag(NAMESPACE_NCX, NCXTags.navLabel); |
||||
serializer.startTag(NAMESPACE_NCX, NCXTags.text); |
||||
serializer.text(tocReference.getTitle()); |
||||
serializer.endTag(NAMESPACE_NCX, NCXTags.text); |
||||
serializer.endTag(NAMESPACE_NCX, NCXTags.navLabel); |
||||
serializer.startTag(NAMESPACE_NCX, NCXTags.content); |
||||
serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, NCXAttributes.src, |
||||
tocReference.getCompleteHref()); |
||||
serializer.endTag(NAMESPACE_NCX, NCXTags.content); |
||||
} |
||||
|
||||
private static void writeNavPointEnd(TOCReference tocReference, |
||||
XmlSerializer serializer) |
||||
throws IllegalArgumentException, IllegalStateException, IOException { |
||||
serializer.endTag(NAMESPACE_NCX, NCXTags.navPoint); |
||||
} |
||||
} |
@ -0,0 +1,334 @@ |
||||
package me.ag2s.epublib.epub; |
||||
|
||||
import android.util.Log; |
||||
|
||||
import org.w3c.dom.Document; |
||||
import org.w3c.dom.Element; |
||||
import org.w3c.dom.Node; |
||||
import org.w3c.dom.NodeList; |
||||
import org.xmlpull.v1.XmlSerializer; |
||||
|
||||
import java.io.ByteArrayOutputStream; |
||||
import java.io.IOException; |
||||
import java.io.UnsupportedEncodingException; |
||||
import java.net.URLDecoder; |
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
import java.util.zip.ZipEntry; |
||||
import java.util.zip.ZipOutputStream; |
||||
|
||||
import me.ag2s.epublib.Constants; |
||||
import me.ag2s.epublib.domain.Author; |
||||
import me.ag2s.epublib.domain.Book; |
||||
import me.ag2s.epublib.domain.Identifier; |
||||
import me.ag2s.epublib.domain.MediaTypes; |
||||
import me.ag2s.epublib.domain.Resource; |
||||
import me.ag2s.epublib.domain.TOCReference; |
||||
import me.ag2s.epublib.domain.TableOfContents; |
||||
import me.ag2s.epublib.util.ResourceUtil; |
||||
import me.ag2s.epublib.util.StringUtil; |
||||
|
||||
/** |
||||
* Writes the ncx document as defined by namespace http://www.daisy.org/z3986/2005/ncx/
|
||||
* |
||||
* @author paul |
||||
*/ |
||||
public class NCXDocumentV2 extends NCXDocument{ |
||||
|
||||
public static final String NAMESPACE_NCX = "http://www.daisy.org/z3986/2005/ncx/"; |
||||
public static final String PREFIX_NCX = "ncx"; |
||||
public static final String NCX_ITEM_ID = "ncx"; |
||||
public static final String DEFAULT_NCX_HREF = "toc.ncx"; |
||||
public static final String PREFIX_DTB = "dtb"; |
||||
|
||||
private static String TAG= NCXDocumentV2.class.getName(); |
||||
|
||||
private interface NCXTags { |
||||
|
||||
String ncx = "ncx"; |
||||
String meta = "meta"; |
||||
String navPoint = "navPoint"; |
||||
String navMap = "navMap"; |
||||
String navLabel = "navLabel"; |
||||
String content = "content"; |
||||
String text = "text"; |
||||
String docTitle = "docTitle"; |
||||
String docAuthor = "docAuthor"; |
||||
String head = "head"; |
||||
} |
||||
|
||||
private interface NCXAttributes { |
||||
|
||||
String src = "src"; |
||||
String name = "name"; |
||||
String content = "content"; |
||||
String id = "id"; |
||||
String playOrder = "playOrder"; |
||||
String clazz = "class"; |
||||
String version = "version"; |
||||
} |
||||
|
||||
private interface NCXAttributeValues { |
||||
|
||||
String chapter = "chapter"; |
||||
String version = "2005-1"; |
||||
|
||||
} |
||||
|
||||
public static Resource read(Book book, EpubReader epubReader) { |
||||
Resource ncxResource = null; |
||||
if (book.getSpine().getTocResource() == null) { |
||||
Log.e(TAG,"Book does not contain a table of contents file"); |
||||
return ncxResource; |
||||
} |
||||
try { |
||||
ncxResource = book.getSpine().getTocResource(); |
||||
if (ncxResource == null) { |
||||
return ncxResource; |
||||
} |
||||
Log.d(TAG,ncxResource.getHref()); |
||||
Document ncxDocument = ResourceUtil.getAsDocument(ncxResource); |
||||
Element navMapElement = DOMUtil |
||||
.getFirstElementByTagNameNS(ncxDocument.getDocumentElement(), |
||||
NAMESPACE_NCX, NCXTags.navMap); |
||||
|
||||
TableOfContents tableOfContents = new TableOfContents( |
||||
readTOCReferences(navMapElement.getChildNodes(), book)); |
||||
book.setTableOfContents(tableOfContents); |
||||
} catch (Exception e) { |
||||
Log.e(TAG,e.getMessage(), e); |
||||
} |
||||
return ncxResource; |
||||
} |
||||
|
||||
static List<TOCReference> readTOCReferences(NodeList navpoints, |
||||
Book book) { |
||||
if (navpoints == null) { |
||||
return new ArrayList<>(); |
||||
} |
||||
List<TOCReference> result = new ArrayList<>( |
||||
navpoints.getLength()); |
||||
for (int i = 0; i < navpoints.getLength(); i++) { |
||||
Node node = navpoints.item(i); |
||||
if (node.getNodeType() != Document.ELEMENT_NODE) { |
||||
continue; |
||||
} |
||||
if (!(node.getLocalName().equals(NCXTags.navPoint))) { |
||||
continue; |
||||
} |
||||
TOCReference tocReference = readTOCReference((Element) node, book); |
||||
result.add(tocReference); |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
static TOCReference readTOCReference(Element navpointElement, Book book) { |
||||
String label = readNavLabel(navpointElement); |
||||
//Log.d(TAG,"label:"+label);
|
||||
String tocResourceRoot = StringUtil |
||||
.substringBeforeLast(book.getSpine().getTocResource().getHref(), '/'); |
||||
if (tocResourceRoot.length() == book.getSpine().getTocResource().getHref() |
||||
.length()) { |
||||
tocResourceRoot = ""; |
||||
} else { |
||||
tocResourceRoot = tocResourceRoot + "/"; |
||||
} |
||||
String reference = StringUtil |
||||
.collapsePathDots(tocResourceRoot + readNavReference(navpointElement)); |
||||
String href = StringUtil |
||||
.substringBefore(reference, Constants.FRAGMENT_SEPARATOR_CHAR); |
||||
String fragmentId = StringUtil |
||||
.substringAfter(reference, Constants.FRAGMENT_SEPARATOR_CHAR); |
||||
Resource resource = book.getResources().getByHref(href); |
||||
if (resource == null) { |
||||
Log.e(TAG,"Resource with href " + href + " in NCX document not found"); |
||||
} |
||||
TOCReference result = new TOCReference(label, resource, fragmentId); |
||||
List<TOCReference> childTOCReferences = readTOCReferences( |
||||
navpointElement.getChildNodes(), book); |
||||
result.setChildren(childTOCReferences); |
||||
return result; |
||||
} |
||||
|
||||
private static String readNavReference(Element navpointElement) { |
||||
Element contentElement = DOMUtil |
||||
.getFirstElementByTagNameNS(navpointElement, NAMESPACE_NCX, |
||||
NCXTags.content); |
||||
String result = DOMUtil |
||||
.getAttribute(contentElement, NAMESPACE_NCX, NCXAttributes.src); |
||||
try { |
||||
result = URLDecoder.decode(result, Constants.CHARACTER_ENCODING); |
||||
} catch (UnsupportedEncodingException e) { |
||||
Log.e(TAG,e.getMessage()); |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
private static String readNavLabel(Element navpointElement) { |
||||
//Log.d(TAG,navpointElement.getTagName());
|
||||
Element navLabel = DOMUtil |
||||
.getFirstElementByTagNameNS(navpointElement, NAMESPACE_NCX, |
||||
NCXTags.navLabel); |
||||
return DOMUtil.getTextChildrenContent(DOMUtil |
||||
.getFirstElementByTagNameNS(navLabel, NAMESPACE_NCX, NCXTags.text)); |
||||
} |
||||
|
||||
|
||||
public static void write(EpubWriter epubWriter, Book book, |
||||
ZipOutputStream resultStream) throws IOException { |
||||
resultStream |
||||
.putNextEntry(new ZipEntry(book.getSpine().getTocResource().getHref())); |
||||
XmlSerializer out = EpubProcessorSupport.createXmlSerializer(resultStream); |
||||
write(out, book); |
||||
out.flush(); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Generates a resource containing an xml document containing the table of contents of the book in ncx format. |
||||
* |
||||
* @param xmlSerializer the serializer used |
||||
* @param book the book to serialize |
||||
* |
||||
* @1throws FactoryConfigurationError |
||||
* @throws IOException |
||||
* @throws IllegalStateException |
||||
* @throws IllegalArgumentException |
||||
*/ |
||||
public static void write(XmlSerializer xmlSerializer, Book book) |
||||
throws IllegalArgumentException, IllegalStateException, IOException { |
||||
write(xmlSerializer, book.getMetadata().getIdentifiers(), book.getTitle(), |
||||
book.getMetadata().getAuthors(), book.getTableOfContents()); |
||||
} |
||||
|
||||
public static Resource createNCXResource(Book book) |
||||
throws IllegalArgumentException, IllegalStateException, IOException { |
||||
return createNCXResource(book.getMetadata().getIdentifiers(), |
||||
book.getTitle(), book.getMetadata().getAuthors(), |
||||
book.getTableOfContents()); |
||||
} |
||||
|
||||
public static Resource createNCXResource(List<Identifier> identifiers, |
||||
String title, List<Author> authors, TableOfContents tableOfContents) |
||||
throws IllegalArgumentException, IllegalStateException, IOException { |
||||
ByteArrayOutputStream data = new ByteArrayOutputStream(); |
||||
XmlSerializer out = EpubProcessorSupport.createXmlSerializer(data); |
||||
write(out, identifiers, title, authors, tableOfContents); |
||||
Resource resource = new Resource(NCX_ITEM_ID, data.toByteArray(), |
||||
DEFAULT_NCX_HREF, MediaTypes.NCX); |
||||
return resource; |
||||
} |
||||
|
||||
public static void write(XmlSerializer serializer, |
||||
List<Identifier> identifiers, String title, List<Author> authors, |
||||
TableOfContents tableOfContents) |
||||
throws IllegalArgumentException, IllegalStateException, IOException { |
||||
serializer.startDocument(Constants.CHARACTER_ENCODING, false); |
||||
serializer.setPrefix(EpubWriter.EMPTY_NAMESPACE_PREFIX, NAMESPACE_NCX); |
||||
serializer.startTag(NAMESPACE_NCX, NCXTags.ncx); |
||||
// serializer.writeNamespace("ncx", NAMESPACE_NCX);
|
||||
// serializer.attribute("xmlns", NAMESPACE_NCX);
|
||||
serializer |
||||
.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, NCXAttributes.version, |
||||
NCXAttributeValues.version); |
||||
serializer.startTag(NAMESPACE_NCX, NCXTags.head); |
||||
|
||||
for (Identifier identifier : identifiers) { |
||||
writeMetaElement(identifier.getScheme(), identifier.getValue(), |
||||
serializer); |
||||
} |
||||
|
||||
writeMetaElement("generator", Constants.EPUB4J_GENERATOR_NAME, serializer); |
||||
writeMetaElement("depth", String.valueOf(tableOfContents.calculateDepth()), |
||||
serializer); |
||||
writeMetaElement("totalPageCount", "0", serializer); |
||||
writeMetaElement("maxPageNumber", "0", serializer); |
||||
|
||||
serializer.endTag(NAMESPACE_NCX, "head"); |
||||
|
||||
serializer.startTag(NAMESPACE_NCX, NCXTags.docTitle); |
||||
serializer.startTag(NAMESPACE_NCX, NCXTags.text); |
||||
// write the first title
|
||||
serializer.text(StringUtil.defaultIfNull(title)); |
||||
serializer.endTag(NAMESPACE_NCX, NCXTags.text); |
||||
serializer.endTag(NAMESPACE_NCX, NCXTags.docTitle); |
||||
|
||||
for (Author author : authors) { |
||||
serializer.startTag(NAMESPACE_NCX, NCXTags.docAuthor); |
||||
serializer.startTag(NAMESPACE_NCX, NCXTags.text); |
||||
serializer.text(author.getLastname() + ", " + author.getFirstname()); |
||||
serializer.endTag(NAMESPACE_NCX, NCXTags.text); |
||||
serializer.endTag(NAMESPACE_NCX, NCXTags.docAuthor); |
||||
} |
||||
|
||||
serializer.startTag(NAMESPACE_NCX, NCXTags.navMap); |
||||
writeNavPoints(tableOfContents.getTocReferences(), 1, serializer); |
||||
serializer.endTag(NAMESPACE_NCX, NCXTags.navMap); |
||||
|
||||
serializer.endTag(NAMESPACE_NCX, "ncx"); |
||||
serializer.endDocument(); |
||||
} |
||||
|
||||
|
||||
private static void writeMetaElement(String dtbName, String content, |
||||
XmlSerializer serializer) |
||||
throws IllegalArgumentException, IllegalStateException, IOException { |
||||
serializer.startTag(NAMESPACE_NCX, NCXTags.meta); |
||||
serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, NCXAttributes.name, |
||||
PREFIX_DTB + ":" + dtbName); |
||||
serializer |
||||
.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, NCXAttributes.content, |
||||
content); |
||||
serializer.endTag(NAMESPACE_NCX, NCXTags.meta); |
||||
} |
||||
|
||||
private static int writeNavPoints(List<TOCReference> tocReferences, |
||||
int playOrder, |
||||
XmlSerializer serializer) |
||||
throws IllegalArgumentException, IllegalStateException, IOException { |
||||
for (TOCReference tocReference : tocReferences) { |
||||
if (tocReference.getResource() == null) { |
||||
playOrder = writeNavPoints(tocReference.getChildren(), playOrder, |
||||
serializer); |
||||
continue; |
||||
} |
||||
writeNavPointStart(tocReference, playOrder, serializer); |
||||
playOrder++; |
||||
if (!tocReference.getChildren().isEmpty()) { |
||||
playOrder = writeNavPoints(tocReference.getChildren(), playOrder, |
||||
serializer); |
||||
} |
||||
writeNavPointEnd(tocReference, serializer); |
||||
} |
||||
return playOrder; |
||||
} |
||||
|
||||
|
||||
private static void writeNavPointStart(TOCReference tocReference, |
||||
int playOrder, XmlSerializer serializer) |
||||
throws IllegalArgumentException, IllegalStateException, IOException { |
||||
serializer.startTag(NAMESPACE_NCX, NCXTags.navPoint); |
||||
serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, NCXAttributes.id, |
||||
"navPoint-" + playOrder); |
||||
serializer |
||||
.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, NCXAttributes.playOrder, |
||||
String.valueOf(playOrder)); |
||||
serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, NCXAttributes.clazz, |
||||
NCXAttributeValues.chapter); |
||||
serializer.startTag(NAMESPACE_NCX, NCXTags.navLabel); |
||||
serializer.startTag(NAMESPACE_NCX, NCXTags.text); |
||||
serializer.text(tocReference.getTitle()); |
||||
serializer.endTag(NAMESPACE_NCX, NCXTags.text); |
||||
serializer.endTag(NAMESPACE_NCX, NCXTags.navLabel); |
||||
serializer.startTag(NAMESPACE_NCX, NCXTags.content); |
||||
serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, NCXAttributes.src, |
||||
tocReference.getCompleteHref()); |
||||
serializer.endTag(NAMESPACE_NCX, NCXTags.content); |
||||
} |
||||
|
||||
private static void writeNavPointEnd(TOCReference tocReference, |
||||
XmlSerializer serializer) |
||||
throws IllegalArgumentException, IllegalStateException, IOException { |
||||
serializer.endTag(NAMESPACE_NCX, NCXTags.navPoint); |
||||
} |
||||
} |
@ -0,0 +1,206 @@ |
||||
package me.ag2s.epublib.epub; |
||||
|
||||
import android.util.Log; |
||||
|
||||
import org.w3c.dom.Document; |
||||
import org.w3c.dom.Element; |
||||
import org.w3c.dom.Node; |
||||
import org.w3c.dom.NodeList; |
||||
import org.xmlpull.v1.XmlSerializer; |
||||
|
||||
import java.io.ByteArrayOutputStream; |
||||
import java.io.IOException; |
||||
import java.io.UnsupportedEncodingException; |
||||
import java.net.URLDecoder; |
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
import java.util.zip.ZipEntry; |
||||
import java.util.zip.ZipOutputStream; |
||||
|
||||
import me.ag2s.epublib.Constants; |
||||
import me.ag2s.epublib.domain.Author; |
||||
import me.ag2s.epublib.domain.Book; |
||||
import me.ag2s.epublib.domain.Identifier; |
||||
import me.ag2s.epublib.domain.MediaTypes; |
||||
import me.ag2s.epublib.domain.Resource; |
||||
import me.ag2s.epublib.domain.TOCReference; |
||||
import me.ag2s.epublib.domain.TableOfContents; |
||||
import me.ag2s.epublib.util.ResourceUtil; |
||||
import me.ag2s.epublib.util.StringUtil; |
||||
|
||||
public class NCXDocumentV3 extends NCXDocument { |
||||
public static final String NAMESPACE_NCX = ""; |
||||
public static final String PREFIX_NCX = "html"; |
||||
public static final String NCX_ITEM_ID = "ncx"; |
||||
public static final String DEFAULT_NCX_HREF = "toc.xhtml"; |
||||
public static final String PREFIX_DTB = "dtb"; |
||||
private static String TAG = NCXDocumentV3.class.getName(); |
||||
|
||||
private interface NCXTags { |
||||
//String nav="nav";
|
||||
//String li="li";
|
||||
//String ncx = "ncx";
|
||||
String meta = "meta"; |
||||
String navPoint = "li"; |
||||
String navMap = "nav"; |
||||
String navLabel = "a"; |
||||
String content = "a"; |
||||
String text = "text"; |
||||
String docTitle = "docTitle"; |
||||
String docAuthor = "docAuthor"; |
||||
String head = "head"; |
||||
} |
||||
|
||||
private interface NCXAttributes { |
||||
|
||||
String src = "href"; |
||||
String name = "name"; |
||||
String content = "content"; |
||||
String id = "id"; |
||||
String playOrder = "playOrder"; |
||||
String clazz = "class"; |
||||
String version = "version"; |
||||
} |
||||
|
||||
private interface NCXAttributeValues { |
||||
|
||||
String chapter = "chapter"; |
||||
String version = "2007"; |
||||
|
||||
} |
||||
|
||||
public static Resource read(Book book, EpubReader epubReader) { |
||||
Resource ncxResource = null; |
||||
if (book.getSpine().getTocResource() == null) { |
||||
Log.e(TAG, "Book does not contain a table of contents file"); |
||||
return ncxResource; |
||||
} |
||||
try { |
||||
ncxResource = book.getSpine().getTocResource(); |
||||
if (ncxResource == null) { |
||||
return ncxResource; |
||||
} |
||||
//Log.d(TAG, ncxResource.getHref());
|
||||
|
||||
Document ncxDocument = ResourceUtil.getAsDocument(ncxResource); |
||||
//Log.d(TAG, ncxDocument.getNodeName());
|
||||
|
||||
Element navMapElement = (Element) ncxDocument.getElementsByTagName("nav").item(0); |
||||
navMapElement = (Element) navMapElement.getElementsByTagName("ol").item(0); |
||||
Log.d(TAG, navMapElement.getTagName()); |
||||
|
||||
TableOfContents tableOfContents = new TableOfContents( |
||||
readTOCReferences(navMapElement.getChildNodes(), book)); |
||||
Log.d(TAG, tableOfContents.toString()); |
||||
book.setTableOfContents(tableOfContents); |
||||
} catch (Exception e) { |
||||
Log.e(TAG, e.getMessage(), e); |
||||
} |
||||
return ncxResource; |
||||
} |
||||
|
||||
public static List<TOCReference> doToc(Node n, Book book) { |
||||
List<TOCReference> result = new ArrayList<>(); |
||||
|
||||
if (n == null || n.getNodeType() != Document.ELEMENT_NODE) { |
||||
return result; |
||||
} else { |
||||
Element el = (Element) n; |
||||
NodeList nodeList = el.getElementsByTagName("li"); |
||||
for (int i = 0; i < nodeList.getLength(); i++) { |
||||
result.add(readTOCReference((Element) nodeList.item(i), book)); |
||||
} |
||||
} |
||||
|
||||
|
||||
return result; |
||||
} |
||||
|
||||
|
||||
static List<TOCReference> readTOCReferences(NodeList navpoints, |
||||
Book book) { |
||||
if (navpoints == null) { |
||||
return new ArrayList<>(); |
||||
} |
||||
//Log.d(TAG, "readTOCReferences:navpoints.getLength()" + navpoints.getLength());
|
||||
List<TOCReference> result = new ArrayList<>(); |
||||
for (int i = 0; i < navpoints.getLength(); i++) { |
||||
Node node = navpoints.item(i); |
||||
if (node == null || node.getNodeType() != Document.ELEMENT_NODE) { |
||||
continue; |
||||
} else { |
||||
Element el = (Element) node; |
||||
if (el.getTagName().equals("li")) { |
||||
result.add(readTOCReference(el, book)); |
||||
} |
||||
//NodeList nodeList=el.getElementsByTagName("li");
|
||||
//for (int i=0;i<nodeList.getLength();i++){
|
||||
//result.add(readTOCReference((Element) nodeList.item(i),book));
|
||||
// }
|
||||
} |
||||
//result.addAll(doToc(node, book));
|
||||
} |
||||
|
||||
|
||||
return result; |
||||
} |
||||
|
||||
|
||||
static TOCReference readTOCReference(Element navpointElement, Book book) { |
||||
String label = readNavLabel(navpointElement); |
||||
//Log.d(TAG, "label:" + label);
|
||||
String tocResourceRoot = StringUtil |
||||
.substringBeforeLast(book.getSpine().getTocResource().getHref(), '/'); |
||||
if (tocResourceRoot.length() == book.getSpine().getTocResource().getHref() |
||||
.length()) { |
||||
tocResourceRoot = ""; |
||||
} else { |
||||
tocResourceRoot = tocResourceRoot + "/"; |
||||
} |
||||
|
||||
String reference = StringUtil |
||||
.collapsePathDots(tocResourceRoot + readNavReference(navpointElement)); |
||||
String href = StringUtil |
||||
.substringBefore(reference, Constants.FRAGMENT_SEPARATOR_CHAR); |
||||
String fragmentId = StringUtil |
||||
.substringAfter(reference, Constants.FRAGMENT_SEPARATOR_CHAR); |
||||
Resource resource = book.getResources().getByHref(href); |
||||
if (resource == null) { |
||||
Log.e(TAG, "Resource with href " + href + " in NCX document not found"); |
||||
} |
||||
TOCReference result = new TOCReference(label, resource, fragmentId); |
||||
List<TOCReference> childTOCReferences = doToc(navpointElement, book); |
||||
//readTOCReferences(
|
||||
//navpointElement.getChildNodes(), book);
|
||||
result.setChildren(childTOCReferences); |
||||
return result; |
||||
} |
||||
|
||||
private static String readNavReference(Element navpointElement) { |
||||
//Log.d(TAG, "readNavReference:" + navpointElement.getTagName());
|
||||
Element contentElement = DOMUtil |
||||
.getFirstElementByTagNameNS(navpointElement, NAMESPACE_NCX, |
||||
NCXDocumentV3.NCXTags.content); |
||||
String result = DOMUtil |
||||
.getAttribute(contentElement, NAMESPACE_NCX, NCXDocumentV3.NCXAttributes.src); |
||||
try { |
||||
result = URLDecoder.decode(result, Constants.CHARACTER_ENCODING); |
||||
} catch (UnsupportedEncodingException e) { |
||||
Log.e(TAG, e.getMessage()); |
||||
} |
||||
|
||||
return result; |
||||
|
||||
} |
||||
|
||||
private static String readNavLabel(Element navpointElement) { |
||||
//Log.d(TAG, "readNavLabel:" + navpointElement.getTagName());
|
||||
Element navLabel = DOMUtil |
||||
.getFirstElementByTagNameNS(navpointElement, NAMESPACE_NCX, |
||||
NCXDocumentV3.NCXTags.navLabel); |
||||
return navLabel.getTextContent();//DOMUtil.getTextChildrenContent(DOMUtil
|
||||
//.getFirstElementByTagNameNS(navLabel, NAMESPACE_NCX, NCXDocumentV3.NCXTags.text));
|
||||
} |
||||
|
||||
|
||||
} |
@ -0,0 +1,89 @@ |
||||
package me.ag2s.epublib.epub; |
||||
|
||||
|
||||
/** |
||||
* Functionality shared by the PackageDocumentReader and the PackageDocumentWriter |
||||
* |
||||
* @author paul |
||||
* |
||||
*/ |
||||
public class PackageDocumentBase { |
||||
|
||||
public static final String BOOK_ID_ID = "BookId"; |
||||
public static final String NAMESPACE_OPF = "http://www.idpf.org/2007/opf"; |
||||
public static final String NAMESPACE_DUBLIN_CORE = "http://purl.org/dc/elements/1.1/"; |
||||
public static final String PREFIX_DUBLIN_CORE = "dc"; |
||||
//public static final String PREFIX_OPF = "opf";
|
||||
//Some Epub Reader not reconize opt:packge,So just let it empty;
|
||||
public static final String PREFIX_OPF = ""; |
||||
public static final String version="version"; |
||||
public static final String dateFormat = "yyyy-MM-dd"; |
||||
|
||||
protected interface DCTags { |
||||
|
||||
String title = "title"; |
||||
String creator = "creator"; |
||||
String subject = "subject"; |
||||
String description = "description"; |
||||
String publisher = "publisher"; |
||||
String contributor = "contributor"; |
||||
String date = "date"; |
||||
String type = "type"; |
||||
String format = "format"; |
||||
String identifier = "identifier"; |
||||
String source = "source"; |
||||
String language = "language"; |
||||
String relation = "relation"; |
||||
String coverage = "coverage"; |
||||
String rights = "rights"; |
||||
} |
||||
|
||||
protected interface DCAttributes { |
||||
|
||||
String scheme = "scheme"; |
||||
String id = "id"; |
||||
} |
||||
|
||||
protected interface OPFTags { |
||||
|
||||
String metadata = "metadata"; |
||||
String meta = "meta"; |
||||
String manifest = "manifest"; |
||||
String packageTag = "package"; |
||||
String itemref = "itemref"; |
||||
String spine = "spine"; |
||||
String reference = "reference"; |
||||
String guide = "guide"; |
||||
String item = "item"; |
||||
} |
||||
|
||||
protected interface OPFAttributes { |
||||
|
||||
String uniqueIdentifier = "unique-identifier"; |
||||
String idref = "idref"; |
||||
String name = "name"; |
||||
String content = "content"; |
||||
String type = "type"; |
||||
String href = "href"; |
||||
String linear = "linear"; |
||||
String event = "event"; |
||||
String role = "role"; |
||||
String file_as = "file-as"; |
||||
String id = "id"; |
||||
String media_type = "media-type"; |
||||
String title = "title"; |
||||
String toc = "toc"; |
||||
String version = "version"; |
||||
String scheme = "scheme"; |
||||
String property = "property"; |
||||
String properties="properties"; |
||||
} |
||||
|
||||
protected interface OPFValues { |
||||
|
||||
String meta_cover = "cover"; |
||||
String reference_cover = "cover"; |
||||
String no = "no"; |
||||
String generator = "generator"; |
||||
} |
||||
} |
@ -0,0 +1,223 @@ |
||||
package me.ag2s.epublib.epub; |
||||
|
||||
import android.util.Log; |
||||
|
||||
import me.ag2s.epublib.domain.Author; |
||||
import me.ag2s.epublib.domain.Date; |
||||
import me.ag2s.epublib.domain.Identifier; |
||||
import me.ag2s.epublib.domain.Metadata; |
||||
import me.ag2s.epublib.util.StringUtil; |
||||
//import io.documentnode.minilog.Logger;
|
||||
import java.util.ArrayList; |
||||
import java.util.HashMap; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import javax.xml.namespace.QName; |
||||
import org.w3c.dom.Document; |
||||
import org.w3c.dom.Element; |
||||
import org.w3c.dom.Node; |
||||
import org.w3c.dom.NodeList; |
||||
|
||||
/** |
||||
* Reads the package document metadata. |
||||
* |
||||
* In its own separate class because the PackageDocumentReader became a bit large and unwieldy. |
||||
* |
||||
* @author paul |
||||
*/ |
||||
// package
|
||||
class PackageDocumentMetadataReader extends PackageDocumentBase { |
||||
|
||||
private static String TAG= PackageDocumentMetadataReader.class.getName(); |
||||
|
||||
public static Metadata readMetadata(Document packageDocument) { |
||||
Metadata result = new Metadata(); |
||||
Element metadataElement = DOMUtil |
||||
.getFirstElementByTagNameNS(packageDocument.getDocumentElement(), |
||||
NAMESPACE_OPF, OPFTags.metadata); |
||||
if (metadataElement == null) { |
||||
Log.e(TAG,"Package does not contain element " + OPFTags.metadata); |
||||
return result; |
||||
} |
||||
result.setTitles(DOMUtil |
||||
.getElementsTextChild(metadataElement, NAMESPACE_DUBLIN_CORE, |
||||
DCTags.title)); |
||||
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; |
||||
} |
||||
|
||||
/** |
||||
* consumes meta tags that have a property attribute as defined in the standard. For example: |
||||
* <meta property="rendition:layout">pre-paginated</meta> |
||||
* @param metadataElement |
||||
* @return |
||||
*/ |
||||
private static Map<QName, String> readOtherProperties( |
||||
Element metadataElement) { |
||||
Map<QName, String> result = new HashMap<QName, String>(); |
||||
|
||||
NodeList metaTags = metadataElement.getElementsByTagName(OPFTags.meta); |
||||
for (int i = 0; i < metaTags.getLength(); i++) { |
||||
Node metaNode = metaTags.item(i); |
||||
Node property = metaNode.getAttributes() |
||||
.getNamedItem(OPFAttributes.property); |
||||
if (property != null) { |
||||
String name = property.getNodeValue(); |
||||
String value = metaNode.getTextContent(); |
||||
result.put(new QName(name), value); |
||||
} |
||||
} |
||||
|
||||
return result; |
||||
} |
||||
|
||||
/** |
||||
* consumes meta tags that have a property attribute as defined in the standard. For example: |
||||
* <meta property="rendition:layout">pre-paginated</meta> |
||||
* @param metadataElement |
||||
* @return |
||||
*/ |
||||
private static Map<String, String> readMetaProperties( |
||||
Element metadataElement) { |
||||
Map<String, String> result = new HashMap<String, String>(); |
||||
|
||||
NodeList metaTags = metadataElement.getElementsByTagName(OPFTags.meta); |
||||
for (int i = 0; i < metaTags.getLength(); i++) { |
||||
Element metaElement = (Element) metaTags.item(i); |
||||
String name = metaElement.getAttribute(OPFAttributes.name); |
||||
String value = metaElement.getAttribute(OPFAttributes.content); |
||||
result.put(name, value); |
||||
} |
||||
|
||||
return result; |
||||
} |
||||
|
||||
private static String getBookIdId(Document document) { |
||||
Element packageElement = DOMUtil |
||||
.getFirstElementByTagNameNS(document.getDocumentElement(), |
||||
NAMESPACE_OPF, OPFTags.packageTag); |
||||
if (packageElement == null) { |
||||
return null; |
||||
} |
||||
String result = packageElement |
||||
.getAttributeNS(NAMESPACE_OPF, OPFAttributes.uniqueIdentifier); |
||||
return result; |
||||
} |
||||
|
||||
private static List<Author> readCreators(Element metadataElement) { |
||||
return readAuthors(DCTags.creator, metadataElement); |
||||
} |
||||
|
||||
private static List<Author> readContributors(Element metadataElement) { |
||||
return readAuthors(DCTags.contributor, metadataElement); |
||||
} |
||||
|
||||
private static List<Author> readAuthors(String authorTag, |
||||
Element metadataElement) { |
||||
NodeList elements = metadataElement |
||||
.getElementsByTagNameNS(NAMESPACE_DUBLIN_CORE, authorTag); |
||||
List<Author> result = new ArrayList<Author>(elements.getLength()); |
||||
for (int i = 0; i < elements.getLength(); i++) { |
||||
Element authorElement = (Element) elements.item(i); |
||||
Author author = createAuthor(authorElement); |
||||
if (author != null) { |
||||
result.add(author); |
||||
} |
||||
} |
||||
return result; |
||||
|
||||
} |
||||
|
||||
private static List<Date> readDates(Element metadataElement) { |
||||
NodeList elements = metadataElement |
||||
.getElementsByTagNameNS(NAMESPACE_DUBLIN_CORE, DCTags.date); |
||||
List<Date> result = new ArrayList<Date>(elements.getLength()); |
||||
for (int i = 0; i < elements.getLength(); i++) { |
||||
Element dateElement = (Element) elements.item(i); |
||||
Date date; |
||||
try { |
||||
date = new Date(DOMUtil.getTextChildrenContent(dateElement), |
||||
dateElement.getAttributeNS(NAMESPACE_OPF, OPFAttributes.event)); |
||||
result.add(date); |
||||
} catch (IllegalArgumentException e) { |
||||
Log.e(TAG,e.getMessage()); |
||||
} |
||||
} |
||||
return result; |
||||
|
||||
} |
||||
|
||||
private static Author createAuthor(Element authorElement) { |
||||
String authorString = DOMUtil.getTextChildrenContent(authorElement); |
||||
if (StringUtil.isBlank(authorString)) { |
||||
return null; |
||||
} |
||||
int spacePos = authorString.lastIndexOf(' '); |
||||
Author result; |
||||
if (spacePos < 0) { |
||||
result = new Author(authorString); |
||||
} else { |
||||
result = new Author(authorString.substring(0, spacePos), |
||||
authorString.substring(spacePos + 1)); |
||||
} |
||||
result.setRole( |
||||
authorElement.getAttributeNS(NAMESPACE_OPF, OPFAttributes.role)); |
||||
return result; |
||||
} |
||||
|
||||
|
||||
private static List<Identifier> readIdentifiers(Element metadataElement) { |
||||
NodeList identifierElements = metadataElement |
||||
.getElementsByTagNameNS(NAMESPACE_DUBLIN_CORE, DCTags.identifier); |
||||
if (identifierElements.getLength() == 0) { |
||||
Log.e(TAG,"Package does not contain element " + DCTags.identifier); |
||||
return new ArrayList<Identifier>(); |
||||
} |
||||
String bookIdId = getBookIdId(metadataElement.getOwnerDocument()); |
||||
List<Identifier> result = new ArrayList<Identifier>( |
||||
identifierElements.getLength()); |
||||
for (int i = 0; i < identifierElements.getLength(); i++) { |
||||
Element identifierElement = (Element) identifierElements.item(i); |
||||
String schemeName = identifierElement |
||||
.getAttributeNS(NAMESPACE_OPF, DCAttributes.scheme); |
||||
String identifierValue = DOMUtil |
||||
.getTextChildrenContent(identifierElement); |
||||
if (StringUtil.isBlank(identifierValue)) { |
||||
continue; |
||||
} |
||||
Identifier identifier = new Identifier(schemeName, identifierValue); |
||||
if (identifierElement.getAttribute("id").equals(bookIdId)) { |
||||
identifier.setBookId(true); |
||||
} |
||||
result.add(identifier); |
||||
} |
||||
return result; |
||||
} |
||||
} |
@ -0,0 +1,176 @@ |
||||
package me.ag2s.epublib.epub; |
||||
|
||||
import me.ag2s.epublib.Constants; |
||||
import me.ag2s.epublib.domain.Author; |
||||
import me.ag2s.epublib.domain.Book; |
||||
import me.ag2s.epublib.domain.Date; |
||||
import me.ag2s.epublib.domain.Identifier; |
||||
import me.ag2s.epublib.util.StringUtil; |
||||
import java.io.IOException; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import javax.xml.namespace.QName; |
||||
import org.xmlpull.v1.XmlSerializer; |
||||
|
||||
public class PackageDocumentMetadataWriter extends PackageDocumentBase { |
||||
|
||||
/** |
||||
* Writes the book's metadata. |
||||
* |
||||
* @param book |
||||
* @param serializer |
||||
* @throws IOException |
||||
* @throws IllegalStateException |
||||
* @throws IllegalArgumentException |
||||
*/ |
||||
public static void writeMetaData(Book book, XmlSerializer serializer) |
||||
throws IllegalArgumentException, IllegalStateException, IOException { |
||||
serializer.startTag(NAMESPACE_OPF, OPFTags.metadata); |
||||
serializer.setPrefix(PREFIX_DUBLIN_CORE, NAMESPACE_DUBLIN_CORE); |
||||
serializer.setPrefix(PREFIX_OPF, NAMESPACE_OPF); |
||||
|
||||
writeIdentifiers(book.getMetadata().getIdentifiers(), serializer); |
||||
writeSimpleMetdataElements(DCTags.title, book.getMetadata().getTitles(), |
||||
serializer); |
||||
writeSimpleMetdataElements(DCTags.subject, book.getMetadata().getSubjects(), |
||||
serializer); |
||||
writeSimpleMetdataElements(DCTags.description, |
||||
book.getMetadata().getDescriptions(), serializer); |
||||
writeSimpleMetdataElements(DCTags.publisher, |
||||
book.getMetadata().getPublishers(), serializer); |
||||
writeSimpleMetdataElements(DCTags.type, book.getMetadata().getTypes(), |
||||
serializer); |
||||
writeSimpleMetdataElements(DCTags.rights, book.getMetadata().getRights(), |
||||
serializer); |
||||
|
||||
// write authors
|
||||
for (Author author : book.getMetadata().getAuthors()) { |
||||
serializer.startTag(NAMESPACE_DUBLIN_CORE, DCTags.creator); |
||||
serializer.attribute(NAMESPACE_OPF, OPFAttributes.role, |
||||
author.getRelator().getCode()); |
||||
serializer.attribute(NAMESPACE_OPF, OPFAttributes.file_as, |
||||
author.getLastname() + ", " + author.getFirstname()); |
||||
serializer.text(author.getFirstname() + " " + author.getLastname()); |
||||
serializer.endTag(NAMESPACE_DUBLIN_CORE, DCTags.creator); |
||||
} |
||||
|
||||
// write contributors
|
||||
for (Author author : book.getMetadata().getContributors()) { |
||||
serializer.startTag(NAMESPACE_DUBLIN_CORE, DCTags.contributor); |
||||
serializer.attribute(NAMESPACE_OPF, OPFAttributes.role, |
||||
author.getRelator().getCode()); |
||||
serializer.attribute(NAMESPACE_OPF, OPFAttributes.file_as, |
||||
author.getLastname() + ", " + author.getFirstname()); |
||||
serializer.text(author.getFirstname() + " " + author.getLastname()); |
||||
serializer.endTag(NAMESPACE_DUBLIN_CORE, DCTags.contributor); |
||||
} |
||||
|
||||
// write dates
|
||||
for (Date date : book.getMetadata().getDates()) { |
||||
serializer.startTag(NAMESPACE_DUBLIN_CORE, DCTags.date); |
||||
if (date.getEvent() != null) { |
||||
serializer.attribute(NAMESPACE_OPF, OPFAttributes.event, |
||||
date.getEvent().toString()); |
||||
} |
||||
serializer.text(date.getValue()); |
||||
serializer.endTag(NAMESPACE_DUBLIN_CORE, DCTags.date); |
||||
} |
||||
|
||||
// write language
|
||||
if (StringUtil.isNotBlank(book.getMetadata().getLanguage())) { |
||||
serializer.startTag(NAMESPACE_DUBLIN_CORE, "language"); |
||||
serializer.text(book.getMetadata().getLanguage()); |
||||
serializer.endTag(NAMESPACE_DUBLIN_CORE, "language"); |
||||
} |
||||
|
||||
// write other properties
|
||||
if (book.getMetadata().getOtherProperties() != null) { |
||||
for (Map.Entry<QName, String> mapEntry : book.getMetadata() |
||||
.getOtherProperties().entrySet()) { |
||||
serializer.startTag(mapEntry.getKey().getNamespaceURI(), OPFTags.meta); |
||||
serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, |
||||
OPFAttributes.property, mapEntry.getKey().getLocalPart()); |
||||
serializer.text(mapEntry.getValue()); |
||||
serializer.endTag(mapEntry.getKey().getNamespaceURI(), OPFTags.meta); |
||||
|
||||
} |
||||
} |
||||
|
||||
// write coverimage
|
||||
if (book.getCoverImage() != null) { // write the cover image
|
||||
serializer.startTag(NAMESPACE_OPF, OPFTags.meta); |
||||
serializer |
||||
.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.name, |
||||
OPFValues.meta_cover); |
||||
serializer |
||||
.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.content, |
||||
book.getCoverImage().getId()); |
||||
serializer.endTag(NAMESPACE_OPF, OPFTags.meta); |
||||
} |
||||
|
||||
// write generator
|
||||
serializer.startTag(NAMESPACE_OPF, OPFTags.meta); |
||||
serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.name, |
||||
OPFValues.generator); |
||||
serializer |
||||
.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.content, |
||||
Constants.EPUB4J_GENERATOR_NAME); |
||||
serializer.endTag(NAMESPACE_OPF, OPFTags.meta); |
||||
|
||||
serializer.endTag(NAMESPACE_OPF, OPFTags.metadata); |
||||
} |
||||
|
||||
private static void writeSimpleMetdataElements(String tagName, |
||||
List<String> values, XmlSerializer serializer) |
||||
throws IllegalArgumentException, IllegalStateException, IOException { |
||||
for (String value : values) { |
||||
if (StringUtil.isBlank(value)) { |
||||
continue; |
||||
} |
||||
serializer.startTag(NAMESPACE_DUBLIN_CORE, tagName); |
||||
serializer.text(value); |
||||
serializer.endTag(NAMESPACE_DUBLIN_CORE, tagName); |
||||
} |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Writes out the complete list of Identifiers to the package document. |
||||
* The first identifier for which the bookId is true is made the bookId identifier. |
||||
* If no identifier has bookId == true then the first bookId identifier is written as the primary. |
||||
* |
||||
* @param identifiers |
||||
* @param serializer |
||||
* @throws IOException |
||||
* @throws IllegalStateException |
||||
* @throws IllegalArgumentException |
||||
* @ |
||||
*/ |
||||
private static void writeIdentifiers(List<Identifier> identifiers, |
||||
XmlSerializer serializer) |
||||
throws IllegalArgumentException, IllegalStateException, IOException { |
||||
Identifier bookIdIdentifier = Identifier.getBookIdIdentifier(identifiers); |
||||
if (bookIdIdentifier == null) { |
||||
return; |
||||
} |
||||
|
||||
serializer.startTag(NAMESPACE_DUBLIN_CORE, DCTags.identifier); |
||||
serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, DCAttributes.id, |
||||
BOOK_ID_ID); |
||||
serializer.attribute(NAMESPACE_OPF, OPFAttributes.scheme, |
||||
bookIdIdentifier.getScheme()); |
||||
serializer.text(bookIdIdentifier.getValue()); |
||||
serializer.endTag(NAMESPACE_DUBLIN_CORE, DCTags.identifier); |
||||
|
||||
for (Identifier identifier : identifiers.subList(1, identifiers.size())) { |
||||
if (identifier == bookIdIdentifier) { |
||||
continue; |
||||
} |
||||
serializer.startTag(NAMESPACE_DUBLIN_CORE, DCTags.identifier); |
||||
serializer.attribute(NAMESPACE_OPF, "scheme", identifier.getScheme()); |
||||
serializer.text(identifier.getValue()); |
||||
serializer.endTag(NAMESPACE_DUBLIN_CORE, DCTags.identifier); |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,425 @@ |
||||
package me.ag2s.epublib.epub; |
||||
|
||||
import android.util.Log; |
||||
import android.widget.Toast; |
||||
|
||||
import me.ag2s.epublib.Constants; |
||||
import me.ag2s.epublib.domain.Book; |
||||
import me.ag2s.epublib.domain.Guide; |
||||
import me.ag2s.epublib.domain.GuideReference; |
||||
import me.ag2s.epublib.domain.MediaType; |
||||
import me.ag2s.epublib.domain.Resource; |
||||
import me.ag2s.epublib.domain.Resources; |
||||
import me.ag2s.epublib.domain.Spine; |
||||
import me.ag2s.epublib.domain.SpineReference; |
||||
import me.ag2s.epublib.domain.MediaTypes; |
||||
import me.ag2s.epublib.util.ResourceUtil; |
||||
import me.ag2s.epublib.util.StringUtil; |
||||
//import io.documentnode.minilog.Logger;
|
||||
import java.io.IOException; |
||||
import java.io.UnsupportedEncodingException; |
||||
import java.net.URLDecoder; |
||||
import java.util.ArrayList; |
||||
import java.util.Collection; |
||||
import java.util.Collections; |
||||
import java.util.HashMap; |
||||
import java.util.HashSet; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.Set; |
||||
import javax.xml.parsers.ParserConfigurationException; |
||||
import org.w3c.dom.Document; |
||||
import org.w3c.dom.Element; |
||||
import org.w3c.dom.NodeList; |
||||
import org.xml.sax.SAXException; |
||||
|
||||
/** |
||||
* Reads the opf package document as defined by namespace http://www.idpf.org/2007/opf
|
||||
* |
||||
* @author paul |
||||
* |
||||
*/ |
||||
public class PackageDocumentReader extends PackageDocumentBase { |
||||
|
||||
private static String TAG= PackageDocumentReader.class.getName(); |
||||
private static final String[] POSSIBLE_NCX_ITEM_IDS = new String[]{"toc", |
||||
"ncx", "ncxtoc","htmltoc"}; |
||||
|
||||
|
||||
public static void read( |
||||
Resource packageResource, EpubReader epubReader, Book book, |
||||
Resources resources) |
||||
throws UnsupportedEncodingException, SAXException, IOException, ParserConfigurationException { |
||||
Document packageDocument = ResourceUtil.getAsDocument(packageResource); |
||||
String packageHref = packageResource.getHref(); |
||||
resources = fixHrefs(packageHref, resources); |
||||
readGuide(packageDocument, epubReader, book, resources); |
||||
|
||||
// Books sometimes use non-identifier ids. We map these here to legal ones
|
||||
Map<String, String> idMapping = new HashMap<String, String>(); |
||||
String version=DOMUtil.getAttribute(packageDocument.getDocumentElement(),PREFIX_OPF,PackageDocumentBase.version); |
||||
|
||||
resources = readManifest(packageDocument, packageHref, epubReader, |
||||
resources, idMapping); |
||||
book.setResources(resources); |
||||
book.setVersion(version); |
||||
readCover(packageDocument, book); |
||||
book.setMetadata( |
||||
PackageDocumentMetadataReader.readMetadata(packageDocument)); |
||||
book.setSpine(readSpine(packageDocument, book.getResources(), idMapping)); |
||||
|
||||
// if we did not find a cover page then we make the first page of the book the cover page
|
||||
if (book.getCoverPage() == null && book.getSpine().size() > 0) { |
||||
book.setCoverPage(book.getSpine().getResource(0)); |
||||
} |
||||
} |
||||
|
||||
// private static Resource readCoverImage(Element metadataElement, Resources resources) {
|
||||
// String coverResourceId = DOMUtil.getFindAttributeValue(metadataElement.getOwnerDocument(), NAMESPACE_OPF, OPFTags.meta, OPFAttributes.name, OPFValues.meta_cover, OPFAttributes.content);
|
||||
// if (StringUtil.isBlank(coverResourceId)) {
|
||||
// return null;
|
||||
// }
|
||||
// Resource coverResource = resources.getByIdOrHref(coverResourceId);
|
||||
// return coverResource;
|
||||
// }
|
||||
|
||||
|
||||
/** |
||||
* Reads the manifest containing the resource ids, hrefs and mediatypes. |
||||
* |
||||
* @param packageDocument |
||||
* @param packageHref |
||||
* @param epubReader |
||||
* @param resources |
||||
* @param idMapping |
||||
* @return a Map with resources, with their id's as key. |
||||
*/ |
||||
private static Resources readManifest(Document packageDocument, |
||||
String packageHref, |
||||
EpubReader epubReader, Resources resources, |
||||
Map<String, String> idMapping) { |
||||
Element manifestElement = DOMUtil |
||||
.getFirstElementByTagNameNS(packageDocument.getDocumentElement(), |
||||
NAMESPACE_OPF, OPFTags.manifest); |
||||
Resources result = new Resources(); |
||||
if (manifestElement == null) { |
||||
Log.e(TAG, |
||||
"Package document does not contain element " + OPFTags.manifest); |
||||
return result; |
||||
} |
||||
NodeList itemElements = manifestElement |
||||
.getElementsByTagNameNS(NAMESPACE_OPF, OPFTags.item); |
||||
for (int i = 0; i < itemElements.getLength(); i++) { |
||||
Element itemElement = (Element) itemElements.item(i); |
||||
String id = DOMUtil |
||||
.getAttribute(itemElement, NAMESPACE_OPF, OPFAttributes.id); |
||||
String href = DOMUtil |
||||
.getAttribute(itemElement, NAMESPACE_OPF, OPFAttributes.href); |
||||
String properties=DOMUtil.getAttribute(itemElement,NAMESPACE_OPF,OPFAttributes.properties); |
||||
try { |
||||
href = URLDecoder.decode(href, Constants.CHARACTER_ENCODING); |
||||
} catch (UnsupportedEncodingException e) { |
||||
Log.e(TAG,e.getMessage()); |
||||
} |
||||
String mediaTypeName = DOMUtil |
||||
.getAttribute(itemElement, NAMESPACE_OPF, OPFAttributes.media_type); |
||||
Resource resource = resources.remove(href); |
||||
if (resource == null) { |
||||
Log.e(TAG,"resource with href '" + href + "' not found"); |
||||
continue; |
||||
} |
||||
resource.setId(id); |
||||
resource.setProperties(properties); |
||||
MediaType mediaType = MediaTypes.getMediaTypeByName(mediaTypeName); |
||||
if (mediaType != null) { |
||||
resource.setMediaType(mediaType); |
||||
} |
||||
result.add(resource); |
||||
idMapping.put(id, resource.getId()); |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Reads the book's guide. |
||||
* Here some more attempts are made at finding the cover page. |
||||
* |
||||
* @param packageDocument |
||||
* @param epubReader |
||||
* @param book |
||||
* @param resources |
||||
*/ |
||||
private static void readGuide(Document packageDocument, |
||||
EpubReader epubReader, Book book, Resources resources) { |
||||
Element guideElement = DOMUtil |
||||
.getFirstElementByTagNameNS(packageDocument.getDocumentElement(), |
||||
NAMESPACE_OPF, OPFTags.guide); |
||||
if (guideElement == null) { |
||||
return; |
||||
} |
||||
Guide guide = book.getGuide(); |
||||
NodeList guideReferences = guideElement |
||||
.getElementsByTagNameNS(NAMESPACE_OPF, OPFTags.reference); |
||||
for (int i = 0; i < guideReferences.getLength(); i++) { |
||||
Element referenceElement = (Element) guideReferences.item(i); |
||||
String resourceHref = DOMUtil |
||||
.getAttribute(referenceElement, NAMESPACE_OPF, OPFAttributes.href); |
||||
if (StringUtil.isBlank(resourceHref)) { |
||||
continue; |
||||
} |
||||
Resource resource = resources.getByHref(StringUtil |
||||
.substringBefore(resourceHref, Constants.FRAGMENT_SEPARATOR_CHAR)); |
||||
if (resource == null) { |
||||
Log.e(TAG,"Guide is referencing resource with href " + resourceHref |
||||
+ " which could not be found"); |
||||
continue; |
||||
} |
||||
String type = DOMUtil |
||||
.getAttribute(referenceElement, NAMESPACE_OPF, OPFAttributes.type); |
||||
if (StringUtil.isBlank(type)) { |
||||
Log.e(TAG,"Guide is referencing resource with href " + resourceHref |
||||
+ " which is missing the 'type' attribute"); |
||||
continue; |
||||
} |
||||
String title = DOMUtil |
||||
.getAttribute(referenceElement, NAMESPACE_OPF, OPFAttributes.title); |
||||
if (GuideReference.COVER.equalsIgnoreCase(type)) { |
||||
continue; // cover is handled elsewhere
|
||||
} |
||||
GuideReference reference = new GuideReference(resource, type, title, |
||||
StringUtil |
||||
.substringAfter(resourceHref, Constants.FRAGMENT_SEPARATOR_CHAR)); |
||||
guide.addReference(reference); |
||||
} |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Strips off the package prefixes up to the href of the packageHref. |
||||
* |
||||
* 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 resources |
||||
* @param idMapping |
||||
* @return the document's spine, containing all sections in reading order. |
||||
*/ |
||||
private static Spine readSpine(Document packageDocument, Resources resources, |
||||
Map<String, String> idMapping) { |
||||
|
||||
Element spineElement = DOMUtil |
||||
.getFirstElementByTagNameNS(packageDocument.getDocumentElement(), |
||||
NAMESPACE_OPF, OPFTags.spine); |
||||
if (spineElement == null) { |
||||
Log.e(TAG,"Element " + OPFTags.spine |
||||
+ " not found in package document, generating one automatically"); |
||||
return generateSpineFromResources(resources); |
||||
} |
||||
Spine result = new Spine(); |
||||
String tocResourceId = DOMUtil |
||||
.getAttribute(spineElement, NAMESPACE_OPF, OPFAttributes.toc); |
||||
result |
||||
.setTocResource(findTableOfContentsResource(tocResourceId, resources)); |
||||
NodeList spineNodes = packageDocument |
||||
.getElementsByTagNameNS(NAMESPACE_OPF, OPFTags.itemref); |
||||
List<SpineReference> spineReferences = new ArrayList<SpineReference>( |
||||
spineNodes.getLength()); |
||||
for (int i = 0; i < spineNodes.getLength(); i++) { |
||||
Element spineItem = (Element) spineNodes.item(i); |
||||
String itemref = DOMUtil |
||||
.getAttribute(spineItem, NAMESPACE_OPF, OPFAttributes.idref); |
||||
if (StringUtil.isBlank(itemref)) { |
||||
Log.e(TAG,"itemref with missing or empty idref"); // XXX
|
||||
continue; |
||||
} |
||||
String id = idMapping.get(itemref); |
||||
if (id == null) { |
||||
id = itemref; |
||||
} |
||||
Resource resource = resources.getByIdOrHref(id); |
||||
if (resource == null) { |
||||
Log.e(TAG,"resource with id \'" + id + "\' not found"); |
||||
continue; |
||||
} |
||||
|
||||
SpineReference spineReference = new SpineReference(resource); |
||||
if (OPFValues.no.equalsIgnoreCase(DOMUtil |
||||
.getAttribute(spineItem, NAMESPACE_OPF, OPFAttributes.linear))) { |
||||
spineReference.setLinear(false); |
||||
} |
||||
spineReferences.add(spineReference); |
||||
} |
||||
result.setSpineReferences(spineReferences); |
||||
return result; |
||||
} |
||||
|
||||
/** |
||||
* Creates a spine out of all resources in the resources. |
||||
* The generated spine consists of all XHTML pages in order of their href. |
||||
* |
||||
* @param resources |
||||
* @return a spine created out of all resources in the resources. |
||||
*/ |
||||
private static Spine generateSpineFromResources(Resources resources) { |
||||
Spine result = new Spine(); |
||||
List<String> resourceHrefs = new ArrayList<String>(); |
||||
resourceHrefs.addAll(resources.getAllHrefs()); |
||||
Collections.sort(resourceHrefs, String.CASE_INSENSITIVE_ORDER); |
||||
for (String resourceHref : resourceHrefs) { |
||||
Resource resource = resources.getByHref(resourceHref); |
||||
if (resource.getMediaType() == MediaTypes.NCX) { |
||||
result.setTocResource(resource); |
||||
} else if (resource.getMediaType() == MediaTypes.XHTML) { |
||||
result.addSpineReference(new SpineReference(resource)); |
||||
} |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* The spine tag should contain a 'toc' attribute with as value the resource id of the table of contents resource. |
||||
* |
||||
* 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 tocResourceId |
||||
* @param resources |
||||
* @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(MediaTypes.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; |
||||
} |
||||
} |
||||
} |
||||
//For EPUB3
|
||||
if (tocResource==null){ |
||||
tocResource=resources.getByProperties("nav"); |
||||
} |
||||
|
||||
if (tocResource == null) { |
||||
Log.e(TAG, |
||||
"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<String> findCoverHrefs(Document packageDocument) { |
||||
|
||||
Set<String> result = new HashSet<String>(); |
||||
|
||||
// try and find a meta tag with name = 'cover' and a non-blank id
|
||||
String coverResourceId = DOMUtil |
||||
.getFindAttributeValue(packageDocument, NAMESPACE_OPF, |
||||
OPFTags.meta, OPFAttributes.name, OPFValues.meta_cover, |
||||
OPFAttributes.content); |
||||
|
||||
if (StringUtil.isNotBlank(coverResourceId)) { |
||||
String coverHref = DOMUtil |
||||
.getFindAttributeValue(packageDocument, NAMESPACE_OPF, |
||||
OPFTags.item, OPFAttributes.id, coverResourceId, |
||||
OPFAttributes.href); |
||||
if (StringUtil.isNotBlank(coverHref)) { |
||||
result.add(coverHref); |
||||
} else { |
||||
result.add( |
||||
coverResourceId); // maybe there was a cover href put in the cover id attribute
|
||||
} |
||||
} |
||||
// try and find a reference tag with type is 'cover' and reference is not blank
|
||||
String coverHref = DOMUtil |
||||
.getFindAttributeValue(packageDocument, NAMESPACE_OPF, |
||||
OPFTags.reference, OPFAttributes.type, OPFValues.reference_cover, |
||||
OPFAttributes.href); |
||||
if (StringUtil.isNotBlank(coverHref)) { |
||||
result.add(coverHref); |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
/** |
||||
* Finds the cover resource in the packageDocument and adds it to the book if found. |
||||
* Keeps the cover resource in the resources map |
||||
* @param packageDocument |
||||
* @param book |
||||
* @1param resources |
||||
*/ |
||||
private static void readCover(Document packageDocument, Book book) { |
||||
|
||||
Collection<String> coverHrefs = findCoverHrefs(packageDocument); |
||||
for (String coverHref : coverHrefs) { |
||||
Resource resource = book.getResources().getByHref(coverHref); |
||||
if (resource == null) { |
||||
Log.e(TAG,"Cover resource " + coverHref + " not found"); |
||||
continue; |
||||
} |
||||
if (resource.getMediaType() == MediaTypes.XHTML) { |
||||
book.setCoverPage(resource); |
||||
} else if (MediaTypes.isBitmapImage(resource.getMediaType())) { |
||||
book.setCoverImage(resource); |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
} |
@ -0,0 +1,254 @@ |
||||
package me.ag2s.epublib.epub; |
||||
|
||||
import android.util.Log; |
||||
|
||||
import me.ag2s.epublib.Constants; |
||||
import me.ag2s.epublib.domain.Book; |
||||
import me.ag2s.epublib.domain.Guide; |
||||
import me.ag2s.epublib.domain.GuideReference; |
||||
import me.ag2s.epublib.domain.MediaTypes; |
||||
import me.ag2s.epublib.domain.Resource; |
||||
import me.ag2s.epublib.domain.Spine; |
||||
import me.ag2s.epublib.domain.SpineReference; |
||||
import me.ag2s.epublib.util.ResourceUtil; |
||||
import me.ag2s.epublib.util.StringUtil; |
||||
//import io.documentnode.minilog.Logger;
|
||||
import java.io.IOException; |
||||
import java.util.ArrayList; |
||||
import java.util.Collections; |
||||
import java.util.Comparator; |
||||
import java.util.List; |
||||
//import javax.xml.stream.XMLStreamException;
|
||||
import org.xmlpull.v1.XmlSerializer; |
||||
|
||||
/** |
||||
* Writes the opf package document as defined by namespace http://www.idpf.org/2007/opf
|
||||
* |
||||
* @author paul |
||||
* |
||||
*/ |
||||
public class PackageDocumentWriter extends PackageDocumentBase { |
||||
|
||||
//private static final Logger log = Logger.create(PackageDocumentWriter.class);
|
||||
private static String TAG= PackageDocumentWriter.class.getName(); |
||||
|
||||
public static void write(EpubWriter epubWriter, XmlSerializer serializer, |
||||
Book book) throws IOException { |
||||
try { |
||||
serializer.startDocument(Constants.CHARACTER_ENCODING, false); |
||||
serializer.setPrefix(PREFIX_OPF, NAMESPACE_OPF); |
||||
serializer.setPrefix(PREFIX_DUBLIN_CORE, NAMESPACE_DUBLIN_CORE); |
||||
serializer.startTag(NAMESPACE_OPF, OPFTags.packageTag); |
||||
serializer |
||||
.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.version, |
||||
"2.0"); |
||||
serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, |
||||
OPFAttributes.uniqueIdentifier, BOOK_ID_ID); |
||||
|
||||
PackageDocumentMetadataWriter.writeMetaData(book, serializer); |
||||
|
||||
writeManifest(book, epubWriter, serializer); |
||||
writeSpine(book, epubWriter, serializer); |
||||
writeGuide(book, epubWriter, serializer); |
||||
|
||||
serializer.endTag(NAMESPACE_OPF, OPFTags.packageTag); |
||||
serializer.endDocument(); |
||||
serializer.flush(); |
||||
} catch (IOException e) { |
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace(); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Writes the package's spine. |
||||
* |
||||
* @param book |
||||
* @param epubWriter |
||||
* @param serializer |
||||
* @throws IOException |
||||
* @throws IllegalStateException |
||||
* @throws IllegalArgumentException |
||||
* 1@throws XMLStreamException |
||||
*/ |
||||
private static void writeSpine(Book book, EpubWriter epubWriter, |
||||
XmlSerializer serializer) |
||||
throws IllegalArgumentException, IllegalStateException, IOException { |
||||
serializer.startTag(NAMESPACE_OPF, OPFTags.spine); |
||||
Resource tocResource = book.getSpine().getTocResource(); |
||||
String tocResourceId = tocResource.getId(); |
||||
serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.toc, |
||||
tocResourceId); |
||||
|
||||
if (book.getCoverPage() != null // there is a cover page
|
||||
&& book.getSpine().findFirstResourceById(book.getCoverPage().getId()) |
||||
< 0) { // cover page is not already in the spine
|
||||
// write the cover html file
|
||||
serializer.startTag(NAMESPACE_OPF, OPFTags.itemref); |
||||
serializer |
||||
.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.idref, |
||||
book.getCoverPage().getId()); |
||||
serializer |
||||
.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.linear, |
||||
"no"); |
||||
serializer.endTag(NAMESPACE_OPF, OPFTags.itemref); |
||||
} |
||||
writeSpineItems(book.getSpine(), serializer); |
||||
serializer.endTag(NAMESPACE_OPF, OPFTags.spine); |
||||
} |
||||
|
||||
|
||||
private static void writeManifest(Book book, EpubWriter epubWriter, |
||||
XmlSerializer serializer) |
||||
throws IllegalArgumentException, IllegalStateException, IOException { |
||||
serializer.startTag(NAMESPACE_OPF, OPFTags.manifest); |
||||
|
||||
serializer.startTag(NAMESPACE_OPF, OPFTags.item); |
||||
serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.id, |
||||
epubWriter.getNcxId()); |
||||
serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.href, |
||||
epubWriter.getNcxHref()); |
||||
serializer |
||||
.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.media_type, |
||||
epubWriter.getNcxMediaType()); |
||||
serializer.endTag(NAMESPACE_OPF, OPFTags.item); |
||||
|
||||
// writeCoverResources(book, serializer);
|
||||
|
||||
for (Resource resource : getAllResourcesSortById(book)) { |
||||
writeItem(book, resource, serializer); |
||||
} |
||||
|
||||
serializer.endTag(NAMESPACE_OPF, OPFTags.manifest); |
||||
} |
||||
|
||||
private static List<Resource> getAllResourcesSortById(Book book) { |
||||
List<Resource> allResources = new ArrayList<Resource>( |
||||
book.getResources().getAll()); |
||||
Collections.sort(allResources, new Comparator<Resource>() { |
||||
|
||||
@Override |
||||
public int compare(Resource resource1, Resource resource2) { |
||||
return resource1.getId().compareToIgnoreCase(resource2.getId()); |
||||
} |
||||
}); |
||||
return allResources; |
||||
} |
||||
|
||||
/** |
||||
* Writes a resources as an item element |
||||
* @param resource |
||||
* @param serializer |
||||
* @throws IOException |
||||
* @throws IllegalStateException |
||||
* @throws IllegalArgumentException |
||||
* 1@throws XMLStreamException |
||||
*/ |
||||
private static void writeItem(Book book, Resource resource, |
||||
XmlSerializer serializer) |
||||
throws IllegalArgumentException, IllegalStateException, IOException { |
||||
if (resource == null || |
||||
(resource.getMediaType() == MediaTypes.NCX |
||||
&& book.getSpine().getTocResource() != null)) { |
||||
return; |
||||
} |
||||
if (StringUtil.isBlank(resource.getId())) { |
||||
// log.error("resource id must not be empty (href: " + resource.getHref()
|
||||
// + ", mediatype:" + resource.getMediaType() + ")");
|
||||
Log.e(TAG,"resource id must not be empty (href: " + resource.getHref() |
||||
+ ", mediatype:" + resource.getMediaType() + ")"); |
||||
return; |
||||
} |
||||
if (StringUtil.isBlank(resource.getHref())) { |
||||
// log.error("resource href must not be empty (id: " + resource.getId()
|
||||
// + ", mediatype:" + resource.getMediaType() + ")");
|
||||
Log.e(TAG,"resource href must not be empty (id: " + resource.getId() |
||||
+ ", mediatype:" + resource.getMediaType() + ")"); |
||||
return; |
||||
} |
||||
if (resource.getMediaType() == null) { |
||||
// log.error("resource mediatype must not be empty (id: " + resource.getId()
|
||||
// + ", href:" + resource.getHref() + ")");
|
||||
Log.e(TAG,"resource mediatype must not be empty (id: " + resource.getId() |
||||
+ ", href:" + resource.getHref() + ")"); |
||||
return; |
||||
} |
||||
serializer.startTag(NAMESPACE_OPF, OPFTags.item); |
||||
serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.id, |
||||
resource.getId()); |
||||
serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.href, |
||||
resource.getHref()); |
||||
serializer |
||||
.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.media_type, |
||||
resource.getMediaType().getName()); |
||||
serializer.endTag(NAMESPACE_OPF, OPFTags.item); |
||||
} |
||||
|
||||
/** |
||||
* List all spine references |
||||
* @throws IOException |
||||
* @throws IllegalStateException |
||||
* @throws IllegalArgumentException |
||||
*/ |
||||
private static void writeSpineItems(Spine spine, XmlSerializer serializer) |
||||
throws IllegalArgumentException, IllegalStateException, IOException { |
||||
for (SpineReference spineReference : spine.getSpineReferences()) { |
||||
serializer.startTag(NAMESPACE_OPF, OPFTags.itemref); |
||||
serializer |
||||
.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.idref, |
||||
spineReference.getResourceId()); |
||||
if (!spineReference.isLinear()) { |
||||
serializer |
||||
.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.linear, |
||||
OPFValues.no); |
||||
} |
||||
serializer.endTag(NAMESPACE_OPF, OPFTags.itemref); |
||||
} |
||||
} |
||||
|
||||
private static void writeGuide(Book book, EpubWriter epubWriter, |
||||
XmlSerializer serializer) |
||||
throws IllegalArgumentException, IllegalStateException, IOException { |
||||
serializer.startTag(NAMESPACE_OPF, OPFTags.guide); |
||||
ensureCoverPageGuideReferenceWritten(book.getGuide(), epubWriter, |
||||
serializer); |
||||
for (GuideReference reference : book.getGuide().getReferences()) { |
||||
writeGuideReference(reference, serializer); |
||||
} |
||||
serializer.endTag(NAMESPACE_OPF, OPFTags.guide); |
||||
} |
||||
|
||||
private static void ensureCoverPageGuideReferenceWritten(Guide guide, |
||||
EpubWriter epubWriter, XmlSerializer serializer) |
||||
throws IllegalArgumentException, IllegalStateException, IOException { |
||||
if (!(guide.getGuideReferencesByType(GuideReference.COVER).isEmpty())) { |
||||
return; |
||||
} |
||||
Resource coverPage = guide.getCoverPage(); |
||||
if (coverPage != null) { |
||||
writeGuideReference( |
||||
new GuideReference(guide.getCoverPage(), GuideReference.COVER, |
||||
GuideReference.COVER), serializer); |
||||
} |
||||
} |
||||
|
||||
|
||||
private static void writeGuideReference(GuideReference reference, |
||||
XmlSerializer serializer) |
||||
throws IllegalArgumentException, IllegalStateException, IOException { |
||||
if (reference == null) { |
||||
return; |
||||
} |
||||
serializer.startTag(NAMESPACE_OPF, OPFTags.reference); |
||||
serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.type, |
||||
reference.getType()); |
||||
serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.href, |
||||
reference.getCompleteHref()); |
||||
if (StringUtil.isNotBlank(reference.getTitle())) { |
||||
serializer |
||||
.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.title, |
||||
reference.getTitle()); |
||||
} |
||||
serializer.endTag(NAMESPACE_OPF, OPFTags.reference); |
||||
} |
||||
} |
@ -0,0 +1,173 @@ |
||||
package me.ag2s.epublib.epub; |
||||
|
||||
import android.util.Log; |
||||
|
||||
import me.ag2s.epublib.domain.EpubResourceProvider; |
||||
import me.ag2s.epublib.domain.LazyResource; |
||||
import me.ag2s.epublib.domain.LazyResourceProvider; |
||||
import me.ag2s.epublib.domain.MediaType; |
||||
import me.ag2s.epublib.domain.MediaTypes; |
||||
import me.ag2s.epublib.domain.Resource; |
||||
import me.ag2s.epublib.domain.Resources; |
||||
import me.ag2s.epublib.util.CollectionUtil; |
||||
import me.ag2s.epublib.util.ResourceUtil; |
||||
import java.io.IOException; |
||||
import java.util.Collection; |
||||
import java.util.Collections; |
||||
import java.util.Enumeration; |
||||
import java.util.List; |
||||
import java.util.zip.ZipEntry; |
||||
import java.util.zip.ZipException; |
||||
import java.util.zip.ZipFile; |
||||
import java.util.zip.ZipInputStream; |
||||
|
||||
|
||||
/** |
||||
* Loads Resources from inputStreams, ZipFiles, etc |
||||
* |
||||
* @author paul |
||||
* |
||||
*/ |
||||
public class ResourcesLoader { |
||||
|
||||
private static String TAG=ResourceUtil.class.getName(); |
||||
|
||||
|
||||
/** |
||||
* Loads the entries of the zipFile as resources. |
||||
* |
||||
* The MediaTypes that are in the lazyLoadedTypes will not get their |
||||
* contents loaded, but are stored as references to entries into the |
||||
* ZipFile and are loaded on demand by the Resource system. |
||||
* |
||||
* @param zipFile |
||||
* @param defaultHtmlEncoding |
||||
* @param lazyLoadedTypes |
||||
* @return |
||||
* @throws IOException |
||||
*/ |
||||
public static Resources loadResources(ZipFile zipFile, |
||||
String defaultHtmlEncoding, |
||||
List<MediaType> lazyLoadedTypes) throws IOException { |
||||
|
||||
LazyResourceProvider resourceProvider = |
||||
new EpubResourceProvider(zipFile.getName()); |
||||
|
||||
Resources result = new Resources(); |
||||
Enumeration<? extends ZipEntry> entries = zipFile.entries(); |
||||
|
||||
while (entries.hasMoreElements()) { |
||||
ZipEntry zipEntry = entries.nextElement(); |
||||
|
||||
if (zipEntry == null || zipEntry.isDirectory()) { |
||||
continue; |
||||
} |
||||
|
||||
String href = zipEntry.getName(); |
||||
|
||||
Resource resource; |
||||
|
||||
if (shouldLoadLazy(href, lazyLoadedTypes)) { |
||||
resource = new LazyResource(resourceProvider, zipEntry.getSize(), href); |
||||
} else { |
||||
resource = ResourceUtil |
||||
.createResource(zipEntry, zipFile.getInputStream(zipEntry)); |
||||
} |
||||
|
||||
if (resource.getMediaType() == MediaTypes.XHTML) { |
||||
resource.setInputEncoding(defaultHtmlEncoding); |
||||
} |
||||
result.add(resource); |
||||
} |
||||
|
||||
return result; |
||||
} |
||||
|
||||
/** |
||||
* Whether the given href will load a mediaType that is in the |
||||
* collection of lazilyLoadedMediaTypes. |
||||
* |
||||
* @param href |
||||
* @param lazilyLoadedMediaTypes |
||||
* @return Whether the given href will load a mediaType that is |
||||
* in the collection of lazilyLoadedMediaTypes. |
||||
*/ |
||||
private static boolean shouldLoadLazy(String href, |
||||
Collection<MediaType> lazilyLoadedMediaTypes) { |
||||
if (CollectionUtil.isEmpty(lazilyLoadedMediaTypes)) { |
||||
return false; |
||||
} |
||||
MediaType mediaType = MediaTypes.determineMediaType(href); |
||||
return lazilyLoadedMediaTypes.contains(mediaType); |
||||
} |
||||
|
||||
/** |
||||
* Loads all entries from the ZipInputStream as Resources. |
||||
* |
||||
* Loads the contents of all ZipEntries into memory. |
||||
* Is fast, but may lead to memory problems when reading large books |
||||
* on devices with small amounts of memory. |
||||
* |
||||
* @param zipInputStream |
||||
* @param defaultHtmlEncoding |
||||
* @return |
||||
* @throws IOException |
||||
*/ |
||||
public static Resources loadResources(ZipInputStream zipInputStream, |
||||
String defaultHtmlEncoding) throws IOException { |
||||
Resources result = new Resources(); |
||||
ZipEntry zipEntry; |
||||
do { |
||||
// get next valid zipEntry
|
||||
zipEntry = getNextZipEntry(zipInputStream); |
||||
if ((zipEntry == null) || zipEntry.isDirectory()) { |
||||
continue; |
||||
} |
||||
|
||||
// store resource
|
||||
Resource resource = ResourceUtil.createResource(zipEntry, zipInputStream); |
||||
if (resource.getMediaType() == MediaTypes.XHTML) { |
||||
resource.setInputEncoding(defaultHtmlEncoding); |
||||
} |
||||
result.add(resource); |
||||
} while (zipEntry != null); |
||||
|
||||
return result; |
||||
} |
||||
|
||||
|
||||
private static ZipEntry getNextZipEntry(ZipInputStream zipInputStream) |
||||
throws IOException { |
||||
try { |
||||
return zipInputStream.getNextEntry(); |
||||
} catch (ZipException e) { |
||||
//see <a href="https://github.com/psiegman/epublib/issues/122">Issue #122 Infinite loop</a>.
|
||||
//when reading a file that is not a real zip archive or a zero length file, zipInputStream.getNextEntry()
|
||||
//throws an exception and does not advance, so loadResources enters an infinite loop
|
||||
//log.error("Invalid or damaged zip file.", e);
|
||||
Log.e(TAG,"",e); |
||||
try { |
||||
zipInputStream.closeEntry(); |
||||
} catch (Exception ignored) { |
||||
} |
||||
throw e; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Loads all entries from the ZipInputStream as Resources. |
||||
* |
||||
* Loads the contents of all ZipEntries into memory. |
||||
* Is fast, but may lead to memory problems when reading large books |
||||
* on devices with small amounts of memory. |
||||
* |
||||
* @param zipFile |
||||
* @param defaultHtmlEncoding |
||||
* @return |
||||
* @throws IOException |
||||
*/ |
||||
public static Resources loadResources(ZipFile zipFile, |
||||
String defaultHtmlEncoding) throws IOException { |
||||
return loadResources(zipFile, defaultHtmlEncoding, Collections.emptyList()); |
||||
} |
||||
} |
@ -0,0 +1,70 @@ |
||||
package me.ag2s.epublib.util; |
||||
|
||||
import java.util.Collection; |
||||
import java.util.Enumeration; |
||||
import java.util.Iterator; |
||||
import java.util.List; |
||||
|
||||
public class CollectionUtil { |
||||
|
||||
/** |
||||
* Wraps an Enumeration around an Iterator |
||||
* @author paul.siegmann |
||||
* |
||||
* @param <T> |
||||
*/ |
||||
private static class IteratorEnumerationAdapter<T> implements Enumeration<T> { |
||||
|
||||
private Iterator<T> iterator; |
||||
|
||||
public IteratorEnumerationAdapter(Iterator<T> iter) { |
||||
this.iterator = iter; |
||||
} |
||||
|
||||
@Override |
||||
public boolean hasMoreElements() { |
||||
return iterator.hasNext(); |
||||
} |
||||
|
||||
@Override |
||||
public T nextElement() { |
||||
return iterator.next(); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Creates an Enumeration out of the given Iterator. |
||||
* @param <T> |
||||
* @param it |
||||
* @return an Enumeration created out of the given Iterator. |
||||
*/ |
||||
public static <T> Enumeration<T> createEnumerationFromIterator( |
||||
Iterator<T> it) { |
||||
return new IteratorEnumerationAdapter<T>(it); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Returns the first element of the list, null if the list is null or empty. |
||||
* |
||||
* @param <T> |
||||
* @param list |
||||
* @return the first element of the list, null if the list is null or empty. |
||||
*/ |
||||
public static <T> T first(List<T> list) { |
||||
if (list == null || list.isEmpty()) { |
||||
return null; |
||||
} |
||||
return list.get(0); |
||||
} |
||||
|
||||
/** |
||||
* Whether the given collection is null or has no elements. |
||||
* |
||||
* @param collection |
||||
* @return Whether the given collection is null or has no elements. |
||||
*/ |
||||
public static boolean isEmpty(Collection<?> collection) { |
||||
return collection == null || collection.isEmpty(); |
||||
} |
||||
} |
@ -0,0 +1,165 @@ |
||||
package me.ag2s.epublib.util; |
||||
|
||||
import java.io.BufferedInputStream; |
||||
import java.io.ByteArrayOutputStream; |
||||
import java.io.IOException; |
||||
import java.io.InputStream; |
||||
import java.io.OutputStream; |
||||
import java.io.Reader; |
||||
import java.io.StringWriter; |
||||
import java.io.Writer; |
||||
|
||||
/** |
||||
* Most of the functions herein are re-implementations of the ones in |
||||
* apache io IOUtils. |
||||
* |
||||
* 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. |
||||
*/ |
||||
public class IOUtil { |
||||
|
||||
public static final int IO_COPY_BUFFER_SIZE = 1024 * 4; |
||||
|
||||
/** |
||||
* Gets the contents of the Reader as a byte[], with the given character encoding. |
||||
* |
||||
* @param in |
||||
* @param encoding |
||||
* @return the contents of the Reader as a byte[], with the given character encoding. |
||||
* @throws IOException |
||||
*/ |
||||
public static byte[] toByteArray(Reader in, String encoding) |
||||
throws IOException { |
||||
StringWriter out = new StringWriter(); |
||||
copy(in, out); |
||||
out.flush(); |
||||
return out.toString().getBytes(encoding); |
||||
} |
||||
|
||||
/** |
||||
* Returns the contents of the InputStream as a byte[] |
||||
* |
||||
* @param in |
||||
* @return the contents of the InputStream as a byte[] |
||||
* @throws IOException |
||||
*/ |
||||
public static byte[] toByteArray(InputStream in) throws IOException { |
||||
ByteArrayOutputStream result = new ByteArrayOutputStream(); |
||||
copy(in, result); |
||||
result.flush(); |
||||
return result.toByteArray(); |
||||
} |
||||
|
||||
/** |
||||
* Reads data from the InputStream, using the specified buffer size. |
||||
* |
||||
* This is meant for situations where memory is tight, since |
||||
* it prevents buffer expansion. |
||||
* |
||||
* @param in the stream to read data from |
||||
* @param size the size of the array to create |
||||
* @return the array, or null |
||||
* @throws IOException |
||||
*/ |
||||
public static byte[] toByteArray(InputStream in, int size) |
||||
throws IOException { |
||||
|
||||
try { |
||||
ByteArrayOutputStream result; |
||||
|
||||
if (size > 0) { |
||||
result = new ByteArrayOutputStream(size); |
||||
} else { |
||||
result = new ByteArrayOutputStream(); |
||||
} |
||||
|
||||
copy(in, result); |
||||
result.flush(); |
||||
return result.toByteArray(); |
||||
} catch (OutOfMemoryError error) { |
||||
//Return null so it gets loaded lazily.
|
||||
return null; |
||||
} |
||||
|
||||
} |
||||
|
||||
|
||||
/** |
||||
* if totalNrRead < 0 then totalNrRead is returned, if |
||||
* (nrRead + totalNrRead) < Integer.MAX_VALUE then nrRead + totalNrRead |
||||
* is returned, -1 otherwise. |
||||
* |
||||
* @param nrRead |
||||
* @param totalNrNread |
||||
* @return if totalNrRead < 0 then totalNrRead is returned, if |
||||
* (nrRead + totalNrRead) < Integer.MAX_VALUE then nrRead + totalNrRead |
||||
* is returned, -1 otherwise. |
||||
*/ |
||||
protected static int calcNewNrReadSize(int nrRead, int totalNrNread) { |
||||
if (totalNrNread < 0) { |
||||
return totalNrNread; |
||||
} |
||||
if (totalNrNread > (Integer.MAX_VALUE - nrRead)) { |
||||
return -1; |
||||
} else { |
||||
return (totalNrNread + nrRead); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Copies the contents of the InputStream to the OutputStream. |
||||
* |
||||
* @param in |
||||
* @param out |
||||
* @return the nr of bytes read, or -1 if the amount > Integer.MAX_VALUE |
||||
* @throws IOException |
||||
*/ |
||||
public static int copy(InputStream in, OutputStream out) |
||||
throws IOException { |
||||
byte[] buffer = new byte[IO_COPY_BUFFER_SIZE]; |
||||
int readSize = -1; |
||||
int result = 0; |
||||
while ((readSize = in.read(buffer)) >= 0) { |
||||
out.write(buffer, 0, readSize); |
||||
result = calcNewNrReadSize(readSize, result); |
||||
} |
||||
out.flush(); |
||||
return result; |
||||
} |
||||
|
||||
/** |
||||
* Copies the contents of the Reader to the Writer. |
||||
* |
||||
* @param in |
||||
* @param out |
||||
* @return the nr of characters read, or -1 if the amount > Integer.MAX_VALUE |
||||
* @throws IOException |
||||
*/ |
||||
public static int copy(Reader in, Writer out) throws IOException { |
||||
char[] buffer = new char[IO_COPY_BUFFER_SIZE]; |
||||
int readSize = -1; |
||||
int result = 0; |
||||
while ((readSize = in.read(buffer)) >= 0) { |
||||
out.write(buffer, 0, readSize); |
||||
result = calcNewNrReadSize(readSize, result); |
||||
} |
||||
out.flush(); |
||||
return result; |
||||
} |
||||
|
||||
public static String Stream2String(InputStream inputStream) { |
||||
String str=""; |
||||
try { |
||||
BufferedInputStream bis = new BufferedInputStream(inputStream); |
||||
ByteArrayOutputStream buf = new ByteArrayOutputStream(); |
||||
for (int result = bis.read(); result != -1; result = bis.read()) { |
||||
buf.write((byte) result); |
||||
} |
||||
str=buf.toString("UTF-8"); |
||||
}catch (Exception e){ |
||||
str=null; |
||||
} |
||||
// StandardCharsets.UTF_8.name() > JDK 7
|
||||
return str; |
||||
} |
||||
} |
@ -0,0 +1,32 @@ |
||||
package me.ag2s.epublib.util; |
||||
|
||||
import java.io.IOException; |
||||
import java.io.OutputStream; |
||||
|
||||
/** |
||||
* OutputStream with the close() disabled. |
||||
* We write multiple documents to a ZipOutputStream. |
||||
* Some of the formatters call a close() after writing their data. |
||||
* We don't want them to do that, so we wrap regular OutputStreams in this NoCloseOutputStream. |
||||
* |
||||
* @author paul |
||||
*/ |
||||
public class NoCloseOutputStream extends OutputStream { |
||||
|
||||
private OutputStream outputStream; |
||||
|
||||
public NoCloseOutputStream(OutputStream outputStream) { |
||||
this.outputStream = outputStream; |
||||
} |
||||
|
||||
@Override |
||||
public void write(int b) throws IOException { |
||||
outputStream.write(b); |
||||
} |
||||
|
||||
/** |
||||
* A close() that does not call it's parent's close() |
||||
*/ |
||||
public void close() { |
||||
} |
||||
} |
@ -0,0 +1,35 @@ |
||||
package me.ag2s.epublib.util; |
||||
|
||||
import java.io.IOException; |
||||
import java.io.Writer; |
||||
|
||||
/** |
||||
* Writer with the close() disabled. |
||||
* We write multiple documents to a ZipOutputStream. |
||||
* Some of the formatters call a close() after writing their data. |
||||
* We don't want them to do that, so we wrap regular Writers in this NoCloseWriter. |
||||
* |
||||
* @author paul |
||||
*/ |
||||
public class NoCloseWriter extends Writer { |
||||
|
||||
private Writer writer; |
||||
|
||||
public NoCloseWriter(Writer writer) { |
||||
this.writer = writer; |
||||
} |
||||
|
||||
@Override |
||||
public void close() throws IOException { |
||||
} |
||||
|
||||
@Override |
||||
public void flush() throws IOException { |
||||
writer.flush(); |
||||
} |
||||
|
||||
@Override |
||||
public void write(char[] cbuf, int off, int len) throws IOException { |
||||
writer.write(cbuf, off, len); |
||||
} |
||||
} |
@ -0,0 +1,174 @@ |
||||
package me.ag2s.epublib.util; |
||||
|
||||
import me.ag2s.epublib.Constants; |
||||
import me.ag2s.epublib.domain.MediaType; |
||||
import me.ag2s.epublib.domain.Resource; |
||||
import me.ag2s.epublib.epub.EpubProcessorSupport; |
||||
import me.ag2s.epublib.domain.MediaTypes; |
||||
import java.io.File; |
||||
import java.io.FileInputStream; |
||||
import java.io.IOException; |
||||
import java.io.InputStream; |
||||
import java.io.Reader; |
||||
import java.io.UnsupportedEncodingException; |
||||
import java.util.zip.ZipEntry; |
||||
import java.util.zip.ZipInputStream; |
||||
|
||||
import javax.xml.parsers.DocumentBuilder; |
||||
import javax.xml.parsers.ParserConfigurationException; |
||||
|
||||
import org.w3c.dom.Document; |
||||
import org.xml.sax.InputSource; |
||||
import org.xml.sax.SAXException; |
||||
|
||||
/** |
||||
* Various resource utility methods |
||||
* |
||||
* @author paul |
||||
*/ |
||||
public class ResourceUtil { |
||||
|
||||
public static Resource createHTMLResource(String title, String string) { |
||||
String html=createHtml(title,string); |
||||
MediaType mediaTypeProperty = MediaTypes.XHTML; |
||||
byte[] data = html.getBytes(); |
||||
Resource result = new Resource(data, mediaTypeProperty); |
||||
return result; |
||||
} |
||||
public static Resource createHTMLResource(String title, String string,String href) { |
||||
String html=createHtml(title,string); |
||||
MediaType mediaTypeProperty = MediaTypes.XHTML; |
||||
byte[] data = html.getBytes(); |
||||
Resource result = new Resource(null,data,href ,mediaTypeProperty); |
||||
return result; |
||||
} |
||||
private static String createHtml(String title,String txt){ |
||||
StringBuilder body= new StringBuilder(); |
||||
for(String s:txt.split("\\r?\\n")){ |
||||
s=s.trim(); |
||||
if(s!=null&&s.length()!=0) { |
||||
body.append("<p>").append(s).append("</p>"); |
||||
} |
||||
|
||||
} |
||||
String html="<?xml version=\"1.0\" encoding=\"utf-8\"?>"; |
||||
html+="<html xmlns=\"http://www.w3.org/1999/xhtml\" xmlns:epub=\"http://www.idpf.org/2007/ops\">"; |
||||
html+="<head><title>"+title+"</title>" + |
||||
"<link rel=\"stylesheet\" type=\"text/css\" href=\"css/style.css\"/>" + |
||||
"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/></head>"; |
||||
html+="<body><h1>"+title+"</h1>" + |
||||
body+ |
||||
"</body></html>"; |
||||
|
||||
return html; |
||||
} |
||||
|
||||
public static Resource createResource(File file) throws IOException { |
||||
if (file == null) { |
||||
return null; |
||||
} |
||||
MediaType mediaType = MediaTypes.determineMediaType(file.getName()); |
||||
byte[] data = IOUtil.toByteArray(new FileInputStream(file)); |
||||
Resource result = new Resource(data, mediaType); |
||||
return result; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Creates a resource with as contents a html page with the given title. |
||||
* |
||||
* @param title |
||||
* @param href |
||||
* @return a resource with as contents a html page with the given title. |
||||
*/ |
||||
public static Resource createResource(String title, String href) { |
||||
String content = |
||||
"<html><head><title>" + title + "</title></head><body><h1>" + title |
||||
+ "</h1></body></html>"; |
||||
return new Resource(null, content.getBytes(), href, MediaTypes.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 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; |
||||
} |
||||
} |
@ -0,0 +1,202 @@ |
||||
package me.ag2s.epublib.util; |
||||
/* |
||||
* 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 <a href="mailto:jk2006@engineer.com">John Kristian</a> |
||||
*/ |
||||
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(); |
||||
} |
||||
|
||||
} |
||||
|
@ -0,0 +1,275 @@ |
||||
package me.ag2s.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<String> parts = new ArrayList<String>(Arrays.asList(stringParts)); |
||||
for (int i = 0; i < parts.size() - 1; i++) { |
||||
String currentDir = parts.get(i); |
||||
if (currentDir.length() == 0 || currentDir.equals(".")) { |
||||
parts.remove(i); |
||||
i--; |
||||
} else if (currentDir.equals("..")) { |
||||
parts.remove(i - 1); |
||||
parts.remove(i - 1); |
||||
i -= 2; |
||||
} |
||||
} |
||||
StringBuilder result = new StringBuilder(); |
||||
if (path.startsWith("/")) { |
||||
result.append('/'); |
||||
} |
||||
for (int i = 0; i < parts.size(); i++) { |
||||
result.append(parts.get(i)); |
||||
if (i < (parts.size() - 1)) { |
||||
result.append('/'); |
||||
} |
||||
} |
||||
return result.toString(); |
||||
} |
||||
|
||||
/** |
||||
* Whether the String is not null, not zero-length and does not contain of |
||||
* only whitespace. |
||||
* |
||||
* @param text |
||||
* @return Whether the String is not null, not zero-length and does not contain of |
||||
*/ |
||||
public static boolean isNotBlank(String text) { |
||||
return !isBlank(text); |
||||
} |
||||
|
||||
/** |
||||
* Whether the String is null, zero-length and does contain only whitespace. |
||||
* |
||||
* @return Whether the String is null, zero-length and does contain only whitespace. |
||||
*/ |
||||
public static boolean isBlank(String text) { |
||||
if (isEmpty(text)) { |
||||
return true; |
||||
} |
||||
for (int i = 0; i < text.length(); i++) { |
||||
if (!Character.isWhitespace(text.charAt(i))) { |
||||
return false; |
||||
} |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
/** |
||||
* Whether the given string is null or zero-length. |
||||
* |
||||
* @param text the input for this method |
||||
* @return Whether the given string is null or zero-length. |
||||
*/ |
||||
public static boolean isEmpty(String text) { |
||||
return (text == null) || (text.length() == 0); |
||||
} |
||||
|
||||
/** |
||||
* Whether the given source string ends with the given suffix, ignoring |
||||
* case. |
||||
* |
||||
* @param source |
||||
* @param suffix |
||||
* @return Whether the given source string ends with the given suffix, ignoring case. |
||||
*/ |
||||
public static boolean endsWithIgnoreCase(String source, String suffix) { |
||||
if (isEmpty(suffix)) { |
||||
return true; |
||||
} |
||||
if (isEmpty(source)) { |
||||
return false; |
||||
} |
||||
if (suffix.length() > source.length()) { |
||||
return false; |
||||
} |
||||
return source.substring(source.length() - suffix.length()) |
||||
.toLowerCase().endsWith(suffix.toLowerCase()); |
||||
} |
||||
|
||||
/** |
||||
* If the given text is null return "", the original text otherwise. |
||||
* |
||||
* @param text |
||||
* @return If the given text is null "", the original text otherwise. |
||||
*/ |
||||
public static String defaultIfNull(String text) { |
||||
return defaultIfNull(text, ""); |
||||
} |
||||
|
||||
/** |
||||
* If the given text is null return "", the given defaultValue otherwise. |
||||
* |
||||
* @param text |
||||
* @param defaultValue |
||||
* @return If the given text is null "", the given defaultValue otherwise. |
||||
*/ |
||||
public static String defaultIfNull(String text, String defaultValue) { |
||||
if (text == null) { |
||||
return defaultValue; |
||||
} |
||||
return text; |
||||
} |
||||
|
||||
/** |
||||
* Null-safe string comparator |
||||
* |
||||
* @param text1 |
||||
* @param text2 |
||||
* @return whether the two strings are equal |
||||
*/ |
||||
public static boolean equals(String text1, String text2) { |
||||
if (text1 == null) { |
||||
return (text2 == null); |
||||
} |
||||
return text1.equals(text2); |
||||
} |
||||
|
||||
/** |
||||
* Pretty toString printer. |
||||
* |
||||
* @param keyValues |
||||
* @return a string representation of the input values |
||||
*/ |
||||
public static String toString(Object... keyValues) { |
||||
StringBuilder result = new StringBuilder(); |
||||
result.append('['); |
||||
for (int i = 0; i < keyValues.length; i += 2) { |
||||
if (i > 0) { |
||||
result.append(", "); |
||||
} |
||||
result.append(keyValues[i]); |
||||
result.append(": "); |
||||
Object value = null; |
||||
if ((i + 1) < keyValues.length) { |
||||
value = keyValues[i + 1]; |
||||
} |
||||
if (value == null) { |
||||
result.append("<null>"); |
||||
} else { |
||||
result.append('\''); |
||||
result.append(value); |
||||
result.append('\''); |
||||
} |
||||
} |
||||
result.append(']'); |
||||
return result.toString(); |
||||
} |
||||
|
||||
public static int hashCode(String... values) { |
||||
int result = 31; |
||||
for (int i = 0; i < values.length; i++) { |
||||
result ^= String.valueOf(values[i]).hashCode(); |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
/** |
||||
* Gives the substring of the given text before the given separator. |
||||
* |
||||
* If the text does not contain the given separator then the given text is |
||||
* returned. |
||||
* |
||||
* @param text |
||||
* @param separator |
||||
* @return the substring of the given text before the given separator. |
||||
*/ |
||||
public static String substringBefore(String text, char separator) { |
||||
if (isEmpty(text)) { |
||||
return text; |
||||
} |
||||
int sepPos = text.indexOf(separator); |
||||
if (sepPos < 0) { |
||||
return text; |
||||
} |
||||
return text.substring(0, sepPos); |
||||
} |
||||
|
||||
/** |
||||
* Gives the substring of the given text before the last occurrence of the |
||||
* given separator. |
||||
* |
||||
* If the text does not contain the given separator then the given text is |
||||
* returned. |
||||
* |
||||
* @param text |
||||
* @param separator |
||||
* @return the substring of the given text before the last occurrence of the given separator. |
||||
*/ |
||||
public static String substringBeforeLast(String text, char separator) { |
||||
if (isEmpty(text)) { |
||||
return text; |
||||
} |
||||
int cPos = text.lastIndexOf(separator); |
||||
if (cPos < 0) { |
||||
return text; |
||||
} |
||||
return text.substring(0, cPos); |
||||
} |
||||
|
||||
/** |
||||
* Gives the substring of the given text after the last occurrence of the |
||||
* given separator. |
||||
* |
||||
* If the text does not contain the given separator then "" is returned. |
||||
* |
||||
* @param text |
||||
* @param separator |
||||
* @return the substring of the given text after the last occurrence of the given separator. |
||||
*/ |
||||
public static String substringAfterLast(String text, char separator) { |
||||
if (isEmpty(text)) { |
||||
return text; |
||||
} |
||||
int cPos = text.lastIndexOf(separator); |
||||
if (cPos < 0) { |
||||
return ""; |
||||
} |
||||
return text.substring(cPos + 1); |
||||
} |
||||
|
||||
/** |
||||
* Gives the substring of the given text after the given separator. |
||||
* |
||||
* If the text does not contain the given separator then "" is returned. |
||||
* |
||||
* @param text the input text |
||||
* @param c the separator char |
||||
* @return the substring of the given text after the given separator. |
||||
*/ |
||||
public static String substringAfter(String text, char c) { |
||||
if (isEmpty(text)) { |
||||
return text; |
||||
} |
||||
int cPos = text.indexOf(c); |
||||
if (cPos < 0) { |
||||
return ""; |
||||
} |
||||
return text.substring(cPos + 1); |
||||
} |
||||
} |
@ -0,0 +1,340 @@ |
||||
/* |
||||
* 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. |
||||
*/ |
||||
package me.ag2s.epublib.util.commons.io; |
||||
|
||||
import java.io.IOException; |
||||
import java.io.InputStream; |
||||
import java.util.Arrays; |
||||
import java.util.List; |
||||
|
||||
/** |
||||
* This class is used to wrap a stream that includes an encoded |
||||
* {@link ByteOrderMark} as its first bytes. |
||||
* |
||||
* This class detects these bytes and, if required, can automatically skip them |
||||
* and return the subsequent byte as the first byte in the stream. |
||||
* |
||||
* The {@link ByteOrderMark} implementation has the following pre-defined BOMs: |
||||
* <ul> |
||||
* <li>UTF-8 - {@link ByteOrderMark#UTF_8}</li> |
||||
* <li>UTF-16BE - {@link ByteOrderMark#UTF_16LE}</li> |
||||
* <li>UTF-16LE - {@link ByteOrderMark#UTF_16BE}</li> |
||||
* </ul> |
||||
* |
||||
* |
||||
* <h3>Example 1 - Detect and exclude a UTF-8 BOM</h3> |
||||
* <pre> |
||||
* BOMInputStream bomIn = new BOMInputStream(in); |
||||
* if (bomIn.hasBOM()) { |
||||
* // has a UTF-8 BOM
|
||||
* } |
||||
* </pre> |
||||
* |
||||
* <h3>Example 2 - Detect a UTF-8 BOM (but don't exclude it)</h3> |
||||
* <pre> |
||||
* boolean include = true; |
||||
* BOMInputStream bomIn = new BOMInputStream(in, include); |
||||
* if (bomIn.hasBOM()) { |
||||
* // has a UTF-8 BOM
|
||||
* } |
||||
* </pre> |
||||
* |
||||
* <h3>Example 3 - Detect Multiple BOMs</h3> |
||||
* <pre> |
||||
* 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
|
||||
* } |
||||
* </pre> |
||||
* |
||||
* @see ByteOrderMark |
||||
* @see <a href="http://en.wikipedia.org/wiki/Byte_order_mark">Wikipedia - Byte Order Mark</a> |
||||
* @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 List<ByteOrderMark> boms; |
||||
private ByteOrderMark byteOrderMark; |
||||
private int[] firstBytes; |
||||
private int fbLength; |
||||
private int fbIndex; |
||||
private int markFbIndex; |
||||
private boolean markedAtStart; |
||||
|
||||
/** |
||||
* Constructs a new BOM InputStream that excludes |
||||
* a {@link ByteOrderMark#UTF_8} BOM. |
||||
* @param delegate the InputStream to delegate to |
||||
*/ |
||||
public BOMInputStream(InputStream delegate) { |
||||
this(delegate, false, ByteOrderMark.UTF_8); |
||||
} |
||||
|
||||
/** |
||||
* Constructs a new BOM InputStream that detects a |
||||
* a {@link ByteOrderMark#UTF_8} and optionally includes it. |
||||
* @param delegate the InputStream to delegate to |
||||
* @param include true to include the UTF-8 BOM or |
||||
* false to exclude it |
||||
*/ |
||||
public BOMInputStream(InputStream delegate, boolean include) { |
||||
this(delegate, include, ByteOrderMark.UTF_8); |
||||
} |
||||
|
||||
/** |
||||
* Constructs a new BOM InputStream that excludes |
||||
* the specified BOMs. |
||||
* @param delegate the InputStream to delegate to |
||||
* @param boms The BOMs to detect and exclude |
||||
*/ |
||||
public BOMInputStream(InputStream delegate, ByteOrderMark... boms) { |
||||
this(delegate, false, boms); |
||||
} |
||||
|
||||
/** |
||||
* Constructs a new BOM InputStream that detects the |
||||
* specified BOMs and optionally includes them. |
||||
* @param delegate the InputStream to delegate to |
||||
* @param include true to include the specified BOMs or |
||||
* false to exclude them |
||||
* @param boms The BOMs to detect and optionally exclude |
||||
*/ |
||||
public BOMInputStream(InputStream delegate, boolean include, ByteOrderMark... boms) { |
||||
super(delegate); |
||||
if (boms == null || boms.length == 0) { |
||||
throw new IllegalArgumentException("No BOMs specified"); |
||||
} |
||||
this.include = include; |
||||
this.boms = Arrays.asList(boms); |
||||
} |
||||
|
||||
/** |
||||
* Indicates whether the stream contains one of the specified BOMs. |
||||
* |
||||
* @return true if the stream has one of the specified BOMs, otherwise false |
||||
* if it does not |
||||
* @throws IOException if an error reading the first bytes of the stream occurs |
||||
*/ |
||||
public boolean hasBOM() throws IOException { |
||||
return (getBOM() != null); |
||||
} |
||||
|
||||
/** |
||||
* Indicates whether the stream contains the specified BOM. |
||||
* |
||||
* @param bom The BOM to check for |
||||
* @return true if the stream has the specified BOM, otherwise false |
||||
* if it does not |
||||
* @throws IllegalArgumentException if the BOM is not one the stream |
||||
* is configured to detect |
||||
* @throws IOException if an error reading the first bytes of the stream occurs |
||||
*/ |
||||
public boolean hasBOM(ByteOrderMark bom) throws IOException { |
||||
if (!boms.contains(bom)) { |
||||
throw new IllegalArgumentException("Stream not configure to detect " + bom); |
||||
} |
||||
return (byteOrderMark != null && getBOM().equals(bom)); |
||||
} |
||||
|
||||
/** |
||||
* Return the BOM (Byte Order Mark). |
||||
* |
||||
* @return The BOM or null if none |
||||
* @throws IOException if an error reading the first bytes of the stream occurs |
||||
*/ |
||||
public ByteOrderMark getBOM() throws IOException { |
||||
if (firstBytes == null) { |
||||
int max = 0; |
||||
for (ByteOrderMark bom : boms) { |
||||
max = Math.max(max, bom.length()); |
||||
} |
||||
firstBytes = new int[max]; |
||||
for (int i = 0; i < firstBytes.length; i++) { |
||||
firstBytes[i] = in.read(); |
||||
fbLength++; |
||||
if (firstBytes[i] < 0) { |
||||
break; |
||||
} |
||||
|
||||
byteOrderMark = find(); |
||||
if (byteOrderMark != null) { |
||||
if (!include) { |
||||
fbLength = 0; |
||||
} |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
return byteOrderMark; |
||||
} |
||||
|
||||
/** |
||||
* Return the BOM charset Name - {@link ByteOrderMark#getCharsetName()}. |
||||
* |
||||
* @return The BOM charset Name or null if no BOM found |
||||
* @throws IOException if an error reading the first bytes of the stream occurs |
||||
* |
||||
*/ |
||||
public String getBOMCharsetName() throws IOException { |
||||
getBOM(); |
||||
return (byteOrderMark == null ? null : byteOrderMark.getCharsetName()); |
||||
} |
||||
|
||||
/** |
||||
* This method reads and either preserves or skips the first bytes in the |
||||
* stream. It behaves like the single-byte <code>read()</code> 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 <code>read()</code> 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 <code>read(byte[], int, int)</code> 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 <code>read(byte[])</code> 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 <code>mark(int)</code> 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 <code>reset()</code> 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 <code>skip(long)</code> 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); |
||||
} |
||||
} |
@ -0,0 +1,170 @@ |
||||
package me.ag2s.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 <a href="http://en.wikipedia.org/wiki/Byte_order_mark">Wikipedia - Byte Order Mark</a> |
||||
* @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 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(); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,238 @@ |
||||
package me.ag2s.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. |
||||
* <p> |
||||
* 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). |
||||
* <p> |
||||
* 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 <code>read()</code> 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 <code>read(byte[])</code> 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 <code>read(byte[], int, int)</code> 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 <code>skip(long)</code> 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 <code>available()</code> 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 <code>close()</code> 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 <code>mark(int)</code> method. |
||||
* @param readlimit read ahead limit |
||||
*/ |
||||
@Override |
||||
public synchronized void mark(int readlimit) { |
||||
in.mark(readlimit); |
||||
} |
||||
|
||||
/** |
||||
* Invokes the delegate's <code>reset()</code> 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 <code>markSupported()</code> 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. |
||||
* <p> |
||||
* Subclasses can override this method to add common pre-processing |
||||
* functionality without having to override all the read methods. |
||||
* The default implementation does nothing. |
||||
* <p> |
||||
* Note this method is <em>not</em> 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. |
||||
* <p> |
||||
* Subclasses can override this method to add common post-processing |
||||
* functionality without having to override all the read methods. |
||||
* The default implementation does nothing. |
||||
* <p> |
||||
* Note this method is <em>not</em> 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. |
||||
* <p> |
||||
* 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; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,752 @@ |
||||
package me.ag2s.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. |
||||
* <p> |
||||
* IMPORTANT: This class is not related in any way to the org.xml.sax.XMLReader. |
||||
* This one IS a character stream. |
||||
* <p> |
||||
* 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). |
||||
* <p> |
||||
* The XmlStreamReader class handles the charset encoding of XML documents in |
||||
* Files, raw streams and HTTP streams by offering a wide set of constructors. |
||||
* <p> |
||||
* 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, <a |
||||
* href="http://diveintomark.org/archives/2004/02/13/xml-media-types"> |
||||
* Determining the character encoding of a feed</a>. |
||||
* <p> |
||||
* Originally developed for <a href="http://rome.dev.java.net">ROME</a> 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. |
||||
* <p> |
||||
* 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. |
||||
* <p> |
||||
* It looks for the UTF-8 BOM first, if none sniffs the XML prolog charset, |
||||
* if this is also missing defaults to UTF-8. |
||||
* <p> |
||||
* 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. |
||||
* <p> |
||||
* It follows the same logic used for files. |
||||
* <p> |
||||
* 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. |
||||
* <p> |
||||
* It follows the same logic used for files. |
||||
* <p> |
||||
* If lenient detection is indicated and the detection above fails as per |
||||
* specifications it then attempts the following: |
||||
* <p> |
||||
* If the content type was 'text/html' it replaces it with 'text/xml' and |
||||
* tries the detection again. |
||||
* <p> |
||||
* Else if the XML prolog had a charset encoding that encoding is used. |
||||
* <p> |
||||
* Else if the content type had a charset encoding that encoding is used. |
||||
* <p> |
||||
* Else 'UTF-8' is used. |
||||
* <p> |
||||
* 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. |
||||
* <p> |
||||
* It follows the same logic used for files. |
||||
* <p> |
||||
* If lenient detection is indicated and the detection above fails as per |
||||
* specifications it then attempts the following: |
||||
* <p> |
||||
* If the content type was 'text/html' it replaces it with 'text/xml' and |
||||
* tries the detection again. |
||||
* <p> |
||||
* Else if the XML prolog had a charset encoding that encoding is used. |
||||
* <p> |
||||
* Else if the content type had a charset encoding that encoding is used. |
||||
* <p> |
||||
* Else 'UTF-8' is used. |
||||
* <p> |
||||
* 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. |
||||
* <p> |
||||
* 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. |
||||
* <p> |
||||
* 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. |
||||
* <p> |
||||
* 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. |
||||
* <p> |
||||
* 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. |
||||
* <p> |
||||
* 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. |
||||
* <p> |
||||
* 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. |
||||
* <p> |
||||
* 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. |
||||
* <p> |
||||
* 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. |
||||
* <p> |
||||
* 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. |
||||
* <p> |
||||
* If lenient detection is indicated and the detection above fails as per |
||||
* specifications it then attempts the following: |
||||
* <p> |
||||
* If the content type was 'text/html' it replaces it with 'text/xml' and |
||||
* tries the detection again. |
||||
* <p> |
||||
* Else if the XML prolog had a charset encoding that encoding is used. |
||||
* <p> |
||||
* Else if the content type had a charset encoding that encoding is used. |
||||
* <p> |
||||
* Else 'UTF-8' is used. |
||||
* <p> |
||||
* 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. |
||||
* <p> |
||||
* 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. |
||||
* <p> |
||||
* If lenient detection is indicated and the detection above fails as per |
||||
* specifications it then attempts the following: |
||||
* <p> |
||||
* If the content type was 'text/html' it replaces it with 'text/xml' and |
||||
* tries the detection again. |
||||
* <p> |
||||
* Else if the XML prolog had a charset encoding that encoding is used. |
||||
* <p> |
||||
* Else if the content type had a charset encoding that encoding is used. |
||||
* <p> |
||||
* Else 'UTF-8' is used. |
||||
* <p> |
||||
* 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 <code>read(char[], int, int)</code> 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 <?xml encoding=...?>, NULL if none. |
||||
* |
||||
* @param is InputStream to create the reader from. |
||||
* @param guessedEnc guessed encoding |
||||
* @return the encoding declared in the <?xml encoding=...?> |
||||
* @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"; |
||||
|
||||
} |
@ -0,0 +1,138 @@ |
||||
package me.ag2s.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. |
||||
* <p> |
||||
* 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. |
||||
* <p> |
||||
* 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. |
||||
* <p> |
||||
* 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; |
||||
} |
||||
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,390 @@ |
||||
<!-- |
||||
|
||||
Title: |
||||
|
||||
The Package Document Type Definition (DTD) for the Open |
||||
eBook Publication Structure Version 1.2 |
||||
|
||||
|
||||
Version: |
||||
|
||||
1.2 |
||||
|
||||
|
||||
Revision: |
||||
|
||||
20020930-x (supercedes 20020605-x) |
||||
|
||||
|
||||
Revision History Note: |
||||
|
||||
This revision, 20020930-x, which supercedes the prior revision |
||||
20020605-x, solely updates the email addresses within this |
||||
comment prologue. No changes whatsover were made to the parsed |
||||
content of this DTD. |
||||
|
||||
|
||||
Previous Released Version: |
||||
|
||||
1.0.1 (Revision of 01-February-2001, "Document Type |
||||
Definition for the Open eBook package version |
||||
1.0.1") |
||||
|
||||
|
||||
Authors: |
||||
|
||||
Version 1.0; 1.0.1 |
||||
|
||||
Steve DeRose <sjd@stg.brown.edu> |
||||
Gunter Hille <hille@abc.de> |
||||
Ben Trafford <ben@legendary.org> |
||||
Garret Wilson <garret@globalmentor.com> |
||||
|
||||
This Version 1.2 updated and edited by: |
||||
|
||||
Jon Noring <jon@noring.name> |
||||
Benjamin Jung <benjamin.jung@deepx.com> |
||||
|
||||
|
||||
Usage: |
||||
|
||||
<?xml version="1.0"?> |
||||
<!DOCTYPE package |
||||
PUBLIC "+//ISBN 0-9673008-1-9//DTD OEB 1.2 Package//EN" |
||||
"http://openebook.org/dtds/oeb-1.2/oebpkg12.dtd"> |
||||
<package unique-identifier="foo"> |
||||
metadata |
||||
manifest |
||||
spine |
||||
tours |
||||
guide |
||||
</package> |
||||
|
||||
|
||||
Summary Description: |
||||
|
||||
This is the Package Document Type Definition (DTD) for |
||||
the Open eBook Publication Structure version 1.2. |
||||
|
||||
Changes to this DTD from version 1.0.1 include: |
||||
|
||||
a. Upgrading the <dc-metadata> content model to |
||||
fully conform with the OEBPS 1.2 specification |
||||
requirements. Specifically, <dc:Language> is |
||||
now required, while in OEBPS 1.0.1 it was |
||||
optional. |
||||
|
||||
b. Updating the mnemonic character entity |
||||
declaration to refer to version 1.2. |
||||
|
||||
c. Updating the xmlns:dc namespace to refer to |
||||
version 1.1 of the Dublin Core Metadata |
||||
Initiative. |
||||
|
||||
d. Editing and updating the various non-parsed |
||||
comments. |
||||
|
||||
e. Revising the layout (e.g., white space |
||||
alteration) to aid in readability. |
||||
|
||||
--> |
||||
|
||||
|
||||
<!-- *************************************************** --> |
||||
|
||||
<!-- XHTML MNEMONIC CHARACTER ENTITIES ................. --> |
||||
|
||||
<!ENTITY % OEBEntities |
||||
PUBLIC "+//ISBN 0-9673008-1-9//DTD OEB 1.2 Entities//EN" |
||||
"http://openebook.org/dtds/oeb-1.2/oeb12.ent"> |
||||
|
||||
%OEBEntities; |
||||
|
||||
<!-- *************************************************** --> |
||||
|
||||
<!-- DATATYPE ENTITIES ................................. --> |
||||
|
||||
<!-- Uniform Resource Identifier (URI), per [RFC2396] --> |
||||
|
||||
<!ENTITY % URI "CDATA"> |
||||
|
||||
<!-- Language code, per [RFC3066] --> |
||||
|
||||
<!ENTITY % LanguageCode "NMTOKEN"> |
||||
|
||||
<!-- *************************************************** --> |
||||
|
||||
<!-- NAMESPACE ENTITIES ................................ --> |
||||
|
||||
<!ENTITY % dc.xmlns |
||||
"'http://purl.org/dc/elements/1.1/'"> |
||||
|
||||
<!ENTITY % oebpk.xmlns |
||||
"'http://openebook.org/namespaces/oeb-package/1.0/'"> |
||||
|
||||
<!-- *************************************************** --> |
||||
|
||||
<!-- ELEMENT ENTITIES .................................. --> |
||||
|
||||
<!-- The entity 'DCMetadataOpt' includes the 12 optional |
||||
<dc:Xxx> children elements of <dc-metadata>. It will |
||||
be used in the <dc-metadata> content model. --> |
||||
|
||||
<!ENTITY % DCMetadataOpt |
||||
"dc:Contributor | |
||||
dc:Coverage | |
||||
dc:Creator | |
||||
dc:Date | |
||||
dc:Description | |
||||
dc:Format | |
||||
dc:Publisher | |
||||
dc:Relation | |
||||
dc:Rights | |
||||
dc:Source | |
||||
dc:Subject | |
||||
dc:Type "> |
||||
|
||||
<!-- *************************************************** --> |
||||
|
||||
<!-- ATTRIBUTE ENTITIES ................................ --> |
||||
|
||||
<!ENTITY % CoreAttributes |
||||
"id ID #IMPLIED"> |
||||
|
||||
<!ENTITY % InternationalAttributes |
||||
"xml:lang %LanguageCode; #IMPLIED"> |
||||
|
||||
<!ENTITY % CommonAttributes |
||||
"%CoreAttributes; |
||||
%InternationalAttributes;"> |
||||
|
||||
<!-- 'DCNamespaceAttribute' is an attribute entity declaring |
||||
the Dublin Core namespace. Used on each <dc:Xxx> element |
||||
to accommodate XML parsers which unnecessarily require |
||||
this. --> |
||||
|
||||
<!ENTITY % DCNamespaceAttribute |
||||
"xmlns:dc %URI; #FIXED %dc.xmlns;"> |
||||
|
||||
<!-- *************************************************** --> |
||||
|
||||
<!-- ELEMENTS AND ATTRIBUTES ........................... --> |
||||
|
||||
<!-- <package> must have as children elements, in this order: |
||||
<metadata>, <manifest>, and <spine>, and optionally may |
||||
include <tours> and/or <guide>. The 'unique-identifier' |
||||
attribute is required for <package> (see comment for |
||||
<dc:Identifier>.) --> |
||||
|
||||
<!ELEMENT package (metadata, manifest, spine, tours?, guide?)> |
||||
<!ATTLIST package |
||||
%CommonAttributes; |
||||
unique-identifier IDREF #REQUIRED |
||||
xmlns %URI; #FIXED %oebpk.xmlns;> |
||||
|
||||
<!-- <metadata> must contain one <dc-metadata>, and |
||||
optionally contain one <x-metadata>. There are no |
||||
attributes for <metadata>. --> |
||||
|
||||
<!ELEMENT metadata (dc-metadata, x-metadata?)> |
||||
|
||||
<!-- <dc-metadata> must contain at least one <dc:Title>, |
||||
one <dc:Identifier>, and one <dc:Language>, and may |
||||
contain one or more of each of the other twelve |
||||
optional <dc:XXX> elements, all in any order. --> |
||||
|
||||
<!ELEMENT dc-metadata |
||||
( (%DCMetadataOpt;)*, |
||||
( (dc:Title, (%DCMetadataOpt; | dc:Title)*, |
||||
( (dc:Identifier, (%DCMetadataOpt; | dc:Title | dc:Identifier)*, |
||||
dc:Language) | |
||||
(dc:Language, (%DCMetadataOpt; | dc:Title | dc:Language)*, |
||||
dc:Identifier) ) ) | |
||||
(dc:Identifier, (%DCMetadataOpt; | dc:Identifier)*, |
||||
( (dc:Title, (%DCMetadataOpt; | dc:Identifier | dc:Title)*, |
||||
dc:Language) | |
||||
|
||||
(dc:Language, (%DCMetadataOpt; | dc:Identifier | dc:Language)*, |
||||
dc:Title) ) ) | |
||||
(dc:Language, (%DCMetadataOpt; | dc:Language)*, |
||||
( (dc:Identifier, (%DCMetadataOpt; | dc:Language | dc:Identifier)*, |
||||
dc:Title) | |
||||
(dc:Title, (%DCMetadataOpt; | dc:Language | dc:Title)*, |
||||
dc:Identifier) ) ) ), |
||||
(%DCMetadataOpt; | dc:Title | dc:Identifier | dc:Language)* )> |
||||
|
||||
<!ATTLIST dc-metadata |
||||
%CommonAttributes; |
||||
%DCNamespaceAttribute; |
||||
xmlns:oebpackage %URI; #FIXED %oebpk.xmlns;> |
||||
|
||||
<!-- Required elements for <dc-metadata>. --> |
||||
|
||||
<!ELEMENT dc:Title (#PCDATA)> |
||||
<!ATTLIST dc:Title |
||||
%CommonAttributes; |
||||
%DCNamespaceAttribute;> |
||||
|
||||
<!-- One <dc:Identifier> must specify an 'id' identical to |
||||
the value of the required <package> 'unique-identifier' |
||||
attribute. --> |
||||
|
||||
<!ELEMENT dc:Identifier (#PCDATA)> |
||||
<!ATTLIST dc:Identifier |
||||
%CommonAttributes; |
||||
%DCNamespaceAttribute; |
||||
scheme NMTOKEN #IMPLIED> |
||||
|
||||
<!ELEMENT dc:Language (#PCDATA)> |
||||
<!ATTLIST dc:Language |
||||
%CommonAttributes; |
||||
%DCNamespaceAttribute;> |
||||
|
||||
<!-- Optional elements for <dc-metadata>. --> |
||||
|
||||
<!ELEMENT dc:Contributor (#PCDATA)> |
||||
<!ATTLIST dc:Contributor |
||||
%CommonAttributes; |
||||
%DCNamespaceAttribute; |
||||
file-as CDATA #IMPLIED |
||||
role NMTOKEN #IMPLIED> |
||||
|
||||
<!ELEMENT dc:Coverage (#PCDATA)> |
||||
<!ATTLIST dc:Coverage |
||||
%CommonAttributes; |
||||
%DCNamespaceAttribute;> |
||||
|
||||
<!ELEMENT dc:Creator (#PCDATA)> |
||||
<!ATTLIST dc:Creator |
||||
%CommonAttributes; |
||||
%DCNamespaceAttribute; |
||||
file-as CDATA #IMPLIED |
||||
role NMTOKEN #IMPLIED> |
||||
|
||||
<!ELEMENT dc:Date (#PCDATA)> |
||||
<!ATTLIST dc:Date |
||||
%CommonAttributes; |
||||
%DCNamespaceAttribute; |
||||
event NMTOKEN #IMPLIED> |
||||
|
||||
<!ELEMENT dc:Description (#PCDATA)> |
||||
<!ATTLIST dc:Description |
||||
%CommonAttributes; |
||||
%DCNamespaceAttribute;> |
||||
|
||||
<!ELEMENT dc:Format (#PCDATA)> |
||||
<!ATTLIST dc:Format |
||||
%CommonAttributes; |
||||
%DCNamespaceAttribute;> |
||||
|
||||
<!ELEMENT dc:Publisher (#PCDATA)> |
||||
<!ATTLIST dc:Publisher |
||||
%CommonAttributes; |
||||
%DCNamespaceAttribute;> |
||||
|
||||
<!ELEMENT dc:Relation (#PCDATA)> |
||||
<!ATTLIST dc:Relation |
||||
%CommonAttributes; |
||||
%DCNamespaceAttribute;> |
||||
|
||||
<!ELEMENT dc:Rights (#PCDATA)> |
||||
<!ATTLIST dc:Rights |
||||
%CommonAttributes; |
||||
%DCNamespaceAttribute;> |
||||
|
||||
<!ELEMENT dc:Source (#PCDATA)> |
||||
<!ATTLIST dc:Source |
||||
%CommonAttributes; |
||||
%DCNamespaceAttribute;> |
||||
|
||||
<!ELEMENT dc:Subject (#PCDATA)> |
||||
<!ATTLIST dc:Subject |
||||
%CommonAttributes; |
||||
%DCNamespaceAttribute;> |
||||
|
||||
<!ELEMENT dc:Type (#PCDATA)> |
||||
|
||||
<!ATTLIST dc:Type |
||||
%CommonAttributes; |
||||
%DCNamespaceAttribute;> |
||||
|
||||
<!-- <x-metadata> must contain at least one <meta>. --> |
||||
|
||||
<!ELEMENT x-metadata (meta+)> |
||||
<!ATTLIST x-metadata %CommonAttributes;> |
||||
|
||||
<!-- Note that 'content' and 'name' are required attributes |
||||
for <meta>. --> |
||||
|
||||
<!ELEMENT meta EMPTY> |
||||
<!ATTLIST meta |
||||
%CommonAttributes; |
||||
content CDATA #REQUIRED |
||||
name NMTOKEN #REQUIRED |
||||
scheme CDATA #IMPLIED> |
||||
|
||||
<!-- <manifest> must contain at least one <item>. --> |
||||
|
||||
<!ELEMENT manifest (item+)> |
||||
<!ATTLIST manifest %CommonAttributes;> |
||||
|
||||
<!-- Note that 'href', 'id' and 'media-type' are required |
||||
attributes for <item>. --> |
||||
|
||||
<!ELEMENT item EMPTY> |
||||
<!ATTLIST item |
||||
%InternationalAttributes; |
||||
fallback IDREF #IMPLIED |
||||
href %URI; #REQUIRED |
||||
id ID #REQUIRED |
||||
media-type CDATA #REQUIRED> |
||||
|
||||
<!-- <spine> must contain at least one <itemref>. --> |
||||
|
||||
<!ELEMENT spine (itemref+)> |
||||
<!ATTLIST spine %CommonAttributes;> |
||||
|
||||
<!-- Note that 'idref' is a required attribute for |
||||
<itemref>. --> |
||||
|
||||
<!ELEMENT itemref EMPTY> |
||||
<!ATTLIST itemref |
||||
%CommonAttributes; |
||||
idref IDREF #REQUIRED> |
||||
|
||||
<!-- <tours> must contain at least one <tour>. --> |
||||
|
||||
<!ELEMENT tours (tour+)> |
||||
<!ATTLIST tours %CommonAttributes;> |
||||
|
||||
<!-- <tour> must contain at least one <site>. Note that |
||||
'title' is a required attribute for <tour>. --> |
||||
|
||||
<!ELEMENT tour (site+)> |
||||
<!ATTLIST tour |
||||
%CommonAttributes; |
||||
title CDATA #REQUIRED> |
||||
|
||||
<!-- Note that 'href' and 'title' are required attributes |
||||
for <site>. --> |
||||
|
||||
<!ELEMENT site EMPTY> |
||||
<!ATTLIST site |
||||
%CommonAttributes; |
||||
href %URI; #REQUIRED |
||||
title CDATA #REQUIRED> |
||||
|
||||
<!-- <guide> must contain at least one <reference>. --> |
||||
|
||||
<!ELEMENT guide (reference+)> |
||||
<!ATTLIST guide %CommonAttributes;> |
||||
|
||||
<!-- Note that 'href', 'title' and 'type' are required |
||||
attributes for <reference>. --> |
||||
|
||||
<!ELEMENT reference EMPTY> |
||||
<!ATTLIST reference |
||||
%CommonAttributes; |
||||
href %URI; #REQUIRED |
||||
title CDATA #REQUIRED |
||||
type NMTOKEN #REQUIRED> |
@ -0,0 +1,269 @@ |
||||
<!-- NCX 2005-1 DTD 2005-06-26 |
||||
file: ncx-2005-1.dtd |
||||
|
||||
Authors: Mark Hakkinen, George Kerscher, Tom McLaughlin, James Pritchett, and Michael Moodie |
||||
Change list: |
||||
2002-02-12 M. Moodie. Changed content model of navLabel element to eliminate ambiguity. |
||||
2002-02-27 M. Moodie. Grammatical changes suggested by editor. |
||||
2004-03-31 J. Pritchett. Various changes per the 2004 change list: |
||||
- Changed internal version numbers from 1.1.0 to 1.2.0 |
||||
- Made audio clipBegin/clipEnd mandatory (change #10) |
||||
- Dropped value attribute from navPoint (change #11) |
||||
- Replaced lang attribute with xml:lang (change #12) |
||||
- Added <pageList> and <pageTarget> elements (change #48) |
||||
- Dropped onFocus and onBlur attributes from navPoint and navTarget (change #49) |
||||
- Added <img> to content models of docTitle and docAuthor (change #50) |
||||
- Removed reference to pages in description of navList (change #52) |
||||
- Added <navInfo> element (change #53) |
||||
- Added default namespace attribute to description of <ncx> (change #L8) |
||||
- Removed pageRef and mapRef attributes |
||||
2004-04-05 J. Pritchett. Changes after feedback from MM and MG to 2004-03-31 version |
||||
- Changed internal version numbers from 1.2.0 to 1.1.2 (per MM e-mail of 3/31) |
||||
- Changed system identifier to use z3986/2004 as path instead of z3986/v100 (per 3/31 con call) |
||||
- Added class attribute to both pageTarget and pageList (per MG e-mail of 4/1) |
||||
- Added comment text describing value attribute for pageTarget and navTarget (per MM e-mail of 3/31) |
||||
- Changed declaration of type attribute on pageTarget to enumerate allowed values |
||||
- Added playOrder attribute to navPoint, navTarget, and pageTarget (per Lloyd's proposal) |
||||
2004-04-05 T. McLaughlin. In description of smilCustomTest, added id and defaultState are to be copied. |
||||
Version update to 1.1.3. |
||||
2004-05-14 T. McLaughlin. Reinstated override attribute to be copied also. Added bookStruct attribute |
||||
and enum list to smilCustomTest. Update to 1.1.4. |
||||
Revised, 4/5/2004: Changed version to 1.1.2 |
||||
Revised, 4/5/2004: Changed system identifier to use '2004' path |
||||
Revised, 4/5/2004: TM, Changed version to 1.1.3 |
||||
Revised, 5/14/2004: TM, Changed version to 1.1.4 |
||||
2004-07-07 M. Moodie Updated version to 1.2.0 everywhere but at top, where version was set to 1.1.5. |
||||
2004-09-15 M. Moodie. Changed uri to URI throughout. Set version to 1.1.6. |
||||
2004-09-16 M. Moodie. Changed version to 1.2.0 |
||||
2005-06-26 M. Gylling. Changed pid, sid, ns uri, and filename for Z3986-2005 |
||||
|
||||
Description: |
||||
|
||||
NCX (Navigation Control for XML applications) is a generalized navigation definition DTD for application |
||||
to Digital Talking Books, eBooks, and general web content models. |
||||
This DTD is an XML application that layers navigation functionality on top of SMIL 2.0 content. |
||||
|
||||
The NCX defines a navigation path/model that may be applied upon existing publications, |
||||
without modification of the existing publication source, so long as the navigation targets within |
||||
the source publication can be directly referenced via a URI. |
||||
|
||||
The following identifiers apply to this DTD: |
||||
"-//NISO//DTD ncx 2005-1//EN" |
||||
"http://www.daisy.org/z3986/2005/ncx-2005-1.dtd" |
||||
--> |
||||
|
||||
<!-- Basic Entities --> |
||||
|
||||
<!ENTITY % i18n |
||||
"xml:lang NMTOKEN #IMPLIED |
||||
dir (ltr|rtl) #IMPLIED" > |
||||
|
||||
<!ENTITY % SMILtimeVal "CDATA" > |
||||
<!ENTITY % URI "CDATA" > |
||||
<!ENTITY % script "CDATA" > |
||||
|
||||
<!-- ELEMENTS --> |
||||
|
||||
<!-- Top Level NCX Container. --> |
||||
<!-- Revised, 3/31/2004: Added pageList to content model --> |
||||
<!ELEMENT ncx (head, docTitle, docAuthor*, navMap, pageList?, navList*)> |
||||
<!-- Revised, 4/5/2004: Changed version to 1.1.2 --> |
||||
<!-- Revised 3/29/2004: Added xmlns --> |
||||
<!-- Revised, 4/5/2004: TM, Changed version to 1.1.3 --> |
||||
<!-- Revised, 5/14/2004: TM, Changed version to 1.1.4 --> |
||||
<!-- Revised, 7/7/2004: MM, Changed version to 1.2.0 --> |
||||
<!ATTLIST ncx |
||||
version CDATA #FIXED "2005-1" |
||||
xmlns %URI; #FIXED "http://www.daisy.org/z3986/2005/ncx/" |
||||
%i18n; |
||||
> |
||||
|
||||
<!-- Document Head - Contains all NCX metadata. |
||||
--> |
||||
|
||||
<!ELEMENT head (smilCustomTest | meta)+> |
||||
|
||||
<!-- 2004-04-05 TM - only id and defaultState are copied --> |
||||
<!-- 2004-05-14 TM - revert to override copied too; added bookStruct attribute --> |
||||
<!-- smilCustomTest - Duplicates customTest data found in SMIL files. Each unique customTest |
||||
element that appears in one or more SMIL files must have its id, defaultState and override |
||||
attributes duplicated in a smilCustomTest element in the NCX. The NCX thus gathers in one |
||||
place all customTest elements used in the SMIL files, for presentation to the user. |
||||
--> |
||||
<!ELEMENT smilCustomTest EMPTY> |
||||
<!ATTLIST smilCustomTest |
||||
id ID #REQUIRED |
||||
defaultState (true|false) 'false' |
||||
override (visible|hidden) 'hidden' |
||||
bookStruct (PAGE_NUMBER|NOTE|NOTE_REFERENCE|ANNOTATION|LINE_NUMBER|OPTIONAL_SIDEBAR|OPTIONAL_PRODUCER_NOTE) #IMPLIED |
||||
> |
||||
|
||||
<!-- Meta Element - metadata about this NCX --> |
||||
<!ELEMENT meta EMPTY> |
||||
<!ATTLIST meta |
||||
name CDATA #REQUIRED |
||||
content CDATA #REQUIRED |
||||
scheme CDATA #IMPLIED |
||||
> |
||||
|
||||
<!-- DocTitle - the title of the document, required and must immediately follow head. |
||||
--> |
||||
|
||||
<!-- Revised, 3/31/2004: Added img to content model --> |
||||
<!ELEMENT docTitle (text, audio?, img?)> |
||||
<!ATTLIST docTitle |
||||
id ID #IMPLIED |
||||
%i18n; |
||||
> |
||||
|
||||
<!-- DocAuthor - the author of the document, immediately follows docTitle. |
||||
--> |
||||
|
||||
<!-- Revised, 3/31/2004: Added img to content model --> |
||||
<!ELEMENT docAuthor (text, audio?, img?)> |
||||
<!ATTLIST docAuthor |
||||
id ID #IMPLIED |
||||
%i18n; |
||||
> |
||||
|
||||
<!-- Navigation Structure - container for all of the NCX objects that are part of the |
||||
hierarchical structure of the document. |
||||
--> |
||||
|
||||
<!-- Revised, 3/31/2004: Added navInfo to content model --> |
||||
<!ELEMENT navMap (navInfo*, navLabel*, navPoint+)> |
||||
<!ATTLIST navMap |
||||
id ID #IMPLIED |
||||
> |
||||
|
||||
<!-- Navigation Point - contains description(s) of target, as well as a pointer to |
||||
entire content of target. |
||||
Hierarchy is represented by nesting navPoints. "class" attribute describes the kind |
||||
of structural unit this object represents (e.g., "chapter", "section"). |
||||
--> |
||||
<!ELEMENT navPoint (navLabel+, content, navPoint*)> |
||||
<!-- Revised, 3/29/2004: Removed onFocus/onBlur --> |
||||
<!-- Revised, 3/29/2004: Removed value --> |
||||
<!-- Revised, 3/31/2004: Removed pageRef --> |
||||
<!-- Revised, 4/5/2004: Added playOrder --> |
||||
<!ATTLIST navPoint |
||||
id ID #REQUIRED |
||||
class CDATA #IMPLIED |
||||
playOrder CDATA #REQUIRED |
||||
> |
||||
|
||||
<!-- Revised, 3/31/2004: Added pageList element --> |
||||
<!-- Page List - Container for pagination information. |
||||
--> |
||||
<!ELEMENT pageList (navInfo*, navLabel*, pageTarget+)> |
||||
<!-- Revised, 4/5/2004: Added class attribute --> |
||||
<!ATTLIST pageList |
||||
id ID #IMPLIED |
||||
class CDATA #IMPLIED |
||||
> |
||||
|
||||
<!-- Revised, 3/31/2004: Added pageTarget element --> |
||||
<!-- Revised, 4/5/2004: Added description of value attribute to comment --> |
||||
<!-- Page Target - Container for |
||||
text, audio, image, and content elements containing navigational |
||||
information for pages. The "value" attribute is a positive integer representing |
||||
the numeric value associated with a page. Combination of values of type and |
||||
value attributes must be unique, when value attribute is present. |
||||
--> |
||||
<!ELEMENT pageTarget (navLabel+, content)> |
||||
<!-- Revised, 4/5/2004: Added class attribute --> |
||||
<!-- Revised, 4/5/2004: Changed declaration of type attribute to enumerate values --> |
||||
<!-- Revised, 4/5/2004: Added playOrder --> |
||||
<!ATTLIST pageTarget |
||||
id ID #IMPLIED |
||||
value CDATA #IMPLIED |
||||
type (front | normal | special) #REQUIRED |
||||
class CDATA #IMPLIED |
||||
playOrder CDATA #REQUIRED |
||||
> |
||||
|
||||
<!-- Navigation List - container for distinct, flat sets of navigable elements, e.g. |
||||
notes, figures, tables, etc. Essentially a flat version of navMap. The "class" attribute |
||||
describes the type of object contained in this navList, using dtbook element names, e.g., note. |
||||
--> |
||||
|
||||
<!-- Revised, 3/31/2004: Added navInfo to content model --> |
||||
<!ELEMENT navList (navInfo*, navLabel+, navTarget+) > |
||||
<!ATTLIST navList |
||||
id ID #IMPLIED |
||||
class CDATA #IMPLIED |
||||
> |
||||
|
||||
<!-- Revised, 4/5/2004: Added description of value attribute to comment --> |
||||
<!-- Navigation Target - contains description(s) of target, as well as a pointer to |
||||
entire content of target. |
||||
navTargets are the equivalent of navPoints for use in navLists. "class" attribute |
||||
describes the kind of structure this target represents, using its dtbook element |
||||
name, e.g., note. The "value" attribute is a positive integer representing the |
||||
numeric value associated with the navTarget. |
||||
--> |
||||
|
||||
<!ELEMENT navTarget (navLabel+, content) > |
||||
<!-- Revised, 3/29/2004: Removed onFocus/onBlur --> |
||||
<!-- Revised, 3/31/2004: Removed mapRef --> |
||||
<!-- Revised, 4/5/2004: Added playOrder --> |
||||
<!ATTLIST navTarget |
||||
id ID #REQUIRED |
||||
class CDATA #IMPLIED |
||||
value CDATA #IMPLIED |
||||
playOrder CDATA #REQUIRED |
||||
> |
||||
|
||||
|
||||
<!-- Revised, 3/31/2004: Added navInfo element --> |
||||
<!-- Navigation Information - Contains an informative comment |
||||
about a navMap, pageList, or navList in various media for presentation to the user. |
||||
--> |
||||
<!ELEMENT navInfo (((text, audio?) | audio), img?)> |
||||
<!ATTLIST navInfo |
||||
%i18n; |
||||
> |
||||
|
||||
|
||||
<!-- Navigation Label - Contains a description of a given <navMap>, <navPoint>, |
||||
<navList>, or <navTarget> in various media for presentation to the user. Can be |
||||
repeated so descriptions can be provided in multiple languages. --> |
||||
<!ELEMENT navLabel (((text, audio?) | audio), img?)> |
||||
<!ATTLIST navLabel |
||||
%i18n; |
||||
> |
||||
|
||||
|
||||
<!-- Content Element - pointer into SMIL to beginning of navPoint. --> |
||||
<!ELEMENT content EMPTY> |
||||
<!ATTLIST content |
||||
id ID #IMPLIED |
||||
src %URI; #REQUIRED |
||||
> |
||||
|
||||
<!-- Text Element - Contains text of docTitle, navPoint heading, navTarget (e.g., page number), |
||||
or label for navMap or navList. --> |
||||
<!ELEMENT text (#PCDATA)> |
||||
<!ATTLIST text |
||||
id ID #IMPLIED |
||||
class CDATA #IMPLIED |
||||
> |
||||
|
||||
<!-- Audio Element - audio clip of navPoint heading. --> |
||||
<!ELEMENT audio EMPTY> |
||||
<!-- Revised, 3/29/2004: clipBegin/clipEnd now REQUIRED --> |
||||
<!ATTLIST audio |
||||
id ID #IMPLIED |
||||
class CDATA #IMPLIED |
||||
src %URI; #REQUIRED |
||||
clipBegin %SMILtimeVal; #REQUIRED |
||||
clipEnd %SMILtimeVal; #REQUIRED |
||||
> |
||||
|
||||
<!-- Image Element - image that may accompany heading. --> |
||||
<!ELEMENT img EMPTY> |
||||
<!ATTLIST img |
||||
id ID #IMPLIED |
||||
class CDATA #IMPLIED |
||||
src %URI; #REQUIRED |
||||
> |
@ -0,0 +1,242 @@ |
||||
<!-- ...................................................................... --> |
||||
<!-- XHTML Ruby Module .................................................... --> |
||||
<!-- file: xhtml-ruby-1.mod |
||||
|
||||
This is XHTML, a reformulation of HTML as a modular XML application. |
||||
Copyright 1999-2001 W3C (MIT, INRIA, Keio), All Rights Reserved. |
||||
Revision: $Id: xhtml-ruby-1.mod,v 4.0 2001/04/03 23:14:33 altheim Exp $ |
||||
|
||||
This module is based on the W3C Ruby Annotation Specification: |
||||
|
||||
http://www.w3.org/TR/ruby |
||||
|
||||
This DTD module is identified by the PUBLIC and SYSTEM identifiers: |
||||
|
||||
PUBLIC "-//W3C//ELEMENTS XHTML Ruby 1.0//EN" |
||||
SYSTEM "http://www.w3.org/TR/ruby/xhtml-ruby-1.mod" |
||||
|
||||
...................................................................... --> |
||||
|
||||
<!-- Ruby Elements |
||||
|
||||
ruby, rbc, rtc, rb, rt, rp |
||||
|
||||
This module declares the elements and their attributes used to |
||||
support ruby annotation markup. |
||||
--> |
||||
|
||||
<!-- declare qualified element type names: |
||||
--> |
||||
<!ENTITY % ruby.qname "ruby" > |
||||
<!ENTITY % rbc.qname "rbc" > |
||||
<!ENTITY % rtc.qname "rtc" > |
||||
<!ENTITY % rb.qname "rb" > |
||||
<!ENTITY % rt.qname "rt" > |
||||
<!ENTITY % rp.qname "rp" > |
||||
|
||||
<!-- rp fallback is included by default. |
||||
--> |
||||
<!ENTITY % Ruby.fallback "INCLUDE" > |
||||
<!ENTITY % Ruby.fallback.mandatory "IGNORE" > |
||||
|
||||
<!-- Complex ruby is included by default; it may be |
||||
overridden by other modules to ignore it. |
||||
--> |
||||
<!ENTITY % Ruby.complex "INCLUDE" > |
||||
|
||||
<!-- Fragments for the content model of the ruby element --> |
||||
<![%Ruby.fallback;[ |
||||
<![%Ruby.fallback.mandatory;[ |
||||
<!ENTITY % Ruby.content.simple |
||||
"( %rb.qname;, %rp.qname;, %rt.qname;, %rp.qname; )" |
||||
> |
||||
]]> |
||||
<!ENTITY % Ruby.content.simple |
||||
"( %rb.qname;, ( %rt.qname; | ( %rp.qname;, %rt.qname;, %rp.qname; ) ) )" |
||||
> |
||||
]]> |
||||
<!ENTITY % Ruby.content.simple "( %rb.qname;, %rt.qname; )" > |
||||
|
||||
<![%Ruby.complex;[ |
||||
<!ENTITY % Ruby.content.complex |
||||
"| ( %rbc.qname;, %rtc.qname;, %rtc.qname;? )" |
||||
> |
||||
]]> |
||||
<!ENTITY % Ruby.content.complex "" > |
||||
|
||||
<!-- Content models of the rb and the rt elements are intended to |
||||
allow other inline-level elements of its parent markup language, |
||||
but it should not include ruby descendent elements. The following |
||||
parameter entity %NoRuby.content; can be used to redefine |
||||
those content models with minimum effort. It's defined as |
||||
'( #PCDATA )' by default. |
||||
--> |
||||
<!ENTITY % NoRuby.content "( #PCDATA )" > |
||||
|
||||
<!-- one or more digits (NUMBER) --> |
||||
<!ENTITY % Number.datatype "CDATA" > |
||||
|
||||
<!-- ruby element ...................................... --> |
||||
|
||||
<!ENTITY % ruby.element "INCLUDE" > |
||||
<![%ruby.element;[ |
||||
<!ENTITY % ruby.content |
||||
"( %Ruby.content.simple; %Ruby.content.complex; )" |
||||
> |
||||
<!ELEMENT %ruby.qname; %ruby.content; > |
||||
<!-- end of ruby.element -->]]> |
||||
|
||||
<![%Ruby.complex;[ |
||||
<!-- rbc (ruby base component) element ................. --> |
||||
|
||||
<!ENTITY % rbc.element "INCLUDE" > |
||||
<![%rbc.element;[ |
||||
<!ENTITY % rbc.content |
||||
"(%rb.qname;)+" |
||||
> |
||||
<!ELEMENT %rbc.qname; %rbc.content; > |
||||
<!-- end of rbc.element -->]]> |
||||
|
||||
<!-- rtc (ruby text component) element ................. --> |
||||
|
||||
<!ENTITY % rtc.element "INCLUDE" > |
||||
<![%rtc.element;[ |
||||
<!ENTITY % rtc.content |
||||
"(%rt.qname;)+" |
||||
> |
||||
<!ELEMENT %rtc.qname; %rtc.content; > |
||||
<!-- end of rtc.element -->]]> |
||||
]]> |
||||
|
||||
<!-- rb (ruby base) element ............................ --> |
||||
|
||||
<!ENTITY % rb.element "INCLUDE" > |
||||
<![%rb.element;[ |
||||
<!-- %rb.content; uses %NoRuby.content; as its content model, |
||||
which is '( #PCDATA )' by default. It may be overridden |
||||
by other modules to allow other inline-level elements |
||||
of its parent markup language, but it should not include |
||||
ruby descendent elements. |
||||
--> |
||||
<!ENTITY % rb.content "%NoRuby.content;" > |
||||
<!ELEMENT %rb.qname; %rb.content; > |
||||
<!-- end of rb.element -->]]> |
||||
|
||||
<!-- rt (ruby text) element ............................ --> |
||||
|
||||
<!ENTITY % rt.element "INCLUDE" > |
||||
<![%rt.element;[ |
||||
<!-- %rt.content; uses %NoRuby.content; as its content model, |
||||
which is '( #PCDATA )' by default. It may be overridden |
||||
by other modules to allow other inline-level elements |
||||
of its parent markup language, but it should not include |
||||
ruby descendent elements. |
||||
--> |
||||
<!ENTITY % rt.content "%NoRuby.content;" > |
||||
|
||||
<!ELEMENT %rt.qname; %rt.content; > |
||||
<!-- end of rt.element -->]]> |
||||
|
||||
<!-- rbspan attribute is used for complex ruby only ...... --> |
||||
<![%Ruby.complex;[ |
||||
<!ENTITY % rt.attlist "INCLUDE" > |
||||
<![%rt.attlist;[ |
||||
<!ATTLIST %rt.qname; |
||||
rbspan %Number.datatype; "1" |
||||
> |
||||
<!-- end of rt.attlist -->]]> |
||||
]]> |
||||
|
||||
<!-- rp (ruby parenthesis) element ..................... --> |
||||
|
||||
<![%Ruby.fallback;[ |
||||
<!ENTITY % rp.element "INCLUDE" > |
||||
<![%rp.element;[ |
||||
<!ENTITY % rp.content |
||||
"( #PCDATA )" |
||||
> |
||||
<!ELEMENT %rp.qname; %rp.content; > |
||||
<!-- end of rp.element -->]]> |
||||
]]> |
||||
|
||||
<!-- Ruby Common Attributes |
||||
|
||||
The following optional ATTLIST declarations provide an easy way |
||||
to define common attributes for ruby elements. These declarations |
||||
are ignored by default. |
||||
|
||||
Ruby elements are intended to have common attributes of its |
||||
parent markup language. For example, if a markup language defines |
||||
common attributes as a parameter entity %attrs;, you may add |
||||
those attributes by just declaring the following parameter entities |
||||
|
||||
<!ENTITY % Ruby.common.attlists "INCLUDE" > |
||||
<!ENTITY % Ruby.common.attrib "%attrs;" > |
||||
|
||||
before including the Ruby module. |
||||
--> |
||||
|
||||
<!ENTITY % Ruby.common.attlists "IGNORE" > |
||||
<![%Ruby.common.attlists;[ |
||||
<!ENTITY % Ruby.common.attrib "" > |
||||
|
||||
<!-- common attributes for ruby ........................ --> |
||||
|
||||
<!ENTITY % Ruby.common.attlist "INCLUDE" > |
||||
<![%Ruby.common.attlist;[ |
||||
<!ATTLIST %ruby.qname; |
||||
%Ruby.common.attrib; |
||||
> |
||||
<!-- end of Ruby.common.attlist -->]]> |
||||
|
||||
<![%Ruby.complex;[ |
||||
<!-- common attributes for rbc ......................... --> |
||||
|
||||
<!ENTITY % Rbc.common.attlist "INCLUDE" > |
||||
<![%Rbc.common.attlist;[ |
||||
<!ATTLIST %rbc.qname; |
||||
%Ruby.common.attrib; |
||||
> |
||||
<!-- end of Rbc.common.attlist -->]]> |
||||
|
||||
<!-- common attributes for rtc ......................... --> |
||||
|
||||
<!ENTITY % Rtc.common.attlist "INCLUDE" > |
||||
<![%Rtc.common.attlist;[ |
||||
<!ATTLIST %rtc.qname; |
||||
%Ruby.common.attrib; |
||||
> |
||||
<!-- end of Rtc.common.attlist -->]]> |
||||
]]> |
||||
|
||||
<!-- common attributes for rb .......................... --> |
||||
|
||||
<!ENTITY % Rb.common.attlist "INCLUDE" > |
||||
<![%Rb.common.attlist;[ |
||||
<!ATTLIST %rb.qname; |
||||
%Ruby.common.attrib; |
||||
> |
||||
<!-- end of Rb.common.attlist -->]]> |
||||
|
||||
<!-- common attributes for rt .......................... --> |
||||
|
||||
<!ENTITY % Rt.common.attlist "INCLUDE" > |
||||
<![%Rt.common.attlist;[ |
||||
<!ATTLIST %rt.qname; |
||||
%Ruby.common.attrib; |
||||
> |
||||
<!-- end of Rt.common.attlist -->]]> |
||||
|
||||
<![%Ruby.fallback;[ |
||||
<!-- common attributes for rp .......................... --> |
||||
|
||||
<!ENTITY % Rp.common.attlist "INCLUDE" > |
||||
<![%Rp.common.attlist;[ |
||||
<!ATTLIST %rp.qname; |
||||
%Ruby.common.attrib; |
||||
> |
||||
<!-- end of Rp.common.attlist -->]]> |
||||
]]> |
||||
]]> |
||||
|
||||
<!-- end of xhtml-ruby-1.mod --> |
@ -0,0 +1,51 @@ |
||||
<!-- ...................................................................... --> |
||||
<!-- XHTML Base Architecture Module ...................................... --> |
||||
<!-- file: xhtml-arch-1.mod |
||||
|
||||
This is XHTML, a reformulation of HTML as a modular XML application. |
||||
Copyright 1998-2005 W3C (MIT, ERCIM, Keio), All Rights Reserved. |
||||
Revision: $Id: xhtml-arch-1.mod,v 1.1 2010/07/29 13:42:46 bertails Exp $ SMI |
||||
|
||||
This DTD module is identified by the PUBLIC and SYSTEM identifiers: |
||||
|
||||
PUBLIC "-//W3C//ELEMENTS XHTML Base Architecture 1.0//EN" |
||||
SYSTEM "http://www.w3.org/MarkUp/DTD/xhtml-arch-1.mod" |
||||
|
||||
Revisions: |
||||
(none) |
||||
....................................................................... --> |
||||
|
||||
<!-- This optional module includes declarations that enable XHTML to be used |
||||
as a base architecture according to the 'Architectural Forms Definition |
||||
Requirements' (Annex A.3, ISO/IEC 10744, 2nd edition). For more information |
||||
on use of architectural forms, see the HyTime web site at: |
||||
|
||||
http://www.hytime.org/ |
||||
--> |
||||
|
||||
<?IS10744 ArcBase xhtml ?> |
||||
|
||||
<!NOTATION xhtml PUBLIC "-//W3C//NOTATION AFDR ARCBASE XHTML 1.1//EN" > |
||||
|
||||
<!-- Entity declaration for associated Architectural DTD |
||||
--> |
||||
<!ENTITY xhtml-arch.dtd |
||||
PUBLIC "-//W3C//DTD XHTML Architecture 1.1//EN" |
||||
"xhtml11-arch.dtd" > |
||||
|
||||
<?IS10744:arch xhtml |
||||
public-id = "-//W3C//NOTATION AFDR ARCBASE XHTML 1.1//EN" |
||||
dtd-public-id = "-//W3C//DTD XHTML 1.1//EN" |
||||
dtd-system-id = "xhtml11.dtd" |
||||
doc-elem-form = "html" |
||||
form-att = "html" |
||||
renamer-att = "htnames" |
||||
suppressor-att = "htsupp" |
||||
data-ignore-att = "htign" |
||||
auto = "ArcAuto" |
||||
options = "HtModReq HtModOpt" |
||||
HtModReq = "Framework Text Hypertext Lists Structure" |
||||
HtModOpt = "Standard" |
||||
?> |
||||
|
||||
<!-- end of xhtml-arch-1.mod --> |
@ -0,0 +1,142 @@ |
||||
<!-- ...................................................................... --> |
||||
<!-- XHTML Common Attributes Module ...................................... --> |
||||
<!-- file: xhtml-attribs-1.mod |
||||
|
||||
This is XHTML, a reformulation of HTML as a modular XML application. |
||||
Copyright 1998-2005 W3C (MIT, ERCIM, Keio), All Rights Reserved. |
||||
Revision: $Id: xhtml-attribs-1.mod,v 1.1 2010/07/29 13:42:46 bertails Exp $ SMI |
||||
|
||||
This DTD module is identified by the PUBLIC and SYSTEM identifiers: |
||||
|
||||
PUBLIC "-//W3C//ENTITIES XHTML Common Attributes 1.0//EN" |
||||
SYSTEM "http://www.w3.org/MarkUp/DTD/xhtml-attribs-1.mod" |
||||
|
||||
Revisions: |
||||
(none) |
||||
....................................................................... --> |
||||
|
||||
<!-- Common Attributes |
||||
|
||||
This module declares many of the common attributes for the XHTML DTD. |
||||
%NS.decl.attrib; is declared in the XHTML Qname module. |
||||
|
||||
Note that this file was extended in XHTML Modularization 1.1 to |
||||
include declarations of "global" versions of the attribute collections. |
||||
The global versions of the attributes are for use on elements in other |
||||
namespaces. The global version of "common" includes the xmlns declaration |
||||
for the prefixed version of the xhtml namespace. If you are only using a |
||||
specific attribute or an individual attribute collection, you must also |
||||
include the XHTML.xmlns.attrib.prefixed PE on your elements. |
||||
--> |
||||
|
||||
<!ENTITY % id.attrib |
||||
"id ID #IMPLIED" |
||||
> |
||||
|
||||
<![%XHTML.global.attrs.prefixed;[ |
||||
<!ENTITY % XHTML.global.id.attrib |
||||
"%XHTML.prefix;:id ID #IMPLIED" |
||||
> |
||||
]]> |
||||
|
||||
<!ENTITY % class.attrib |
||||
"class CDATA #IMPLIED" |
||||
> |
||||
|
||||
<![%XHTML.global.attrs.prefixed;[ |
||||
<!ENTITY % XHTML.global.class.attrib |
||||
"%XHTML.prefix;:class CDATA #IMPLIED" |
||||
> |
||||
]]> |
||||
|
||||
<!ENTITY % title.attrib |
||||
"title %Text.datatype; #IMPLIED" |
||||
> |
||||
|
||||
<![%XHTML.global.attrs.prefixed;[ |
||||
<!ENTITY % XHTML.global.title.attrib |
||||
"%XHTML.prefix;:title %Text.datatype; #IMPLIED" |
||||
> |
||||
]]> |
||||
|
||||
<!ENTITY % Core.extra.attrib "" > |
||||
|
||||
<!ENTITY % Core.attrib |
||||
"%XHTML.xmlns.attrib; |
||||
%id.attrib; |
||||
%class.attrib; |
||||
%title.attrib; |
||||
xml:space ( preserve ) #FIXED 'preserve' |
||||
%Core.extra.attrib;" |
||||
> |
||||
|
||||
<!ENTITY % XHTML.global.core.extra.attrib "" > |
||||
|
||||
<![%XHTML.global.attrs.prefixed;[ |
||||
|
||||
<!ENTITY % XHTML.global.core.attrib |
||||
"%XHTML.global.id.attrib; |
||||
%XHTML.global.class.attrib; |
||||
%XHTML.global.title.attrib; |
||||
%XHTML.global.core.extra.attrib;" |
||||
> |
||||
]]> |
||||
|
||||
<!ENTITY % XHTML.global.core.attrib "" > |
||||
|
||||
|
||||
<!ENTITY % lang.attrib |
||||
"xml:lang %LanguageCode.datatype; #IMPLIED" |
||||
> |
||||
|
||||
<![%XHTML.bidi;[ |
||||
<!ENTITY % dir.attrib |
||||
"dir ( ltr | rtl ) #IMPLIED" |
||||
> |
||||
|
||||
<!ENTITY % I18n.attrib |
||||
"%dir.attrib; |
||||
%lang.attrib;" |
||||
> |
||||
|
||||
<![%XHTML.global.attrs.prefixed;[ |
||||
<!ENTITY XHTML.global.i18n.attrib |
||||
"%XHTML.prefix;:dir ( ltr | rtl ) #IMPLIED |
||||
%lang.attrib;" |
||||
> |
||||
]]> |
||||
<!ENTITY XHTML.global.i18n.attrib "" > |
||||
|
||||
]]> |
||||
<!ENTITY % I18n.attrib |
||||
"%lang.attrib;" |
||||
> |
||||
<!ENTITY % XHTML.global.i18n.attrib |
||||
"%lang.attrib;" |
||||
> |
||||
|
||||
<!ENTITY % Common.extra.attrib "" > |
||||
<!ENTITY % XHTML.global.common.extra.attrib "" > |
||||
|
||||
<!-- intrinsic event attributes declared previously |
||||
--> |
||||
<!ENTITY % Events.attrib "" > |
||||
|
||||
<!ENTITY % XHTML.global.events.attrib "" > |
||||
|
||||
<!ENTITY % Common.attrib |
||||
"%Core.attrib; |
||||
%I18n.attrib; |
||||
%Events.attrib; |
||||
%Common.extra.attrib;" |
||||
> |
||||
|
||||
<!ENTITY % XHTML.global.common.attrib |
||||
"%XHTML.xmlns.attrib.prefixed; |
||||
%XHTML.global.core.attrib; |
||||
%XHTML.global.i18n.attrib; |
||||
%XHTML.global.events.attrib; |
||||
%XHTML.global.common.extra.attrib;" |
||||
> |
||||
|
||||
<!-- end of xhtml-attribs-1.mod --> |
@ -0,0 +1,53 @@ |
||||
<!-- ...................................................................... --> |
||||
<!-- XHTML Base Element Module ........................................... --> |
||||
<!-- file: xhtml-base-1.mod |
||||
|
||||
This is XHTML, a reformulation of HTML as a modular XML application. |
||||
Copyright 1998-2005 W3C (MIT, ERCIM, Keio), All Rights Reserved. |
||||
Revision: $Id: xhtml-base-1.mod,v 1.1 2010/07/29 13:42:46 bertails Exp $ SMI |
||||
|
||||
This DTD module is identified by the PUBLIC and SYSTEM identifiers: |
||||
|
||||
PUBLIC "-//W3C//ELEMENTS XHTML Base Element 1.0//EN" |
||||
SYSTEM "http://www.w3.org/MarkUp/DTD/xhtml-base-1.mod" |
||||
|
||||
Revisions: |
||||
(none) |
||||
....................................................................... --> |
||||
|
||||
<!-- Base element |
||||
|
||||
base |
||||
|
||||
This module declares the base element type and its attributes, |
||||
used to define a base URI against which relative URIs in the |
||||
document will be resolved. |
||||
|
||||
Note that this module also redeclares the content model for |
||||
the head element to include the base element. |
||||
--> |
||||
|
||||
<!-- base: Document Base URI ........................... --> |
||||
|
||||
<!ENTITY % base.element "INCLUDE" > |
||||
<![%base.element;[ |
||||
<!ENTITY % base.content "EMPTY" > |
||||
<!ENTITY % base.qname "base" > |
||||
<!ELEMENT %base.qname; %base.content; > |
||||
<!-- end of base.element -->]]> |
||||
|
||||
<!ENTITY % base.attlist "INCLUDE" > |
||||
<![%base.attlist;[ |
||||
<!ATTLIST %base.qname; |
||||
%XHTML.xmlns.attrib; |
||||
href %URI.datatype; #REQUIRED |
||||
> |
||||
<!-- end of base.attlist -->]]> |
||||
|
||||
<!ENTITY % head.content |
||||
"( %HeadOpts.mix;, |
||||
( ( %title.qname;, %HeadOpts.mix;, ( %base.qname;, %HeadOpts.mix; )? ) |
||||
| ( %base.qname;, %HeadOpts.mix;, ( %title.qname;, %HeadOpts.mix; ))))" |
||||
> |
||||
|
||||
<!-- end of xhtml-base-1.mod --> |
@ -0,0 +1,47 @@ |
||||
<!-- ...................................................................... --> |
||||
<!-- XHTML BDO Element Module ............................................. --> |
||||
<!-- file: xhtml-bdo-1.mod |
||||
|
||||
This is XHTML, a reformulation of HTML as a modular XML application. |
||||
Copyright 1998-2005 W3C (MIT, ERCIM, Keio), All Rights Reserved. |
||||
Revision: $Id: xhtml-bdo-1.mod,v 1.1 2010/07/29 13:42:46 bertails Exp $ SMI |
||||
|
||||
This DTD module is identified by the PUBLIC and SYSTEM identifiers: |
||||
|
||||
PUBLIC "-//W3C//ELEMENTS XHTML BDO Element 1.0//EN" |
||||
SYSTEM "http://www.w3.org/MarkUp/DTD/xhtml-bdo-1.mod" |
||||
|
||||
Revisions: |
||||
(none) |
||||
....................................................................... --> |
||||
|
||||
<!-- Bidirectional Override (bdo) Element |
||||
|
||||
This modules declares the element 'bdo', used to override the |
||||
Unicode bidirectional algorithm for selected fragments of text. |
||||
|
||||
DEPENDENCIES: |
||||
Relies on the conditional section keyword %XHTML.bidi; declared |
||||
as "INCLUDE". Bidirectional text support includes both the bdo |
||||
element and the 'dir' attribute. |
||||
--> |
||||
|
||||
<!ENTITY % bdo.element "INCLUDE" > |
||||
<![%bdo.element;[ |
||||
<!ENTITY % bdo.content |
||||
"( #PCDATA | %Inline.mix; )*" |
||||
> |
||||
<!ENTITY % bdo.qname "bdo" > |
||||
<!ELEMENT %bdo.qname; %bdo.content; > |
||||
<!-- end of bdo.element -->]]> |
||||
|
||||
<!ENTITY % bdo.attlist "INCLUDE" > |
||||
<![%bdo.attlist;[ |
||||
<!ATTLIST %bdo.qname; |
||||
%Core.attrib; |
||||
%lang.attrib; |
||||
dir ( ltr | rtl ) #REQUIRED |
||||
> |
||||
]]> |
||||
|
||||
<!-- end of xhtml-bdo-1.mod --> |
@ -0,0 +1,164 @@ |
||||
<!-- ...................................................................... --> |
||||
<!-- XHTML Block Phrasal Module .......................................... --> |
||||
<!-- file: xhtml-blkphras-1.mod |
||||
|
||||
This is XHTML, a reformulation of HTML as a modular XML application. |
||||
Copyright 1998-2005 W3C (MIT, ERCIM, Keio), All Rights Reserved. |
||||
Revision: $Id: xhtml-blkphras-1.mod,v 1.1 2010/07/29 13:42:46 bertails Exp $ SMI |
||||
|
||||
This DTD module is identified by the PUBLIC and SYSTEM identifiers: |
||||
|
||||
PUBLIC "-//W3C//ELEMENTS XHTML Block Phrasal 1.0//EN" |
||||
SYSTEM "http://www.w3.org/MarkUp/DTD/xhtml-blkphras-1.mod" |
||||
|
||||
Revisions: |
||||
(none) |
||||
....................................................................... --> |
||||
|
||||
<!-- Block Phrasal |
||||
|
||||
address, blockquote, pre, h1, h2, h3, h4, h5, h6 |
||||
|
||||
This module declares the elements and their attributes used to |
||||
support block-level phrasal markup. |
||||
--> |
||||
|
||||
<!ENTITY % address.element "INCLUDE" > |
||||
<![%address.element;[ |
||||
<!ENTITY % address.content |
||||
"( #PCDATA | %Inline.mix; )*" > |
||||
<!ENTITY % address.qname "address" > |
||||
<!ELEMENT %address.qname; %address.content; > |
||||
<!-- end of address.element -->]]> |
||||
|
||||
<!ENTITY % address.attlist "INCLUDE" > |
||||
<![%address.attlist;[ |
||||
<!ATTLIST %address.qname; |
||||
%Common.attrib; |
||||
> |
||||
<!-- end of address.attlist -->]]> |
||||
|
||||
<!ENTITY % blockquote.element "INCLUDE" > |
||||
<![%blockquote.element;[ |
||||
<!ENTITY % blockquote.content |
||||
"( %Block.mix; )*" |
||||
> |
||||
<!ENTITY % blockquote.qname "blockquote" > |
||||
<!ELEMENT %blockquote.qname; %blockquote.content; > |
||||
<!-- end of blockquote.element -->]]> |
||||
|
||||
<!ENTITY % blockquote.attlist "INCLUDE" > |
||||
<![%blockquote.attlist;[ |
||||
<!ATTLIST %blockquote.qname; |
||||
%Common.attrib; |
||||
cite %URI.datatype; #IMPLIED |
||||
> |
||||
<!-- end of blockquote.attlist -->]]> |
||||
|
||||
<!ENTITY % pre.element "INCLUDE" > |
||||
<![%pre.element;[ |
||||
<!ENTITY % pre.content |
||||
"( #PCDATA |
||||
| %InlStruct.class; |
||||
%InlPhras.class; |
||||
| %tt.qname; | %i.qname; | %b.qname; |
||||
%I18n.class; |
||||
%Anchor.class; |
||||
| %map.qname; |
||||
%Misc.class; |
||||
%Inline.extra; )*" |
||||
> |
||||
<!ENTITY % pre.qname "pre" > |
||||
<!ELEMENT %pre.qname; %pre.content; > |
||||
<!-- end of pre.element -->]]> |
||||
|
||||
<!ENTITY % pre.attlist "INCLUDE" > |
||||
<![%pre.attlist;[ |
||||
<!ATTLIST %pre.qname; |
||||
%Common.attrib; |
||||
> |
||||
<!-- end of pre.attlist -->]]> |
||||
|
||||
<!-- ................... Heading Elements ................... --> |
||||
|
||||
<!ENTITY % Heading.content "( #PCDATA | %Inline.mix; )*" > |
||||
|
||||
<!ENTITY % h1.element "INCLUDE" > |
||||
<![%h1.element;[ |
||||
<!ENTITY % h1.qname "h1" > |
||||
<!ELEMENT %h1.qname; %Heading.content; > |
||||
<!-- end of h1.element -->]]> |
||||
|
||||
<!ENTITY % h1.attlist "INCLUDE" > |
||||
<![%h1.attlist;[ |
||||
<!ATTLIST %h1.qname; |
||||
%Common.attrib; |
||||
> |
||||
<!-- end of h1.attlist -->]]> |
||||
|
||||
<!ENTITY % h2.element "INCLUDE" > |
||||
<![%h2.element;[ |
||||
<!ENTITY % h2.qname "h2" > |
||||
<!ELEMENT %h2.qname; %Heading.content; > |
||||
<!-- end of h2.element -->]]> |
||||
|
||||
<!ENTITY % h2.attlist "INCLUDE" > |
||||
<![%h2.attlist;[ |
||||
<!ATTLIST %h2.qname; |
||||
%Common.attrib; |
||||
> |
||||
<!-- end of h2.attlist -->]]> |
||||
|
||||
<!ENTITY % h3.element "INCLUDE" > |
||||
<![%h3.element;[ |
||||
<!ENTITY % h3.qname "h3" > |
||||
<!ELEMENT %h3.qname; %Heading.content; > |
||||
<!-- end of h3.element -->]]> |
||||
|
||||
<!ENTITY % h3.attlist "INCLUDE" > |
||||
<![%h3.attlist;[ |
||||
<!ATTLIST %h3.qname; |
||||
%Common.attrib; |
||||
> |
||||
<!-- end of h3.attlist -->]]> |
||||
|
||||
<!ENTITY % h4.element "INCLUDE" > |
||||
<![%h4.element;[ |
||||
<!ENTITY % h4.qname "h4" > |
||||
<!ELEMENT %h4.qname; %Heading.content; > |
||||
<!-- end of h4.element -->]]> |
||||
|
||||
<!ENTITY % h4.attlist "INCLUDE" > |
||||
<![%h4.attlist;[ |
||||
<!ATTLIST %h4.qname; |
||||
%Common.attrib; |
||||
> |
||||
<!-- end of h4.attlist -->]]> |
||||
|
||||
<!ENTITY % h5.element "INCLUDE" > |
||||
<![%h5.element;[ |
||||
<!ENTITY % h5.qname "h5" > |
||||
<!ELEMENT %h5.qname; %Heading.content; > |
||||
<!-- end of h5.element -->]]> |
||||
|
||||
<!ENTITY % h5.attlist "INCLUDE" > |
||||
<![%h5.attlist;[ |
||||
<!ATTLIST %h5.qname; |
||||
%Common.attrib; |
||||
> |
||||
<!-- end of h5.attlist -->]]> |
||||
|
||||
<!ENTITY % h6.element "INCLUDE" > |
||||
<![%h6.element;[ |
||||
<!ENTITY % h6.qname "h6" > |
||||
<!ELEMENT %h6.qname; %Heading.content; > |
||||
<!-- end of h6.element -->]]> |
||||
|
||||
<!ENTITY % h6.attlist "INCLUDE" > |
||||
<![%h6.attlist;[ |
||||
<!ATTLIST %h6.qname; |
||||
%Common.attrib; |
||||
> |
||||
<!-- end of h6.attlist -->]]> |
||||
|
||||
<!-- end of xhtml-blkphras-1.mod --> |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue