From 0bbaaf157479efb62fa3fc64c2c2d3baa59df649 Mon Sep 17 00:00:00 2001 From: ag2s20150909 Date: Thu, 25 Mar 2021 14:24:27 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E8=87=AA=E5=AE=9A=E4=B9=89ep?= =?UTF-8?q?ublib?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle | 11 +- .../io/legado/app/model/localBook/EPUBFile.kt | 19 +- epublib/.gitignore | 1 + epublib/build.gradle | 40 + epublib/consumer-rules.pro | 0 epublib/proguard-rules.pro | 21 + .../ag2s/epublib/ExampleInstrumentedTest.java | 26 + epublib/src/main/AndroidManifest.xml | 7 + .../main/java/com/jcraft/jzlib/Adler32.java | 139 ++ .../src/main/java/com/jcraft/jzlib/CRC32.java | 179 ++ .../main/java/com/jcraft/jzlib/Checksum.java | 43 + .../main/java/com/jcraft/jzlib/Deflate.java | 1757 +++++++++++++++++ .../main/java/com/jcraft/jzlib/Deflater.java | 171 ++ .../jcraft/jzlib/DeflaterOutputStream.java | 181 ++ .../java/com/jcraft/jzlib/GZIPException.java | 44 + .../java/com/jcraft/jzlib/GZIPHeader.java | 195 ++ .../com/jcraft/jzlib/GZIPInputStream.java | 145 ++ .../com/jcraft/jzlib/GZIPOutputStream.java | 90 + .../main/java/com/jcraft/jzlib/InfBlocks.java | 614 ++++++ .../main/java/com/jcraft/jzlib/InfCodes.java | 610 ++++++ .../main/java/com/jcraft/jzlib/InfTree.java | 518 +++++ .../main/java/com/jcraft/jzlib/Inflate.java | 751 +++++++ .../main/java/com/jcraft/jzlib/Inflater.java | 168 ++ .../com/jcraft/jzlib/InflaterInputStream.java | 247 +++ .../src/main/java/com/jcraft/jzlib/JZlib.java | 92 + .../java/com/jcraft/jzlib/StaticTree.java | 148 ++ .../src/main/java/com/jcraft/jzlib/Tree.java | 367 ++++ .../java/com/jcraft/jzlib/ZInputStream.java | 126 ++ .../java/com/jcraft/jzlib/ZOutputStream.java | 159 ++ .../main/java/com/jcraft/jzlib/ZStream.java | 377 ++++ .../com/jcraft/jzlib/ZStreamException.java | 44 + .../main/java/me/ag2s/epublib/Constants.java | 12 + .../browsersupport/NavigationEvent.java | 155 ++ .../NavigationEventListener.java | 18 + .../browsersupport/NavigationHistory.java | 207 ++ .../epublib/browsersupport/Navigator.java | 219 ++ .../epublib/browsersupport/package-info.java | 7 + .../java/me/ag2s/epublib/domain/Author.java | 79 + .../java/me/ag2s/epublib/domain/Book.java | 316 +++ .../java/me/ag2s/epublib/domain/Date.java | 104 + .../epublib/domain/EpubResourceProvider.java | 33 + .../java/me/ag2s/epublib/domain/Guide.java | 128 ++ .../ag2s/epublib/domain/GuideReference.java | 102 + .../me/ag2s/epublib/domain/Identifier.java | 129 ++ .../me/ag2s/epublib/domain/LazyResource.java | 135 ++ .../epublib/domain/LazyResourceProvider.java | 12 + .../domain/ManifestItemProperties.java | 21 + .../domain/ManifestItemRefProperties.java | 16 + .../epublib/domain/ManifestProperties.java | 6 + .../me/ag2s/epublib/domain/MediaType.java | 73 + .../me/ag2s/epublib/domain/MediaTypes.java | 95 + .../java/me/ag2s/epublib/domain/Metadata.java | 230 +++ .../java/me/ag2s/epublib/domain/Relator.java | 1144 +++++++++++ .../java/me/ag2s/epublib/domain/Resource.java | 327 +++ .../epublib/domain/ResourceInputStream.java | 36 + .../epublib/domain/ResourceReference.java | 43 + .../me/ag2s/epublib/domain/Resources.java | 387 ++++ .../java/me/ag2s/epublib/domain/Spine.java | 190 ++ .../ag2s/epublib/domain/SpineReference.java | 52 + .../me/ag2s/epublib/domain/TOCReference.java | 59 + .../ag2s/epublib/domain/TableOfContents.java | 259 +++ .../domain/TitledResourceReference.java | 78 + .../me/ag2s/epublib/epub/BookProcessor.java | 20 + .../epublib/epub/BookProcessorPipeline.java | 72 + .../java/me/ag2s/epublib/epub/DOMUtil.java | 134 ++ .../epublib/epub/EpubProcessorSupport.java | 138 ++ .../java/me/ag2s/epublib/epub/EpubReader.java | 169 ++ .../java/me/ag2s/epublib/epub/EpubWriter.java | 184 ++ .../me/ag2s/epublib/epub/HtmlProcessor.java | 9 + .../main/java/me/ag2s/epublib/epub/Main.java | 5 + .../me/ag2s/epublib/epub/NCXDocument.java | 297 +++ .../me/ag2s/epublib/epub/NCXDocumentV2.java | 334 ++++ .../me/ag2s/epublib/epub/NCXDocumentV3.java | 206 ++ .../epublib/epub/PackageDocumentBase.java | 89 + .../epub/PackageDocumentMetadataReader.java | 223 +++ .../epub/PackageDocumentMetadataWriter.java | 176 ++ .../epublib/epub/PackageDocumentReader.java | 425 ++++ .../epublib/epub/PackageDocumentWriter.java | 254 +++ .../me/ag2s/epublib/epub/ResourcesLoader.java | 173 ++ .../me/ag2s/epublib/util/CollectionUtil.java | 70 + .../java/me/ag2s/epublib/util/IOUtil.java | 165 ++ .../epublib/util/NoCloseOutputStream.java | 32 + .../me/ag2s/epublib/util/NoCloseWriter.java | 35 + .../me/ag2s/epublib/util/ResourceUtil.java | 174 ++ .../epublib/util/StreamWriterDelegate.txt | 202 ++ .../java/me/ag2s/epublib/util/StringUtil.java | 275 +++ .../util/commons/io/BOMInputStream.java | 340 ++++ .../util/commons/io/ByteOrderMark.java | 170 ++ .../util/commons/io/ProxyInputStream.java | 238 +++ .../util/commons/io/XmlStreamReader.java | 752 +++++++ .../commons/io/XmlStreamReaderException.java | 138 ++ .../dtd/openebook.org/dtds/oeb-1.2/oeb12.ent | 1135 +++++++++++ .../openebook.org/dtds/oeb-1.2/oebpkg12.dtd | 390 ++++ .../www.daisy.org/z3986/2005/ncx-2005-1.dtd | 269 +++ .../dtd/www.w3.org/TR/ruby/xhtml-ruby-1.mod | 242 +++ .../xhtml-modularization/DTD/xhtml-arch-1.mod | 51 + .../DTD/xhtml-attribs-1.mod | 142 ++ .../xhtml-modularization/DTD/xhtml-base-1.mod | 53 + .../xhtml-modularization/DTD/xhtml-bdo-1.mod | 47 + .../DTD/xhtml-blkphras-1.mod | 164 ++ .../DTD/xhtml-blkpres-1.mod | 40 + .../DTD/xhtml-blkstruct-1.mod | 57 + .../DTD/xhtml-charent-1.mod | 39 + .../DTD/xhtml-csismap-1.mod | 114 ++ .../DTD/xhtml-datatypes-1.mod | 103 + .../DTD/xhtml-datatypes-1.mod.1 | 103 + .../xhtml-modularization/DTD/xhtml-edit-1.mod | 66 + .../DTD/xhtml-events-1.mod | 135 ++ .../xhtml-modularization/DTD/xhtml-form-1.mod | 292 +++ .../DTD/xhtml-framework-1.mod | 97 + .../DTD/xhtml-hypertext-1.mod | 54 + .../DTD/xhtml-image-1.mod | 51 + .../DTD/xhtml-inlphras-1.mod | 203 ++ .../DTD/xhtml-inlpres-1.mod | 138 ++ .../DTD/xhtml-inlstruct-1.mod | 62 + .../DTD/xhtml-inlstyle-1.mod | 34 + .../xhtml-modularization/DTD/xhtml-lat1.ent | 196 ++ .../xhtml-modularization/DTD/xhtml-link-1.mod | 59 + .../xhtml-modularization/DTD/xhtml-list-1.mod | 129 ++ .../xhtml-modularization/DTD/xhtml-meta-1.mod | 47 + .../DTD/xhtml-notations-1.mod | 114 ++ .../DTD/xhtml-object-1.mod | 60 + .../DTD/xhtml-param-1.mod | 48 + .../xhtml-modularization/DTD/xhtml-pres-1.mod | 38 + .../DTD/xhtml-qname-1.mod | 318 +++ .../DTD/xhtml-script-1.mod | 67 + .../DTD/xhtml-special.ent | 80 + .../DTD/xhtml-ssismap-1.mod | 32 + .../DTD/xhtml-struct-1.mod | 136 ++ .../DTD/xhtml-style-1.mod | 48 + .../xhtml-modularization/DTD/xhtml-symbol.ent | 237 +++ .../DTD/xhtml-symbol.ent.1 | 237 +++ .../DTD/xhtml-table-1.mod | 333 ++++ .../xhtml-modularization/DTD/xhtml-text-1.mod | 52 + .../DTD/xhtml11-model-1.mod | 252 +++ .../www.w3.org/TR/xhtml1/DTD/xhtml-lat1.ent | 196 ++ .../TR/xhtml1/DTD/xhtml-special.ent | 80 + .../www.w3.org/TR/xhtml1/DTD/xhtml-symbol.ent | 237 +++ .../TR/xhtml1/DTD/xhtml1-strict.dtd | 978 +++++++++ .../TR/xhtml1/DTD/xhtml1-transitional.dtd | 1201 +++++++++++ .../dtd/www.w3.org/TR/xhtml11/DTD/xhtml11.dtd | 294 +++ epublib/src/main/resources/log4j.properties | 55 + .../java/me/ag2s/epublib/ExampleUnitTest.java | 17 + settings.gradle | 1 + 144 files changed, 27070 insertions(+), 14 deletions(-) create mode 100644 epublib/.gitignore create mode 100644 epublib/build.gradle create mode 100644 epublib/consumer-rules.pro create mode 100644 epublib/proguard-rules.pro create mode 100644 epublib/src/androidTest/java/me/ag2s/epublib/ExampleInstrumentedTest.java create mode 100644 epublib/src/main/AndroidManifest.xml create mode 100644 epublib/src/main/java/com/jcraft/jzlib/Adler32.java create mode 100644 epublib/src/main/java/com/jcraft/jzlib/CRC32.java create mode 100644 epublib/src/main/java/com/jcraft/jzlib/Checksum.java create mode 100644 epublib/src/main/java/com/jcraft/jzlib/Deflate.java create mode 100644 epublib/src/main/java/com/jcraft/jzlib/Deflater.java create mode 100644 epublib/src/main/java/com/jcraft/jzlib/DeflaterOutputStream.java create mode 100644 epublib/src/main/java/com/jcraft/jzlib/GZIPException.java create mode 100644 epublib/src/main/java/com/jcraft/jzlib/GZIPHeader.java create mode 100644 epublib/src/main/java/com/jcraft/jzlib/GZIPInputStream.java create mode 100644 epublib/src/main/java/com/jcraft/jzlib/GZIPOutputStream.java create mode 100644 epublib/src/main/java/com/jcraft/jzlib/InfBlocks.java create mode 100644 epublib/src/main/java/com/jcraft/jzlib/InfCodes.java create mode 100644 epublib/src/main/java/com/jcraft/jzlib/InfTree.java create mode 100644 epublib/src/main/java/com/jcraft/jzlib/Inflate.java create mode 100644 epublib/src/main/java/com/jcraft/jzlib/Inflater.java create mode 100644 epublib/src/main/java/com/jcraft/jzlib/InflaterInputStream.java create mode 100644 epublib/src/main/java/com/jcraft/jzlib/JZlib.java create mode 100644 epublib/src/main/java/com/jcraft/jzlib/StaticTree.java create mode 100644 epublib/src/main/java/com/jcraft/jzlib/Tree.java create mode 100644 epublib/src/main/java/com/jcraft/jzlib/ZInputStream.java create mode 100644 epublib/src/main/java/com/jcraft/jzlib/ZOutputStream.java create mode 100644 epublib/src/main/java/com/jcraft/jzlib/ZStream.java create mode 100644 epublib/src/main/java/com/jcraft/jzlib/ZStreamException.java create mode 100644 epublib/src/main/java/me/ag2s/epublib/Constants.java create mode 100644 epublib/src/main/java/me/ag2s/epublib/browsersupport/NavigationEvent.java create mode 100644 epublib/src/main/java/me/ag2s/epublib/browsersupport/NavigationEventListener.java create mode 100644 epublib/src/main/java/me/ag2s/epublib/browsersupport/NavigationHistory.java create mode 100644 epublib/src/main/java/me/ag2s/epublib/browsersupport/Navigator.java create mode 100644 epublib/src/main/java/me/ag2s/epublib/browsersupport/package-info.java create mode 100644 epublib/src/main/java/me/ag2s/epublib/domain/Author.java create mode 100644 epublib/src/main/java/me/ag2s/epublib/domain/Book.java create mode 100644 epublib/src/main/java/me/ag2s/epublib/domain/Date.java create mode 100644 epublib/src/main/java/me/ag2s/epublib/domain/EpubResourceProvider.java create mode 100644 epublib/src/main/java/me/ag2s/epublib/domain/Guide.java create mode 100644 epublib/src/main/java/me/ag2s/epublib/domain/GuideReference.java create mode 100644 epublib/src/main/java/me/ag2s/epublib/domain/Identifier.java create mode 100644 epublib/src/main/java/me/ag2s/epublib/domain/LazyResource.java create mode 100644 epublib/src/main/java/me/ag2s/epublib/domain/LazyResourceProvider.java create mode 100644 epublib/src/main/java/me/ag2s/epublib/domain/ManifestItemProperties.java create mode 100644 epublib/src/main/java/me/ag2s/epublib/domain/ManifestItemRefProperties.java create mode 100644 epublib/src/main/java/me/ag2s/epublib/domain/ManifestProperties.java create mode 100644 epublib/src/main/java/me/ag2s/epublib/domain/MediaType.java create mode 100644 epublib/src/main/java/me/ag2s/epublib/domain/MediaTypes.java create mode 100644 epublib/src/main/java/me/ag2s/epublib/domain/Metadata.java create mode 100644 epublib/src/main/java/me/ag2s/epublib/domain/Relator.java create mode 100644 epublib/src/main/java/me/ag2s/epublib/domain/Resource.java create mode 100644 epublib/src/main/java/me/ag2s/epublib/domain/ResourceInputStream.java create mode 100644 epublib/src/main/java/me/ag2s/epublib/domain/ResourceReference.java create mode 100644 epublib/src/main/java/me/ag2s/epublib/domain/Resources.java create mode 100644 epublib/src/main/java/me/ag2s/epublib/domain/Spine.java create mode 100644 epublib/src/main/java/me/ag2s/epublib/domain/SpineReference.java create mode 100644 epublib/src/main/java/me/ag2s/epublib/domain/TOCReference.java create mode 100644 epublib/src/main/java/me/ag2s/epublib/domain/TableOfContents.java create mode 100644 epublib/src/main/java/me/ag2s/epublib/domain/TitledResourceReference.java create mode 100644 epublib/src/main/java/me/ag2s/epublib/epub/BookProcessor.java create mode 100644 epublib/src/main/java/me/ag2s/epublib/epub/BookProcessorPipeline.java create mode 100644 epublib/src/main/java/me/ag2s/epublib/epub/DOMUtil.java create mode 100644 epublib/src/main/java/me/ag2s/epublib/epub/EpubProcessorSupport.java create mode 100644 epublib/src/main/java/me/ag2s/epublib/epub/EpubReader.java create mode 100644 epublib/src/main/java/me/ag2s/epublib/epub/EpubWriter.java create mode 100644 epublib/src/main/java/me/ag2s/epublib/epub/HtmlProcessor.java create mode 100644 epublib/src/main/java/me/ag2s/epublib/epub/Main.java create mode 100644 epublib/src/main/java/me/ag2s/epublib/epub/NCXDocument.java create mode 100644 epublib/src/main/java/me/ag2s/epublib/epub/NCXDocumentV2.java create mode 100644 epublib/src/main/java/me/ag2s/epublib/epub/NCXDocumentV3.java create mode 100644 epublib/src/main/java/me/ag2s/epublib/epub/PackageDocumentBase.java create mode 100644 epublib/src/main/java/me/ag2s/epublib/epub/PackageDocumentMetadataReader.java create mode 100644 epublib/src/main/java/me/ag2s/epublib/epub/PackageDocumentMetadataWriter.java create mode 100644 epublib/src/main/java/me/ag2s/epublib/epub/PackageDocumentReader.java create mode 100644 epublib/src/main/java/me/ag2s/epublib/epub/PackageDocumentWriter.java create mode 100644 epublib/src/main/java/me/ag2s/epublib/epub/ResourcesLoader.java create mode 100644 epublib/src/main/java/me/ag2s/epublib/util/CollectionUtil.java create mode 100644 epublib/src/main/java/me/ag2s/epublib/util/IOUtil.java create mode 100644 epublib/src/main/java/me/ag2s/epublib/util/NoCloseOutputStream.java create mode 100644 epublib/src/main/java/me/ag2s/epublib/util/NoCloseWriter.java create mode 100644 epublib/src/main/java/me/ag2s/epublib/util/ResourceUtil.java create mode 100644 epublib/src/main/java/me/ag2s/epublib/util/StreamWriterDelegate.txt create mode 100644 epublib/src/main/java/me/ag2s/epublib/util/StringUtil.java create mode 100644 epublib/src/main/java/me/ag2s/epublib/util/commons/io/BOMInputStream.java create mode 100644 epublib/src/main/java/me/ag2s/epublib/util/commons/io/ByteOrderMark.java create mode 100644 epublib/src/main/java/me/ag2s/epublib/util/commons/io/ProxyInputStream.java create mode 100644 epublib/src/main/java/me/ag2s/epublib/util/commons/io/XmlStreamReader.java create mode 100644 epublib/src/main/java/me/ag2s/epublib/util/commons/io/XmlStreamReaderException.java create mode 100644 epublib/src/main/resources/dtd/openebook.org/dtds/oeb-1.2/oeb12.ent create mode 100644 epublib/src/main/resources/dtd/openebook.org/dtds/oeb-1.2/oebpkg12.dtd create mode 100644 epublib/src/main/resources/dtd/www.daisy.org/z3986/2005/ncx-2005-1.dtd create mode 100644 epublib/src/main/resources/dtd/www.w3.org/TR/ruby/xhtml-ruby-1.mod create mode 100644 epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-arch-1.mod create mode 100644 epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-attribs-1.mod create mode 100644 epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-base-1.mod create mode 100644 epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-bdo-1.mod create mode 100644 epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-blkphras-1.mod create mode 100644 epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-blkpres-1.mod create mode 100644 epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-blkstruct-1.mod create mode 100644 epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-charent-1.mod create mode 100644 epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-csismap-1.mod create mode 100644 epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-datatypes-1.mod create mode 100644 epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-datatypes-1.mod.1 create mode 100644 epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-edit-1.mod create mode 100644 epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-events-1.mod create mode 100644 epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-form-1.mod create mode 100644 epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-framework-1.mod create mode 100644 epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-hypertext-1.mod create mode 100644 epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-image-1.mod create mode 100644 epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-inlphras-1.mod create mode 100644 epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-inlpres-1.mod create mode 100644 epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-inlstruct-1.mod create mode 100644 epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-inlstyle-1.mod create mode 100644 epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-lat1.ent create mode 100644 epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-link-1.mod create mode 100644 epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-list-1.mod create mode 100644 epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-meta-1.mod create mode 100644 epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-notations-1.mod create mode 100644 epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-object-1.mod create mode 100644 epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-param-1.mod create mode 100644 epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-pres-1.mod create mode 100644 epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-qname-1.mod create mode 100644 epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-script-1.mod create mode 100644 epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-special.ent create mode 100644 epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-ssismap-1.mod create mode 100644 epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-struct-1.mod create mode 100644 epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-style-1.mod create mode 100644 epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-symbol.ent create mode 100644 epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-symbol.ent.1 create mode 100644 epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-table-1.mod create mode 100644 epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-text-1.mod create mode 100644 epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml11-model-1.mod create mode 100644 epublib/src/main/resources/dtd/www.w3.org/TR/xhtml1/DTD/xhtml-lat1.ent create mode 100644 epublib/src/main/resources/dtd/www.w3.org/TR/xhtml1/DTD/xhtml-special.ent create mode 100644 epublib/src/main/resources/dtd/www.w3.org/TR/xhtml1/DTD/xhtml-symbol.ent create mode 100644 epublib/src/main/resources/dtd/www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd create mode 100644 epublib/src/main/resources/dtd/www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd create mode 100644 epublib/src/main/resources/dtd/www.w3.org/TR/xhtml11/DTD/xhtml11.dtd create mode 100644 epublib/src/main/resources/log4j.properties create mode 100644 epublib/src/test/java/me/ag2s/epublib/ExampleUnitTest.java diff --git a/app/build.gradle b/app/build.gradle index e4dfb8f5d..f1c74fd5c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -111,8 +111,9 @@ kapt { } dependencies { + implementation project(path: ':epublib') coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.1' - implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation fileTree(dir: 'libs', include: ['*.jar','*.aar']) testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test:runner:1.3.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' @@ -211,10 +212,10 @@ dependencies { //转换繁体 implementation 'com.github.liuyueyi.quick-chinese-transfer:quick-transfer-core:0.1.3' //epub - implementation('com.positiondev.epublib:epublib-core:3.1') { - exclude group: 'org.slf4j' - exclude group: 'xmlpull' - } +// implementation('com.positiondev.epublib:epublib-core:3.1') { +// exclude group: 'org.slf4j' +// exclude group: 'xmlpull' +// } } diff --git a/app/src/main/java/io/legado/app/model/localBook/EPUBFile.kt b/app/src/main/java/io/legado/app/model/localBook/EPUBFile.kt index 20871ad72..c64468264 100644 --- a/app/src/main/java/io/legado/app/model/localBook/EPUBFile.kt +++ b/app/src/main/java/io/legado/app/model/localBook/EPUBFile.kt @@ -8,14 +8,13 @@ import io.legado.app.data.appDb import io.legado.app.data.entities.BookChapter import io.legado.app.data.entities.EpubChapter import io.legado.app.utils.* -import net.sf.jazzlib.ZipEntry -import net.sf.jazzlib.ZipInputStream -import nl.siegmann.epublib.domain.Book -import nl.siegmann.epublib.domain.Resources -import nl.siegmann.epublib.domain.TOCReference -import nl.siegmann.epublib.epub.EpubReader -import nl.siegmann.epublib.service.MediatypeService -import nl.siegmann.epublib.util.ResourceUtil + +import me.ag2s.epublib.domain.Book +import me.ag2s.epublib.domain.MediaTypes +import me.ag2s.epublib.domain.Resources +import me.ag2s.epublib.domain.TOCReference +import me.ag2s.epublib.epub.EpubReader +import me.ag2s.epublib.util.ResourceUtil import org.jsoup.Jsoup import splitties.init.appCtx import java.io.File @@ -24,6 +23,8 @@ import java.io.IOException import java.io.InputStream import java.nio.charset.Charset import java.util.* +import java.util.zip.ZipEntry +import java.util.zip.ZipInputStream class EPUBFile(var book: io.legado.app.data.entities.Book) { @@ -111,7 +112,7 @@ class EPUBFile(var book: io.legado.app.data.entities.Book) { zipEntry = inZip.nextEntry if ((zipEntry == null) || zipEntry.isDirectory || zipEntry == ZipEntry("")) continue val resource = ResourceUtil.createResource(zipEntry, inZip) - if (resource.mediaType == MediatypeService.XHTML) resource.inputEncoding = "UTF-8" + if (resource.mediaType == MediaTypes.XHTML) resource.inputEncoding = "UTF-8" if (zipEntry.name.endsWith(".opf")) { /*掌上书苑有很多自制书OPF的nameSpace格式不标准,强制修复成正确的格式*/ val newS = String(resource.data).replace( diff --git a/epublib/.gitignore b/epublib/.gitignore new file mode 100644 index 000000000..42afabfd2 --- /dev/null +++ b/epublib/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/epublib/build.gradle b/epublib/build.gradle new file mode 100644 index 000000000..176ef0013 --- /dev/null +++ b/epublib/build.gradle @@ -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' +} \ No newline at end of file diff --git a/epublib/consumer-rules.pro b/epublib/consumer-rules.pro new file mode 100644 index 000000000..e69de29bb diff --git a/epublib/proguard-rules.pro b/epublib/proguard-rules.pro new file mode 100644 index 000000000..481bb4348 --- /dev/null +++ b/epublib/proguard-rules.pro @@ -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 \ No newline at end of file diff --git a/epublib/src/androidTest/java/me/ag2s/epublib/ExampleInstrumentedTest.java b/epublib/src/androidTest/java/me/ag2s/epublib/ExampleInstrumentedTest.java new file mode 100644 index 000000000..05c81a804 --- /dev/null +++ b/epublib/src/androidTest/java/me/ag2s/epublib/ExampleInstrumentedTest.java @@ -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 Testing documentation + */ +@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()); + } +} \ No newline at end of file diff --git a/epublib/src/main/AndroidManifest.xml b/epublib/src/main/AndroidManifest.xml new file mode 100644 index 000000000..827b5c1cd --- /dev/null +++ b/epublib/src/main/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/epublib/src/main/java/com/jcraft/jzlib/Adler32.java b/epublib/src/main/java/com/jcraft/jzlib/Adler32.java new file mode 100644 index 000000000..a789a5a1a --- /dev/null +++ b/epublib/src/main/java/com/jcraft/jzlib/Adler32.java @@ -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(); + } +*/ +} diff --git a/epublib/src/main/java/com/jcraft/jzlib/CRC32.java b/epublib/src/main/java/com/jcraft/jzlib/CRC32.java new file mode 100644 index 000000000..30457968f --- /dev/null +++ b/epublib/src/main/java/com/jcraft/jzlib/CRC32.java @@ -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; + } +} diff --git a/epublib/src/main/java/com/jcraft/jzlib/Checksum.java b/epublib/src/main/java/com/jcraft/jzlib/Checksum.java new file mode 100644 index 000000000..1139093c8 --- /dev/null +++ b/epublib/src/main/java/com/jcraft/jzlib/Checksum.java @@ -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(); +} diff --git a/epublib/src/main/java/com/jcraft/jzlib/Deflate.java b/epublib/src/main/java/com/jcraft/jzlib/Deflate.java new file mode 100644 index 000000000..cfda0f0bc --- /dev/null +++ b/epublib/src/main/java/com/jcraft/jzlib/Deflate.java @@ -0,0 +1,1757 @@ +/* -*-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; + +public +final class Deflate implements Cloneable { + + static final private int MAX_MEM_LEVEL=9; + + static final private int Z_DEFAULT_COMPRESSION=-1; + + static final private int MAX_WBITS=15; // 32K LZ77 window + static final private int DEF_MEM_LEVEL=8; + + static class Config{ + int good_length; // reduce lazy search above this match length + int max_lazy; // do not perform lazy search above this match length + int nice_length; // quit search above this match length + int max_chain; + int func; + Config(int good_length, int max_lazy, + int nice_length, int max_chain, int func){ + this.good_length=good_length; + this.max_lazy=max_lazy; + this.nice_length=nice_length; + this.max_chain=max_chain; + this.func=func; + } + } + + static final private int STORED=0; + static final private int FAST=1; + static final private int SLOW=2; + static final private Config[] config_table; + static{ + config_table=new Config[10]; + // good lazy nice chain + config_table[0]=new Config(0, 0, 0, 0, STORED); + config_table[1]=new Config(4, 4, 8, 4, FAST); + config_table[2]=new Config(4, 5, 16, 8, FAST); + config_table[3]=new Config(4, 6, 32, 32, FAST); + + config_table[4]=new Config(4, 4, 16, 16, SLOW); + config_table[5]=new Config(8, 16, 32, 32, SLOW); + config_table[6]=new Config(8, 16, 128, 128, SLOW); + config_table[7]=new Config(8, 32, 128, 256, SLOW); + config_table[8]=new Config(32, 128, 258, 1024, SLOW); + config_table[9]=new Config(32, 258, 258, 4096, SLOW); + } + + static final private String[] z_errmsg = { + "need dictionary", // Z_NEED_DICT 2 + "stream end", // Z_STREAM_END 1 + "", // Z_OK 0 + "file error", // Z_ERRNO (-1) + "stream error", // Z_STREAM_ERROR (-2) + "data error", // Z_DATA_ERROR (-3) + "insufficient memory", // Z_MEM_ERROR (-4) + "buffer error", // Z_BUF_ERROR (-5) + "incompatible version",// Z_VERSION_ERROR (-6) + "" + }; + + // block not completed, need more input or more output + static final private int NeedMore=0; + + // block flush performed + static final private int BlockDone=1; + + // finish started, need only more output at next deflate + static final private int FinishStarted=2; + + // finish done, accept no more input or output + static final private int FinishDone=3; + + // preset dictionary flag in zlib header + static final private int PRESET_DICT=0x20; + + static final private int Z_FILTERED=1; + static final private int Z_HUFFMAN_ONLY=2; + static final private int Z_DEFAULT_STRATEGY=0; + + 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 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 INIT_STATE=42; + static final private int BUSY_STATE=113; + static final private int FINISH_STATE=666; + + // The deflate compression method + static final private int Z_DEFLATED=8; + + static final private int STORED_BLOCK=0; + static final private int STATIC_TREES=1; + static final private int DYN_TREES=2; + + // The three kinds of block type + static final private int Z_BINARY=0; + static final private int Z_ASCII=1; + static final private int Z_UNKNOWN=2; + + static final private int Buf_size=8*2; + + // repeat previous bit length 3-6 times (2 bits of repeat count) + static final private int REP_3_6=16; + + // repeat a zero length 3-10 times (3 bits of repeat count) + static final private int REPZ_3_10=17; + + // repeat a zero length 11-138 times (7 bits of repeat count) + static final private int REPZ_11_138=18; + + static final private int MIN_MATCH=3; + static final private int MAX_MATCH=258; + static final private int MIN_LOOKAHEAD=(MAX_MATCH+MIN_MATCH+1); + + static final private int MAX_BITS=15; + static final private int D_CODES=30; + static final private int BL_CODES=19; + static final private int LENGTH_CODES=29; + static final private int LITERALS=256; + static final private int L_CODES=(LITERALS+1+LENGTH_CODES); + static final private int HEAP_SIZE=(2*L_CODES+1); + + static final private int END_BLOCK=256; + + ZStream strm; // pointer back to this zlib stream + int status; // as the name implies + byte[] pending_buf; // output still pending + int pending_buf_size; // size of pending_buf + int pending_out; // next pending byte to output to the stream + int pending; // nb of bytes in the pending buffer + int wrap = 1; + byte data_type; // UNKNOWN, BINARY or ASCII + byte method; // STORED (for zip only) or DEFLATED + int last_flush; // value of flush param for previous deflate call + + int w_size; // LZ77 window size (32K by default) + int w_bits; // log2(w_size) (8..16) + int w_mask; // w_size - 1 + + byte[] window; + // Sliding window. Input bytes are read into the second half of the window, + // and move to the first half later to keep a dictionary of at least wSize + // bytes. With this organization, matches are limited to a distance of + // wSize-MAX_MATCH bytes, but this ensures that IO is always + // performed with a length multiple of the block size. Also, it limits + // the window size to 64K, which is quite useful on MSDOS. + // To do: use the user input buffer as sliding window. + + int window_size; + // Actual size of window: 2*wSize, except when the user input buffer + // is directly used as sliding window. + + short[] prev; + // Link to older string with same hash index. To limit the size of this + // array to 64K, this link is maintained only for the last 32K strings. + // An index in this array is thus a window index modulo 32K. + + short[] head; // Heads of the hash chains or NIL. + + int ins_h; // hash index of string to be inserted + int hash_size; // number of elements in hash table + int hash_bits; // log2(hash_size) + int hash_mask; // hash_size-1 + + // Number of bits by which ins_h must be shifted at each input + // step. It must be such that after MIN_MATCH steps, the oldest + // byte no longer takes part in the hash key, that is: + // hash_shift * MIN_MATCH >= hash_bits + int hash_shift; + + // Window position at the beginning of the current output block. Gets + // negative when the window is moved backwards. + + int block_start; + + int match_length; // length of best match + int prev_match; // previous match + int match_available; // set if previous match exists + int strstart; // start of string to insert + int match_start; // start of matching string + int lookahead; // number of valid bytes ahead in window + + // Length of the best match at previous step. Matches not greater than this + // are discarded. This is used in the lazy match evaluation. + int prev_length; + + // To speed up deflation, hash chains are never searched beyond this + // length. A higher limit improves compression ratio but degrades the speed. + int max_chain_length; + + // Attempt to find a better match only when the current match is strictly + // smaller than this value. This mechanism is used only for compression + // levels >= 4. + int max_lazy_match; + + // Insert new strings in the hash table only if the match length is not + // greater than this length. This saves time but degrades compression. + // max_insert_length is used only for compression levels <= 3. + + int level; // compression level (1..9) + int strategy; // favor or force Huffman coding + + // Use a faster search when the previous match is longer than this + int good_match; + + // Stop searching when current match exceeds this + int nice_match; + + short[] dyn_ltree; // literal and length tree + short[] dyn_dtree; // distance tree + short[] bl_tree; // Huffman tree for bit lengths + + Tree l_desc=new Tree(); // desc for literal tree + Tree d_desc=new Tree(); // desc for distance tree + Tree bl_desc=new Tree(); // desc for bit length tree + + // number of codes at each bit length for an optimal tree + short[] bl_count=new short[MAX_BITS+1]; + // working area to be used in Tree#gen_codes() + short[] next_code=new short[MAX_BITS+1]; + + // heap used to build the Huffman trees + int[] heap=new int[2*L_CODES+1]; + + int heap_len; // number of elements in the heap + int heap_max; // element of largest frequency + // The sons of heap[n] are heap[2*n] and heap[2*n+1]. heap[0] is not used. + // The same heap array is used to build all trees. + + // Depth of each subtree used as tie breaker for trees of equal frequency + byte[] depth=new byte[2*L_CODES+1]; + + byte[] l_buf; // index for literals or lengths */ + + // Size of match buffer for literals/lengths. There are 4 reasons for + // limiting lit_bufsize to 64K: + // - frequencies can be kept in 16 bit counters + // - if compression is not successful for the first block, all input + // data is still in the window so we can still emit a stored block even + // when input comes from standard input. (This can also be done for + // all blocks if lit_bufsize is not greater than 32K.) + // - if compression is not successful for a file smaller than 64K, we can + // even emit a stored file instead of a stored block (saving 5 bytes). + // This is applicable only for zip (not gzip or zlib). + // - creating new Huffman trees less frequently may not provide fast + // adaptation to changes in the input data statistics. (Take for + // example a binary file with poorly compressible code followed by + // a highly compressible string table.) Smaller buffer sizes give + // fast adaptation but have of course the overhead of transmitting + // trees more frequently. + // - I can't count above 4 + int lit_bufsize; + + int last_lit; // running index in l_buf + + // Buffer for distances. To simplify the code, d_buf and l_buf have + // the same number of elements. To use different lengths, an extra flag + // array would be necessary. + + int d_buf; // index of pendig_buf + + int opt_len; // bit length of current block with optimal trees + int static_len; // bit length of current block with static trees + int matches; // number of string matches in current block + int last_eob_len; // bit length of EOB code for last block + + // Output buffer. bits are inserted starting at the bottom (least + // significant bits). + short bi_buf; + + // Number of valid bits in bi_buf. All bits above the last valid bit + // are always zero. + int bi_valid; + + GZIPHeader gheader = null; + + Deflate(ZStream strm){ + this.strm=strm; + dyn_ltree=new short[HEAP_SIZE*2]; + dyn_dtree=new short[(2*D_CODES+1)*2]; // distance tree + bl_tree=new short[(2*BL_CODES+1)*2]; // Huffman tree for bit lengths + } + + void lm_init() { + window_size=2*w_size; + + head[hash_size-1]=0; + for(int i=0; i= 3; max_blindex--) { + if (bl_tree[Tree.bl_order[max_blindex]*2+1] != 0) break; + } + // Update opt_len to include the bit length tree and counts + opt_len += 3*(max_blindex+1) + 5+5+4; + + return max_blindex; + } + + + // Send the header for a block using dynamic Huffman trees: the counts, the + // lengths of the bit length codes, the literal tree and the distance tree. + // IN assertion: lcodes >= 257, dcodes >= 1, blcodes >= 4. + void send_all_trees(int lcodes, int dcodes, int blcodes){ + int rank; // index in bl_order + + send_bits(lcodes-257, 5); // not +255 as stated in appnote.txt + send_bits(dcodes-1, 5); + send_bits(blcodes-4, 4); // not -3 as stated in appnote.txt + for (rank = 0; rank < blcodes; rank++) { + send_bits(bl_tree[Tree.bl_order[rank]*2+1], 3); + } + send_tree(dyn_ltree, lcodes-1); // literal tree + send_tree(dyn_dtree, dcodes-1); // distance tree + } + + // Send a literal or distance tree in compressed form, using the codes in + // bl_tree. + void send_tree (short[] tree,// the tree to be sent + int max_code // and its largest code of non zero frequency + ){ + int n; // iterates over all tree elements + int prevlen = -1; // last emitted length + int curlen; // length of current code + int nextlen = tree[0*2+1]; // length of next code + int count = 0; // repeat count of the current code + int max_count = 7; // max repeat count + int min_count = 4; // min repeat count + + if (nextlen == 0){ max_count = 138; min_count = 3; } + + for (n = 0; n <= max_code; n++) { + curlen = nextlen; nextlen = tree[(n+1)*2+1]; + if(++count < max_count && curlen == nextlen) { + continue; + } + else if(count < min_count) { + do { send_code(curlen, bl_tree); } while (--count != 0); + } + else if(curlen != 0){ + if(curlen != prevlen){ + send_code(curlen, bl_tree); count--; + } + send_code(REP_3_6, bl_tree); + send_bits(count-3, 2); + } + else if(count <= 10){ + send_code(REPZ_3_10, bl_tree); + send_bits(count-3, 3); + } + else{ + send_code(REPZ_11_138, bl_tree); + send_bits(count-11, 7); + } + count = 0; prevlen = curlen; + if(nextlen == 0){ + max_count = 138; min_count = 3; + } + else if(curlen == nextlen){ + max_count = 6; min_count = 3; + } + else{ + max_count = 7; min_count = 4; + } + } + } + + // Output a byte on the stream. + // IN assertion: there is enough room in pending_buf. + final void put_byte(byte[] p, int start, int len){ + System.arraycopy(p, start, pending_buf, pending, len); + pending+=len; + } + + final void put_byte(byte c){ + pending_buf[pending++]=c; + } + final void put_short(int w) { + put_byte((byte)(w/*&0xff*/)); + put_byte((byte)(w>>>8)); + } + final void putShortMSB(int b){ + put_byte((byte)(b>>8)); + put_byte((byte)(b/*&0xff*/)); + } + + final void send_code(int c, short[] tree){ + int c2=c*2; + send_bits((tree[c2]&0xffff), (tree[c2+1]&0xffff)); + } + + void send_bits(int value, int length){ + int len = length; + if (bi_valid > (int)Buf_size - len) { + int val = value; +// bi_buf |= (val << bi_valid); + bi_buf |= ((val << bi_valid)&0xffff); + put_short(bi_buf); + bi_buf = (short)(val >>> (Buf_size - bi_valid)); + bi_valid += len - Buf_size; + } else { +// bi_buf |= (value) << bi_valid; + bi_buf |= (((value) << bi_valid)&0xffff); + bi_valid += len; + } + } + + // Send one empty static block to give enough lookahead for inflate. + // This takes 10 bits, of which 7 may remain in the bit buffer. + // The current inflate code requires 9 bits of lookahead. If the + // last two codes for the previous block (real code plus EOB) were coded + // on 5 bits or less, inflate may have only 5+3 bits of lookahead to decode + // the last real code. In this case we send two empty static blocks instead + // of one. (There are no problems if the previous block is stored or fixed.) + // To simplify the code, we assume the worst case of last real code encoded + // on one bit only. + void _tr_align(){ + send_bits(STATIC_TREES<<1, 3); + send_code(END_BLOCK, StaticTree.static_ltree); + + bi_flush(); + + // Of the 10 bits for the empty block, we have already sent + // (10 - bi_valid) bits. The lookahead for the last real code (before + // the EOB of the previous block) was thus at least one plus the length + // of the EOB plus what we have just sent of the empty static block. + if (1 + last_eob_len + 10 - bi_valid < 9) { + send_bits(STATIC_TREES<<1, 3); + send_code(END_BLOCK, StaticTree.static_ltree); + bi_flush(); + } + last_eob_len = 7; + } + + + // Save the match info and tally the frequency counts. Return true if + // the current block must be flushed. + boolean _tr_tally (int dist, // distance of matched string + int lc // match length-MIN_MATCH or unmatched char (if dist==0) + ){ + + pending_buf[d_buf+last_lit*2] = (byte)(dist>>>8); + pending_buf[d_buf+last_lit*2+1] = (byte)dist; + + l_buf[last_lit] = (byte)lc; last_lit++; + + if (dist == 0) { + // lc is the unmatched char + dyn_ltree[lc*2]++; + } + else { + matches++; + // Here, lc is the match length - MIN_MATCH + dist--; // dist = match distance - 1 + dyn_ltree[(Tree._length_code[lc]+LITERALS+1)*2]++; + dyn_dtree[Tree.d_code(dist)*2]++; + } + + if ((last_lit & 0x1fff) == 0 && level > 2) { + // Compute an upper bound for the compressed length + int out_length = last_lit*8; + int in_length = strstart - block_start; + int dcode; + for (dcode = 0; dcode < D_CODES; dcode++) { + out_length += (int)dyn_dtree[dcode*2] * + (5L+Tree.extra_dbits[dcode]); + } + out_length >>>= 3; + if ((matches < (last_lit/2)) && out_length < in_length/2) return true; + } + + return (last_lit == lit_bufsize-1); + // We avoid equality with lit_bufsize because of wraparound at 64K + // on 16 bit machines and because stored blocks are restricted to + // 64K-1 bytes. + } + + // Send the block data compressed using the given Huffman trees + void compress_block(short[] ltree, short[] dtree){ + int dist; // distance of matched string + int lc; // match length or unmatched char (if dist == 0) + int lx = 0; // running index in l_buf + int code; // the code to send + int extra; // number of extra bits to send + + if (last_lit != 0){ + do{ + dist=((pending_buf[d_buf+lx*2]<<8)&0xff00)| + (pending_buf[d_buf+lx*2+1]&0xff); + lc=(l_buf[lx])&0xff; lx++; + + if(dist == 0){ + send_code(lc, ltree); // send a literal byte + } + else{ + // Here, lc is the match length - MIN_MATCH + code = Tree._length_code[lc]; + + send_code(code+LITERALS+1, ltree); // send the length code + extra = Tree.extra_lbits[code]; + if(extra != 0){ + lc -= Tree.base_length[code]; + send_bits(lc, extra); // send the extra length bits + } + dist--; // dist is now the match distance - 1 + code = Tree.d_code(dist); + + send_code(code, dtree); // send the distance code + extra = Tree.extra_dbits[code]; + if (extra != 0) { + dist -= Tree.base_dist[code]; + send_bits(dist, extra); // send the extra distance bits + } + } // literal or match pair ? + + // Check that the overlay between pending_buf and d_buf+l_buf is ok: + } + while (lx < last_lit); + } + + send_code(END_BLOCK, ltree); + last_eob_len = ltree[END_BLOCK*2+1]; + } + + // Set the data type to ASCII or BINARY, using a crude approximation: + // binary if more than 20% of the bytes are <= 6 or >= 128, ascii otherwise. + // IN assertion: the fields freq of dyn_ltree are set and the total of all + // frequencies does not exceed 64K (to fit in an int on 16 bit machines). + void set_data_type(){ + int n = 0; + int ascii_freq = 0; + int bin_freq = 0; + while(n<7){ bin_freq += dyn_ltree[n*2]; n++;} + while(n<128){ ascii_freq += dyn_ltree[n*2]; n++;} + while(n (ascii_freq >>> 2) ? Z_BINARY : Z_ASCII); + } + + // Flush the bit buffer, keeping at most 7 bits in it. + void bi_flush(){ + if (bi_valid == 16) { + put_short(bi_buf); + bi_buf=0; + bi_valid=0; + } + else if (bi_valid >= 8) { + put_byte((byte)bi_buf); + bi_buf>>>=8; + bi_valid-=8; + } + } + + // Flush the bit buffer and align the output on a byte boundary + void bi_windup(){ + if (bi_valid > 8) { + put_short(bi_buf); + } else if (bi_valid > 0) { + put_byte((byte)bi_buf); + } + bi_buf = 0; + bi_valid = 0; + } + + // Copy a stored block, storing first the length and its + // one's complement if requested. + void copy_block(int buf, // the input data + int len, // its length + boolean header // true if block header must be written + ){ + int index=0; + bi_windup(); // align on byte boundary + last_eob_len = 8; // enough lookahead for inflate + + if (header) { + put_short((short)len); + put_short((short)~len); + } + + // while(len--!=0) { + // put_byte(window[buf+index]); + // index++; + // } + put_byte(window, buf, len); + } + + void flush_block_only(boolean eof){ + _tr_flush_block(block_start>=0 ? block_start : -1, + strstart-block_start, + eof); + block_start=strstart; + strm.flush_pending(); + } + + // Copy without compression as much as possible from the input stream, return + // the current block state. + // This function does not insert new strings in the dictionary since + // uncompressible data is probably not useful. This function is used + // only for the level=0 compression option. + // NOTE: this function should be optimized to avoid extra copying from + // window to pending_buf. + int deflate_stored(int flush){ + // Stored blocks are limited to 0xffff bytes, pending_buf is limited + // to pending_buf_size, and each stored block has a 5 byte header: + + int max_block_size = 0xffff; + int max_start; + + if(max_block_size > pending_buf_size - 5) { + max_block_size = pending_buf_size - 5; + } + + // Copy as much as possible from input to output: + while(true){ + // Fill the window as much as possible: + if(lookahead<=1){ + fill_window(); + if(lookahead==0 && flush==Z_NO_FLUSH) return NeedMore; + if(lookahead==0) break; // flush the current block + } + + strstart+=lookahead; + lookahead=0; + + // Emit a stored block if pending_buf will be full: + max_start=block_start+max_block_size; + if(strstart==0|| strstart>=max_start) { + // strstart == 0 is possible when wraparound on 16-bit machine + lookahead = (int)(strstart-max_start); + strstart = (int)max_start; + + flush_block_only(false); + if(strm.avail_out==0) return NeedMore; + + } + + // Flush if we may have to slide, otherwise block_start may become + // negative and the data will be gone: + if(strstart-block_start >= w_size-MIN_LOOKAHEAD) { + flush_block_only(false); + if(strm.avail_out==0) return NeedMore; + } + } + + flush_block_only(flush == Z_FINISH); + if(strm.avail_out==0) + return (flush == Z_FINISH) ? FinishStarted : NeedMore; + + return flush == Z_FINISH ? FinishDone : BlockDone; + } + + // Send a stored block + void _tr_stored_block(int buf, // input block + int stored_len, // length of input block + boolean eof // true if this is the last block for a file + ){ + send_bits((STORED_BLOCK<<1)+(eof?1:0), 3); // send block type + copy_block(buf, stored_len, true); // with header + } + + // Determine the best encoding for the current block: dynamic trees, static + // trees or store, and output the encoded block to the zip file. + void _tr_flush_block(int buf, // input block, or NULL if too old + int stored_len, // length of input block + boolean eof // true if this is the last block for a file + ) { + int opt_lenb, static_lenb;// opt_len and static_len in bytes + int max_blindex = 0; // index of last bit length code of non zero freq + + // Build the Huffman trees unless a stored block is forced + if(level > 0) { + // Check if the file is ascii or binary + if(data_type == Z_UNKNOWN) set_data_type(); + + // Construct the literal and distance trees + l_desc.build_tree(this); + + d_desc.build_tree(this); + + // At this point, opt_len and static_len are the total bit lengths of + // the compressed block data, excluding the tree representations. + + // Build the bit length tree for the above two trees, and get the index + // in bl_order of the last bit length code to send. + max_blindex=build_bl_tree(); + + // Determine the best encoding. Compute first the block length in bytes + opt_lenb=(opt_len+3+7)>>>3; + static_lenb=(static_len+3+7)>>>3; + + if(static_lenb<=opt_lenb) opt_lenb=static_lenb; + } + else { + opt_lenb=static_lenb=stored_len+5; // force a stored block + } + + if(stored_len+4<=opt_lenb && buf != -1){ + // 4: two words for the lengths + // The test buf != NULL is only necessary if LIT_BUFSIZE > WSIZE. + // Otherwise we can't have processed more than WSIZE input bytes since + // the last block flush, because compression would have been + // successful. If LIT_BUFSIZE <= WSIZE, it is never too late to + // transform a block into a stored block. + _tr_stored_block(buf, stored_len, eof); + } + else if(static_lenb == opt_lenb){ + send_bits((STATIC_TREES<<1)+(eof?1:0), 3); + compress_block(StaticTree.static_ltree, StaticTree.static_dtree); + } + else{ + send_bits((DYN_TREES<<1)+(eof?1:0), 3); + send_all_trees(l_desc.max_code+1, d_desc.max_code+1, max_blindex+1); + compress_block(dyn_ltree, dyn_dtree); + } + + // The above check is made mod 2^32, for files larger than 512 MB + // and uLong implemented on 32 bits. + + init_block(); + + if(eof){ + bi_windup(); + } + } + + // Fill the window when the lookahead becomes insufficient. + // Updates strstart and lookahead. + // + // IN assertion: lookahead < MIN_LOOKAHEAD + // OUT assertions: strstart <= window_size-MIN_LOOKAHEAD + // At least one byte has been read, or avail_in == 0; reads are + // performed for at least two bytes (required for the zip translate_eol + // option -- not supported here). + void fill_window(){ + int n, m; + int p; + int more; // Amount of free space at the end of the window. + + do{ + more = (window_size-lookahead-strstart); + + // Deal with !@#$% 64K limit: + if(more==0 && strstart==0 && lookahead==0){ + more = w_size; + } + else if(more==-1) { + // Very unlikely, but possible on 16 bit machine if strstart == 0 + // and lookahead == 1 (input done one byte at time) + more--; + + // If the window is almost full and there is insufficient lookahead, + // move the upper half to the lower one to make room in the upper half. + } + else if(strstart >= w_size+ w_size-MIN_LOOKAHEAD) { + System.arraycopy(window, w_size, window, 0, w_size); + match_start-=w_size; + strstart-=w_size; // we now have strstart >= MAX_DIST + block_start-=w_size; + + // Slide the hash table (could be avoided with 32 bit values + // at the expense of memory usage). We slide even when level == 0 + // to keep the hash table consistent if we switch back to level > 0 + // later. (Using level 0 permanently is not an optimal usage of + // zlib, so we don't care about this pathological case.) + + n = hash_size; + p=n; + do { + m = (head[--p]&0xffff); + head[p]=(m>=w_size ? (short)(m-w_size) : 0); + } + while (--n != 0); + + n = w_size; + p = n; + do { + m = (prev[--p]&0xffff); + prev[p] = (m >= w_size ? (short)(m-w_size) : 0); + // If n is not on any hash chain, prev[n] is garbage but + // its value will never be used. + } + while (--n!=0); + more += w_size; + } + + if (strm.avail_in == 0) return; + + // If there was no sliding: + // strstart <= WSIZE+MAX_DIST-1 && lookahead <= MIN_LOOKAHEAD - 1 && + // more == window_size - lookahead - strstart + // => more >= window_size - (MIN_LOOKAHEAD-1 + WSIZE + MAX_DIST-1) + // => more >= window_size - 2*WSIZE + 2 + // In the BIG_MEM or MMAP case (not yet supported), + // window_size == input_size + MIN_LOOKAHEAD && + // strstart + s->lookahead <= input_size => more >= MIN_LOOKAHEAD. + // Otherwise, window_size == 2*WSIZE so more >= 2. + // If there was sliding, more >= WSIZE. So in all cases, more >= 2. + + n = strm.read_buf(window, strstart + lookahead, more); + lookahead += n; + + // Initialize the hash value now that we have some input: + if(lookahead >= MIN_MATCH) { + ins_h = window[strstart]&0xff; + ins_h=(((ins_h)<= MIN_MATCH){ + ins_h=(((ins_h)<=MIN_MATCH){ + // check_match(strstart, match_start, match_length); + + bflush=_tr_tally(strstart-match_start, match_length-MIN_MATCH); + + lookahead -= match_length; + + // Insert new strings in the hash table only if the match length + // is not too large. This saves time but degrades compression. + if(match_length <= max_lazy_match && + lookahead >= MIN_MATCH) { + match_length--; // string at strstart already in hash table + do{ + strstart++; + + ins_h=((ins_h<= MIN_MATCH) { + ins_h=(((ins_h)< 4096))) { + + // If prev_match is also MIN_MATCH, match_start is garbage + // but we will ignore the current match anyway. + match_length = MIN_MATCH-1; + } + } + + // If there was a match at the previous step and the current + // match is not better, output the previous match: + if(prev_length >= MIN_MATCH && match_length <= prev_length) { + int max_insert = strstart + lookahead - MIN_MATCH; + // Do not insert strings in hash table beyond this. + + // check_match(strstart-1, prev_match, prev_length); + + bflush=_tr_tally(strstart-1-prev_match, prev_length - MIN_MATCH); + + // Insert in hash table all strings up to the end of the match. + // strstart-1 and strstart are already inserted. If there is not + // enough lookahead, the last two strings are not inserted in + // the hash table. + lookahead -= prev_length-1; + prev_length -= 2; + do{ + if(++strstart <= max_insert) { + ins_h=(((ins_h)<(w_size-MIN_LOOKAHEAD) ? + strstart-(w_size-MIN_LOOKAHEAD) : 0; + int nice_match=this.nice_match; + + // Stop when cur_match becomes <= limit. To simplify the code, + // we prevent matches with the string of window index 0. + + int wmask = w_mask; + + int strend = strstart + MAX_MATCH; + byte scan_end1 = window[scan+best_len-1]; + byte scan_end = window[scan+best_len]; + + // The code is optimized for HASH_BITS >= 8 and MAX_MATCH-2 multiple of 16. + // It is easy to get rid of this optimization if necessary. + + // Do not waste too much time if we already have a good match: + if (prev_length >= good_match) { + chain_length >>= 2; + } + + // Do not look for matches beyond the end of the input. This is necessary + // to make deflate deterministic. + if (nice_match > lookahead) nice_match = lookahead; + + do { + match = cur_match; + + // Skip to next match if the match length cannot increase + // or if the match length is less than 2: + if (window[match+best_len] != scan_end || + window[match+best_len-1] != scan_end1 || + window[match] != window[scan] || + window[++match] != window[scan+1]) continue; + + // The check at best_len-1 can be removed because it will be made + // again later. (This heuristic is not always a win.) + // It is not necessary to compare scan[2] and match[2] since they + // are always equal when the other bytes match, given that + // the hash keys are equal and that HASH_BITS >= 8. + scan += 2; match++; + + // We check for insufficient lookahead only every 8th comparison; + // the 256th check will be made at strstart+258. + do { + } while (window[++scan] == window[++match] && + window[++scan] == window[++match] && + window[++scan] == window[++match] && + window[++scan] == window[++match] && + window[++scan] == window[++match] && + window[++scan] == window[++match] && + window[++scan] == window[++match] && + window[++scan] == window[++match] && + scan < strend); + + len = MAX_MATCH - (int)(strend - scan); + scan = strend - MAX_MATCH; + + if(len>best_len) { + match_start = cur_match; + best_len = len; + if (len >= nice_match) break; + scan_end1 = window[scan+best_len-1]; + scan_end = window[scan+best_len]; + } + + } while ((cur_match = (prev[cur_match & wmask]&0xffff)) > limit + && --chain_length != 0); + + if (best_len <= lookahead) return best_len; + return lookahead; + } + + int deflateInit(int level, int bits, int memlevel){ + return deflateInit(level, Z_DEFLATED, bits, memlevel, + Z_DEFAULT_STRATEGY); + } + + int deflateInit(int level, int bits){ + return deflateInit(level, Z_DEFLATED, bits, DEF_MEM_LEVEL, + Z_DEFAULT_STRATEGY); + } + int deflateInit(int level){ + return deflateInit(level, MAX_WBITS); + } + private int deflateInit(int level, int method, int windowBits, + int memLevel, int strategy){ + int wrap = 1; + // byte[] my_version=ZLIB_VERSION; + + // + // if (version == null || version[0] != my_version[0] + // || stream_size != sizeof(z_stream)) { + // return Z_VERSION_ERROR; + // } + + strm.msg = null; + + if (level == Z_DEFAULT_COMPRESSION) level = 6; + + if (windowBits < 0) { // undocumented feature: suppress zlib header + wrap = 0; + windowBits = -windowBits; + } + else if(windowBits > 15){ + wrap = 2; + windowBits -= 16; + strm.adler=new CRC32(); + } + + if (memLevel < 1 || memLevel > MAX_MEM_LEVEL || + method != Z_DEFLATED || + windowBits < 9 || windowBits > 15 || level < 0 || level > 9 || + strategy < 0 || strategy > Z_HUFFMAN_ONLY) { + return Z_STREAM_ERROR; + } + + strm.dstate = (Deflate)this; + + this.wrap = wrap; + w_bits = windowBits; + w_size = 1 << w_bits; + w_mask = w_size - 1; + + hash_bits = memLevel + 7; + hash_size = 1 << hash_bits; + hash_mask = hash_size - 1; + hash_shift = ((hash_bits+MIN_MATCH-1)/MIN_MATCH); + + window = new byte[w_size*2]; + prev = new short[w_size]; + head = new short[hash_size]; + + lit_bufsize = 1 << (memLevel + 6); // 16K elements by default + + // We overlay pending_buf and d_buf+l_buf. This works since the average + // output size for (length,distance) codes is <= 24 bits. + pending_buf = new byte[lit_bufsize*3]; + pending_buf_size = lit_bufsize*3; + + d_buf = lit_bufsize; + l_buf = new byte[lit_bufsize]; + + this.level = level; + + this.strategy = strategy; + this.method = (byte)method; + + return deflateReset(); + } + + int deflateReset(){ + strm.total_in = strm.total_out = 0; + strm.msg = null; // + strm.data_type = Z_UNKNOWN; + + pending = 0; + pending_out = 0; + + if(wrap < 0){ + wrap = -wrap; + } + status = (wrap==0) ? BUSY_STATE : INIT_STATE; + strm.adler.reset(); + + last_flush = Z_NO_FLUSH; + + tr_init(); + lm_init(); + return Z_OK; + } + + int deflateEnd(){ + if(status!=INIT_STATE && status!=BUSY_STATE && status!=FINISH_STATE){ + return Z_STREAM_ERROR; + } + // Deallocate in reverse order of allocations: + pending_buf=null; + l_buf=null; + head=null; + prev=null; + window=null; + // free + // dstate=null; + return status == BUSY_STATE ? Z_DATA_ERROR : Z_OK; + } + + int deflateParams(int _level, int _strategy){ + int err=Z_OK; + + if(_level == Z_DEFAULT_COMPRESSION){ + _level = 6; + } + if(_level < 0 || _level > 9 || + _strategy < 0 || _strategy > Z_HUFFMAN_ONLY) { + return Z_STREAM_ERROR; + } + + if(config_table[level].func!=config_table[_level].func && + strm.total_in != 0) { + // Flush the last buffer: + err = strm.deflate(Z_PARTIAL_FLUSH); + } + + if(level != _level) { + level = _level; + max_lazy_match = config_table[level].max_lazy; + good_match = config_table[level].good_length; + nice_match = config_table[level].nice_length; + max_chain_length = config_table[level].max_chain; + } + strategy = _strategy; + return err; + } + + int deflateSetDictionary (byte[] dictionary, int dictLength){ + int length = dictLength; + int index=0; + + if(dictionary == null || status != INIT_STATE) + return Z_STREAM_ERROR; + + strm.adler.update(dictionary, 0, dictLength); + + if(length < MIN_MATCH) return Z_OK; + if(length > w_size-MIN_LOOKAHEAD){ + length = w_size-MIN_LOOKAHEAD; + index=dictLength-length; // use the tail of the dictionary + } + System.arraycopy(dictionary, index, window, 0, length); + strstart = length; + block_start = length; + + // Insert all strings in the hash table (except for the last two bytes). + // s->lookahead stays null, so s->ins_h will be recomputed at the next + // call of fill_window. + + ins_h = window[0]&0xff; + ins_h=(((ins_h)<Z_FINISH || flush<0){ + return Z_STREAM_ERROR; + } + + if(strm.next_out == null || + (strm.next_in == null && strm.avail_in != 0) || + (status == FINISH_STATE && flush != Z_FINISH)) { + strm.msg=z_errmsg[Z_NEED_DICT-(Z_STREAM_ERROR)]; + return Z_STREAM_ERROR; + } + if(strm.avail_out == 0){ + strm.msg=z_errmsg[Z_NEED_DICT-(Z_BUF_ERROR)]; + return Z_BUF_ERROR; + } + + old_flush = last_flush; + last_flush = flush; + + // Write the zlib header + if(status == INIT_STATE) { + if(wrap == 2){ + getGZIPHeader().put(this); + status=BUSY_STATE; + strm.adler.reset(); + } + else{ + int header = (Z_DEFLATED+((w_bits-8)<<4))<<8; + int level_flags=((level-1)&0xff)>>1; + + if(level_flags>3) level_flags=3; + header |= (level_flags<<6); + if(strstart!=0) header |= PRESET_DICT; + header+=31-(header % 31); + + status=BUSY_STATE; + putShortMSB(header); + + + // Save the adler32 of the preset dictionary: + if(strstart!=0){ + long adler=strm.adler.getValue(); + putShortMSB((int)(adler>>>16)); + putShortMSB((int)(adler&0xffff)); + } + strm.adler.reset(); + } + } + + // Flush as much pending output as possible + if(pending != 0) { + strm.flush_pending(); + if(strm.avail_out == 0) { + // Since avail_out is 0, deflate will be called again with + // more output space, but possibly with both pending and + // avail_in equal to zero. There won't be anything to do, + // but this is not an error situation so make sure we + // return OK instead of BUF_ERROR at next call of deflate: + last_flush = -1; + return Z_OK; + } + + // Make sure there is something to do and avoid duplicate consecutive + // flushes. For repeated and useless calls with Z_FINISH, we keep + // returning Z_STREAM_END instead of Z_BUFF_ERROR. + } + else if(strm.avail_in==0 && flush <= old_flush && + flush != Z_FINISH) { + strm.msg=z_errmsg[Z_NEED_DICT-(Z_BUF_ERROR)]; + return Z_BUF_ERROR; + } + + // User must not provide more input after the first FINISH: + if(status == FINISH_STATE && strm.avail_in != 0) { + strm.msg=z_errmsg[Z_NEED_DICT-(Z_BUF_ERROR)]; + return Z_BUF_ERROR; + } + + // Start a new block or continue the current one. + if(strm.avail_in!=0 || lookahead!=0 || + (flush != Z_NO_FLUSH && status != FINISH_STATE)) { + int bstate=-1; + switch(config_table[level].func){ + case STORED: + bstate = deflate_stored(flush); + break; + case FAST: + bstate = deflate_fast(flush); + break; + case SLOW: + bstate = deflate_slow(flush); + break; + default: + } + + if (bstate==FinishStarted || bstate==FinishDone) { + status = FINISH_STATE; + } + if (bstate==NeedMore || bstate==FinishStarted) { + if(strm.avail_out == 0) { + last_flush = -1; // avoid BUF_ERROR next call, see above + } + return Z_OK; + // If flush != Z_NO_FLUSH && avail_out == 0, the next call + // of deflate should use the same flush parameter to make sure + // that the flush is complete. So we don't have to output an + // empty block here, this will be done at next call. This also + // ensures that for a very small output buffer, we emit at most + // one empty block. + } + + if (bstate==BlockDone) { + if(flush == Z_PARTIAL_FLUSH) { + _tr_align(); + } + else { // FULL_FLUSH or SYNC_FLUSH + _tr_stored_block(0, 0, false); + // For a full flush, this empty block will be recognized + // as a special marker by inflate_sync(). + if(flush == Z_FULL_FLUSH) { + //state.head[s.hash_size-1]=0; + for(int i=0; i>8)&0xff)); + put_byte((byte)((adler>>16)&0xff)); + put_byte((byte)((adler>>24)&0xff)); + put_byte((byte)(strm.total_in&0xff)); + put_byte((byte)((strm.total_in>>8)&0xff)); + put_byte((byte)((strm.total_in>>16)&0xff)); + put_byte((byte)((strm.total_in>>24)&0xff)); + + getGZIPHeader().setCRC(adler); + } + else{ + // Write the zlib trailer (adler32) + long adler=strm.adler.getValue(); + putShortMSB((int)(adler>>>16)); + putShortMSB((int)(adler&0xffff)); + } + + strm.flush_pending(); + + // If avail_out is zero, the application will call deflate again + // to flush the rest. + + if(wrap > 0) wrap = -wrap; // write the trailer only once! + return pending != 0 ? Z_OK : Z_STREAM_END; + } + + static int deflateCopy(ZStream dest, ZStream src){ + + if(src.dstate == null){ + return Z_STREAM_ERROR; + } + + if(src.next_in!=null){ + dest.next_in = new byte[src.next_in.length]; + System.arraycopy(src.next_in, 0, dest.next_in, 0, src.next_in.length); + } + dest.next_in_index = src.next_in_index; + dest.avail_in = src.avail_in; + dest.total_in = src.total_in; + + if(src.next_out!=null){ + dest.next_out = new byte[src.next_out.length]; + System.arraycopy(src.next_out, 0, dest.next_out ,0 , src.next_out.length); + } + + dest.next_out_index = src.next_out_index; + dest.avail_out = src.avail_out; + dest.total_out = src.total_out; + + dest.msg = src.msg; + dest.data_type = src.data_type; + dest.adler = src.adler.copy(); + + try{ + dest.dstate = (Deflate)src.dstate.clone(); + dest.dstate.strm = dest; + } + catch(CloneNotSupportedException e){ + // + } + return Z_OK; + } + + public Object clone() throws CloneNotSupportedException { + Deflate dest = (Deflate)super.clone(); + + dest.pending_buf = dup(dest.pending_buf); + dest.d_buf = dest.d_buf; + dest.l_buf = dup(dest.l_buf); + dest.window = dup(dest.window); + + dest.prev = dup(dest.prev); + dest.head = dup(dest.head); + dest.dyn_ltree = dup(dest.dyn_ltree); + dest.dyn_dtree = dup(dest.dyn_dtree); + dest.bl_tree = dup(dest.bl_tree); + + dest.bl_count = dup(dest.bl_count); + dest.next_code = dup(dest.next_code); + dest.heap = dup(dest.heap); + dest.depth = dup(dest.depth); + + dest.l_desc.dyn_tree = dest.dyn_ltree; + dest.d_desc.dyn_tree = dest.dyn_dtree; + dest.bl_desc.dyn_tree = dest.bl_tree; + + /* + dest.l_desc.stat_desc = StaticTree.static_l_desc; + dest.d_desc.stat_desc = StaticTree.static_d_desc; + dest.bl_desc.stat_desc = StaticTree.static_bl_desc; + */ + + if(dest.gheader!=null){ + dest.gheader = (GZIPHeader)dest.gheader.clone(); + } + + return dest; + } + + private byte[] dup(byte[] buf){ + byte[] foo = new byte[buf.length]; + System.arraycopy(buf, 0, foo, 0, foo.length); + return foo; + } + private short[] dup(short[] buf){ + short[] foo = new short[buf.length]; + System.arraycopy(buf, 0, foo, 0, foo.length); + return foo; + } + private int[] dup(int[] buf){ + int[] foo = new int[buf.length]; + System.arraycopy(buf, 0, foo, 0, foo.length); + return foo; + } + + synchronized GZIPHeader getGZIPHeader(){ + if(gheader==null){ + gheader = new GZIPHeader(); + } + return gheader; + } +} diff --git a/epublib/src/main/java/com/jcraft/jzlib/Deflater.java b/epublib/src/main/java/com/jcraft/jzlib/Deflater.java new file mode 100644 index 000000000..ce0580ddb --- /dev/null +++ b/epublib/src/main/java/com/jcraft/jzlib/Deflater.java @@ -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); + } +} diff --git a/epublib/src/main/java/com/jcraft/jzlib/DeflaterOutputStream.java b/epublib/src/main/java/com/jcraft/jzlib/DeflaterOutputStream.java new file mode 100644 index 000000000..4c9f0d253 --- /dev/null +++ b/epublib/src/main/java/com/jcraft/jzlib/DeflaterOutputStream.java @@ -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; + } +} diff --git a/epublib/src/main/java/com/jcraft/jzlib/GZIPException.java b/epublib/src/main/java/com/jcraft/jzlib/GZIPException.java new file mode 100644 index 000000000..0beef40df --- /dev/null +++ b/epublib/src/main/java/com/jcraft/jzlib/GZIPException.java @@ -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); + } +} diff --git a/epublib/src/main/java/com/jcraft/jzlib/GZIPHeader.java b/epublib/src/main/java/com/jcraft/jzlib/GZIPHeader.java new file mode 100644 index 000000000..9ec89afa7 --- /dev/null +++ b/epublib/src/main/java/com/jcraft/jzlib/GZIPHeader.java @@ -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; + } +} diff --git a/epublib/src/main/java/com/jcraft/jzlib/GZIPInputStream.java b/epublib/src/main/java/com/jcraft/jzlib/GZIPInputStream.java new file mode 100644 index 000000000..5d29dca72 --- /dev/null +++ b/epublib/src/main/java/com/jcraft/jzlib/GZIPInputStream.java @@ -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>> 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)<>> 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)(qn) 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)< 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>>=(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)<>>=(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)<>>=(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)<>>=(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 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; + } +} diff --git a/epublib/src/main/java/com/jcraft/jzlib/InfCodes.java b/epublib/src/main/java/com/jcraft/jzlib/InfCodes.java new file mode 100644 index 000000000..aaf69cd42 --- /dev/null +++ b/epublib/src/main/java/com/jcraft/jzlib/InfCodes.java @@ -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= 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>>=(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)<>=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)<>=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)<>=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 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= 258 && n >= 10 + // get literal/length code + while(k<(20)){ // max bits for literal/length code + n--; + b|=(z.next_in[p++]&0xff)<>=(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)<>=(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)<>=(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)>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)>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)>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)>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; + } +} diff --git a/epublib/src/main/java/com/jcraft/jzlib/InfTree.java b/epublib/src/main/java/com/jcraft/jzlib/InfTree.java new file mode 100644 index 000000000..80a2b7b64 --- /dev/null +++ b/epublib/src/main/java/com/jcraft/jzlib/InfTree.java @@ -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>>= 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> 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<>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<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>=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; + } + } +} diff --git a/epublib/src/main/java/com/jcraft/jzlib/Inflater.java b/epublib/src/main/java/com/jcraft/jzlib/Inflater.java new file mode 100644 index 000000000..0fb0b098b --- /dev/null +++ b/epublib/src/main/java/com/jcraft/jzlib/Inflater.java @@ -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*/; + } +} diff --git a/epublib/src/main/java/com/jcraft/jzlib/InflaterInputStream.java b/epublib/src/main/java/com/jcraft/jzlib/InflaterInputStream.java new file mode 100644 index 000000000..0420582a9 --- /dev/null +++ b/epublib/src/main/java/com/jcraft/jzlib/InflaterInputStream.java @@ -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; + } +} \ No newline at end of file diff --git a/epublib/src/main/java/com/jcraft/jzlib/JZlib.java b/epublib/src/main/java/com/jcraft/jzlib/JZlib.java new file mode 100644 index 000000000..a4bb3410f --- /dev/null +++ b/epublib/src/main/java/com/jcraft/jzlib/JZlib.java @@ -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); + } +} diff --git a/epublib/src/main/java/com/jcraft/jzlib/StaticTree.java b/epublib/src/main/java/com/jcraft/jzlib/StaticTree.java new file mode 100644 index 000000000..e35931c36 --- /dev/null +++ b/epublib/src/main/java/com/jcraft/jzlib/StaticTree.java @@ -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; + } +} diff --git a/epublib/src/main/java/com/jcraft/jzlib/Tree.java b/epublib/src/main/java/com/jcraft/jzlib/Tree.java new file mode 100644 index 000000000..38cb40f2a --- /dev/null +++ b/epublib/src/main/java/com/jcraft/jzlib/Tree.java @@ -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 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=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<>>=1; + res<<=1; + } + while(--len>0); + return res>>>1; + } +} + diff --git a/epublib/src/main/java/com/jcraft/jzlib/ZInputStream.java b/epublib/src/main/java/com/jcraft/jzlib/ZInputStream.java new file mode 100644 index 000000000..cbd38e151 --- /dev/null +++ b/epublib/src/main/java/com/jcraft/jzlib/ZInputStream.java @@ -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(n0){ + 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(); + } + +} diff --git a/epublib/src/main/java/com/jcraft/jzlib/ZStream.java b/epublib/src/main/java/com/jcraft/jzlib/ZStream.java new file mode 100644 index 000000000..0afa4fd04 --- /dev/null +++ b/epublib/src/main/java/com/jcraft/jzlib/ZStream.java @@ -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; } +} diff --git a/epublib/src/main/java/com/jcraft/jzlib/ZStreamException.java b/epublib/src/main/java/com/jcraft/jzlib/ZStreamException.java new file mode 100644 index 000000000..308bb8a1e --- /dev/null +++ b/epublib/src/main/java/com/jcraft/jzlib/ZStreamException.java @@ -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); + } +} diff --git a/epublib/src/main/java/me/ag2s/epublib/Constants.java b/epublib/src/main/java/me/ag2s/epublib/Constants.java new file mode 100644 index 000000000..37da37912 --- /dev/null +++ b/epublib/src/main/java/me/ag2s/epublib/Constants.java @@ -0,0 +1,12 @@ +package me.ag2s.epublib; + + +public interface Constants { + + String CHARACTER_ENCODING = "UTF-8"; + String DOCTYPE_XHTML = ""; + String NAMESPACE_XHTML = "http://www.w3.org/1999/xhtml"; + String EPUB4J_GENERATOR_NAME = "EpubLib"; + char FRAGMENT_SEPARATOR_CHAR = '#'; + String DEFAULT_TOC_ID = "toc"; +} diff --git a/epublib/src/main/java/me/ag2s/epublib/browsersupport/NavigationEvent.java b/epublib/src/main/java/me/ag2s/epublib/browsersupport/NavigationEvent.java new file mode 100644 index 000000000..90b4d9b20 --- /dev/null +++ b/epublib/src/main/java/me/ag2s/epublib/browsersupport/NavigationEvent.java @@ -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(); + } +} diff --git a/epublib/src/main/java/me/ag2s/epublib/browsersupport/NavigationEventListener.java b/epublib/src/main/java/me/ag2s/epublib/browsersupport/NavigationEventListener.java new file mode 100644 index 000000000..747d1999d --- /dev/null +++ b/epublib/src/main/java/me/ag2s/epublib/browsersupport/NavigationEventListener.java @@ -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); +} \ No newline at end of file diff --git a/epublib/src/main/java/me/ag2s/epublib/browsersupport/NavigationHistory.java b/epublib/src/main/java/me/ag2s/epublib/browsersupport/NavigationHistory.java new file mode 100644 index 000000000..12595e925 --- /dev/null +++ b/epublib/src/main/java/me/ag2s/epublib/browsersupport/NavigationHistory.java @@ -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 locations = new ArrayList(); + 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(); + 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. + *
+ * 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.
+ * move(1) to go one position forward.
+ * + * @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; + } +} diff --git a/epublib/src/main/java/me/ag2s/epublib/browsersupport/Navigator.java b/epublib/src/main/java/me/ag2s/epublib/browsersupport/Navigator.java new file mode 100644 index 000000000..6ef314d49 --- /dev/null +++ b/epublib/src/main/java/me/ag2s/epublib/browsersupport/Navigator.java @@ -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 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; + } +} diff --git a/epublib/src/main/java/me/ag2s/epublib/browsersupport/package-info.java b/epublib/src/main/java/me/ag2s/epublib/browsersupport/package-info.java new file mode 100644 index 000000000..1ca74f1e4 --- /dev/null +++ b/epublib/src/main/java/me/ag2s/epublib/browsersupport/package-info.java @@ -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; diff --git a/epublib/src/main/java/me/ag2s/epublib/domain/Author.java b/epublib/src/main/java/me/ag2s/epublib/domain/Author.java new file mode 100644 index 000000000..dff314135 --- /dev/null +++ b/epublib/src/main/java/me/ag2s/epublib/domain/Author.java @@ -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; + } +} diff --git a/epublib/src/main/java/me/ag2s/epublib/domain/Book.java b/epublib/src/main/java/me/ag2s/epublib/domain/Book.java new file mode 100644 index 000000000..405190a46 --- /dev/null +++ b/epublib/src/main/java/me/ag2s/epublib/domain/Book.java @@ -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.
+ * A Book as 3 indexes into these Resources, as per the epub specification.
+ *
+ *
Spine
+ *
these are the Resources to be shown when a user reads the book from + * start to finish.
+ *
Table of Contents
+ *
The table of contents. Table of Contents references may be in a + * different order and contain different Resources than the spine, and often do. + *
Guide
+ *
The Guide has references to a set of special Resources like the + * cover page, the Glossary, the copyright page, etc. + *
+ *

+ * 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. + *

+ * + * 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. + *

+ * Consists of a list of "reachable" resources: + *

    + *
  • The coverpage
  • + *
  • The resources of the Spine that are not already in the result
  • + *
  • The resources of the Table of Contents that are not already in the + * result
  • + *
  • The resources of the Guide that are not already in the result
  • + *
+ * 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 getContents() { + Map result = new LinkedHashMap(); + 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(result.values()); + } + + private static void addToContentsResult(Resource resource, + Map 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; + } +} + diff --git a/epublib/src/main/java/me/ag2s/epublib/domain/Date.java b/epublib/src/main/java/me/ag2s/epublib/domain/Date.java new file mode 100644 index 000000000..419dc4701 --- /dev/null +++ b/epublib/src/main/java/me/ag2s/epublib/domain/Date.java @@ -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; + } +} + diff --git a/epublib/src/main/java/me/ag2s/epublib/domain/EpubResourceProvider.java b/epublib/src/main/java/me/ag2s/epublib/domain/EpubResourceProvider.java new file mode 100644 index 000000000..5b38e39f6 --- /dev/null +++ b/epublib/src/main/java/me/ag2s/epublib/domain/EpubResourceProvider.java @@ -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); + } +} diff --git a/epublib/src/main/java/me/ag2s/epublib/domain/Guide.java b/epublib/src/main/java/me/ag2s/epublib/domain/Guide.java new file mode 100644 index 000000000..a6fab3879 --- /dev/null +++ b/epublib/src/main/java/me/ag2s/epublib/domain/Guide.java @@ -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 references = new ArrayList(); + private static final int COVERPAGE_NOT_FOUND = -1; + private static final int COVERPAGE_UNITIALIZED = -2; + + private int coverPageIndex = -1; + + public List getReferences() { + return references; + } + + public void setReferences(List 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 getGuideReferencesByType( + String referenceTypeName) { + List result = new ArrayList(); + for (GuideReference guideReference : references) { + if (referenceTypeName.equalsIgnoreCase(guideReference.getType())) { + result.add(guideReference); + } + } + return result; + } +} diff --git a/epublib/src/main/java/me/ag2s/epublib/domain/GuideReference.java b/epublib/src/main/java/me/ag2s/epublib/domain/GuideReference.java new file mode 100644 index 000000000..2f009825d --- /dev/null +++ b/epublib/src/main/java/me/ag2s/epublib/domain/GuideReference.java @@ -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; + } +} diff --git a/epublib/src/main/java/me/ag2s/epublib/domain/Identifier.java b/epublib/src/main/java/me/ag2s/epublib/domain/Identifier.java new file mode 100644 index 000000000..626425655 --- /dev/null +++ b/epublib/src/main/java/me/ag2s/epublib/domain/Identifier.java @@ -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 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; + } +} diff --git a/epublib/src/main/java/me/ag2s/epublib/domain/LazyResource.java b/epublib/src/main/java/me/ag2s/epublib/domain/LazyResource.java new file mode 100644 index 000000000..d7629ee04 --- /dev/null +++ b/epublib/src/main/java/me/ag2s/epublib/domain/LazyResource.java @@ -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; + } +} diff --git a/epublib/src/main/java/me/ag2s/epublib/domain/LazyResourceProvider.java b/epublib/src/main/java/me/ag2s/epublib/domain/LazyResourceProvider.java new file mode 100644 index 000000000..09b60e596 --- /dev/null +++ b/epublib/src/main/java/me/ag2s/epublib/domain/LazyResourceProvider.java @@ -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; +} diff --git a/epublib/src/main/java/me/ag2s/epublib/domain/ManifestItemProperties.java b/epublib/src/main/java/me/ag2s/epublib/domain/ManifestItemProperties.java new file mode 100644 index 000000000..4d5e6151a --- /dev/null +++ b/epublib/src/main/java/me/ag2s/epublib/domain/ManifestItemProperties.java @@ -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; + } +} diff --git a/epublib/src/main/java/me/ag2s/epublib/domain/ManifestItemRefProperties.java b/epublib/src/main/java/me/ag2s/epublib/domain/ManifestItemRefProperties.java new file mode 100644 index 000000000..228cd71fd --- /dev/null +++ b/epublib/src/main/java/me/ag2s/epublib/domain/ManifestItemRefProperties.java @@ -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; + } +} diff --git a/epublib/src/main/java/me/ag2s/epublib/domain/ManifestProperties.java b/epublib/src/main/java/me/ag2s/epublib/domain/ManifestProperties.java new file mode 100644 index 000000000..26892d7b0 --- /dev/null +++ b/epublib/src/main/java/me/ag2s/epublib/domain/ManifestProperties.java @@ -0,0 +1,6 @@ +package me.ag2s.epublib.domain; + +public interface ManifestProperties { + + String getName(); +} diff --git a/epublib/src/main/java/me/ag2s/epublib/domain/MediaType.java b/epublib/src/main/java/me/ag2s/epublib/domain/MediaType.java new file mode 100644 index 000000000..1e6163d6d --- /dev/null +++ b/epublib/src/main/java/me/ag2s/epublib/domain/MediaType.java @@ -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 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 extensions) { + super(); + this.name = name; + this.defaultExtension = defaultExtension; + this.extensions = extensions; + } + + public String getName() { + return name; + } + + + public String getDefaultExtension() { + return defaultExtension; + } + + + public Collection 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; + } +} diff --git a/epublib/src/main/java/me/ag2s/epublib/domain/MediaTypes.java b/epublib/src/main/java/me/ag2s/epublib/domain/MediaTypes.java new file mode 100644 index 000000000..d221d5315 --- /dev/null +++ b/epublib/src/main/java/me/ag2s/epublib/domain/MediaTypes.java @@ -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 mediaTypesByName = new HashMap(); + + 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); + } +} diff --git a/epublib/src/main/java/me/ag2s/epublib/domain/Metadata.java b/epublib/src/main/java/me/ag2s/epublib/domain/Metadata.java new file mode 100644 index 000000000..e2d1168ec --- /dev/null +++ b/epublib/src/main/java/me/ag2s/epublib/domain/Metadata.java @@ -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 authors = new ArrayList(); + private List contributors = new ArrayList(); + private List dates = new ArrayList(); + private String language = DEFAULT_LANGUAGE; + private Map otherProperties = new HashMap(); + private List rights = new ArrayList(); + private List titles = new ArrayList(); + private List identifiers = new ArrayList(); + private List subjects = new ArrayList(); + private String format = MediaTypes.EPUB.getName(); + private List types = new ArrayList(); + private List descriptions = new ArrayList(); + private List publishers = new ArrayList(); + private Map metaAttributes = new HashMap(); + + 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 getOtherProperties() { + return otherProperties; + } + + public void setOtherProperties(Map otherProperties) { + this.otherProperties = otherProperties; + } + + public Date addDate(Date date) { + this.dates.add(date); + return date; + } + + public List getDates() { + return dates; + } + + public void setDates(List dates) { + this.dates = dates; + } + + public Author addAuthor(Author author) { + authors.add(author); + return author; + } + + public List getAuthors() { + return authors; + } + + public void setAuthors(List authors) { + this.authors = authors; + } + + public Author addContributor(Author contributor) { + contributors.add(contributor); + return contributor; + } + + public List getContributors() { + return contributors; + } + + public void setContributors(List contributors) { + this.contributors = contributors; + } + + public String getLanguage() { + return language; + } + + public void setLanguage(String language) { + this.language = language; + } + + public List getSubjects() { + return subjects; + } + + public void setSubjects(List subjects) { + this.subjects = subjects; + } + + public void setRights(List rights) { + this.rights = rights; + } + + public List 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 titles) { + this.titles = titles; + } + + public List getTitles() { + return titles; + } + + public String addPublisher(String publisher) { + this.publishers.add(publisher); + return publisher; + } + + public void setPublishers(List publishers) { + this.publishers = publishers; + } + + public List getPublishers() { + return publishers; + } + + public String addDescription(String description) { + this.descriptions.add(description); + return description; + } + + public void setDescriptions(List descriptions) { + this.descriptions = descriptions; + } + + public List 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 identifiers) { + this.identifiers = identifiers; + autoGeneratedId = false; + } + + public List 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 getTypes() { + return types; + } + + public void setTypes(List types) { + this.types = types; + } + + public String getMetaAttribute(String name) { + return metaAttributes.get(name); + } + + public void setMetaAttributes(Map metaAttributes) { + this.metaAttributes = metaAttributes; + } +} diff --git a/epublib/src/main/java/me/ag2s/epublib/domain/Relator.java b/epublib/src/main/java/me/ag2s/epublib/domain/Relator.java new file mode 100644 index 000000000..b9637588d --- /dev/null +++ b/epublib/src/main/java/me/ag2s/epublib/domain/Relator.java @@ -0,0 +1,1144 @@ +package me.ag2s.epublib.domain; + + +/** + * A relator denotes which role a certain individual had in the creation/modification of the ebook. + * + * Examples are 'creator', 'blurb writer', etc. + * + * This is contains the complete Library of Concress relator list. + * + * @see MARC Code List for Relators + * + * @author paul + */ +public enum Relator { + + /** + * Use for a person or organization who principally exhibits acting skills in a musical or dramatic presentation or entertainment. + */ + ACTOR("act", "Actor"), + + /** + * Use for a person or organization who 1) reworks a musical composition, usually for a different medium, or 2) rewrites novels or stories for motion pictures or other audiovisual medium. + */ + ADAPTER("adp", "Adapter"), + + /** + * Use for a person or organization that reviews, examines and interprets data or information in a specific area. + */ + ANALYST("anl", "Analyst"), + + /** + * Use for a person or organization who draws the two-dimensional figures, manipulates the three dimensional objects and/or also programs the computer to move objects and images for the purpose of animated film processing. Animation cameras, stands, celluloid screens, transparencies and inks are some of the tools of the animator. + */ + ANIMATOR("anm", "Animator"), + + /** + * Use for a person who writes manuscript annotations on a printed item. + */ + ANNOTATOR("ann", "Annotator"), + + /** + * Use for a person or organization responsible for the submission of an application or who is named as eligible for the results of the processing of the application (e.g., bestowing of rights, reward, title, position). + */ + APPLICANT("app", "Applicant"), + + /** + * Use for a person or organization who designs structures or oversees their construction. + */ + ARCHITECT("arc", "Architect"), + + /** + * Use for a person or organization who transcribes a musical composition, usually for a different medium from that of the original; in an arrangement the musical substance remains essentially unchanged. + */ + ARRANGER("arr", "Arranger"), + + /** + * Use for a person (e.g., a painter or sculptor) who makes copies of works of visual art. + */ + ART_COPYIST("acp", "Art copyist"), + + /** + * Use for a person (e.g., a painter) or organization who conceives, and perhaps also implements, an original graphic design or work of art, if specific codes (e.g., [egr], [etr]) are not desired. For book illustrators, prefer Illustrator [ill]. + */ + ARTIST("art", "Artist"), + + /** + * Use for a person responsible for controlling the development of the artistic style of an entire production, including the choice of works to be presented and selection of senior production staff. + */ + ARTISTIC_DIRECTOR("ard", "Artistic director"), + + /** + * Use for a person or organization to whom a license for printing or publishing has been transferred. + */ + ASSIGNEE("asg", "Assignee"), + + /** + * Use for a person or organization associated with or found in an item or collection, which cannot be determined to be that of a Former owner [fmo] or other designated relator indicative of provenance. + */ + ASSOCIATED_NAME("asn", "Associated name"), + + /** + * Use for an author, artist, etc., relating him/her to a work for which there is or once was substantial authority for designating that person as author, creator, etc. of the work. + */ + ATTRIBUTED_NAME("att", "Attributed name"), + + /** + * Use for a person or organization in charge of the estimation and public auctioning of goods, particularly books, artistic works, etc. + */ + AUCTIONEER("auc", "Auctioneer"), + + /** + * Use for a person or organization chiefly responsible for the intellectual or artistic content of a work, usually printed text. This term may also be used when more than one person or body bears such responsibility. + */ + AUTHOR("aut", "Author"), + + /** + * Use for a person or organization whose work is largely quoted or extracted in works to which he or she did not contribute directly. Such quotations are found particularly in exhibition catalogs, collections of photographs, etc. + */ + AUTHOR_IN_QUOTATIONS_OR_TEXT_EXTRACTS("aqt", + "Author in quotations or text extracts"), + + /** + * Use for a person or organization responsible for an afterword, postface, colophon, etc. but who is not the chief author of a work. + */ + AUTHOR_OF_AFTERWORD_COLOPHON_ETC("aft", + "Author of afterword, colophon, etc."), + + /** + * Use for a person or organization responsible for the dialog or spoken commentary for a screenplay or sound recording. + */ + AUTHOR_OF_DIALOG("aud", "Author of dialog"), + + /** + * Use for a person or organization responsible for an introduction, preface, foreword, or other critical introductory matter, but who is not the chief author. + */ + AUTHOR_OF_INTRODUCTION_ETC("aui", "Author of introduction, etc."), + + /** + * Use for a person or organization responsible for a motion picture screenplay, dialog, spoken commentary, etc. + */ + AUTHOR_OF_SCREENPLAY_ETC("aus", "Author of screenplay, etc."), + + /** + * Use for a person or organization responsible for a work upon which the work represented by the catalog record is based. This may be appropriate for adaptations, sequels, continuations, indexes, etc. + */ + BIBLIOGRAPHIC_ANTECEDENT("ant", "Bibliographic antecedent"), + + /** + * Use for a person or organization responsible for the binding of printed or manuscript materials. + */ + BINDER("bnd", "Binder"), + + /** + * Use for a person or organization responsible for the binding design of a book, including the type of binding, the type of materials used, and any decorative aspects of the binding. + */ + BINDING_DESIGNER("bdd", "Binding designer"), + + /** + * Use for the named entity responsible for writing a commendation or testimonial for a work, which appears on or within the publication itself, frequently on the back or dust jacket of print publications or on advertising material for all media. + */ + BLURB_WRITER("blw", "Blurb writer"), + + /** + * Use for a person or organization responsible for the entire graphic design of a book, including arrangement of type and illustration, choice of materials, and process used. + */ + BOOK_DESIGNER("bkd", "Book designer"), + + /** + * Use for a person or organization responsible for the production of books and other print media, if specific codes (e.g., [bkd], [egr], [tyd], [prt]) are not desired. + */ + BOOK_PRODUCER("bkp", "Book producer"), + + /** + * Use for a person or organization responsible for the design of flexible covers designed for or published with a book, including the type of materials used, and any decorative aspects of the bookjacket. + */ + BOOKJACKET_DESIGNER("bjd", "Bookjacket designer"), + + /** + * Use for a person or organization responsible for the design of a book owner's identification label that is most commonly pasted to the inside front cover of a book. + */ + BOOKPLATE_DESIGNER("bpd", "Bookplate designer"), + + /** + * Use for a person or organization who makes books and other bibliographic materials available for purchase. Interest in the materials is primarily lucrative. + */ + BOOKSELLER("bsl", "Bookseller"), + + /** + * Use for a person or organization who writes in an artistic hand, usually as a copyist and or engrosser. + */ + CALLIGRAPHER("cll", "Calligrapher"), + + /** + * Use for a person or organization responsible for the creation of maps and other cartographic materials. + */ + CARTOGRAPHER("ctg", "Cartographer"), + + /** + * Use for a censor, bowdlerizer, expurgator, etc., official or private. + */ + CENSOR("cns", "Censor"), + + /** + * Use for a person or organization who composes or arranges dances or other movements (e.g., "master of swords") for a musical or dramatic presentation or entertainment. + */ + CHOREOGRAPHER("chr", "Choreographer"), + + /** + * Use for a person or organization who is in charge of the images captured for a motion picture film. The cinematographer works under the supervision of a director, and may also be referred to as director of photography. Do not confuse with videographer. + */ + CINEMATOGRAPHER("cng", "Cinematographer"), + + /** + * Use for a person or organization for whom another person or organization is acting. + */ + CLIENT("cli", "Client"), + + /** + * Use for a person or organization that takes a limited part in the elaboration of a work of another person or organization that brings complements (e.g., appendices, notes) to the work. + */ + COLLABORATOR("clb", "Collaborator"), + + /** + * Use for a person or organization who has brought together material from various sources that has been arranged, described, and cataloged as a collection. A collector is neither the creator of the material nor a person to whom manuscripts in the collection may have been addressed. + */ + COLLECTOR("col", "Collector"), + + /** + * Use for a person or organization responsible for the production of photographic prints from film or other colloid that has ink-receptive and ink-repellent surfaces. + */ + COLLOTYPER("clt", "Collotyper"), + + /** + * Use for the named entity responsible for applying color to drawings, prints, photographs, maps, moving images, etc. + */ + COLORIST("clr", "Colorist"), + + /** + * Use for a person or organization who provides interpretation, analysis, or a discussion of the subject matter on a recording, motion picture, or other audiovisual medium. + */ + COMMENTATOR("cmm", "Commentator"), + + /** + * Use for a person or organization responsible for the commentary or explanatory notes about a text. For the writer of manuscript annotations in a printed book, use Annotator [ann]. + */ + COMMENTATOR_FOR_WRITTEN_TEXT("cwt", "Commentator for written text"), + + /** + * Use for a person or organization who produces a work or publication by selecting and putting together material from the works of various persons or bodies. + */ + COMPILER("com", "Compiler"), + + /** + * Use for the party who applies to the courts for redress, usually in an equity proceeding. + */ + COMPLAINANT("cpl", "Complainant"), + + /** + * Use for a complainant who takes an appeal from one court or jurisdiction to another to reverse the judgment, usually in an equity proceeding. + */ + COMPLAINANT_APPELLANT("cpt", "Complainant-appellant"), + + /** + * Use for a complainant against whom an appeal is taken from one court or jurisdiction to another to reverse the judgment, usually in an equity proceeding. + */ + COMPLAINANT_APPELLEE("cpe", "Complainant-appellee"), + + /** + * Use for a person or organization who creates a musical work, usually a piece of music in manuscript or printed form. + */ + COMPOSER("cmp", "Composer"), + + /** + * Use for a person or organization responsible for the creation of metal slug, or molds made of other materials, used to produce the text and images in printed matter. + */ + COMPOSITOR("cmt", "Compositor"), + + /** + * Use for a person or organization responsible for the original idea on which a work is based, this includes the scientific author of an audio-visual item and the conceptor of an advertisement. + */ + CONCEPTOR("ccp", "Conceptor"), + + /** + * Use for a person who directs a performing group (orchestra, chorus, opera, etc.) in a musical or dramatic presentation or entertainment. + */ + CONDUCTOR("cnd", "Conductor"), + + /** + * Use for the named entity responsible for documenting, preserving, or treating printed or manuscript material, works of art, artifacts, or other media. + */ + CONSERVATOR("con", "Conservator"), + + /** + * Use for a person or organization relevant to a resource, who is called upon for professional advice or services in a specialized field of knowledge or training. + */ + CONSULTANT("csl", "Consultant"), + + /** + * Use for a person or organization relevant to a resource, who is engaged specifically to provide an intellectual overview of a strategic or operational task and by analysis, specification, or instruction, to create or propose a cost-effective course of action or solution. + */ + CONSULTANT_TO_A_PROJECT("csp", "Consultant to a project"), + + /** + * Use for the party who opposes, resists, or disputes, in a court of law, a claim, decision, result, etc. + */ + CONTESTANT("cos", "Contestant"), + + /** + * Use for a contestant who takes an appeal from one court of law or jurisdiction to another to reverse the judgment. + */ + CONTESTANT_APPELLANT("cot", "Contestant-appellant"), + + /** + * Use for a contestant against whom an appeal is taken from one court of law or jurisdiction to another to reverse the judgment. + */ + CONTESTANT_APPELLEE("coe", "Contestant-appellee"), + + /** + * Use for the party defending a claim, decision, result, etc. being opposed, resisted, or disputed in a court of law. + */ + CONTESTEE("cts", "Contestee"), + + /** + * Use for a contestee who takes an appeal from one court or jurisdiction to another to reverse the judgment. + */ + CONTESTEE_APPELLANT("ctt", "Contestee-appellant"), + + /** + * Use for a contestee against whom an appeal is taken from one court or jurisdiction to another to reverse the judgment. + */ + CONTESTEE_APPELLEE("cte", "Contestee-appellee"), + + /** + * Use for a person or organization relevant to a resource, who enters into a contract with another person or organization to perform a specific task. + */ + CONTRACTOR("ctr", "Contractor"), + + /** + * Use for a person or organization one whose work has been contributed to a larger work, such as an anthology, serial publication, or other compilation of individual works. Do not use if the sole function in relation to a work is as author, editor, compiler or translator. + */ + CONTRIBUTOR("ctb", "Contributor"), + + /** + * Use for a person or organization listed as a copyright owner at the time of registration. Copyright can be granted or later transferred to another person or organization, at which time the claimant becomes the copyright holder. + */ + COPYRIGHT_CLAIMANT("cpc", "Copyright claimant"), + + /** + * Use for a person or organization to whom copy and legal rights have been granted or transferred for the intellectual content of a work. The copyright holder, although not necessarily the creator of the work, usually has the exclusive right to benefit financially from the sale and use of the work to which the associated copyright protection applies. + */ + COPYRIGHT_HOLDER("cph", "Copyright holder"), + + /** + * Use for a person or organization who is a corrector of manuscripts, such as the scriptorium official who corrected the work of a scribe. For printed matter, use Proofreader. + */ + CORRECTOR("crr", "Corrector"), + + /** + * Use for a person or organization who was either the writer or recipient of a letter or other communication. + */ + CORRESPONDENT("crp", "Correspondent"), + + /** + * Use for a person or organization who designs or makes costumes, fixes hair, etc., for a musical or dramatic presentation or entertainment. + */ + COSTUME_DESIGNER("cst", "Costume designer"), + + /** + * Use for a person or organization responsible for the graphic design of a book cover, album cover, slipcase, box, container, etc. For a person or organization responsible for the graphic design of an entire book, use Book designer; for book jackets, use Bookjacket designer. + */ + COVER_DESIGNER("cov", "Cover designer"), + + /** + * Use for a person or organization responsible for the intellectual or artistic content of a work. + */ + CREATOR("cre", "Creator"), + + /** + * Use for a person or organization responsible for conceiving and organizing an exhibition. + */ + CURATOR_OF_AN_EXHIBITION("cur", "Curator of an exhibition"), + + /** + * Use for a person or organization who principally exhibits dancing skills in a musical or dramatic presentation or entertainment. + */ + DANCER("dnc", "Dancer"), + + /** + * Use for a person or organization that submits data for inclusion in a database or other collection of data. + */ + DATA_CONTRIBUTOR("dtc", "Data contributor"), + + /** + * Use for a person or organization responsible for managing databases or other data sources. + */ + DATA_MANAGER("dtm", "Data manager"), + + /** + * Use for a person or organization to whom a book, manuscript, etc., is dedicated (not the recipient of a gift). + */ + DEDICATEE("dte", "Dedicatee"), + + /** + * Use for the author of a dedication, which may be a formal statement or in epistolary or verse form. + */ + DEDICATOR("dto", "Dedicator"), + + /** + * Use for the party defending or denying allegations made in a suit and against whom relief or recovery is sought in the courts, usually in a legal action. + */ + DEFENDANT("dfd", "Defendant"), + + /** + * Use for a defendant who takes an appeal from one court or jurisdiction to another to reverse the judgment, usually in a legal action. + */ + DEFENDANT_APPELLANT("dft", "Defendant-appellant"), + + /** + * Use for a defendant against whom an appeal is taken from one court or jurisdiction to another to reverse the judgment, usually in a legal action. + */ + DEFENDANT_APPELLEE("dfe", "Defendant-appellee"), + + /** + * Use for the organization granting a degree for which the thesis or dissertation described was presented. + */ + DEGREE_GRANTOR("dgg", "Degree grantor"), + + /** + * Use for a person or organization executing technical drawings from others' designs. + */ + DELINEATOR("dln", "Delineator"), + + /** + * Use for an entity depicted or portrayed in a work, particularly in a work of art. + */ + DEPICTED("dpc", "Depicted"), + + /** + * Use for a person or organization placing material in the physical custody of a library or repository without transferring the legal title. + */ + DEPOSITOR("dpt", "Depositor"), + + /** + * Use for a person or organization responsible for the design if more specific codes (e.g., [bkd], [tyd]) are not desired. + */ + DESIGNER("dsr", "Designer"), + + /** + * Use for a person or organization who is responsible for the general management of a work or who supervises the production of a performance for stage, screen, or sound recording. + */ + DIRECTOR("drt", "Director"), + + /** + * Use for a person who presents a thesis for a university or higher-level educational degree. + */ + DISSERTANT("dis", "Dissertant"), + + /** + * Use for the name of a place from which a resource, e.g., a serial, is distributed. + */ + DISTRIBUTION_PLACE("dbp", "Distribution place"), + + /** + * Use for a person or organization that has exclusive or shared marketing rights for an item. + */ + DISTRIBUTOR("dst", "Distributor"), + + /** + * Use for a person or organization who is the donor of a book, manuscript, etc., to its present owner. Donors to previous owners are designated as Former owner [fmo] or Inscriber [ins]. + */ + DONOR("dnr", "Donor"), + + /** + * Use for a person or organization who prepares artistic or technical drawings. + */ + DRAFTSMAN("drm", "Draftsman"), + + /** + * Use for a person or organization to which authorship has been dubiously or incorrectly ascribed. + */ + DUBIOUS_AUTHOR("dub", "Dubious author"), + + /** + * Use for a person or organization who prepares for publication a work not primarily his/her own, such as by elucidating text, adding introductory or other critical matter, or technically directing an editorial staff. + */ + EDITOR("edt", "Editor"), + + /** + * Use for a person responsible for setting up a lighting rig and focusing the lights for a production, and running the lighting at a performance. + */ + ELECTRICIAN("elg", "Electrician"), + + /** + * Use for a person or organization who creates a duplicate printing surface by pressure molding and electrodepositing of metal that is then backed up with lead for printing. + */ + ELECTROTYPER("elt", "Electrotyper"), + + /** + * Use for a person or organization that is responsible for technical planning and design, particularly with construction. + */ + ENGINEER("eng", "Engineer"), + + /** + * Use for a person or organization who cuts letters, figures, etc. on a surface, such as a wooden or metal plate, for printing. + */ + ENGRAVER("egr", "Engraver"), + + /** + * Use for a person or organization who produces text or images for printing by subjecting metal, glass, or some other surface to acid or the corrosive action of some other substance. + */ + ETCHER("etr", "Etcher"), + + /** + * Use for the name of the place where an event such as a conference or a concert took place. + */ + EVENT_PLACE("evp", "Event place"), + + /** + * Use for a person or organization in charge of the description and appraisal of the value of goods, particularly rare items, works of art, etc. + */ + EXPERT("exp", "Expert"), + + /** + * Use for a person or organization that executed the facsimile. + */ + FACSIMILIST("fac", "Facsimilist"), + + /** + * Use for a person or organization that manages or supervises the work done to collect raw data or do research in an actual setting or environment (typically applies to the natural and social sciences). + */ + FIELD_DIRECTOR("fld", "Field director"), + + /** + * Use for a person or organization who is an editor of a motion picture film. This term is used regardless of the medium upon which the motion picture is produced or manufactured (e.g., acetate film, video tape). + */ + FILM_EDITOR("flm", "Film editor"), + + /** + * Use for a person or organization who is identified as the only party or the party of the first part. In the case of transfer of right, this is the assignor, transferor, licensor, grantor, etc. Multiple parties can be named jointly as the first party + */ + FIRST_PARTY("fpy", "First party"), + + /** + * Use for a person or organization who makes or imitates something of value or importance, especially with the intent to defraud. + */ + FORGER("frg", "Forger"), + + /** + * Use for a person or organization who owned an item at any time in the past. Includes those to whom the material was once presented. A person or organization giving the item to the present owner is designated as Donor [dnr] + */ + FORMER_OWNER("fmo", "Former owner"), + + /** + * Use for a person or organization that furnished financial support for the production of the work. + */ + FUNDER("fnd", "Funder"), + + /** + * Use for a person responsible for geographic information system (GIS) development and integration with global positioning system data. + */ + GEOGRAPHIC_INFORMATION_SPECIALIST("gis", "Geographic information specialist"), + + /** + * Use for a person or organization in memory or honor of whom a book, manuscript, etc. is donated. + */ + HONOREE("hnr", "Honoree"), + + /** + * Use for a person who is invited or regularly leads a program (often broadcast) that includes other guests, performers, etc. (e.g., talk show host). + */ + HOST("hst", "Host"), + + /** + * Use for a person or organization responsible for the decoration of a work (especially manuscript material) with precious metals or color, usually with elaborate designs and motifs. + */ + ILLUMINATOR("ilu", "Illuminator"), + + /** + * Use for a person or organization who conceives, and perhaps also implements, a design or illustration, usually to accompany a written text. + */ + ILLUSTRATOR("ill", "Illustrator"), + + /** + * Use for a person who signs a presentation statement. + */ + INSCRIBER("ins", "Inscriber"), + + /** + * Use for a person or organization who principally plays an instrument in a musical or dramatic presentation or entertainment. + */ + INSTRUMENTALIST("itr", "Instrumentalist"), + + /** + * Use for a person or organization who is interviewed at a consultation or meeting, usually by a reporter, pollster, or some other information gathering agent. + */ + INTERVIEWEE("ive", "Interviewee"), + + /** + * Use for a person or organization who acts as a reporter, pollster, or other information gathering agent in a consultation or meeting involving one or more individuals. + */ + INTERVIEWER("ivr", "Interviewer"), + + /** + * Use for a person or organization who first produces a particular useful item, or develops a new process for obtaining a known item or result. + */ + INVENTOR("inv", "Inventor"), + + /** + * Use for an institution that provides scientific analyses of material samples. + */ + LABORATORY("lbr", "Laboratory"), + + /** + * Use for a person or organization that manages or supervises work done in a controlled setting or environment. + */ + LABORATORY_DIRECTOR("ldr", "Laboratory director"), + + /** + * Use for a person or organization whose work involves coordinating the arrangement of existing and proposed land features and structures. + */ + LANDSCAPE_ARCHITECT("lsa", "Landscape architect"), + + /** + * Use to indicate that a person or organization takes primary responsibility for a particular activity or endeavor. Use with another relator term or code to show the greater importance this person or organization has regarding that particular role. If more than one relator is assigned to a heading, use the Lead relator only if it applies to all the relators. + */ + LEAD("led", "Lead"), + + /** + * Use for a person or organization permitting the temporary use of a book, manuscript, etc., such as for photocopying or microfilming. + */ + LENDER("len", "Lender"), + + /** + * Use for the party who files a libel in an ecclesiastical or admiralty case. + */ + LIBELANT("lil", "Libelant"), + + /** + * Use for a libelant who takes an appeal from one ecclesiastical court or admiralty to another to reverse the judgment. + */ + LIBELANT_APPELLANT("lit", "Libelant-appellant"), + + /** + * Use for a libelant against whom an appeal is taken from one ecclesiastical court or admiralty to another to reverse the judgment. + */ + LIBELANT_APPELLEE("lie", "Libelant-appellee"), + + /** + * Use for a party against whom a libel has been filed in an ecclesiastical court or admiralty. + */ + LIBELEE("lel", "Libelee"), + + /** + * Use for a libelee who takes an appeal from one ecclesiastical court or admiralty to another to reverse the judgment. + */ + LIBELEE_APPELLANT("let", "Libelee-appellant"), + + /** + * Use for a libelee against whom an appeal is taken from one ecclesiastical court or admiralty to another to reverse the judgment. + */ + LIBELEE_APPELLEE("lee", "Libelee-appellee"), + + /** + * Use for a person or organization who is a writer of the text of an opera, oratorio, etc. + */ + LIBRETTIST("lbt", "Librettist"), + + /** + * Use for a person or organization who is an original recipient of the right to print or publish. + */ + LICENSEE("lse", "Licensee"), + + /** + * Use for person or organization who is a signer of the license, imprimatur, etc. + */ + LICENSOR("lso", "Licensor"), + + /** + * Use for a person or organization who designs the lighting scheme for a theatrical presentation, entertainment, motion picture, etc. + */ + LIGHTING_DESIGNER("lgd", "Lighting designer"), + + /** + * Use for a person or organization who prepares the stone or plate for lithographic printing, including a graphic artist creating a design directly on the surface from which printing will be done. + */ + LITHOGRAPHER("ltg", "Lithographer"), + + /** + * Use for a person or organization who is a writer of the text of a song. + */ + LYRICIST("lyr", "Lyricist"), + + /** + * Use for a person or organization that makes an artifactual work (an object made or modified by one or more persons). Examples of artifactual works include vases, cannons or pieces of furniture. + */ + MANUFACTURER("mfr", "Manufacturer"), + + /** + * Use for the named entity responsible for marbling paper, cloth, leather, etc. used in construction of a resource. + */ + MARBLER("mrb", "Marbler"), + + /** + * Use for a person or organization performing the coding of SGML, HTML, or XML markup of metadata, text, etc. + */ + MARKUP_EDITOR("mrk", "Markup editor"), + + /** + * Use for a person or organization primarily responsible for compiling and maintaining the original description of a metadata set (e.g., geospatial metadata set). + */ + METADATA_CONTACT("mdc", "Metadata contact"), + + /** + * Use for a person or organization responsible for decorations, illustrations, letters, etc. cut on a metal surface for printing or decoration. + */ + METAL_ENGRAVER("mte", "Metal-engraver"), + + /** + * Use for a person who leads a program (often broadcast) where topics are discussed, usually with participation of experts in fields related to the discussion. + */ + MODERATOR("mod", "Moderator"), + + /** + * Use for a person or organization that supervises compliance with the contract and is responsible for the report and controls its distribution. Sometimes referred to as the grantee, or controlling agency. + */ + MONITOR("mon", "Monitor"), + + /** + * Use for a person who transcribes or copies musical notation + */ + MUSIC_COPYIST("mcp", "Music copyist"), + + /** + * Use for a person responsible for basic music decisions about a production, including coordinating the work of the composer, the sound editor, and sound mixers, selecting musicians, and organizing and/or conducting sound for rehearsals and performances. + */ + MUSICAL_DIRECTOR("msd", "Musical director"), + + /** + * Use for a person or organization who performs music or contributes to the musical content of a work when it is not possible or desirable to identify the function more precisely. + */ + MUSICIAN("mus", "Musician"), + + /** + * Use for a person who is a speaker relating the particulars of an act, occurrence, or course of events. + */ + NARRATOR("nrt", "Narrator"), + + /** + * Use for a person or organization responsible for opposing a thesis or dissertation. + */ + OPPONENT("opn", "Opponent"), + + /** + * Use for a person or organization responsible for organizing a meeting for which an item is the report or proceedings. + */ + ORGANIZER_OF_MEETING("orm", "Organizer of meeting"), + + /** + * Use for a person or organization performing the work, i.e., the name of a person or organization associated with the intellectual content of the work. This category does not include the publisher or personal affiliation, or sponsor except where it is also the corporate author. + */ + ORIGINATOR("org", "Originator"), + + /** + * Use for relator codes from other lists which have no equivalent in the MARC list or for terms which have not been assigned a code. + */ + OTHER("oth", "Other"), + + /** + * Use for a person or organization that currently owns an item or collection. + */ + OWNER("own", "Owner"), + + /** + * Use for a person or organization responsible for the production of paper, usually from wood, cloth, or other fibrous material. + */ + PAPERMAKER("ppm", "Papermaker"), + + /** + * Use for a person or organization that applied for a patent. + */ + PATENT_APPLICANT("pta", "Patent applicant"), + + /** + * Use for a person or organization that was granted the patent referred to by the item. + */ + PATENT_HOLDER("pth", "Patent holder"), + + /** + * Use for a person or organization responsible for commissioning a work. Usually a patron uses his or her means or influence to support the work of artists, writers, etc. This includes those who commission and pay for individual works. + */ + PATRON("pat", "Patron"), + + /** + * Use for a person or organization who exhibits musical or acting skills in a musical or dramatic presentation or entertainment, if specific codes for those functions ([act], [dnc], [itr], [voc], etc.) are not used. If specific codes are used, [prf] is used for a person whose principal skill is not known or specified. + */ + PERFORMER("prf", "Performer"), + + /** + * Use for an authority (usually a government agency) that issues permits under which work is accomplished. + */ + PERMITTING_AGENCY("pma", "Permitting agency"), + + /** + * Use for a person or organization responsible for taking photographs, whether they are used in their original form or as reproductions. + */ + PHOTOGRAPHER("pht", "Photographer"), + + /** + * Use for the party who complains or sues in court in a personal action, usually in a legal proceeding. + */ + PLAINTIFF("ptf", "Plaintiff"), + + /** + * Use for a plaintiff who takes an appeal from one court or jurisdiction to another to reverse the judgment, usually in a legal proceeding. + */ + PLAINTIFF_APPELLANT("ptt", "Plaintiff-appellant"), + + /** + * Use for a plaintiff against whom an appeal is taken from one court or jurisdiction to another to reverse the judgment, usually in a legal proceeding. + */ + PLAINTIFF_APPELLEE("pte", "Plaintiff-appellee"), + + /** + * Use for a person or organization responsible for the production of plates, usually for the production of printed images and/or text. + */ + PLATEMAKER("plt", "Platemaker"), + + /** + * Use for a person or organization who prints texts, whether from type or plates. + */ + PRINTER("prt", "Printer"), + + /** + * Use for a person or organization who prints illustrations from plates. + */ + PRINTER_OF_PLATES("pop", "Printer of plates"), + + /** + * Use for a person or organization who makes a relief, intaglio, or planographic printing surface. + */ + PRINTMAKER("prm", "Printmaker"), + + /** + * Use for a person or organization primarily responsible for performing or initiating a process, such as is done with the collection of metadata sets. + */ + PROCESS_CONTACT("prc", "Process contact"), + + /** + * Use for a person or organization responsible for the making of a motion picture, including business aspects, management of the productions, and the commercial success of the work. + */ + PRODUCER("pro", "Producer"), + + /** + * Use for a person responsible for all technical and business matters in a production. + */ + PRODUCTION_MANAGER("pmn", "Production manager"), + + /** + * Use for a person or organization associated with the production (props, lighting, special effects, etc.) of a musical or dramatic presentation or entertainment. + */ + PRODUCTION_PERSONNEL("prd", "Production personnel"), + + /** + * Use for a person or organization responsible for the creation and/or maintenance of computer program design documents, source code, and machine-executable digital files and supporting documentation. + */ + PROGRAMMER("prg", "Programmer"), + + /** + * Use for a person or organization with primary responsibility for all essential aspects of a project, or that manages a very large project that demands senior level responsibility, or that has overall responsibility for managing projects, or provides overall direction to a project manager. + */ + PROJECT_DIRECTOR("pdr", "Project director"), + + /** + * Use for a person who corrects printed matter. For manuscripts, use Corrector [crr]. + */ + PROOFREADER("pfr", "Proofreader"), + + /** + * Use for the name of the place where a resource is published. + */ + PUBLICATION_PLACE("pup", "Publication place"), + + /** + * Use for a person or organization that makes printed matter, often text, but also printed music, artwork, etc. available to the public. + */ + PUBLISHER("pbl", "Publisher"), + + /** + * Use for a person or organization who presides over the elaboration of a collective work to ensure its coherence or continuity. This includes editors-in-chief, literary editors, editors of series, etc. + */ + PUBLISHING_DIRECTOR("pbd", "Publishing director"), + + /** + * Use for a person or organization who manipulates, controls, or directs puppets or marionettes in a musical or dramatic presentation or entertainment. + */ + PUPPETEER("ppt", "Puppeteer"), + + /** + * Use for a person or organization to whom correspondence is addressed. + */ + RECIPIENT("rcp", "Recipient"), + + /** + * Use for a person or organization who supervises the technical aspects of a sound or video recording session. + */ + RECORDING_ENGINEER("rce", "Recording engineer"), + + /** + * Use for a person or organization who writes or develops the framework for an item without being intellectually responsible for its content. + */ + REDACTOR("red", "Redactor"), + + /** + * Use for a person or organization who prepares drawings of architectural designs (i.e., renderings) in accurate, representational perspective to show what the project will look like when completed. + */ + RENDERER("ren", "Renderer"), + + /** + * Use for a person or organization who writes or presents reports of news or current events on air or in print. + */ + REPORTER("rpt", "Reporter"), + + /** + * Use for an agency that hosts data or material culture objects and provides services to promote long term, consistent and shared use of those data or objects. + */ + REPOSITORY("rps", "Repository"), + + /** + * Use for a person who directed or managed a research project. + */ + RESEARCH_TEAM_HEAD("rth", "Research team head"), + + /** + * Use for a person who participated in a research project but whose role did not involve direction or management of it. + */ + RESEARCH_TEAM_MEMBER("rtm", "Research team member"), + + /** + * Use for a person or organization responsible for performing research. + */ + RESEARCHER("res", "Researcher"), + + /** + * Use for the party who makes an answer to the courts pursuant to an application for redress, usually in an equity proceeding. + */ + RESPONDENT("rsp", "Respondent"), + + /** + * Use for a respondent who takes an appeal from one court or jurisdiction to another to reverse the judgment, usually in an equity proceeding. + */ + RESPONDENT_APPELLANT("rst", "Respondent-appellant"), + + /** + * Use for a respondent against whom an appeal is taken from one court or jurisdiction to another to reverse the judgment, usually in an equity proceeding. + */ + RESPONDENT_APPELLEE("rse", "Respondent-appellee"), + + /** + * Use for a person or organization legally responsible for the content of the published material. + */ + RESPONSIBLE_PARTY("rpy", "Responsible party"), + + /** + * Use for a person or organization, other than the original choreographer or director, responsible for restaging a choreographic or dramatic work and who contributes minimal new content. + */ + RESTAGER("rsg", "Restager"), + + /** + * Use for a person or organization responsible for the review of a book, motion picture, performance, etc. + */ + REVIEWER("rev", "Reviewer"), + + /** + * Use for a person or organization responsible for parts of a work, often headings or opening parts of a manuscript, that appear in a distinctive color, usually red. + */ + RUBRICATOR("rbr", "Rubricator"), + + /** + * Use for a person or organization who is the author of a motion picture screenplay. + */ + SCENARIST("sce", "Scenarist"), + + /** + * Use for a person or organization who brings scientific, pedagogical, or historical competence to the conception and realization on a work, particularly in the case of audio-visual items. + */ + SCIENTIFIC_ADVISOR("sad", "Scientific advisor"), + + /** + * Use for a person who is an amanuensis and for a writer of manuscripts proper. For a person who makes pen-facsimiles, use Facsimilist [fac]. + */ + SCRIBE("scr", "Scribe"), + + /** + * Use for a person or organization who models or carves figures that are three-dimensional representations. + */ + SCULPTOR("scl", "Sculptor"), + + /** + * Use for a person or organization who is identified as the party of the second part. In the case of transfer of right, this is the assignee, transferee, licensee, grantee, etc. Multiple parties can be named jointly as the second party. + */ + SECOND_PARTY("spy", "Second party"), + + /** + * Use for a person or organization who is a recorder, redactor, or other person responsible for expressing the views of a organization. + */ + SECRETARY("sec", "Secretary"), + + /** + * Use for a person or organization who translates the rough sketches of the art director into actual architectural structures for a theatrical presentation, entertainment, motion picture, etc. Set designers draw the detailed guides and specifications for building the set. + */ + SET_DESIGNER("std", "Set designer"), + + /** + * Use for a person whose signature appears without a presentation or other statement indicative of provenance. When there is a presentation statement, use Inscriber [ins]. + */ + SIGNER("sgn", "Signer"), + + /** + * Use for a person or organization who uses his/her/their voice with or without instrumental accompaniment to produce music. A performance may or may not include actual words. + */ + SINGER("sng", "Singer"), + + /** + * Use for a person who produces and reproduces the sound score (both live and recorded), the installation of microphones, the setting of sound levels, and the coordination of sources of sound for a production. + */ + SOUND_DESIGNER("sds", "Sound designer"), + + /** + * Use for a person who participates in a program (often broadcast) and makes a formalized contribution or presentation generally prepared in advance. + */ + SPEAKER("spk", "Speaker"), + + /** + * Use for a person or organization that issued a contract or under the auspices of which a work has been written, printed, published, etc. + */ + SPONSOR("spn", "Sponsor"), + + /** + * Use for a person who is in charge of everything that occurs on a performance stage, and who acts as chief of all crews and assistant to a director during rehearsals. + */ + STAGE_MANAGER("stm", "Stage manager"), + + /** + * Use for an organization responsible for the development or enforcement of a standard. + */ + STANDARDS_BODY("stn", "Standards body"), + + /** + * Use for a person or organization who creates a new plate for printing by molding or copying another printing surface. + */ + STEREOTYPER("str", "Stereotyper"), + + /** + * Use for a person relaying a story with creative and/or theatrical interpretation. + */ + STORYTELLER("stl", "Storyteller"), + + /** + * Use for a person or organization that supports (by allocating facilities, staff, or other resources) a project, program, meeting, event, data objects, material culture objects, or other entities capable of support. + */ + SUPPORTING_HOST("sht", "Supporting host"), + + /** + * Use for a person or organization who does measurements of tracts of land, etc. to determine location, forms, and boundaries. + */ + SURVEYOR("srv", "Surveyor"), + + /** + * Use for a person who, in the context of a resource, gives instruction in an intellectual subject or demonstrates while teaching physical skills. + */ + TEACHER("tch", "Teacher"), + + /** + * Use for a person who is ultimately in charge of scenery, props, lights and sound for a production. + */ + TECHNICAL_DIRECTOR("tcd", "Technical director"), + + /** + * Use for a person under whose supervision a degree candidate develops and presents a thesis, mémoire, or text of a dissertation. + */ + THESIS_ADVISOR("ths", "Thesis advisor"), + + /** + * Use for a person who prepares a handwritten or typewritten copy from original material, including from dictated or orally recorded material. For makers of pen-facsimiles, use Facsimilist [fac]. + */ + TRANSCRIBER("trc", "Transcriber"), + + /** + * Use for a person or organization who renders a text from one language into another, or from an older form of a language into the modern form. + */ + TRANSLATOR("trl", "Translator"), + + /** + * Use for a person or organization who designed the type face used in a particular item. + */ + TYPE_DESIGNER("tyd", "Type designer"), + + /** + * Use for a person or organization primarily responsible for choice and arrangement of type used in an item. If the typographer is also responsible for other aspects of the graphic design of a book (e.g., Book designer [bkd]), codes for both functions may be needed. + */ + TYPOGRAPHER("tyg", "Typographer"), + + /** + * Use for the name of a place where a university that is associated with a resource is located, for example, a university where an academic dissertation or thesis was presented. + */ + UNIVERSITY_PLACE("uvp", "University place"), + + /** + * Use for a person or organization in charge of a video production, e.g. the video recording of a stage production as opposed to a commercial motion picture. The videographer may be the camera operator or may supervise one or more camera operators. Do not confuse with cinematographer. + */ + VIDEOGRAPHER("vdg", "Videographer"), + + /** + * Use for a person or organization who principally exhibits singing skills in a musical or dramatic presentation or entertainment. + */ + VOCALIST("voc", "Vocalist"), + + /** + * Use for a person who verifies the truthfulness of an event or action. + */ + WITNESS("wit", "Witness"), + + /** + * Use for a person or organization who makes prints by cutting the image in relief on the end-grain of a wood block. + */ + WOOD_ENGRAVER("wde", "Wood-engraver"), + + /** + * Use for a person or organization who makes prints by cutting the image in relief on the plank side of a wood block. + */ + WOODCUTTER("wdc", "Woodcutter"), + + /** + * Use for a person or organization who writes significant material which accompanies a sound recording or other audiovisual material. + */ + WRITER_OF_ACCOMPANYING_MATERIAL("wam", "Writer of accompanying material"); + + private final String code; + private final String name; + + Relator(String code, String name) { + this.code = code; + this.name = name; + } + + public String getCode() { + return code; + } + + public String getName() { + return name; + } + + public static Relator byCode(String code) { + for (Relator relator : Relator.values()) { + if (relator.getCode().equalsIgnoreCase(code)) { + return relator; + } + } + return null; + } + +} diff --git a/epublib/src/main/java/me/ag2s/epublib/domain/Resource.java b/epublib/src/main/java/me/ag2s/epublib/domain/Resource.java new file mode 100644 index 000000000..648bfd8e5 --- /dev/null +++ b/epublib/src/main/java/me/ag2s/epublib/domain/Resource.java @@ -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:
+ * images/cover.jpg
+ * content/chapter1.xhtml
+ * + * @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)); + } +} diff --git a/epublib/src/main/java/me/ag2s/epublib/domain/ResourceInputStream.java b/epublib/src/main/java/me/ag2s/epublib/domain/ResourceInputStream.java new file mode 100644 index 000000000..d6aa68f86 --- /dev/null +++ b/epublib/src/main/java/me/ag2s/epublib/domain/ResourceInputStream.java @@ -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(); + } +} diff --git a/epublib/src/main/java/me/ag2s/epublib/domain/ResourceReference.java b/epublib/src/main/java/me/ag2s/epublib/domain/ResourceReference.java new file mode 100644 index 000000000..ea54d9749 --- /dev/null +++ b/epublib/src/main/java/me/ag2s/epublib/domain/ResourceReference.java @@ -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; + } +} diff --git a/epublib/src/main/java/me/ag2s/epublib/domain/Resources.java b/epublib/src/main/java/me/ag2s/epublib/domain/Resources.java new file mode 100644 index 000000000..f4125b4d9 --- /dev/null +++ b/epublib/src/main/java/me/ag2s/epublib/domain/Resources.java @@ -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 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 getResourceMap() { + return resources; + } + + public Collection 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 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 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 resources) { + this.resources = new HashMap(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 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 getResourcesByMediaType(MediaType mediaType) { + List result = new ArrayList(); + 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 getResourcesByMediaTypes(MediaType[] mediaTypes) { + List result = new ArrayList(); + 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 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 getAllHrefs() { + return resources.keySet(); + } +} diff --git a/epublib/src/main/java/me/ag2s/epublib/domain/Spine.java b/epublib/src/main/java/me/ag2s/epublib/domain/Spine.java new file mode 100644 index 000000000..7f25a986c --- /dev/null +++ b/epublib/src/main/java/me/ag2s/epublib/domain/Spine.java @@ -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 spineReferences; + + public Spine() { + this(new ArrayList()); + } + + /** + * 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 spineReferences) { + this.spineReferences = spineReferences; + } + + public static List createSpineReferences( + Collection resources) { + List result = new ArrayList( + resources.size()); + for (Resource resource : resources) { + result.add(new SpineReference(resource)); + } + return result; + } + + public List getSpineReferences() { + return spineReferences; + } + + public void setSpineReferences(List 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(); + } + 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(); + } +} diff --git a/epublib/src/main/java/me/ag2s/epublib/domain/SpineReference.java b/epublib/src/main/java/me/ag2s/epublib/domain/SpineReference.java new file mode 100644 index 000000000..9c9c9d00f --- /dev/null +++ b/epublib/src/main/java/me/ag2s/epublib/domain/SpineReference.java @@ -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. + * + *
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.)
+ * @see OPF Spine specification + * + * @return whether the section is Primary or Auxiliary. + */ + public boolean isLinear() { + return linear; + } + + public void setLinear(boolean linear) { + this.linear = linear; + } + +} diff --git a/epublib/src/main/java/me/ag2s/epublib/domain/TOCReference.java b/epublib/src/main/java/me/ag2s/epublib/domain/TOCReference.java new file mode 100644 index 000000000..b55ef8122 --- /dev/null +++ b/epublib/src/main/java/me/ag2s/epublib/domain/TOCReference.java @@ -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 children; + private static final Comparator 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 children) { + super(resource, title, fragmentId); + this.children = children; + } + + public static Comparator getComparatorByTitleIgnoreCase() { + return COMPARATOR_BY_TITLE_IGNORE_CASE; + } + + public List getChildren() { + return children; + } + + public TOCReference addChildSection(TOCReference childSection) { + this.children.add(childSection); + return childSection; + } + + public void setChildren(List children) { + this.children = children; + } +} diff --git a/epublib/src/main/java/me/ag2s/epublib/domain/TableOfContents.java b/epublib/src/main/java/me/ag2s/epublib/domain/TableOfContents.java new file mode 100644 index 000000000..0be7c123e --- /dev/null +++ b/epublib/src/main/java/me/ag2s/epublib/domain/TableOfContents.java @@ -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 tocReferences; + + public TableOfContents() { + this(new ArrayList()); + } + + public TableOfContents(List tocReferences) { + this.tocReferences = tocReferences; + } + + public List getTocReferences() { + return tocReferences; + } + + public void setTocReferences(List 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 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: + *
    + *
  • a TOCReference with the title "chapter1" at the root level.
    + * If this TOCReference did not yet exist it will have been created and does not point to any resource
  • + *
  • A TOCReference that has the title "paragraph1". This TOCReference will be the child of TOCReference "chapter1" and + * will point to the given Resource
  • + *
+ * + * @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 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: + *
    + *
  • a TOCReference at the root level.
    + * If this TOCReference did not yet exist it will have been created with a title of "" and does not point to any resource
  • + *
  • A TOCReference that points to the given resource and is a child of the previously created TOCReference.
    + * If this TOCReference didn't exist yet it will be created and have a title of ""
  • + *
+ * + * @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 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 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 getAllUniqueResources() { + Set uniqueHrefs = new HashSet<>(); + List result = new ArrayList<>(); + getAllUniqueResources(uniqueHrefs, result, tocReferences); + return result; + } + + private static void getAllUniqueResources(Set uniqueHrefs, + List result, List 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 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 tocReferences, + int currentDepth) { + int maxChildDepth = 0; + for (TOCReference tocReference : tocReferences) { + int childDepth = calculateDepth(tocReference.getChildren(), 1); + if (childDepth > maxChildDepth) { + maxChildDepth = childDepth; + } + } + return currentDepth + maxChildDepth; + } +} diff --git a/epublib/src/main/java/me/ag2s/epublib/domain/TitledResourceReference.java b/epublib/src/main/java/me/ag2s/epublib/domain/TitledResourceReference.java new file mode 100644 index 000000000..4496fa73e --- /dev/null +++ b/epublib/src/main/java/me/ag2s/epublib/domain/TitledResourceReference.java @@ -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); + } +} diff --git a/epublib/src/main/java/me/ag2s/epublib/epub/BookProcessor.java b/epublib/src/main/java/me/ag2s/epublib/epub/BookProcessor.java new file mode 100644 index 000000000..5a44dfdcf --- /dev/null +++ b/epublib/src/main/java/me/ag2s/epublib/epub/BookProcessor.java @@ -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); +} diff --git a/epublib/src/main/java/me/ag2s/epublib/epub/BookProcessorPipeline.java b/epublib/src/main/java/me/ag2s/epublib/epub/BookProcessorPipeline.java new file mode 100644 index 000000000..9174313f8 --- /dev/null +++ b/epublib/src/main/java/me/ag2s/epublib/epub/BookProcessorPipeline.java @@ -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 bookProcessors; + + public BookProcessorPipeline() { + this(null); + } + + public BookProcessorPipeline(List 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(); + } + this.bookProcessors.add(bookProcessor); + } + + public void addBookProcessors(Collection bookProcessors) { + if (this.bookProcessors == null) { + this.bookProcessors = new ArrayList(); + } + this.bookProcessors.addAll(bookProcessors); + } + + + public List getBookProcessors() { + return bookProcessors; + } + + + public void setBookProcessingPipeline( + List bookProcessingPipeline) { + this.bookProcessors = bookProcessingPipeline; + } + +} diff --git a/epublib/src/main/java/me/ag2s/epublib/epub/DOMUtil.java b/epublib/src/main/java/me/ag2s/epublib/epub/DOMUtil.java new file mode 100644 index 000000000..37702c1ae --- /dev/null +++ b/epublib/src/main/java/me/ag2s/epublib/epub/DOMUtil.java @@ -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 getElementsTextChild(Element parentElement, + String namespace, String tagname) { + NodeList elements = parentElement + .getElementsByTagNameNS(namespace, tagname); + List result = new ArrayList(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(); + } + +} diff --git a/epublib/src/main/java/me/ag2s/epublib/epub/EpubProcessorSupport.java b/epublib/src/main/java/me/ag2s/epublib/epub/EpubProcessorSupport.java new file mode 100644 index 000000000..e0fc6e983 --- /dev/null +++ b/epublib/src/main/java/me/ag2s/epublib/epub/EpubProcessorSupport.java @@ -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; + } +} diff --git a/epublib/src/main/java/me/ag2s/epublib/epub/EpubReader.java b/epublib/src/main/java/me/ag2s/epublib/epub/EpubReader.java new file mode 100644 index 000000000..81dfdaa67 --- /dev/null +++ b/epublib/src/main/java/me/ag2s/epublib/epub/EpubReader.java @@ -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 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"); + } +} diff --git a/epublib/src/main/java/me/ag2s/epublib/epub/EpubWriter.java b/epublib/src/main/java/me/ag2s/epublib/epub/EpubWriter.java new file mode 100644 index 000000000..6506b339a --- /dev/null +++ b/epublib/src/main/java/me/ag2s/epublib/epub/EpubWriter.java @@ -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("\n"); + out.write( + "\n"); + out.write("\t\n"); + out.write( + "\t\t\n"); + out.write("\t\n"); + out.write(""); + 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; + } + +} diff --git a/epublib/src/main/java/me/ag2s/epublib/epub/HtmlProcessor.java b/epublib/src/main/java/me/ag2s/epublib/epub/HtmlProcessor.java new file mode 100644 index 000000000..d895d8b9e --- /dev/null +++ b/epublib/src/main/java/me/ag2s/epublib/epub/HtmlProcessor.java @@ -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); +} diff --git a/epublib/src/main/java/me/ag2s/epublib/epub/Main.java b/epublib/src/main/java/me/ag2s/epublib/epub/Main.java new file mode 100644 index 000000000..86ca14994 --- /dev/null +++ b/epublib/src/main/java/me/ag2s/epublib/epub/Main.java @@ -0,0 +1,5 @@ +package me.ag2s.epublib.epub; + +public class Main { + +} diff --git a/epublib/src/main/java/me/ag2s/epublib/epub/NCXDocument.java b/epublib/src/main/java/me/ag2s/epublib/epub/NCXDocument.java new file mode 100644 index 000000000..7c44ed7f3 --- /dev/null +++ b/epublib/src/main/java/me/ag2s/epublib/epub/NCXDocument.java @@ -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 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 identifiers, + String title, List 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 identifiers, String title, List 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 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); + } +} diff --git a/epublib/src/main/java/me/ag2s/epublib/epub/NCXDocumentV2.java b/epublib/src/main/java/me/ag2s/epublib/epub/NCXDocumentV2.java new file mode 100644 index 000000000..1993177b6 --- /dev/null +++ b/epublib/src/main/java/me/ag2s/epublib/epub/NCXDocumentV2.java @@ -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 readTOCReferences(NodeList navpoints, + Book book) { + if (navpoints == null) { + return new ArrayList<>(); + } + List 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 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 identifiers, + String title, List 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 identifiers, String title, List 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 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); + } +} diff --git a/epublib/src/main/java/me/ag2s/epublib/epub/NCXDocumentV3.java b/epublib/src/main/java/me/ag2s/epublib/epub/NCXDocumentV3.java new file mode 100644 index 000000000..d1f6174cf --- /dev/null +++ b/epublib/src/main/java/me/ag2s/epublib/epub/NCXDocumentV3.java @@ -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 doToc(Node n, Book book) { + List 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 readTOCReferences(NodeList navpoints, + Book book) { + if (navpoints == null) { + return new ArrayList<>(); + } + //Log.d(TAG, "readTOCReferences:navpoints.getLength()" + navpoints.getLength()); + List 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 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)); + } + + +} diff --git a/epublib/src/main/java/me/ag2s/epublib/epub/PackageDocumentBase.java b/epublib/src/main/java/me/ag2s/epublib/epub/PackageDocumentBase.java new file mode 100644 index 000000000..04c8240f9 --- /dev/null +++ b/epublib/src/main/java/me/ag2s/epublib/epub/PackageDocumentBase.java @@ -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"; + } +} \ No newline at end of file diff --git a/epublib/src/main/java/me/ag2s/epublib/epub/PackageDocumentMetadataReader.java b/epublib/src/main/java/me/ag2s/epublib/epub/PackageDocumentMetadataReader.java new file mode 100644 index 000000000..8feab2660 --- /dev/null +++ b/epublib/src/main/java/me/ag2s/epublib/epub/PackageDocumentMetadataReader.java @@ -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 readOtherProperties( + Element metadataElement) { + Map result = new HashMap(); + + 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 readMetaProperties( + Element metadataElement) { + Map result = new HashMap(); + + 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 readCreators(Element metadataElement) { + return readAuthors(DCTags.creator, metadataElement); + } + + private static List readContributors(Element metadataElement) { + return readAuthors(DCTags.contributor, metadataElement); + } + + private static List readAuthors(String authorTag, + Element metadataElement) { + NodeList elements = metadataElement + .getElementsByTagNameNS(NAMESPACE_DUBLIN_CORE, authorTag); + List result = new ArrayList(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 readDates(Element metadataElement) { + NodeList elements = metadataElement + .getElementsByTagNameNS(NAMESPACE_DUBLIN_CORE, DCTags.date); + List result = new ArrayList(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 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(); + } + String bookIdId = getBookIdId(metadataElement.getOwnerDocument()); + List result = new ArrayList( + 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; + } +} diff --git a/epublib/src/main/java/me/ag2s/epublib/epub/PackageDocumentMetadataWriter.java b/epublib/src/main/java/me/ag2s/epublib/epub/PackageDocumentMetadataWriter.java new file mode 100644 index 000000000..c2a47e155 --- /dev/null +++ b/epublib/src/main/java/me/ag2s/epublib/epub/PackageDocumentMetadataWriter.java @@ -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 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 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 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); + } + } + +} diff --git a/epublib/src/main/java/me/ag2s/epublib/epub/PackageDocumentReader.java b/epublib/src/main/java/me/ag2s/epublib/epub/PackageDocumentReader.java new file mode 100644 index 000000000..86ecf3044 --- /dev/null +++ b/epublib/src/main/java/me/ag2s/epublib/epub/PackageDocumentReader.java @@ -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 idMapping = new HashMap(); + 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 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 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 spineReferences = new ArrayList( + 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 resourceHrefs = new ArrayList(); + 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 findCoverHrefs(Document packageDocument) { + + Set result = new HashSet(); + + // 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 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); + } + } + } + + +} diff --git a/epublib/src/main/java/me/ag2s/epublib/epub/PackageDocumentWriter.java b/epublib/src/main/java/me/ag2s/epublib/epub/PackageDocumentWriter.java new file mode 100644 index 000000000..8289da5d7 --- /dev/null +++ b/epublib/src/main/java/me/ag2s/epublib/epub/PackageDocumentWriter.java @@ -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 getAllResourcesSortById(Book book) { + List allResources = new ArrayList( + book.getResources().getAll()); + Collections.sort(allResources, new Comparator() { + + @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); + } +} \ No newline at end of file diff --git a/epublib/src/main/java/me/ag2s/epublib/epub/ResourcesLoader.java b/epublib/src/main/java/me/ag2s/epublib/epub/ResourcesLoader.java new file mode 100644 index 000000000..f2114bc1b --- /dev/null +++ b/epublib/src/main/java/me/ag2s/epublib/epub/ResourcesLoader.java @@ -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 lazyLoadedTypes) throws IOException { + + LazyResourceProvider resourceProvider = + new EpubResourceProvider(zipFile.getName()); + + Resources result = new Resources(); + Enumeration 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 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 Issue #122 Infinite loop. + //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()); + } +} diff --git a/epublib/src/main/java/me/ag2s/epublib/util/CollectionUtil.java b/epublib/src/main/java/me/ag2s/epublib/util/CollectionUtil.java new file mode 100644 index 000000000..e4d152ff6 --- /dev/null +++ b/epublib/src/main/java/me/ag2s/epublib/util/CollectionUtil.java @@ -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 + */ + private static class IteratorEnumerationAdapter implements Enumeration { + + private Iterator iterator; + + public IteratorEnumerationAdapter(Iterator 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 + * @param it + * @return an Enumeration created out of the given Iterator. + */ + public static Enumeration createEnumerationFromIterator( + Iterator it) { + return new IteratorEnumerationAdapter(it); + } + + + /** + * Returns the first element of the list, null if the list is null or empty. + * + * @param + * @param list + * @return the first element of the list, null if the list is null or empty. + */ + public static T first(List 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(); + } +} diff --git a/epublib/src/main/java/me/ag2s/epublib/util/IOUtil.java b/epublib/src/main/java/me/ag2s/epublib/util/IOUtil.java new file mode 100644 index 000000000..823a73b74 --- /dev/null +++ b/epublib/src/main/java/me/ag2s/epublib/util/IOUtil.java @@ -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; + } +} diff --git a/epublib/src/main/java/me/ag2s/epublib/util/NoCloseOutputStream.java b/epublib/src/main/java/me/ag2s/epublib/util/NoCloseOutputStream.java new file mode 100644 index 000000000..d3af0803b --- /dev/null +++ b/epublib/src/main/java/me/ag2s/epublib/util/NoCloseOutputStream.java @@ -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() { + } +} diff --git a/epublib/src/main/java/me/ag2s/epublib/util/NoCloseWriter.java b/epublib/src/main/java/me/ag2s/epublib/util/NoCloseWriter.java new file mode 100644 index 000000000..8587daf15 --- /dev/null +++ b/epublib/src/main/java/me/ag2s/epublib/util/NoCloseWriter.java @@ -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); + } +} diff --git a/epublib/src/main/java/me/ag2s/epublib/util/ResourceUtil.java b/epublib/src/main/java/me/ag2s/epublib/util/ResourceUtil.java new file mode 100644 index 000000000..3467a62b4 --- /dev/null +++ b/epublib/src/main/java/me/ag2s/epublib/util/ResourceUtil.java @@ -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("

").append(s).append("

"); + } + + } + String html=""; + html+=""; + html+=""+title+"" + + "" + + ""; + html+="

"+title+"

" + + body+ + ""; + + 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 = + "" + title + "

" + title + + "

"; + 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; + } +} diff --git a/epublib/src/main/java/me/ag2s/epublib/util/StreamWriterDelegate.txt b/epublib/src/main/java/me/ag2s/epublib/util/StreamWriterDelegate.txt new file mode 100644 index 000000000..b07ad7a3c --- /dev/null +++ b/epublib/src/main/java/me/ag2s/epublib/util/StreamWriterDelegate.txt @@ -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 John Kristian + */ +public abstract class StreamWriterDelegate implements XMLStreamWriter { + + protected StreamWriterDelegate(XMLStreamWriter out) { + this.out = out; + } + + protected XMLStreamWriter out; + + public Object getProperty(String name) + throws IllegalArgumentException { + return out.getProperty(name); + } + + public NamespaceContext getNamespaceContext() { + return out.getNamespaceContext(); + } + + public void setNamespaceContext(NamespaceContext context) + throws XMLStreamException { + out.setNamespaceContext(context); + } + + public void setDefaultNamespace(String uri) + throws XMLStreamException { + out.setDefaultNamespace(uri); + } + + public void writeStartDocument() throws XMLStreamException { + out.writeStartDocument(); + } + + public void writeStartDocument(String version) + throws XMLStreamException { + out.writeStartDocument(version); + } + + public void writeStartDocument(String encoding, String version) + throws XMLStreamException { + out.writeStartDocument(encoding, version); + } + + public void writeDTD(String dtd) throws XMLStreamException { + out.writeDTD(dtd); + } + + public void writeProcessingInstruction(String target) + throws XMLStreamException { + out.writeProcessingInstruction(target); + } + + public void writeProcessingInstruction(String target, String data) + throws XMLStreamException { + out.writeProcessingInstruction(target, data); + } + + public void writeComment(String data) throws XMLStreamException { + out.writeComment(data); + } + + public void writeEmptyElement(String localName) + throws XMLStreamException { + out.writeEmptyElement(localName); + } + + public void writeEmptyElement(String namespaceURI, String localName) + throws XMLStreamException { + out.writeEmptyElement(namespaceURI, localName); + } + + public void writeEmptyElement(String prefix, String localName, + String namespaceURI) throws XMLStreamException { + out.writeEmptyElement(prefix, localName, namespaceURI); + } + + public void writeStartElement(String localName) + throws XMLStreamException { + out.writeStartElement(localName); + } + + public void writeStartElement(String namespaceURI, String localName) + throws XMLStreamException { + out.writeStartElement(namespaceURI, localName); + } + + public void writeStartElement(String prefix, String localName, + String namespaceURI) throws XMLStreamException { + out.writeStartElement(prefix, localName, namespaceURI); + } + + public void writeDefaultNamespace(String namespaceURI) + throws XMLStreamException { + out.writeDefaultNamespace(namespaceURI); + } + + public void writeNamespace(String prefix, String namespaceURI) + throws XMLStreamException { + out.writeNamespace(prefix, namespaceURI); + } + + public String getPrefix(String uri) throws XMLStreamException { + return out.getPrefix(uri); + } + + public void setPrefix(String prefix, String uri) + throws XMLStreamException { + out.setPrefix(prefix, uri); + } + + public void writeAttribute(String localName, String value) + throws XMLStreamException { + out.writeAttribute(localName, value); + } + + public void writeAttribute(String namespaceURI, String localName, + String value) throws XMLStreamException { + out.writeAttribute(namespaceURI, localName, value); + } + + public void writeAttribute(String prefix, String namespaceURI, + String localName, String value) throws XMLStreamException { + out.writeAttribute(prefix, namespaceURI, localName, value); + } + + public void writeCharacters(String text) throws XMLStreamException { + out.writeCharacters(text); + } + + public void writeCharacters(char[] text, int start, int len) + throws XMLStreamException { + out.writeCharacters(text, start, len); + } + + public void writeCData(String data) throws XMLStreamException { + out.writeCData(data); + } + + public void writeEntityRef(String name) throws XMLStreamException { + out.writeEntityRef(name); + } + + public void writeEndElement() throws XMLStreamException { + out.writeEndElement(); + } + + public void writeEndDocument() throws XMLStreamException { + out.writeEndDocument(); + } + + public void flush() throws XMLStreamException { + out.flush(); + } + + public void close() throws XMLStreamException { + out.close(); + } + +} + diff --git a/epublib/src/main/java/me/ag2s/epublib/util/StringUtil.java b/epublib/src/main/java/me/ag2s/epublib/util/StringUtil.java new file mode 100644 index 000000000..551d2af4f --- /dev/null +++ b/epublib/src/main/java/me/ag2s/epublib/util/StringUtil.java @@ -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 parts = new ArrayList(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(""); + } 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); + } +} diff --git a/epublib/src/main/java/me/ag2s/epublib/util/commons/io/BOMInputStream.java b/epublib/src/main/java/me/ag2s/epublib/util/commons/io/BOMInputStream.java new file mode 100644 index 000000000..58f319274 --- /dev/null +++ b/epublib/src/main/java/me/ag2s/epublib/util/commons/io/BOMInputStream.java @@ -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: + *
    + *
  • UTF-8 - {@link ByteOrderMark#UTF_8}
  • + *
  • UTF-16BE - {@link ByteOrderMark#UTF_16LE}
  • + *
  • UTF-16LE - {@link ByteOrderMark#UTF_16BE}
  • + *
+ * + * + *

Example 1 - Detect and exclude a UTF-8 BOM

+ *
+ *      BOMInputStream bomIn = new BOMInputStream(in);
+ *      if (bomIn.hasBOM()) {
+ *          // has a UTF-8 BOM
+ *      }
+ * 
+ * + *

Example 2 - Detect a UTF-8 BOM (but don't exclude it)

+ *
+ *      boolean include = true;
+ *      BOMInputStream bomIn = new BOMInputStream(in, include);
+ *      if (bomIn.hasBOM()) {
+ *          // has a UTF-8 BOM
+ *      }
+ * 
+ * + *

Example 3 - Detect Multiple BOMs

+ *
+ *      BOMInputStream bomIn = new BOMInputStream(in, ByteOrderMark.UTF_16LE, ByteOrderMark.UTF_16BE);
+ *      if (bomIn.hasBOM() == false) {
+ *          // No BOM found
+ *      } else if (bomIn.hasBOM(ByteOrderMark.UTF_16LE)) {
+ *          // has a UTF-16LE BOM
+ *      } else if (bomIn.hasBOM(ByteOrderMark.UTF_16BE)) {
+ *          // has a UTF-16BE BOM
+ *      }
+ * 
+ * + * @see ByteOrderMark + * @see Wikipedia - Byte Order Mark + * @version $Revision: 1052095 $ $Date: 2010-12-22 23:03:20 +0000 (Wed, 22 Dec 2010) $ + * @since Commons IO 2.0 + */ +public class BOMInputStream extends ProxyInputStream { + private final boolean include; + private final List 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 read() method, + * either returning a valid byte or -1 to indicate that the initial bytes + * have been processed already. + * @return the byte read (excluding BOM) or -1 if the end of stream + * @throws IOException if an I/O error occurs + */ + private int readFirstBytes() throws IOException { + getBOM(); + return (fbIndex < fbLength) ? firstBytes[fbIndex++] : -1; + } + + /** + * Find a BOM with the specified bytes. + * + * @return The matched BOM or null if none matched + */ + private ByteOrderMark find() { + for (ByteOrderMark bom : boms) { + if (matches(bom)) { + return bom; + } + } + return null; + } + + /** + * Check if the bytes match a BOM. + * + * @param bom The BOM + * @return true if the bytes match the bom, otherwise false + */ + private boolean matches(ByteOrderMark bom) { + if (bom.length() != fbLength) { + return false; + } + for (int i = 0; i < bom.length(); i++) { + if (bom.get(i) != firstBytes[i]) { + return false; + } + } + return true; + } + + //---------------------------------------------------------------------------- + // Implementation of InputStream + //---------------------------------------------------------------------------- + + /** + * Invokes the delegate's read() method, detecting and + * optionally skipping BOM. + * @return the byte read (excluding BOM) or -1 if the end of stream + * @throws IOException if an I/O error occurs + */ + @Override + public int read() throws IOException { + int b = readFirstBytes(); + return (b >= 0) ? b : in.read(); + } + + /** + * Invokes the delegate's read(byte[], int, int) method, detecting + * and optionally skipping BOM. + * @param buf the buffer to read the bytes into + * @param off The start offset + * @param len The number of bytes to read (excluding BOM) + * @return the number of bytes read or -1 if the end of stream + * @throws IOException if an I/O error occurs + */ + @Override + public int read(byte[] buf, int off, int len) throws IOException { + int firstCount = 0; + int b = 0; + while ((len > 0) && (b >= 0)) { + b = readFirstBytes(); + if (b >= 0) { + buf[off++] = (byte) (b & 0xFF); + len--; + firstCount++; + } + } + int secondCount = in.read(buf, off, len); + return (secondCount < 0) ? (firstCount > 0 ? firstCount : -1) : firstCount + secondCount; + } + + /** + * Invokes the delegate's read(byte[]) method, detecting and + * optionally skipping BOM. + * @param buf the buffer to read the bytes into + * @return the number of bytes read (excluding BOM) + * or -1 if the end of stream + * @throws IOException if an I/O error occurs + */ + @Override + public int read(byte[] buf) throws IOException { + return read(buf, 0, buf.length); + } + + /** + * Invokes the delegate's mark(int) method. + * @param readlimit read ahead limit + */ + @Override + public synchronized void mark(int readlimit) { + markFbIndex = fbIndex; + markedAtStart = (firstBytes == null); + in.mark(readlimit); + } + + /** + * Invokes the delegate's reset() method. + * @throws IOException if an I/O error occurs + */ + @Override + public synchronized void reset() throws IOException { + fbIndex = markFbIndex; + if (markedAtStart) { + firstBytes = null; + } + + in.reset(); + } + + /** + * Invokes the delegate's skip(long) method, detecting + * and optionallyskipping BOM. + * @param n the number of bytes to skip + * @return the number of bytes to skipped or -1 if the end of stream + * @throws IOException if an I/O error occurs + */ + @Override + public long skip(long n) throws IOException { + while ((n > 0) && (readFirstBytes() >= 0)) { + n--; + } + return in.skip(n); + } +} diff --git a/epublib/src/main/java/me/ag2s/epublib/util/commons/io/ByteOrderMark.java b/epublib/src/main/java/me/ag2s/epublib/util/commons/io/ByteOrderMark.java new file mode 100644 index 000000000..b2001d8ab --- /dev/null +++ b/epublib/src/main/java/me/ag2s/epublib/util/commons/io/ByteOrderMark.java @@ -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 Wikipedia - Byte Order Mark + * @version $Id: ByteOrderMark.java 1005099 2010-10-06 16:13:01Z niallp $ + * @since Commons IO 2.0 + */ +public class ByteOrderMark implements Serializable { + + private static final long serialVersionUID = 1L; + + /** UTF-8 BOM */ + public static final ByteOrderMark UTF_8 = new ByteOrderMark("UTF-8", 0xEF, 0xBB, 0xBF); + /** UTF-16BE BOM (Big Endian) */ + public static final ByteOrderMark UTF_16BE = new ByteOrderMark("UTF-16BE", 0xFE, 0xFF); + /** UTF-16LE BOM (Little Endian) */ + public static final ByteOrderMark UTF_16LE = new ByteOrderMark("UTF-16LE", 0xFF, 0xFE); + + private final String charsetName; + private final int[] bytes; + + /** + * Construct a new BOM. + * + * @param charsetName The name of the charset the BOM represents + * @param bytes The BOM's bytes + * @throws IllegalArgumentException if the charsetName is null or + * zero length + * @throws IllegalArgumentException if the bytes are null or zero + * length + */ + public ByteOrderMark(String charsetName, int... bytes) { + if (charsetName == null || charsetName.length() == 0) { + throw new IllegalArgumentException("No charsetName specified"); + } + if (bytes == null || bytes.length == 0) { + throw new IllegalArgumentException("No bytes specified"); + } + this.charsetName = charsetName; + this.bytes = new int[bytes.length]; + System.arraycopy(bytes, 0, this.bytes, 0, bytes.length); + } + + /** + * Return the name of the {@link java.nio.charset.Charset} the BOM represents. + * + * @return the character set name + */ + public String getCharsetName() { + return charsetName; + } + + /** + * Return the length of the BOM's bytes. + * + * @return the length of the BOM's bytes + */ + public int length() { + return bytes.length; + } + + /** + * The byte at the specified position. + * + * @param pos The position + * @return The specified byte + */ + public int get(int pos) { + return bytes[pos]; + } + + /** + * Return a copy of the BOM's bytes. + * + * @return a copy of the BOM's bytes + */ + public byte[] getBytes() { + byte[] copy = new byte[bytes.length]; + for (int i = 0; i < bytes.length; i++) { + copy[i] = (byte)bytes[i]; + } + return copy; + } + + /** + * Indicates if this BOM's bytes equals another. + * + * @param obj The object to compare to + * @return true if the bom's bytes are equal, otherwise + * false + */ + @Override + public boolean equals(Object obj) { + if (!(obj instanceof ByteOrderMark)) { + return false; + } + ByteOrderMark bom = (ByteOrderMark)obj; + if (bytes.length != bom.length()) { + return false; + } + for (int i = 0; i < bytes.length; i++) { + if (bytes[i] != bom.get(i)) { + return false; + } + } + return true; + } + + /** + * Return the hashcode for this BOM. + * + * @return the hashcode for this BOM. + * @see Object#hashCode() + */ + @Override + public int hashCode() { + int hashCode = getClass().hashCode(); + for (int b : bytes) { + hashCode += b; + } + return hashCode; + } + + /** + * Provide a String representation of the BOM. + * + * @return the length of the BOM's bytes + */ + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append(getClass().getSimpleName()); + builder.append('['); + builder.append(charsetName); + builder.append(": "); + for (int i = 0; i < bytes.length; i++) { + if (i > 0) { + builder.append(","); + } + builder.append("0x"); + builder.append(Integer.toHexString(0xFF & bytes[i]).toUpperCase()); + } + builder.append(']'); + return builder.toString(); + } + +} diff --git a/epublib/src/main/java/me/ag2s/epublib/util/commons/io/ProxyInputStream.java b/epublib/src/main/java/me/ag2s/epublib/util/commons/io/ProxyInputStream.java new file mode 100644 index 000000000..894457903 --- /dev/null +++ b/epublib/src/main/java/me/ag2s/epublib/util/commons/io/ProxyInputStream.java @@ -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. + *

+ * It is an alternative base class to FilterInputStream + * to increase reusability, because FilterInputStream changes the + * methods being called, such as read(byte[]) to read(byte[], int, int). + *

+ * See the protected methods for ways in which a subclass can easily decorate + * a stream with custom pre-, post- or error processing functionality. + * + * @author Stephen Colebourne + * @version $Id: ProxyInputStream.java 934041 2010-04-14 17:37:24Z jukka $ + */ +public abstract class ProxyInputStream extends FilterInputStream { + + /** + * Constructs a new ProxyInputStream. + * + * @param proxy the InputStream to delegate to + */ + public ProxyInputStream(InputStream proxy) { + super(proxy); + // the proxy is stored in a protected superclass variable named 'in' + } + + /** + * Invokes the delegate's read() method. + * @return the byte read or -1 if the end of stream + * @throws IOException if an I/O error occurs + */ + @Override + public int read() throws IOException { + try { + beforeRead(1); + int b = in.read(); + afterRead(b != -1 ? 1 : -1); + return b; + } catch (IOException e) { + handleIOException(e); + return -1; + } + } + + /** + * Invokes the delegate's read(byte[]) method. + * @param bts the buffer to read the bytes into + * @return the number of bytes read or -1 if the end of stream + * @throws IOException if an I/O error occurs + */ + @Override + public int read(byte[] bts) throws IOException { + try { + beforeRead(bts != null ? bts.length : 0); + int n = in.read(bts); + afterRead(n); + return n; + } catch (IOException e) { + handleIOException(e); + return -1; + } + } + + /** + * Invokes the delegate's read(byte[], int, int) method. + * @param bts the buffer to read the bytes into + * @param off The start offset + * @param len The number of bytes to read + * @return the number of bytes read or -1 if the end of stream + * @throws IOException if an I/O error occurs + */ + @Override + public int read(byte[] bts, int off, int len) throws IOException { + try { + beforeRead(len); + int n = in.read(bts, off, len); + afterRead(n); + return n; + } catch (IOException e) { + handleIOException(e); + return -1; + } + } + + /** + * Invokes the delegate's skip(long) method. + * @param ln the number of bytes to skip + * @return the actual number of bytes skipped + * @throws IOException if an I/O error occurs + */ + @Override + public long skip(long ln) throws IOException { + try { + return in.skip(ln); + } catch (IOException e) { + handleIOException(e); + return 0; + } + } + + /** + * Invokes the delegate's available() method. + * @return the number of available bytes + * @throws IOException if an I/O error occurs + */ + @Override + public int available() throws IOException { + try { + return super.available(); + } catch (IOException e) { + handleIOException(e); + return 0; + } + } + + /** + * Invokes the delegate's close() method. + * @throws IOException if an I/O error occurs + */ + @Override + public void close() throws IOException { + try { + in.close(); + } catch (IOException e) { + handleIOException(e); + } + } + + /** + * Invokes the delegate's mark(int) method. + * @param readlimit read ahead limit + */ + @Override + public synchronized void mark(int readlimit) { + in.mark(readlimit); + } + + /** + * Invokes the delegate's reset() method. + * @throws IOException if an I/O error occurs + */ + @Override + public synchronized void reset() throws IOException { + try { + in.reset(); + } catch (IOException e) { + handleIOException(e); + } + } + + /** + * Invokes the delegate's markSupported() method. + * @return true if mark is supported, otherwise false + */ + @Override + public boolean markSupported() { + return in.markSupported(); + } + + /** + * Invoked by the read methods before the call is proxied. The number + * of bytes that the caller wanted to read (1 for the {@link #read()} + * method, buffer length for {@link #read(byte[])}, etc.) is given as + * an argument. + *

+ * Subclasses can override this method to add common pre-processing + * functionality without having to override all the read methods. + * The default implementation does nothing. + *

+ * Note this method is not called from {@link #skip(long)} or + * {@link #reset()}. You need to explicitly override those methods if + * you want to add pre-processing steps also to them. + * + * @since Commons IO 2.0 + * @param n number of bytes that the caller asked to be read + * @throws IOException if the pre-processing fails + */ + protected void beforeRead(int n) throws IOException { + } + + /** + * Invoked by the read methods after the proxied call has returned + * successfully. The number of bytes returned to the caller (or -1 if + * the end of stream was reached) is given as an argument. + *

+ * Subclasses can override this method to add common post-processing + * functionality without having to override all the read methods. + * The default implementation does nothing. + *

+ * Note this method is not called from {@link #skip(long)} or + * {@link #reset()}. You need to explicitly override those methods if + * you want to add post-processing steps also to them. + * + * @since Commons IO 2.0 + * @param n number of bytes read, or -1 if the end of stream was reached + * @throws IOException if the post-processing fails + */ + protected void afterRead(int n) throws IOException { + } + + /** + * Handle any IOExceptions thrown. + *

+ * This method provides a point to implement custom exception + * handling. The default behaviour is to re-throw the exception. + * @param e The IOException thrown + * @throws IOException if an I/O error occurs + * @since Commons IO 2.0 + */ + protected void handleIOException(IOException e) throws IOException { + throw e; + } + +} diff --git a/epublib/src/main/java/me/ag2s/epublib/util/commons/io/XmlStreamReader.java b/epublib/src/main/java/me/ag2s/epublib/util/commons/io/XmlStreamReader.java new file mode 100644 index 000000000..99b6f0a19 --- /dev/null +++ b/epublib/src/main/java/me/ag2s/epublib/util/commons/io/XmlStreamReader.java @@ -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. + *

+ * IMPORTANT: This class is not related in any way to the org.xml.sax.XMLReader. + * This one IS a character stream. + *

+ * All this has to be done without consuming characters from the stream, if not + * the XML parser will not recognized the document as a valid XML. This is not + * 100% true, but it's close enough (UTF-8 BOM is not handled by all parsers + * right now, XmlStreamReader handles it and things work in all parsers). + *

+ * The XmlStreamReader class handles the charset encoding of XML documents in + * Files, raw streams and HTTP streams by offering a wide set of constructors. + *

+ * By default the charset encoding detection is lenient, the constructor with + * the lenient flag can be used for an script (following HTTP MIME and XML + * specifications). All this is nicely explained by Mark Pilgrim in his blog, + * Determining the character encoding of a feed. + *

+ * Originally developed for ROME under + * Apache License 2.0. + * + * @author Alejandro Abdelnur + * @version $Id: XmlStreamReader.java 1052161 2010-12-23 03:12:09Z niallp $ + * @see "org.apache.commons.io.output.XmlStreamWriter" + * @since Commons IO 2.0 + */ +public class XmlStreamReader extends Reader { + private static final int BUFFER_SIZE = 4096; + + private static final String UTF_8 = "UTF-8"; + + private static final String US_ASCII = "US-ASCII"; + + private static final String UTF_16BE = "UTF-16BE"; + + private static final String UTF_16LE = "UTF-16LE"; + + private static final String UTF_16 = "UTF-16"; + + private static final String EBCDIC = "CP1047"; + + private static final ByteOrderMark[] BOMS = new ByteOrderMark[] { + ByteOrderMark.UTF_8, + ByteOrderMark.UTF_16BE, + ByteOrderMark.UTF_16LE + }; + private static final ByteOrderMark[] XML_GUESS_BYTES = new ByteOrderMark[] { + new ByteOrderMark(UTF_8, 0x3C, 0x3F, 0x78, 0x6D), + new ByteOrderMark(UTF_16BE, 0x00, 0x3C, 0x00, 0x3F), + new ByteOrderMark(UTF_16LE, 0x3C, 0x00, 0x3F, 0x00), + new ByteOrderMark(EBCDIC, 0x4C, 0x6F, 0xA7, 0x94) + }; + + + private final Reader reader; + + private final String encoding; + + private final String defaultEncoding; + + /** + * Returns the default encoding to use if none is set in HTTP content-type, + * XML prolog and the rules based on content-type are not adequate. + *

+ * If it is NULL the content-type based rules are used. + * + * @return the default encoding to use. + */ + public String getDefaultEncoding() { + return defaultEncoding; + } + + /** + * Creates a Reader for a File. + *

+ * It looks for the UTF-8 BOM first, if none sniffs the XML prolog charset, + * if this is also missing defaults to UTF-8. + *

+ * It does a lenient charset encoding detection, check the constructor with + * the lenient parameter for details. + * + * @param file File to create a Reader from. + * @throws IOException thrown if there is a problem reading the file. + */ + public XmlStreamReader(File file) throws IOException { + this(new FileInputStream(file)); + } + + /** + * Creates a Reader for a raw InputStream. + *

+ * It follows the same logic used for files. + *

+ * It does a lenient charset encoding detection, check the constructor with + * the lenient parameter for details. + * + * @param is InputStream to create a Reader from. + * @throws IOException thrown if there is a problem reading the stream. + */ + public XmlStreamReader(InputStream is) throws IOException { + this(is, true); + } + + /** + * Creates a Reader for a raw InputStream. + *

+ * It follows the same logic used for files. + *

+ * If lenient detection is indicated and the detection above fails as per + * specifications it then attempts the following: + *

+ * If the content type was 'text/html' it replaces it with 'text/xml' and + * tries the detection again. + *

+ * Else if the XML prolog had a charset encoding that encoding is used. + *

+ * Else if the content type had a charset encoding that encoding is used. + *

+ * Else 'UTF-8' is used. + *

+ * If lenient detection is indicated an XmlStreamReaderException is never + * thrown. + * + * @param is InputStream to create a Reader from. + * @param lenient indicates if the charset encoding detection should be + * relaxed. + * @throws IOException thrown if there is a problem reading the stream. + * @throws XmlStreamReaderException thrown if the charset encoding could not + * be determined according to the specs. + */ + public XmlStreamReader(InputStream is, boolean lenient) throws IOException { + this(is, lenient, null); + } + + /** + * Creates a Reader for a raw InputStream. + *

+ * It follows the same logic used for files. + *

+ * If lenient detection is indicated and the detection above fails as per + * specifications it then attempts the following: + *

+ * If the content type was 'text/html' it replaces it with 'text/xml' and + * tries the detection again. + *

+ * Else if the XML prolog had a charset encoding that encoding is used. + *

+ * Else if the content type had a charset encoding that encoding is used. + *

+ * Else 'UTF-8' is used. + *

+ * If lenient detection is indicated an XmlStreamReaderException is never + * thrown. + * + * @param is InputStream to create a Reader from. + * @param lenient indicates if the charset encoding detection should be + * relaxed. + * @param defaultEncoding The default encoding + * @throws IOException thrown if there is a problem reading the stream. + * @throws XmlStreamReaderException thrown if the charset encoding could not + * be determined according to the specs. + */ + public XmlStreamReader(InputStream is, boolean lenient, String defaultEncoding) throws IOException { + this.defaultEncoding = defaultEncoding; + BOMInputStream bom = new BOMInputStream(new BufferedInputStream(is, BUFFER_SIZE), false, BOMS); + BOMInputStream pis = new BOMInputStream(bom, true, XML_GUESS_BYTES); + this.encoding = doRawStream(bom, pis, lenient); + this.reader = new InputStreamReader(pis, encoding); + } + + /** + * Creates a Reader using the InputStream of a URL. + *

+ * If the URL is not of type HTTP and there is not 'content-type' header in + * the fetched data it uses the same logic used for Files. + *

+ * If the URL is a HTTP Url or there is a 'content-type' header in the + * fetched data it uses the same logic used for an InputStream with + * content-type. + *

+ * It does a lenient charset encoding detection, check the constructor with + * the lenient parameter for details. + * + * @param url URL to create a Reader from. + * @throws IOException thrown if there is a problem reading the stream of + * the URL. + */ + public XmlStreamReader(URL url) throws IOException { + this(url.openConnection(), null); + } + + /** + * Creates a Reader using the InputStream of a URLConnection. + *

+ * If the URLConnection is not of type HttpURLConnection and there is not + * 'content-type' header in the fetched data it uses the same logic used for + * files. + *

+ * If the URLConnection is a HTTP Url or there is a 'content-type' header in + * the fetched data it uses the same logic used for an InputStream with + * content-type. + *

+ * It does a lenient charset encoding detection, check the constructor with + * the lenient parameter for details. + * + * @param conn URLConnection to create a Reader from. + * @param defaultEncoding The default encoding + * @throws IOException thrown if there is a problem reading the stream of + * the URLConnection. + */ + public XmlStreamReader(URLConnection conn, String defaultEncoding) throws IOException { + this.defaultEncoding = defaultEncoding; + boolean lenient = true; + String contentType = conn.getContentType(); + InputStream is = conn.getInputStream(); + BOMInputStream bom = new BOMInputStream(new BufferedInputStream(is, BUFFER_SIZE), false, BOMS); + BOMInputStream pis = new BOMInputStream(bom, true, XML_GUESS_BYTES); + if (conn instanceof HttpURLConnection || contentType != null) { + this.encoding = doHttpStream(bom, pis, contentType, lenient); + } else { + this.encoding = doRawStream(bom, pis, lenient); + } + this.reader = new InputStreamReader(pis, encoding); + } + + /** + * Creates a Reader using an InputStream an the associated content-type + * header. + *

+ * First it checks if the stream has BOM. If there is not BOM checks the + * content-type encoding. If there is not content-type encoding checks the + * XML prolog encoding. If there is not XML prolog encoding uses the default + * encoding mandated by the content-type MIME type. + *

+ * It does a lenient charset encoding detection, check the constructor with + * the lenient parameter for details. + * + * @param is InputStream to create the reader from. + * @param httpContentType content-type header to use for the resolution of + * the charset encoding. + * @throws IOException thrown if there is a problem reading the file. + */ + public XmlStreamReader(InputStream is, String httpContentType) + throws IOException { + this(is, httpContentType, true); + } + + /** + * Creates a Reader using an InputStream an the associated content-type + * header. This constructor is lenient regarding the encoding detection. + *

+ * First it checks if the stream has BOM. If there is not BOM checks the + * content-type encoding. If there is not content-type encoding checks the + * XML prolog encoding. If there is not XML prolog encoding uses the default + * encoding mandated by the content-type MIME type. + *

+ * If lenient detection is indicated and the detection above fails as per + * specifications it then attempts the following: + *

+ * If the content type was 'text/html' it replaces it with 'text/xml' and + * tries the detection again. + *

+ * Else if the XML prolog had a charset encoding that encoding is used. + *

+ * Else if the content type had a charset encoding that encoding is used. + *

+ * Else 'UTF-8' is used. + *

+ * If lenient detection is indicated an XmlStreamReaderException is never + * thrown. + * + * @param is InputStream to create the reader from. + * @param httpContentType content-type header to use for the resolution of + * the charset encoding. + * @param lenient indicates if the charset encoding detection should be + * relaxed. + * @param defaultEncoding The default encoding + * @throws IOException thrown if there is a problem reading the file. + * @throws XmlStreamReaderException thrown if the charset encoding could not + * be determined according to the specs. + */ + public XmlStreamReader(InputStream is, String httpContentType, + boolean lenient, String defaultEncoding) throws IOException { + this.defaultEncoding = defaultEncoding; + BOMInputStream bom = new BOMInputStream(new BufferedInputStream(is, BUFFER_SIZE), false, BOMS); + BOMInputStream pis = new BOMInputStream(bom, true, XML_GUESS_BYTES); + this.encoding = doHttpStream(bom, pis, httpContentType, lenient); + this.reader = new InputStreamReader(pis, encoding); + } + + /** + * Creates a Reader using an InputStream an the associated content-type + * header. This constructor is lenient regarding the encoding detection. + *

+ * First it checks if the stream has BOM. If there is not BOM checks the + * content-type encoding. If there is not content-type encoding checks the + * XML prolog encoding. If there is not XML prolog encoding uses the default + * encoding mandated by the content-type MIME type. + *

+ * If lenient detection is indicated and the detection above fails as per + * specifications it then attempts the following: + *

+ * If the content type was 'text/html' it replaces it with 'text/xml' and + * tries the detection again. + *

+ * Else if the XML prolog had a charset encoding that encoding is used. + *

+ * Else if the content type had a charset encoding that encoding is used. + *

+ * Else 'UTF-8' is used. + *

+ * If lenient detection is indicated an XmlStreamReaderException is never + * thrown. + * + * @param is InputStream to create the reader from. + * @param httpContentType content-type header to use for the resolution of + * the charset encoding. + * @param lenient indicates if the charset encoding detection should be + * relaxed. + * @throws IOException thrown if there is a problem reading the file. + * @throws XmlStreamReaderException thrown if the charset encoding could not + * be determined according to the specs. + */ + public XmlStreamReader(InputStream is, String httpContentType, + boolean lenient) throws IOException { + this(is, httpContentType, lenient, null); + } + + /** + * Returns the charset encoding of the XmlStreamReader. + * + * @return charset encoding. + */ + public String getEncoding() { + return encoding; + } + + /** + * Invokes the underlying reader's read(char[], int, int) method. + * @param buf the buffer to read the characters into + * @param offset The start offset + * @param len The number of bytes to read + * @return the number of characters read or -1 if the end of stream + * @throws IOException if an I/O error occurs + */ + @Override + public int read(char[] buf, int offset, int len) throws IOException { + return reader.read(buf, offset, len); + } + + /** + * Closes the XmlStreamReader stream. + * + * @throws IOException thrown if there was a problem closing the stream. + */ + @Override + public void close() throws IOException { + reader.close(); + } + + /** + * Process the raw stream. + * + * @param bom BOMInputStream to detect byte order marks + * @param pis BOMInputStream to guess XML encoding + * @param lenient indicates if the charset encoding detection should be + * relaxed. + * @return the encoding to be used + * @throws IOException thrown if there is a problem reading the stream. + */ + private String doRawStream(BOMInputStream bom, BOMInputStream pis, boolean lenient) + throws IOException { + String bomEnc = bom.getBOMCharsetName(); + String xmlGuessEnc = pis.getBOMCharsetName(); + String xmlEnc = getXmlProlog(pis, xmlGuessEnc); + try { + return calculateRawEncoding(bomEnc, xmlGuessEnc, xmlEnc); + } catch (XmlStreamReaderException ex) { + if (lenient) { + return doLenientDetection(null, ex); + } else { + throw ex; + } + } + } + + /** + * Process a HTTP stream. + * + * @param bom BOMInputStream to detect byte order marks + * @param pis BOMInputStream to guess XML encoding + * @param httpContentType The HTTP content type + * @param lenient indicates if the charset encoding detection should be + * relaxed. + * @return the encoding to be used + * @throws IOException thrown if there is a problem reading the stream. + */ + private String doHttpStream(BOMInputStream bom, BOMInputStream pis, String httpContentType, + boolean lenient) throws IOException { + String bomEnc = bom.getBOMCharsetName(); + String xmlGuessEnc = pis.getBOMCharsetName(); + String xmlEnc = getXmlProlog(pis, xmlGuessEnc); + try { + return calculateHttpEncoding(httpContentType, bomEnc, + xmlGuessEnc, xmlEnc, lenient); + } catch (XmlStreamReaderException ex) { + if (lenient) { + return doLenientDetection(httpContentType, ex); + } else { + throw ex; + } + } + } + + /** + * Do lenient detection. + * + * @param httpContentType content-type header to use for the resolution of + * the charset encoding. + * @param ex The thrown exception + * @return the encoding + * @throws IOException thrown if there is a problem reading the stream. + */ + private String doLenientDetection(String httpContentType, + XmlStreamReaderException ex) throws IOException { + if (httpContentType != null && httpContentType.startsWith("text/html")) { + httpContentType = httpContentType.substring("text/html".length()); + httpContentType = "text/xml" + httpContentType; + try { + return calculateHttpEncoding(httpContentType, ex.getBomEncoding(), + ex.getXmlGuessEncoding(), ex.getXmlEncoding(), true); + } catch (XmlStreamReaderException ex2) { + ex = ex2; + } + } + String encoding = ex.getXmlEncoding(); + if (encoding == null) { + encoding = ex.getContentTypeEncoding(); + } + if (encoding == null) { + encoding = (defaultEncoding == null) ? UTF_8 : defaultEncoding; + } + return encoding; + } + + /** + * Calculate the raw encoding. + * + * @param bomEnc BOM encoding + * @param xmlGuessEnc XML Guess encoding + * @param xmlEnc XML encoding + * @return the raw encoding + * @throws IOException thrown if there is a problem reading the stream. + */ + String calculateRawEncoding(String bomEnc, String xmlGuessEnc, + String xmlEnc) throws IOException { + + // BOM is Null + if (bomEnc == null) { + if (xmlGuessEnc == null || xmlEnc == null) { + return (defaultEncoding == null ? UTF_8 : defaultEncoding); + } + if (xmlEnc.equals(UTF_16) && + (xmlGuessEnc.equals(UTF_16BE) || xmlGuessEnc.equals(UTF_16LE))) { + return xmlGuessEnc; + } + return xmlEnc; + } + + // BOM is UTF-8 + if (bomEnc.equals(UTF_8)) { + if (xmlGuessEnc != null && !xmlGuessEnc.equals(UTF_8)) { + String msg = MessageFormat.format(RAW_EX_1, new Object[] { bomEnc, xmlGuessEnc, xmlEnc }); + throw new XmlStreamReaderException(msg, bomEnc, xmlGuessEnc, xmlEnc); + } + if (xmlEnc != null && !xmlEnc.equals(UTF_8)) { + String msg = MessageFormat.format(RAW_EX_1, new Object[] { bomEnc, xmlGuessEnc, xmlEnc }); + throw new XmlStreamReaderException(msg, bomEnc, xmlGuessEnc, xmlEnc); + } + return bomEnc; + } + + // BOM is UTF-16BE or UTF-16LE + if (bomEnc.equals(UTF_16BE) || bomEnc.equals(UTF_16LE)) { + if (xmlGuessEnc != null && !xmlGuessEnc.equals(bomEnc)) { + String msg = MessageFormat.format(RAW_EX_1, new Object[] { bomEnc, xmlGuessEnc, xmlEnc }); + throw new XmlStreamReaderException(msg, bomEnc, xmlGuessEnc, xmlEnc); + } + if (xmlEnc != null && !xmlEnc.equals(UTF_16) && !xmlEnc.equals(bomEnc)) { + String msg = MessageFormat.format(RAW_EX_1, new Object[] { bomEnc, xmlGuessEnc, xmlEnc }); + throw new XmlStreamReaderException(msg, bomEnc, xmlGuessEnc, xmlEnc); + } + return bomEnc; + } + + // BOM is something else + String msg = MessageFormat.format(RAW_EX_2, new Object[] { bomEnc, xmlGuessEnc, xmlEnc }); + throw new XmlStreamReaderException(msg, bomEnc, xmlGuessEnc, xmlEnc); + } + + + /** + * Calculate the HTTP encoding. + * + * @param httpContentType The HTTP content type + * @param bomEnc BOM encoding + * @param xmlGuessEnc XML Guess encoding + * @param xmlEnc XML encoding + * @param lenient indicates if the charset encoding detection should be + * relaxed. + * @return the HTTP encoding + * @throws IOException thrown if there is a problem reading the stream. + */ + String calculateHttpEncoding(String httpContentType, + String bomEnc, String xmlGuessEnc, String xmlEnc, + boolean lenient) throws IOException { + + // Lenient and has XML encoding + if (lenient && xmlEnc != null) { + return xmlEnc; + } + + // Determine mime/encoding content types from HTTP Content Type + String cTMime = getContentTypeMime(httpContentType); + String cTEnc = getContentTypeEncoding(httpContentType); + boolean appXml = isAppXml(cTMime); + boolean textXml = isTextXml(cTMime); + + // Mime type NOT "application/xml" or "text/xml" + if (!appXml && !textXml) { + String msg = MessageFormat.format(HTTP_EX_3, cTMime, cTEnc, bomEnc, xmlGuessEnc, xmlEnc); + throw new XmlStreamReaderException(msg, cTMime, cTEnc, bomEnc, xmlGuessEnc, xmlEnc); + } + + // No content type encoding + if (cTEnc == null) { + if (appXml) { + return calculateRawEncoding(bomEnc, xmlGuessEnc, xmlEnc); + } else { + return (defaultEncoding == null) ? US_ASCII : defaultEncoding; + } + } + + // UTF-16BE or UTF-16LE content type encoding + if (cTEnc.equals(UTF_16BE) || cTEnc.equals(UTF_16LE)) { + if (bomEnc != null) { + String msg = MessageFormat.format(HTTP_EX_1, cTMime, cTEnc, bomEnc, xmlGuessEnc, xmlEnc); + throw new XmlStreamReaderException(msg, cTMime, cTEnc, bomEnc, xmlGuessEnc, xmlEnc); + } + return cTEnc; + } + + // UTF-16 content type encoding + if (cTEnc.equals(UTF_16)) { + if (bomEnc != null && bomEnc.startsWith(UTF_16)) { + return bomEnc; + } + String msg = MessageFormat.format(HTTP_EX_2, cTMime, cTEnc, bomEnc, xmlGuessEnc, xmlEnc); + throw new XmlStreamReaderException(msg, cTMime, cTEnc, bomEnc, xmlGuessEnc, xmlEnc); + } + + return cTEnc; + } + + /** + * Returns MIME type or NULL if httpContentType is NULL. + * + * @param httpContentType the HTTP content type + * @return The mime content type + */ + static String getContentTypeMime(String httpContentType) { + String mime = null; + if (httpContentType != null) { + int i = httpContentType.indexOf(";"); + if (i >= 0) { + mime = httpContentType.substring(0, i); + } else { + mime = httpContentType; + } + mime = mime.trim(); + } + return mime; + } + + private static final Pattern CHARSET_PATTERN = Pattern + .compile("charset=[\"']?([.[^; \"']]*)[\"']?"); + + /** + * Returns charset parameter value, NULL if not present, NULL if + * httpContentType is NULL. + * + * @param httpContentType the HTTP content type + * @return The content type encoding + */ + static String getContentTypeEncoding(String httpContentType) { + String encoding = null; + if (httpContentType != null) { + int i = httpContentType.indexOf(";"); + if (i > -1) { + String postMime = httpContentType.substring(i + 1); + Matcher m = CHARSET_PATTERN.matcher(postMime); + encoding = (m.find()) ? m.group(1) : null; + encoding = (encoding != null) ? encoding.toUpperCase() : null; + } + } + return encoding; + } + + public static final Pattern ENCODING_PATTERN = Pattern.compile( + "<\\?xml.*encoding[\\s]*=[\\s]*((?:\".[^\"]*\")|(?:'.[^']*'))", + Pattern.MULTILINE); + + /** + * Returns the encoding declared in the , NULL if none. + * + * @param is InputStream to create the reader from. + * @param guessedEnc guessed encoding + * @return the encoding declared in the + * @throws IOException thrown if there is a problem reading the stream. + */ + private static String getXmlProlog(InputStream is, String guessedEnc) + throws IOException { + String encoding = null; + if (guessedEnc != null) { + byte[] bytes = new byte[BUFFER_SIZE]; + is.mark(BUFFER_SIZE); + int offset = 0; + int max = BUFFER_SIZE; + int c = is.read(bytes, offset, max); + int firstGT = -1; + String xmlProlog = null; + while (c != -1 && firstGT == -1 && offset < BUFFER_SIZE) { + offset += c; + max -= c; + c = is.read(bytes, offset, max); + xmlProlog = new String(bytes, 0, offset, guessedEnc); + firstGT = xmlProlog.indexOf('>'); + } + if (firstGT == -1) { + if (c == -1) { + throw new IOException("Unexpected end of XML stream"); + } else { + throw new IOException( + "XML prolog or ROOT element not found on first " + + offset + " bytes"); + } + } + int bytesRead = offset; + if (bytesRead > 0) { + is.reset(); + BufferedReader bReader = new BufferedReader(new StringReader( + xmlProlog.substring(0, firstGT + 1))); + StringBuilder prolog = new StringBuilder(); + String line = bReader.readLine(); + while (line != null) { + prolog.append(line); + line = bReader.readLine(); + } + Matcher m = ENCODING_PATTERN.matcher(prolog); + if (m.find()) { + encoding = m.group(1).toUpperCase(); + encoding = encoding.substring(1, encoding.length() - 1); + } + } + } + return encoding; + } + + /** + * Indicates if the MIME type belongs to the APPLICATION XML family. + * + * @param mime The mime type + * @return true if the mime type belongs to the APPLICATION XML family, + * otherwise false + */ + static boolean isAppXml(String mime) { + return mime != null && + (mime.equals("application/xml") || + mime.equals("application/xml-dtd") || + mime.equals("application/xml-external-parsed-entity") || + (mime.startsWith("application/") && mime.endsWith("+xml"))); + } + + /** + * Indicates if the MIME type belongs to the TEXT XML family. + * + * @param mime The mime type + * @return true if the mime type belongs to the TEXT XML family, + * otherwise false + */ + static boolean isTextXml(String mime) { + return mime != null && + (mime.equals("text/xml") || + mime.equals("text/xml-external-parsed-entity") || + (mime.startsWith("text/") && mime.endsWith("+xml"))); + } + + private static final String RAW_EX_1 = + "Invalid encoding, BOM [{0}] XML guess [{1}] XML prolog [{2}] encoding mismatch"; + + private static final String RAW_EX_2 = + "Invalid encoding, BOM [{0}] XML guess [{1}] XML prolog [{2}] unknown BOM"; + + private static final String HTTP_EX_1 = + "Invalid encoding, CT-MIME [{0}] CT-Enc [{1}] BOM [{2}] XML guess [{3}] XML prolog [{4}], BOM must be NULL"; + + private static final String HTTP_EX_2 = + "Invalid encoding, CT-MIME [{0}] CT-Enc [{1}] BOM [{2}] XML guess [{3}] XML prolog [{4}], encoding mismatch"; + + private static final String HTTP_EX_3 = + "Invalid encoding, CT-MIME [{0}] CT-Enc [{1}] BOM [{2}] XML guess [{3}] XML prolog [{4}], Invalid MIME"; + +} diff --git a/epublib/src/main/java/me/ag2s/epublib/util/commons/io/XmlStreamReaderException.java b/epublib/src/main/java/me/ag2s/epublib/util/commons/io/XmlStreamReaderException.java new file mode 100644 index 000000000..2b3bbec49 --- /dev/null +++ b/epublib/src/main/java/me/ag2s/epublib/util/commons/io/XmlStreamReaderException.java @@ -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. + *

+ * The exception returns the unconsumed InputStream to allow the application to + * do an alternate processing with the stream. Note that the original + * InputStream given to the XmlStreamReader cannot be used as that one has been + * already read. + * + * @author Alejandro Abdelnur + * @version $Id: XmlStreamReaderException.java 1004112 2010-10-04 04:48:25Z niallp $ + * @since Commons IO 2.0 + */ +public class XmlStreamReaderException extends IOException { + + private static final long serialVersionUID = 1L; + + private final String bomEncoding; + + private final String xmlGuessEncoding; + + private final String xmlEncoding; + + private final String contentTypeMime; + + private final String contentTypeEncoding; + + /** + * Creates an exception instance if the charset encoding could not be + * determined. + *

+ * Instances of this exception are thrown by the XmlStreamReader. + * + * @param msg message describing the reason for the exception. + * @param bomEnc BOM encoding. + * @param xmlGuessEnc XML guess encoding. + * @param xmlEnc XML prolog encoding. + */ + public XmlStreamReaderException(String msg, String bomEnc, + String xmlGuessEnc, String xmlEnc) { + this(msg, null, null, bomEnc, xmlGuessEnc, xmlEnc); + } + + /** + * Creates an exception instance if the charset encoding could not be + * determined. + *

+ * Instances of this exception are thrown by the XmlStreamReader. + * + * @param msg message describing the reason for the exception. + * @param ctMime MIME type in the content-type. + * @param ctEnc encoding in the content-type. + * @param bomEnc BOM encoding. + * @param xmlGuessEnc XML guess encoding. + * @param xmlEnc XML prolog encoding. + */ + public XmlStreamReaderException(String msg, String ctMime, String ctEnc, + String bomEnc, String xmlGuessEnc, String xmlEnc) { + super(msg); + contentTypeMime = ctMime; + contentTypeEncoding = ctEnc; + bomEncoding = bomEnc; + xmlGuessEncoding = xmlGuessEnc; + xmlEncoding = xmlEnc; + } + + /** + * Returns the BOM encoding found in the InputStream. + * + * @return the BOM encoding, null if none. + */ + public String getBomEncoding() { + return bomEncoding; + } + + /** + * Returns the encoding guess based on the first bytes of the InputStream. + * + * @return the encoding guess, null if it couldn't be guessed. + */ + public String getXmlGuessEncoding() { + return xmlGuessEncoding; + } + + /** + * Returns the encoding found in the XML prolog of the InputStream. + * + * @return the encoding of the XML prolog, null if none. + */ + public String getXmlEncoding() { + return xmlEncoding; + } + + /** + * Returns the MIME type in the content-type used to attempt determining the + * encoding. + * + * @return the MIME type in the content-type, null if there was not + * content-type or the encoding detection did not involve HTTP. + */ + public String getContentTypeMime() { + return contentTypeMime; + } + + /** + * Returns the encoding in the content-type used to attempt determining the + * encoding. + * + * @return the encoding in the content-type, null if there was not + * content-type, no encoding in it or the encoding detection did not + * involve HTTP. + */ + public String getContentTypeEncoding() { + return contentTypeEncoding; + } +} diff --git a/epublib/src/main/resources/dtd/openebook.org/dtds/oeb-1.2/oeb12.ent b/epublib/src/main/resources/dtd/openebook.org/dtds/oeb-1.2/oeb12.ent new file mode 100644 index 000000000..f7b58d257 --- /dev/null +++ b/epublib/src/main/resources/dtd/openebook.org/dtds/oeb-1.2/oeb12.ent @@ -0,0 +1,1135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/epublib/src/main/resources/dtd/openebook.org/dtds/oeb-1.2/oebpkg12.dtd b/epublib/src/main/resources/dtd/openebook.org/dtds/oeb-1.2/oebpkg12.dtd new file mode 100644 index 000000000..34cc2b10e --- /dev/null +++ b/epublib/src/main/resources/dtd/openebook.org/dtds/oeb-1.2/oebpkg12.dtd @@ -0,0 +1,390 @@ + + + + + + + + + +%OEBEntities; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/epublib/src/main/resources/dtd/www.daisy.org/z3986/2005/ncx-2005-1.dtd b/epublib/src/main/resources/dtd/www.daisy.org/z3986/2005/ncx-2005-1.dtd new file mode 100644 index 000000000..b889c41a5 --- /dev/null +++ b/epublib/src/main/resources/dtd/www.daisy.org/z3986/2005/ncx-2005-1.dtd @@ -0,0 +1,269 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/epublib/src/main/resources/dtd/www.w3.org/TR/ruby/xhtml-ruby-1.mod b/epublib/src/main/resources/dtd/www.w3.org/TR/ruby/xhtml-ruby-1.mod new file mode 100644 index 000000000..a44bb3fa9 --- /dev/null +++ b/epublib/src/main/resources/dtd/www.w3.org/TR/ruby/xhtml-ruby-1.mod @@ -0,0 +1,242 @@ + + + + + + + + + + + + + + + + + + + + + + + +]]> + +]]> + + + +]]> + + + + + + + + + + + + + +]]> + + + + + + +]]> + + + + + + +]]> +]]> + + + + + + + +]]> + + + + + + + + +]]> + + + + +]]> +]]> + + + + + + +]]> +]]> + + + + + + + + + + +]]> + + + + + +]]> + + + + + +]]> +]]> + + + + + +]]> + + + + + +]]> + + + + + +]]> +]]> +]]> + + diff --git a/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-arch-1.mod b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-arch-1.mod new file mode 100644 index 000000000..4a4fa6caa --- /dev/null +++ b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-arch-1.mod @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + diff --git a/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-attribs-1.mod b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-attribs-1.mod new file mode 100644 index 000000000..104e57002 --- /dev/null +++ b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-attribs-1.mod @@ -0,0 +1,142 @@ + + + + + + + + + +]]> + + + + +]]> + + + + +]]> + + + + + + + + +]]> + + + + + + + + + + + +]]> + + +]]> + + + + + + + + + + + + + + + + diff --git a/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-base-1.mod b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-base-1.mod new file mode 100644 index 000000000..dca21ca07 --- /dev/null +++ b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-base-1.mod @@ -0,0 +1,53 @@ + + + + + + + + + + + + +]]> + + + +]]> + + + + diff --git a/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-bdo-1.mod b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-bdo-1.mod new file mode 100644 index 000000000..fcd67bf6b --- /dev/null +++ b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-bdo-1.mod @@ -0,0 +1,47 @@ + + + + + + + + + + +]]> + + + +]]> + + diff --git a/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-blkphras-1.mod b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-blkphras-1.mod new file mode 100644 index 000000000..0eeb16419 --- /dev/null +++ b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-blkphras-1.mod @@ -0,0 +1,164 @@ + + + + + + + + + + +]]> + + + +]]> + + + + + +]]> + + + +]]> + + + + + +]]> + + + +]]> + + + + + + + + +]]> + + + +]]> + + + + +]]> + + + +]]> + + + + +]]> + + + +]]> + + + + +]]> + + + +]]> + + + + +]]> + + + +]]> + + + + +]]> + + + +]]> + + diff --git a/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-blkpres-1.mod b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-blkpres-1.mod new file mode 100644 index 000000000..30968bb7a --- /dev/null +++ b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-blkpres-1.mod @@ -0,0 +1,40 @@ + + + + + + + + + + +]]> + + + +]]> + + diff --git a/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-blkstruct-1.mod b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-blkstruct-1.mod new file mode 100644 index 000000000..ab37c73c0 --- /dev/null +++ b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-blkstruct-1.mod @@ -0,0 +1,57 @@ + + + + + + + + + + +]]> + + + +]]> + + + + + +]]> + + + +]]> + + diff --git a/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-charent-1.mod b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-charent-1.mod new file mode 100644 index 000000000..b1faf15cc --- /dev/null +++ b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-charent-1.mod @@ -0,0 +1,39 @@ + + + + + + + +%xhtml-lat1; + + +%xhtml-symbol; + + +%xhtml-special; + + diff --git a/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-csismap-1.mod b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-csismap-1.mod new file mode 100644 index 000000000..5977f038b --- /dev/null +++ b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-csismap-1.mod @@ -0,0 +1,114 @@ + + + + + + + + + + +]]> + + + + + + +]]> + + + + + + + + + + + + + + + + + + + +]]> + + + +]]> + + diff --git a/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-datatypes-1.mod b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-datatypes-1.mod new file mode 100644 index 000000000..a2ea3ae85 --- /dev/null +++ b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-datatypes-1.mod @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-datatypes-1.mod.1 b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-datatypes-1.mod.1 new file mode 100644 index 000000000..a2ea3ae85 --- /dev/null +++ b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-datatypes-1.mod.1 @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-edit-1.mod b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-edit-1.mod new file mode 100644 index 000000000..2d3d43f1c --- /dev/null +++ b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-edit-1.mod @@ -0,0 +1,66 @@ + + + + + + + + + + + + +]]> + + + +]]> + + + + + + + +]]> + + + +]]> + + diff --git a/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-events-1.mod b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-events-1.mod new file mode 100644 index 000000000..ad8a798cf --- /dev/null +++ b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-events-1.mod @@ -0,0 +1,135 @@ + + + + + + + + + + +]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-form-1.mod b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-form-1.mod new file mode 100644 index 000000000..98b0b926a --- /dev/null +++ b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-form-1.mod @@ -0,0 +1,292 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + +]]> + + + +]]> + + + + + + + + +]]> + + + +]]> + + + + + + +]]> + + + + + +]]> + + + + + + +]]> + + + +]]> + + + + + + +]]> + + + +]]> + + + + + + +]]> + + + +]]> + + + + + + +]]> + + + +]]> + + + + + + + + +]]> + + + +]]> + + + + + + +]]> + + + +]]> + + + + + + +]]> + + + +]]> + + diff --git a/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-framework-1.mod b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-framework-1.mod new file mode 100644 index 000000000..f37976a6c --- /dev/null +++ b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-framework-1.mod @@ -0,0 +1,97 @@ + + + + + + + + +%xhtml-arch.mod;]]> + + + +%xhtml-notations.mod;]]> + + + +%xhtml-datatypes.mod;]]> + + + +%xhtml-xlink.mod; + + + +%xhtml-qname.mod;]]> + + + +%xhtml-events.mod;]]> + + + +%xhtml-attribs.mod;]]> + + + +%xhtml-model.redecl; + + + +%xhtml-model.mod;]]> + + + +%xhtml-charent.mod;]]> + + diff --git a/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-hypertext-1.mod b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-hypertext-1.mod new file mode 100644 index 000000000..85d8348fb --- /dev/null +++ b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-hypertext-1.mod @@ -0,0 +1,54 @@ + + + + + + + + + + + + +]]> + + + +]]> + + diff --git a/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-image-1.mod b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-image-1.mod new file mode 100644 index 000000000..7eea4f9a2 --- /dev/null +++ b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-image-1.mod @@ -0,0 +1,51 @@ + + + + + + + + + + + + +]]> + + + +]]> + + diff --git a/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-inlphras-1.mod b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-inlphras-1.mod new file mode 100644 index 000000000..ebada109f --- /dev/null +++ b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-inlphras-1.mod @@ -0,0 +1,203 @@ + + + + + + + + + + +]]> + + + +]]> + + + + + +]]> + + + +]]> + + + + + +]]> + + + +]]> + + + + + +]]> + + + +]]> + + + + + +]]> + + + +]]> + + + + + +]]> + + + +]]> + + + + + +]]> + + + +]]> + + + + + +]]> + + + +]]> + + + + + +]]> + + + +]]> + + + + + +]]> + + + +]]> + + + + + +]]> + + + +]]> + + diff --git a/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-inlpres-1.mod b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-inlpres-1.mod new file mode 100644 index 000000000..3e41322c4 --- /dev/null +++ b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-inlpres-1.mod @@ -0,0 +1,138 @@ + + + + + + + + + + +]]> + + + +]]> + + + + + +]]> + + + +]]> + + + + + +]]> + + + +]]> + + + + + +]]> + + + +]]> + + + + + +]]> + + + +]]> + + + + + +]]> + + + +]]> + + + + + +]]> + + + +]]> + + diff --git a/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-inlstruct-1.mod b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-inlstruct-1.mod new file mode 100644 index 000000000..4d6bd01a6 --- /dev/null +++ b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-inlstruct-1.mod @@ -0,0 +1,62 @@ + + + + + + + + + + + + + +]]> + + + +]]> + + + + + + + +]]> + + + +]]> + + diff --git a/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-inlstyle-1.mod b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-inlstyle-1.mod new file mode 100644 index 000000000..6d526cd17 --- /dev/null +++ b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-inlstyle-1.mod @@ -0,0 +1,34 @@ + + + + + + + + + + + + diff --git a/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-lat1.ent b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-lat1.ent new file mode 100644 index 000000000..ffee223eb --- /dev/null +++ b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-lat1.ent @@ -0,0 +1,196 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-link-1.mod b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-link-1.mod new file mode 100644 index 000000000..4a15f1dd8 --- /dev/null +++ b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-link-1.mod @@ -0,0 +1,59 @@ + + + + + + + + + + + + +]]> + + + +]]> + + diff --git a/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-list-1.mod b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-list-1.mod new file mode 100644 index 000000000..72bdb25c5 --- /dev/null +++ b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-list-1.mod @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + +]]> + + + +]]> + + + + + + +]]> + + + +]]> + + + + + + +]]> + + + +]]> + + + + + + +]]> + + + +]]> + + + + + + +]]> + + + +]]> + + + + + + +]]> + + + +]]> + + diff --git a/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-meta-1.mod b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-meta-1.mod new file mode 100644 index 000000000..d2f6d2c65 --- /dev/null +++ b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-meta-1.mod @@ -0,0 +1,47 @@ + + + + + + + + + + + + +]]> + + + +]]> + + diff --git a/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-notations-1.mod b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-notations-1.mod new file mode 100644 index 000000000..2da12d023 --- /dev/null +++ b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-notations-1.mod @@ -0,0 +1,114 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-object-1.mod b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-object-1.mod new file mode 100644 index 000000000..bee7aeb02 --- /dev/null +++ b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-object-1.mod @@ -0,0 +1,60 @@ + + + + + + + + + + + + +]]> + + + +]]> + + diff --git a/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-param-1.mod b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-param-1.mod new file mode 100644 index 000000000..4ba079160 --- /dev/null +++ b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-param-1.mod @@ -0,0 +1,48 @@ + + + + + + + + + + + + +]]> + + + +]]> + + diff --git a/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-pres-1.mod b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-pres-1.mod new file mode 100644 index 000000000..42a0d6dfe --- /dev/null +++ b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-pres-1.mod @@ -0,0 +1,38 @@ + + + + + + + + +%xhtml-inlpres.mod;]]> + + + +%xhtml-blkpres.mod;]]> + + diff --git a/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-qname-1.mod b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-qname-1.mod new file mode 100644 index 000000000..35c180a68 --- /dev/null +++ b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-qname-1.mod @@ -0,0 +1,318 @@ + + + + + + + + + + + + + + + + + + + + + + + + + +]]> + + + + +%xhtml-qname-extra.mod; + + + + + + + + + +]]> + + + + + + + + + + + + + + + + + + + +]]> + + + + + + + +]]> + + + + +%xhtml-qname.redecl; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-script-1.mod b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-script-1.mod new file mode 100644 index 000000000..0152ab02f --- /dev/null +++ b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-script-1.mod @@ -0,0 +1,67 @@ + + + + + + + + + + + + +]]> + + + +]]> + + + + + + + +]]> + + + +]]> + + diff --git a/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-special.ent b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-special.ent new file mode 100644 index 000000000..ca358b2fe --- /dev/null +++ b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-special.ent @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-ssismap-1.mod b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-ssismap-1.mod new file mode 100644 index 000000000..45da878fd --- /dev/null +++ b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-ssismap-1.mod @@ -0,0 +1,32 @@ + + + + + + + + + + + diff --git a/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-struct-1.mod b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-struct-1.mod new file mode 100644 index 000000000..c826f0f07 --- /dev/null +++ b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-struct-1.mod @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + +]]> + + + +]]> + + + + + + + +]]> + + + + + + +]]> + + + + + + + +]]> + + + +]]> + + + + + + + +]]> + + + +]]> + + + + + + + + +]]> + + diff --git a/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-style-1.mod b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-style-1.mod new file mode 100644 index 000000000..dc85a9e6e --- /dev/null +++ b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-style-1.mod @@ -0,0 +1,48 @@ + + + + + + + + + + + + +]]> + + + +]]> + + diff --git a/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-symbol.ent b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-symbol.ent new file mode 100644 index 000000000..63c2abfa6 --- /dev/null +++ b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-symbol.ent @@ -0,0 +1,237 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-symbol.ent.1 b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-symbol.ent.1 new file mode 100644 index 000000000..63c2abfa6 --- /dev/null +++ b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-symbol.ent.1 @@ -0,0 +1,237 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-table-1.mod b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-table-1.mod new file mode 100644 index 000000000..540b7346e --- /dev/null +++ b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-table-1.mod @@ -0,0 +1,333 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +]]> + + + +]]> + + + + + + +]]> + + + +]]> + + + + + + + + +]]> + + + +]]> + + + + + + + + +]]> + + + +]]> + + + + + + + + +]]> + + + +]]> + + + + + + + + +]]> + + + +]]> + + + + + + + + +]]> + + + +]]> + + + + + + +]]> + + + +]]> + + + + + + + + +]]> + + + +]]> + + + + + + +]]> + + + +]]> + + diff --git a/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-text-1.mod b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-text-1.mod new file mode 100644 index 000000000..a461e1e14 --- /dev/null +++ b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-text-1.mod @@ -0,0 +1,52 @@ + + + + + + + + +%xhtml-inlstruct.mod;]]> + + + +%xhtml-inlphras.mod;]]> + + + +%xhtml-blkstruct.mod;]]> + + + +%xhtml-blkphras.mod;]]> + + diff --git a/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml11-model-1.mod b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml11-model-1.mod new file mode 100644 index 000000000..eb834f3d3 --- /dev/null +++ b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml11-model-1.mod @@ -0,0 +1,252 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml1/DTD/xhtml-lat1.ent b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml1/DTD/xhtml-lat1.ent new file mode 100644 index 000000000..ffee223eb --- /dev/null +++ b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml1/DTD/xhtml-lat1.ent @@ -0,0 +1,196 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml1/DTD/xhtml-special.ent b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml1/DTD/xhtml-special.ent new file mode 100644 index 000000000..ca358b2fe --- /dev/null +++ b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml1/DTD/xhtml-special.ent @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml1/DTD/xhtml-symbol.ent b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml1/DTD/xhtml-symbol.ent new file mode 100644 index 000000000..63c2abfa6 --- /dev/null +++ b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml1/DTD/xhtml-symbol.ent @@ -0,0 +1,237 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd new file mode 100644 index 000000000..2927b9ece --- /dev/null +++ b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd @@ -0,0 +1,978 @@ + + + + + +%HTMLlat1; + + +%HTMLsymbol; + + +%HTMLspecial; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd new file mode 100644 index 000000000..628f27ac5 --- /dev/null +++ b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd @@ -0,0 +1,1201 @@ + + + + + +%HTMLlat1; + + +%HTMLsymbol; + + +%HTMLspecial; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml11/DTD/xhtml11.dtd b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml11/DTD/xhtml11.dtd new file mode 100644 index 000000000..2a999b5b3 --- /dev/null +++ b/epublib/src/main/resources/dtd/www.w3.org/TR/xhtml11/DTD/xhtml11.dtd @@ -0,0 +1,294 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +]]> + + + + + + +%xhtml-inlstyle.mod;]]> + + + + + + + +%xhtml-framework.mod;]]> + + + + +]]> + + + + +%xhtml-text.mod;]]> + + + + +%xhtml-hypertext.mod;]]> + + + + +%xhtml-list.mod;]]> + + + + + + +%xhtml-edit.mod;]]> + + + + +%xhtml-bdo.mod;]]> + + + + + + +%xhtml-ruby.mod;]]> + + + + +%xhtml-pres.mod;]]> + + + + +%xhtml-link.mod;]]> + + + + +%xhtml-meta.mod;]]> + + + + +%xhtml-base.mod;]]> + + + + +%xhtml-script.mod;]]> + + + + +%xhtml-style.mod;]]> + + + + +%xhtml-image.mod;]]> + + + + +%xhtml-csismap.mod;]]> + + + + +%xhtml-ssismap.mod;]]> + + + + +%xhtml-param.mod;]]> + + + + +%xhtml-object.mod;]]> + + + + +%xhtml-table.mod;]]> + + + + +%xhtml-form.mod;]]> + + + + +%xhtml-legacy.mod;]]> + + + + +%xhtml-struct.mod;]]> + + + diff --git a/epublib/src/main/resources/log4j.properties b/epublib/src/main/resources/log4j.properties new file mode 100644 index 000000000..bdfcdfe7f --- /dev/null +++ b/epublib/src/main/resources/log4j.properties @@ -0,0 +1,55 @@ +#------------------------------------------------------------------------------ +# +# The following properties set the logging levels and log appender. The +# log4j.rootCategory variable defines the default log level and one or more +# appenders. For the console, use 'S'. For the daily rolling file, use 'R'. +# For an HTML formatted log, use 'H'. +# +# To override the default (rootCategory) log level, define a property of the +# form (see below for available values): +# +# log4j.logger. = +# +# Available logger names: +# TODO +# +# Possible Log Levels: +# FATAL, ERROR, WARN, INFO, DEBUG +# +#------------------------------------------------------------------------------ +log4j.rootCategory=INFO, S + +#------------------------------------------------------------------------------ +# +# The following properties configure the console (stdout) appender. +# See http://logging.apache.org/log4j/docs/api/index.html for details. +# +#------------------------------------------------------------------------------ +log4j.appender.S = org.apache.log4j.ConsoleAppender +log4j.appender.S.layout = org.apache.log4j.PatternLayout +log4j.appender.S.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss} [%p] %l %m%n + +#------------------------------------------------------------------------------ +# +# The following properties configure the Daily Rolling File appender. +# See http://logging.apache.org/log4j/docs/api/index.html for details. +# +#------------------------------------------------------------------------------ +log4j.appender.R = org.apache.log4j.DailyRollingFileAppender +log4j.appender.R.File = logs/epublib.log +log4j.appender.R.Append = true +log4j.appender.R.DatePattern = '.'yyy-MM-dd +log4j.appender.R.layout = org.apache.log4j.PatternLayout +log4j.appender.R.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss} %c{1} [%p] %m%n + +#------------------------------------------------------------------------------ +# +# The following properties configure the Rolling File appender in HTML. +# See http://logging.apache.org/log4j/docs/api/index.html for details. +# +#------------------------------------------------------------------------------ +log4j.appender.H = org.apache.log4j.RollingFileAppender +log4j.appender.H.File = logs/epublib_log.html +log4j.appender.H.MaxFileSize = 100KB +log4j.appender.H.Append = false +log4j.appender.H.layout = org.apache.log4j.HTMLLayout \ No newline at end of file diff --git a/epublib/src/test/java/me/ag2s/epublib/ExampleUnitTest.java b/epublib/src/test/java/me/ag2s/epublib/ExampleUnitTest.java new file mode 100644 index 000000000..a9ef021cc --- /dev/null +++ b/epublib/src/test/java/me/ag2s/epublib/ExampleUnitTest.java @@ -0,0 +1,17 @@ +package me.ag2s.epublib; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Example local unit test, which will execute on the development machine (host). + * + * @see Testing documentation + */ +public class ExampleUnitTest { + @Test + public void addition_isCorrect() { + assertEquals(4, 2 + 2); + } +} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index e7b4def49..60c0c6c5c 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1,2 @@ include ':app' +include ':epublib'