lollipop版本分开,有些新特性android5.0和6.0不支持
This commit is contained in:
1 parent
0959e50f53
commit
3cf3231da5
258 files changed
+12817
-13529
No files matched your search
@@ -0,0 +1,88 @@
|
||||
package me.ag2s.epublib.domain;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import me.ag2s.epublib.util.StringUtil;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("NullableProblems")
|
||||
public String toString() {
|
||||
return this.lastname + ", " + this.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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置贡献者的角色
|
||||
*
|
||||
* @param code 角色编号
|
||||
*/
|
||||
|
||||
public void setRole(String code) {
|
||||
Relator result = Relator.byCode(code);
|
||||
if (result == null) {
|
||||
result = Relator.AUTHOR;
|
||||
}
|
||||
this.relator = result;
|
||||
}
|
||||
|
||||
public Relator getRelator() {
|
||||
return relator;
|
||||
}
|
||||
|
||||
|
||||
public void setRelator(Relator relator) {
|
||||
this.relator = relator;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package me.ag2s.epublib.domain;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Locale;
|
||||
|
||||
import me.ag2s.epublib.epub.PackageDocumentBase;
|
||||
|
||||
/**
|
||||
* A Date used by the book's metadata.
|
||||
* <p>
|
||||
* 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;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("NullableProblems")
|
||||
public String toString() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private Event event;
|
||||
private String dateString;
|
||||
|
||||
public Date() {
|
||||
this(new java.util.Date(), Event.CREATION);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("NullableProblems")
|
||||
public String toString() {
|
||||
if (event == null) {
|
||||
return dateString;
|
||||
}
|
||||
return "" + event + ":" + dateString;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,323 @@
|
||||
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.
|
||||
* <p>
|
||||
* All resources of a Book (html, css, xml, fonts, images) are represented
|
||||
* as Resources. See getResources() for access to these.<br/>
|
||||
* A Book as 3 indexes into these Resources, as per the epub specification.<br/>
|
||||
* <dl>
|
||||
* <dt>Spine</dt>
|
||||
* <dd>these are the Resources to be shown when a user reads the book from
|
||||
* start to finish.</dd>
|
||||
* <dt>Table of Contents<dt>
|
||||
* <dd>The table of contents. Table of Contents references may be in a
|
||||
* different order and contain different Resources than the spine, and often do.
|
||||
* <dt>Guide</dt>
|
||||
* <dd>The Guide has references to a set of special Resources like the
|
||||
* cover page, the Glossary, the copyright page, etc.
|
||||
* </dl>
|
||||
* <p/>
|
||||
* The complication is that these 3 indexes may and usually do point to
|
||||
* different pages.
|
||||
* A chapter may be split up in 2 pieces to fit it in to memory. Then the
|
||||
* spine will contain both pieces, but the Table of Contents only the first.
|
||||
* <p>
|
||||
* The Content page may be in the Table of Contents, the Guide, but not
|
||||
* in the Spine.
|
||||
* Etc.
|
||||
* <p/>
|
||||
* <p>
|
||||
* Please see the illustration at: doc/schema.svg
|
||||
*
|
||||
* @author paul
|
||||
* @author jake
|
||||
*/
|
||||
public class EpubBook 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 final Guide guide = new Guide();
|
||||
private Resource opfResource;
|
||||
private Resource ncxResource;
|
||||
private Resource coverImage;
|
||||
|
||||
|
||||
private String version = "2.0";
|
||||
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public void setVersion(String version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public boolean isEpub3() {
|
||||
return this.version.startsWith("3.");
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnusedReturnValue")
|
||||
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 parentSection
|
||||
* @param sectionTitle sectionTitle
|
||||
* @param resource resource
|
||||
* @param fragmentId 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 title
|
||||
* @param resource resource
|
||||
* @param fragmentId 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;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
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;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
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.notContainsByHref(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.notContainsByHref(coverImage.getHref())) {
|
||||
resources.add(coverImage);
|
||||
}
|
||||
this.coverImage = coverImage;
|
||||
}
|
||||
|
||||
/**
|
||||
* The guide; contains references to special sections of the book like
|
||||
* colophon, glossary, etc.
|
||||
*
|
||||
* @return The guide; contains references to special sections of the book
|
||||
* like colophon, glossary, etc.
|
||||
*/
|
||||
public Guide getGuide() {
|
||||
return guide;
|
||||
}
|
||||
|
||||
/**
|
||||
* All Resources of the Book that can be reached via the Spine, the
|
||||
* TableOfContents or the Guide.
|
||||
* <p/>
|
||||
* Consists of a list of "reachable" resources:
|
||||
* <ul>
|
||||
* <li>The coverpage</li>
|
||||
* <li>The resources of the Spine that are not already in the result</li>
|
||||
* <li>The resources of the Table of Contents that are not already in the
|
||||
* result</li>
|
||||
* <li>The resources of the Guide that are not already in the result</li>
|
||||
* </ul>
|
||||
* To get all html files that make up the epub file use
|
||||
* {@link #getResources()}
|
||||
*
|
||||
* @return All Resources of the Book that can be reached via the Spine,
|
||||
* the TableOfContents or the Guide.
|
||||
*/
|
||||
public List<Resource> getContents() {
|
||||
Map<String, Resource> result = new LinkedHashMap<>();
|
||||
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<String, Resource> allReachableResources) {
|
||||
if (resource != null && (!allReachableResources
|
||||
.containsKey(resource.getHref()))) {
|
||||
allReachableResources.put(resource.getHref(), resource);
|
||||
}
|
||||
}
|
||||
|
||||
public Resource getOpfResource() {
|
||||
return opfResource;
|
||||
}
|
||||
|
||||
public void setOpfResource(Resource opfResource) {
|
||||
this.opfResource = opfResource;
|
||||
}
|
||||
|
||||
public void setNcxResource(Resource ncxResource) {
|
||||
this.ncxResource = ncxResource;
|
||||
}
|
||||
|
||||
public Resource getNcxResource() {
|
||||
return ncxResource;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package me.ag2s.epublib.domain;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import me.ag2s.epublib.util.zip.ZipEntryWrapper;
|
||||
import me.ag2s.epublib.util.zip.ZipFileWrapper;
|
||||
|
||||
/**
|
||||
* @author jake
|
||||
*/
|
||||
public class EpubResourceProvider implements LazyResourceProvider {
|
||||
|
||||
|
||||
private final ZipFileWrapper zipFileWrapper;
|
||||
|
||||
|
||||
public EpubResourceProvider(ZipFileWrapper zipFileWrapper) {
|
||||
this.zipFileWrapper = zipFileWrapper;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public InputStream getResourceStream(@NonNull String href) throws IOException {
|
||||
|
||||
//ZipFile zipFile = new ZipFile(epubFilename);
|
||||
ZipEntryWrapper zipEntry = zipFileWrapper.getEntry(href);
|
||||
if (zipEntry == null) {
|
||||
//zipFile.close();
|
||||
throw new IllegalStateException(
|
||||
"Cannot find entry " + href + " in epub file " + zipFileWrapper);
|
||||
}
|
||||
return new ResourceInputStream(zipFileWrapper.getInputStream(zipEntry));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package me.ag2s.epublib.domain;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* 用于创建epub,添加大文件(如大量图片)时容易OOM,使用LazyResource,避免OOM.
|
||||
*/
|
||||
|
||||
public class FileResourceProvider implements LazyResourceProvider {
|
||||
//需要导入资源的父目录
|
||||
String dir;
|
||||
|
||||
/**
|
||||
* 创建一个文件夹里面文件夹的LazyResourceProvider,用于LazyResource。
|
||||
*
|
||||
* @param parentDir 文件的目录
|
||||
*/
|
||||
public FileResourceProvider(String parentDir) {
|
||||
this.dir = parentDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个文件夹里面文件夹的LazyResourceProvider,用于LazyResource。
|
||||
*
|
||||
* @param parentFile 文件夹
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public FileResourceProvider(File parentFile) {
|
||||
this.dir = parentFile.getPath();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据子文件名href,再父目录下读取文件获取FileInputStream
|
||||
*
|
||||
* @param href 子文件名href
|
||||
* @return 对应href的FileInputStream
|
||||
* @throws IOException 抛出IOException
|
||||
*/
|
||||
@Override
|
||||
public InputStream getResourceStream(String href) throws IOException {
|
||||
return new FileInputStream(new File(dir, href));
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
* <p>
|
||||
* It is an optional part of an epub, and support for the various types
|
||||
* of references varies by reader.
|
||||
* <p>
|
||||
* The only part of this that is heavily used is the cover page.
|
||||
*
|
||||
* @author paul
|
||||
*/
|
||||
public class Guide implements Serializable {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = -6256645339915751189L;
|
||||
|
||||
public static final String DEFAULT_COVER_TITLE = GuideReference.COVER;
|
||||
|
||||
private List<GuideReference> references = new ArrayList<>();
|
||||
private static final int COVERPAGE_NOT_FOUND = -1;
|
||||
private static final int COVERPAGE_UNITIALIZED = -2;
|
||||
|
||||
private int coverPageIndex = -1;
|
||||
|
||||
public List<GuideReference> getReferences() {
|
||||
return references;
|
||||
}
|
||||
|
||||
public void setReferences(List<GuideReference> references) {
|
||||
this.references = references;
|
||||
uncheckCoverPage();
|
||||
}
|
||||
|
||||
private void uncheckCoverPage() {
|
||||
coverPageIndex = COVERPAGE_UNITIALIZED;
|
||||
}
|
||||
|
||||
public GuideReference getCoverReference() {
|
||||
checkCoverPage();
|
||||
if (coverPageIndex >= 0) {
|
||||
return references.get(coverPageIndex);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnusedReturnValue")
|
||||
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);
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnusedReturnValue")
|
||||
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 referenceTypeName
|
||||
* @return A list of all GuideReferences that have the given
|
||||
* referenceTypeName (ignoring case).
|
||||
*/
|
||||
public List<GuideReference> getGuideReferencesByType(
|
||||
String referenceTypeName) {
|
||||
List<GuideReference> result = new ArrayList<>();
|
||||
for (GuideReference guideReference : references) {
|
||||
if (referenceTypeName.equalsIgnoreCase(guideReference.getType())) {
|
||||
result.add(guideReference);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package me.ag2s.epublib.domain;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import me.ag2s.epublib.util.StringUtil;
|
||||
|
||||
|
||||
/**
|
||||
* These are references to elements of the book's guide.
|
||||
*
|
||||
* @author paul
|
||||
* @see Guide
|
||||
*/
|
||||
public class GuideReference extends TitledResourceReference
|
||||
implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = -316179702440631834L;
|
||||
|
||||
/**
|
||||
* the book cover(s), jacket information, etc.
|
||||
*/
|
||||
public static final 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.
|
||||
* <p>
|
||||
* source: http://en.wikipedia.org/wiki/Epigraph_%28literature%29
|
||||
*/
|
||||
public static String EPIGRAPH = "epigraph";
|
||||
|
||||
public static String FOREWORD = "foreword";
|
||||
|
||||
/**
|
||||
* list of illustrations
|
||||
*/
|
||||
public static String LOI = "loi";
|
||||
|
||||
/**
|
||||
* list of tables
|
||||
*/
|
||||
public static String LOT = "lot";
|
||||
public static String NOTES = "notes";
|
||||
public static String PREFACE = "preface";
|
||||
|
||||
/**
|
||||
* A page of content (e.g. "Chapter 1")
|
||||
*/
|
||||
public static String TEXT = "text";
|
||||
|
||||
private String type;
|
||||
|
||||
public GuideReference(Resource resource) {
|
||||
this(resource, null);
|
||||
}
|
||||
|
||||
public GuideReference(Resource resource, String title) {
|
||||
super(resource, title);
|
||||
}
|
||||
|
||||
public GuideReference(Resource resource, String type, String title) {
|
||||
this(resource, type, title, null);
|
||||
}
|
||||
|
||||
public GuideReference(Resource resource, String type, String title,
|
||||
String fragmentId) {
|
||||
super(resource, title, fragmentId);
|
||||
this.type = StringUtil.isNotBlank(type) ? type.toLowerCase() : null;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
package me.ag2s.epublib.domain;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import me.ag2s.epublib.util.StringUtil;
|
||||
|
||||
/**
|
||||
* A Book's identifier.
|
||||
* <p>
|
||||
* Defaults to a random UUID and scheme "UUID"
|
||||
*
|
||||
* @author paul
|
||||
*/
|
||||
public class Identifier implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 955949951416391810L;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
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.
|
||||
* <p>
|
||||
* If no identifier has bookId == true then the first bookId identifier
|
||||
* is written as the primary.
|
||||
*
|
||||
* @param identifiers i
|
||||
* @return The first identifier for which the bookId is true is made
|
||||
* the bookId identifier.
|
||||
*/
|
||||
public static Identifier getBookIdIdentifier(List<Identifier> identifiers) {
|
||||
if (identifiers == null || identifiers.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Identifier result = null;
|
||||
for (Identifier identifier : identifiers) {
|
||||
if (identifier.isBookId()) {
|
||||
result = identifier;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (result == null) {
|
||||
result = identifiers.get(0);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public String getScheme() {
|
||||
return scheme;
|
||||
}
|
||||
|
||||
public void setScheme(String scheme) {
|
||||
this.scheme = scheme;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
|
||||
public void setBookId(boolean bookId) {
|
||||
this.bookId = bookId;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This bookId property allows the book creator to add multiple ids and
|
||||
* tell the epubwriter which one to write out as the bookId.
|
||||
* <p>
|
||||
* 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);
|
||||
}
|
||||
|
||||
@SuppressWarnings("NullableProblems")
|
||||
@Override
|
||||
public String toString() {
|
||||
if (StringUtil.isBlank(scheme)) {
|
||||
return "" + value;
|
||||
}
|
||||
return "" + scheme + ":" + value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
package me.ag2s.epublib.domain;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import me.ag2s.epublib.util.IOUtil;
|
||||
|
||||
/**
|
||||
* 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 final String TAG = getClass().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);
|
||||
}
|
||||
|
||||
public LazyResource(LazyResourceProvider resourceProvider, String href, String originalHref) {
|
||||
this(resourceProvider, -1, href, originalHref);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Lazy resource, by not actually loading the data for this entry.
|
||||
* <p>
|
||||
* 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;
|
||||
}
|
||||
|
||||
public LazyResource(
|
||||
LazyResourceProvider resourceProvider, long size, String href, String originalHref) {
|
||||
super(null, null, href, originalHref, 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 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 IOException
|
||||
*/
|
||||
public void initialize() throws IOException {
|
||||
getData();
|
||||
}
|
||||
|
||||
/**
|
||||
* The contents of the resource as a byte[]
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* If this resource was not lazy-loaded, this is a no-op.
|
||||
*/
|
||||
public void close() {
|
||||
if (this.resourceProvider != null) {
|
||||
this.data = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if the data for this resource has been loaded into memory.
|
||||
*
|
||||
* @return true if data was loaded.
|
||||
*/
|
||||
public boolean isInitialized() {
|
||||
return data != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the size of this resource in bytes.
|
||||
*
|
||||
* @return the size.
|
||||
*/
|
||||
public long getSize() {
|
||||
if (data != null) {
|
||||
return data.length;
|
||||
}
|
||||
|
||||
return cachedSize;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package me.ag2s.epublib.domain;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* @author jake
|
||||
*/
|
||||
public interface LazyResourceProvider {
|
||||
|
||||
InputStream getResourceStream(String href) throws IOException;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package me.ag2s.epublib.domain;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public enum ManifestItemProperties implements ManifestProperties {
|
||||
COVER_IMAGE("cover-image"),
|
||||
MATHML("mathml"),
|
||||
NAV("nav"),
|
||||
REMOTE_RESOURCES("remote-resources"),
|
||||
SCRIPTED("scripted"),
|
||||
SVG("svg"),
|
||||
SWITCH("switch");
|
||||
|
||||
private final String name;
|
||||
|
||||
ManifestItemProperties(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package me.ag2s.epublib.domain;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public enum ManifestItemRefProperties implements ManifestProperties {
|
||||
PAGE_SPREAD_LEFT("page-spread-left"),
|
||||
PAGE_SPREAD_RIGHT("page-spread-right");
|
||||
|
||||
private final String name;
|
||||
|
||||
ManifestItemRefProperties(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package me.ag2s.epublib.domain;
|
||||
|
||||
public interface ManifestProperties {
|
||||
|
||||
String getName();
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package me.ag2s.epublib.domain;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* MediaType is used to tell the type of content a resource is.
|
||||
* <p>
|
||||
* Examples of mediatypes are image/gif, text/css and application/xhtml+xml
|
||||
* <p>
|
||||
* All allowed mediaTypes are maintained bye the MediaTypeService.
|
||||
*
|
||||
* @author paul
|
||||
* @see MediaTypes
|
||||
*/
|
||||
public class MediaType implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = -7256091153727506788L;
|
||||
private final String name;
|
||||
private final String defaultExtension;
|
||||
private final Collection<String> extensions;
|
||||
|
||||
public MediaType(String name, String defaultExtension) {
|
||||
this(name, defaultExtension, new String[]{defaultExtension});
|
||||
}
|
||||
|
||||
public MediaType(String name, String defaultExtension,
|
||||
String[] extensions) {
|
||||
this(name, defaultExtension, Arrays.asList(extensions));
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
if (name == null) {
|
||||
return 0;
|
||||
}
|
||||
return name.hashCode();
|
||||
}
|
||||
|
||||
public MediaType(String name, String defaultExtension,
|
||||
Collection<String> mextensions) {
|
||||
super();
|
||||
this.name = name;
|
||||
this.defaultExtension = defaultExtension;
|
||||
this.extensions = mextensions;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
|
||||
public String getDefaultExtension() {
|
||||
return defaultExtension;
|
||||
}
|
||||
|
||||
|
||||
public Collection<String> getExtensions() {
|
||||
return extensions;
|
||||
}
|
||||
|
||||
public boolean equals(Object otherMediaType) {
|
||||
if (!(otherMediaType instanceof MediaType)) {
|
||||
return false;
|
||||
}
|
||||
return name.equals(((MediaType) otherMediaType).getName());
|
||||
}
|
||||
|
||||
@SuppressWarnings("NullableProblems")
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package me.ag2s.epublib.domain;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import me.ag2s.epublib.util.StringUtil;
|
||||
|
||||
|
||||
/**
|
||||
* 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 final MediaType[] mediaTypes = new MediaType[]{
|
||||
XHTML, EPUB, JPG, PNG, GIF, CSS, SVG, TTF, NCX, XPGT, OPENTYPE, WOFF,
|
||||
SMIL, PLS, JAVASCRIPT, MP3, MP4, OGG
|
||||
};
|
||||
|
||||
public static final Map<String, MediaType> mediaTypesByName = new HashMap<>();
|
||||
|
||||
static {
|
||||
for (MediaType mediaType : mediaTypes) {
|
||||
mediaTypesByName.put(mediaType.getName(), mediaType);
|
||||
}
|
||||
}
|
||||
|
||||
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 filename
|
||||
* @return the MediaType based on the file extension.
|
||||
*/
|
||||
public static MediaType determineMediaType(String filename) {
|
||||
for (MediaType mediaType : mediaTypesByName.values()) {
|
||||
for (String extension : mediaType.getExtensions()) {
|
||||
if (StringUtil.endsWithIgnoreCase(filename, extension)) {
|
||||
return mediaType;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static MediaType getMediaTypeByName(String mediaTypeName) {
|
||||
return mediaTypesByName.get(mediaTypeName);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
package me.ag2s.epublib.domain;
|
||||
|
||||
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;
|
||||
|
||||
import me.ag2s.epublib.util.StringUtil;
|
||||
|
||||
/**
|
||||
* A Book's collection of Metadata.
|
||||
* In the future it should contain all Dublin Core attributes, for now
|
||||
* it contains a set of often-used ones.
|
||||
*
|
||||
* @author paul
|
||||
*/
|
||||
public class Metadata implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = -2437262888962149444L;
|
||||
|
||||
public static final String DEFAULT_LANGUAGE = "en";
|
||||
|
||||
private boolean autoGeneratedId;//true;
|
||||
private List<Author> authors = new ArrayList<>();
|
||||
private List<Author> contributors = new ArrayList<>();
|
||||
private List<Date> dates = new ArrayList<>();
|
||||
private String language = DEFAULT_LANGUAGE;
|
||||
private Map<QName, String> otherProperties = new HashMap<>();
|
||||
private List<String> rights = new ArrayList<>();
|
||||
private List<String> titles = new ArrayList<>();
|
||||
private List<Identifier> identifiers = new ArrayList<>();
|
||||
private List<String> subjects = new ArrayList<>();
|
||||
private String format = MediaTypes.EPUB.getName();
|
||||
private List<String> types = new ArrayList<>();
|
||||
private List<String> descriptions = new ArrayList<>();
|
||||
private List<String> publishers = new ArrayList<>();
|
||||
private Map<String, String> metaAttributes = new HashMap<>();
|
||||
|
||||
public Metadata() {
|
||||
identifiers.add(new Identifier());
|
||||
autoGeneratedId = true;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public boolean isAutoGeneratedId() {
|
||||
return autoGeneratedId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Metadata properties not hard-coded like the author, title, etc.
|
||||
*
|
||||
* @return Metadata properties not hard-coded like the author, title, etc.
|
||||
*/
|
||||
public Map<QName, String> getOtherProperties() {
|
||||
return otherProperties;
|
||||
}
|
||||
|
||||
public void setOtherProperties(Map<QName, String> otherProperties) {
|
||||
this.otherProperties = otherProperties;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public Date addDate(Date date) {
|
||||
this.dates.add(date);
|
||||
return date;
|
||||
}
|
||||
|
||||
public List<Date> getDates() {
|
||||
return dates;
|
||||
}
|
||||
|
||||
public void setDates(List<Date> dates) {
|
||||
this.dates = dates;
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnusedReturnValue")
|
||||
public Author addAuthor(Author author) {
|
||||
authors.add(author);
|
||||
return author;
|
||||
}
|
||||
|
||||
public List<Author> getAuthors() {
|
||||
return authors;
|
||||
}
|
||||
|
||||
public void setAuthors(List<Author> authors) {
|
||||
this.authors = authors;
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnusedReturnValue")
|
||||
public Author addContributor(Author contributor) {
|
||||
contributors.add(contributor);
|
||||
return contributor;
|
||||
}
|
||||
|
||||
public List<Author> getContributors() {
|
||||
return contributors;
|
||||
}
|
||||
|
||||
public void setContributors(List<Author> contributors) {
|
||||
this.contributors = contributors;
|
||||
}
|
||||
|
||||
public String getLanguage() {
|
||||
return language;
|
||||
}
|
||||
|
||||
public void setLanguage(String language) {
|
||||
this.language = language;
|
||||
}
|
||||
|
||||
public List<String> getSubjects() {
|
||||
return subjects;
|
||||
}
|
||||
|
||||
public void setSubjects(List<String> subjects) {
|
||||
this.subjects = subjects;
|
||||
}
|
||||
|
||||
public void setRights(List<String> rights) {
|
||||
this.rights = rights;
|
||||
}
|
||||
|
||||
public List<String> getRights() {
|
||||
return rights;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the first non-blank title of the book.
|
||||
* Will return "" if no title found.
|
||||
*
|
||||
* @return the first non-blank title of the book.
|
||||
*/
|
||||
public String getFirstTitle() {
|
||||
if (titles == null || titles.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
for (String title : titles) {
|
||||
if (StringUtil.isNotBlank(title)) {
|
||||
return title;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public String addTitle(String title) {
|
||||
this.titles.add(title);
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitles(List<String> titles) {
|
||||
this.titles = titles;
|
||||
}
|
||||
|
||||
public List<String> getTitles() {
|
||||
return titles;
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnusedReturnValue")
|
||||
public String addPublisher(String publisher) {
|
||||
this.publishers.add(publisher);
|
||||
return publisher;
|
||||
}
|
||||
|
||||
public void setPublishers(List<String> publishers) {
|
||||
this.publishers = publishers;
|
||||
}
|
||||
|
||||
public List<String> getPublishers() {
|
||||
return publishers;
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnusedReturnValue")
|
||||
public String addDescription(String description) {
|
||||
this.descriptions.add(description);
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescriptions(List<String> descriptions) {
|
||||
this.descriptions = descriptions;
|
||||
}
|
||||
|
||||
public List<String> getDescriptions() {
|
||||
return descriptions;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public Identifier addIdentifier(Identifier identifier) {
|
||||
if (autoGeneratedId && (!(identifiers.isEmpty()))) {
|
||||
identifiers.set(0, identifier);
|
||||
} else {
|
||||
identifiers.add(identifier);
|
||||
}
|
||||
autoGeneratedId = false;
|
||||
return identifier;
|
||||
}
|
||||
|
||||
public void setIdentifiers(List<Identifier> identifiers) {
|
||||
this.identifiers = identifiers;
|
||||
autoGeneratedId = false;
|
||||
}
|
||||
|
||||
public List<Identifier> getIdentifiers() {
|
||||
return identifiers;
|
||||
}
|
||||
|
||||
public void setFormat(String format) {
|
||||
this.format = format;
|
||||
}
|
||||
|
||||
public String getFormat() {
|
||||
return format;
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnusedReturnValue")
|
||||
public String addType(String type) {
|
||||
this.types.add(type);
|
||||
return type;
|
||||
}
|
||||
|
||||
public List<String> getTypes() {
|
||||
return types;
|
||||
}
|
||||
|
||||
public void setTypes(List<String> types) {
|
||||
this.types = types;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public String getMetaAttribute(String name) {
|
||||
return metaAttributes.get(name);
|
||||
}
|
||||
|
||||
public void setMetaAttributes(Map<String, String> metaAttributes) {
|
||||
this.metaAttributes = metaAttributes;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,336 @@
|
||||
package me.ag2s.epublib.domain;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.Reader;
|
||||
import java.io.Serializable;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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 final String originalHref;
|
||||
private MediaType mediaType;
|
||||
private String inputEncoding;
|
||||
protected byte[] data;
|
||||
|
||||
/**
|
||||
* Creates an empty Resource with the given href.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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 href The location of the resource within the epub. Example: "chapter1.html".
|
||||
* @see MediaTypes#determineMediaType(String)
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @param in The Resource's contents
|
||||
* @param href The location of the resource within the epub. Example: "cover.jpg".
|
||||
* @see MediaTypes#determineMediaType(String)
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @param in The Resource's contents
|
||||
* @param href The location of the resource within the epub. Example: "cover.jpg".
|
||||
* @see MediaTypes#determineMediaType(String)
|
||||
* <p>
|
||||
* Assumes that if the data is of a text type (html/css/etc) then the encoding will be UTF-8
|
||||
* <p>
|
||||
* 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.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
public Resource(String id, byte[] data, String href, String originalHref, MediaType mediaType) {
|
||||
this(id, data, href, originalHref, 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;
|
||||
}
|
||||
|
||||
public Resource(String id, byte[] data, String href, String originalHref, MediaType mediaType,
|
||||
String inputEncoding) {
|
||||
this.id = id;
|
||||
this.href = href;
|
||||
this.originalHref = originalHref;
|
||||
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 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.
|
||||
* <p>
|
||||
* 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 the data of the Resource
|
||||
*/
|
||||
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 Resource's id
|
||||
*/
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* The resources Id.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* Example:<br/>
|
||||
* images/cover.jpg<br/>
|
||||
* content/chapter1.xhtml<br/>
|
||||
*
|
||||
* @return The location of the resource within the contents folder of the epub file.
|
||||
*/
|
||||
public String getHref() {
|
||||
return href;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the Resource's href.
|
||||
*
|
||||
* @param href Resource's 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 Resource's input character encoding.
|
||||
*/
|
||||
public void setInputEncoding(String encoding) {
|
||||
this.inputEncoding = encoding;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the contents of the Resource as Reader.
|
||||
* <p>
|
||||
* 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 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;
|
||||
}
|
||||
|
||||
@SuppressWarnings("NullableProblems")
|
||||
public String toString() {
|
||||
return StringUtil.toString("id", id,
|
||||
"title", title,
|
||||
"encoding", inputEncoding,
|
||||
"mediaType", mediaType,
|
||||
"href", href,
|
||||
"size", (data == null ? 0 : data.length));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package me.ag2s.epublib.domain;
|
||||
|
||||
import java.io.FilterInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* A wrapper class for closing a AndroidZipFile 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.
|
||||
*/
|
||||
public ResourceInputStream(InputStream in) {
|
||||
super(in);
|
||||
//this.zipFile = zipFile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
super.close();
|
||||
|
||||
//zipFile.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package me.ag2s.epublib.domain;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public class ResourceReference implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 2596967243557743048L;
|
||||
|
||||
protected Resource resource;
|
||||
|
||||
public ResourceReference(Resource resource) {
|
||||
this.resource = resource;
|
||||
}
|
||||
|
||||
|
||||
public Resource getResource() {
|
||||
return resource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Besides setting the resource it also sets the fragmentId to null.
|
||||
*
|
||||
* @param resource resource
|
||||
*/
|
||||
public void setResource(Resource resource) {
|
||||
this.resource = resource;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The id of the reference referred to.
|
||||
* <p>
|
||||
* null of the reference is null or has a null id itself.
|
||||
*
|
||||
* @return The id of the reference referred to.
|
||||
*/
|
||||
public String getResourceId() {
|
||||
if (resource != null) {
|
||||
return resource.getId();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,417 @@
|
||||
package me.ag2s.epublib.domain;
|
||||
|
||||
import android.util.Base64;
|
||||
|
||||
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;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import me.ag2s.epublib.Constants;
|
||||
import me.ag2s.epublib.util.StringUtil;
|
||||
|
||||
/**
|
||||
* All the resources that make up the book.
|
||||
* XHTML files, images and epub xml documents must be here.
|
||||
*
|
||||
* @author paul
|
||||
*/
|
||||
public class Resources implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 2450876953383871451L;
|
||||
private static final String IMAGE_PREFIX = "image_";
|
||||
private static final String ITEM_PREFIX = "item_";
|
||||
private int lastId = 1;
|
||||
|
||||
private Map<String, Resource> resources = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Adds a resource to the resources.
|
||||
* <p>
|
||||
* Fixes the resources id and href if necessary.
|
||||
*
|
||||
* @param resource 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 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 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 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 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 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 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_PREFIX + counter + mediaType.getDefaultExtension();
|
||||
} else {
|
||||
return ITEM_PREFIX + 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.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public Map<String, Resource> getResourceMap() {
|
||||
return resources;
|
||||
}
|
||||
|
||||
public Collection<Resource> getAll() {
|
||||
return resources.values();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Whether there exists a resource with the given href
|
||||
*
|
||||
* @param href href
|
||||
* @return Whether there exists a resource with the given href
|
||||
*/
|
||||
public boolean notContainsByHref(String href) {
|
||||
if (StringUtil.isBlank(href)) {
|
||||
return true;
|
||||
} else {
|
||||
return !resources.containsKey(
|
||||
StringUtil.substringBefore(href, Constants.FRAGMENT_SEPARATOR_CHAR));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether there exists a resource with the given href
|
||||
*
|
||||
* @param href href
|
||||
* @return Whether there exists a resource with the given href
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public boolean containsByHref(String href) {
|
||||
return !notContainsByHref(href);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the collection of Resources to the given collection of resources
|
||||
*
|
||||
* @param resources resources
|
||||
*/
|
||||
public void set(Collection<Resource> resources) {
|
||||
this.resources.clear();
|
||||
addAll(resources);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds all resources from the given Collection of resources to the existing collection.
|
||||
*
|
||||
* @param resources resources
|
||||
*/
|
||||
public void addAll(Collection<Resource> resources) {
|
||||
for (Resource resource : resources) {
|
||||
fixResourceHref(resource);
|
||||
this.resources.put(resource.getHref(), resource);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the collection of Resources to the given collection of resources
|
||||
*
|
||||
* @param resources A map with as keys the resources href and as values the Resources
|
||||
*/
|
||||
public void set(Map<String, Resource> resources) {
|
||||
this.resources = new HashMap<>(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 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 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);
|
||||
|
||||
Pattern dataUriRegex = Pattern.compile("data:([\\w/\\-\\.]+);base64,(.*)");
|
||||
Matcher dataUriMatcher = dataUriRegex.matcher(href);
|
||||
if (dataUriMatcher.find()) {
|
||||
String dataUriMediaTypeString = dataUriMatcher.group(1);
|
||||
MediaType dataUriMediaType = new MediaType(dataUriMediaTypeString, "." + StringUtil.substringAfterLast(dataUriMediaTypeString, '/'));
|
||||
byte[] dataUriData = Base64.decode(dataUriMatcher.group(2), Base64.DEFAULT);
|
||||
return new Resource(dataUriData, dataUriMediaType);
|
||||
} else {
|
||||
return resources.get(href);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the first resource (random order) with the give mediatype.
|
||||
* <p>
|
||||
* Useful for looking up the table of contents as it's supposed to be the only resource with NCX mediatype.
|
||||
*
|
||||
* @param mediaType 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.
|
||||
* <p>
|
||||
* Useful for looking up the table of contents as it's supposed to be the only resource with NCX mediatype.
|
||||
*
|
||||
* @param mediaType mediaType
|
||||
* @return the first resource (random order) with the give mediatype.
|
||||
*/
|
||||
public static Resource findFirstResourceByMediaType(
|
||||
Collection<Resource> resources, MediaType mediaType) {
|
||||
for (Resource resource : resources) {
|
||||
if (resource.getMediaType() == mediaType) {
|
||||
return resource;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* All resources that have the given MediaType.
|
||||
*
|
||||
* @param mediaType mediaType
|
||||
* @return All resources that have the given MediaType.
|
||||
*/
|
||||
public List<Resource> getResourcesByMediaType(MediaType mediaType) {
|
||||
List<Resource> 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 mediaType
|
||||
* @return All Resources that match any of the given list of MediaTypes
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public List<Resource> getResourcesByMediaTypes(MediaType[] mediaTypes) {
|
||||
List<Resource> 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<MediaType> mediaTypesList = Arrays.asList(mediaTypes);
|
||||
for (Resource resource : getAll()) {
|
||||
if (mediaTypesList.contains(resource.getMediaType())) {
|
||||
result.add(resource);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* All resource hrefs
|
||||
*
|
||||
* @return all resource hrefs
|
||||
*/
|
||||
public Collection<String> getAllHrefs() {
|
||||
return resources.keySet();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
package me.ag2s.epublib.domain;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import me.ag2s.epublib.util.StringUtil;
|
||||
|
||||
/**
|
||||
* The spine sections are the sections of the book in the order in which the book should be read.
|
||||
* <p>
|
||||
* This contrasts with the Table of Contents sections which is an index into the Book's sections.
|
||||
*
|
||||
* @author paul
|
||||
* @see TableOfContents
|
||||
*/
|
||||
public class Spine implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 3878483958947357246L;
|
||||
private Resource tocResource;
|
||||
private List<SpineReference> spineReferences;
|
||||
|
||||
public Spine() {
|
||||
this(new ArrayList<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a spine out of all the resources in the table of contents.
|
||||
*
|
||||
* @param tableOfContents tableOfContents
|
||||
*/
|
||||
public Spine(TableOfContents tableOfContents) {
|
||||
this.spineReferences = createSpineReferences(
|
||||
tableOfContents.getAllUniqueResources());
|
||||
}
|
||||
|
||||
public Spine(List<SpineReference> spineReferences) {
|
||||
this.spineReferences = spineReferences;
|
||||
}
|
||||
|
||||
public static List<SpineReference> createSpineReferences(
|
||||
Collection<Resource> resources) {
|
||||
List<SpineReference> result = new ArrayList<>(
|
||||
resources.size());
|
||||
for (Resource resource : resources) {
|
||||
result.add(new SpineReference(resource));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public List<SpineReference> getSpineReferences() {
|
||||
return spineReferences;
|
||||
}
|
||||
|
||||
public void setSpineReferences(List<SpineReference> spineReferences) {
|
||||
this.spineReferences = spineReferences;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the resource at the given index.
|
||||
* Null if not found.
|
||||
*
|
||||
* @param index 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.
|
||||
* <p>
|
||||
* Null if not found.
|
||||
*
|
||||
* @param resourceId 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 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
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
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 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 currentResource
|
||||
* @return something < 0 if not found.
|
||||
*/
|
||||
public int getResourceIndex(Resource currentResource) {
|
||||
if (currentResource == null) {
|
||||
return -1;
|
||||
}
|
||||
return getResourceIndex(currentResource.getHref());
|
||||
}
|
||||
|
||||
/**
|
||||
* The first position within the spine of a resource with the given href.
|
||||
*
|
||||
* @return something < 0 if not found.
|
||||
*/
|
||||
public int getResourceIndex(String resourceHref) {
|
||||
int result = -1;
|
||||
if (StringUtil.isBlank(resourceHref)) {
|
||||
return result;
|
||||
}
|
||||
for (int i = 0; i < spineReferences.size(); i++) {
|
||||
if (resourceHref.equals(spineReferences.get(i).getResource().getHref())) {
|
||||
result = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the spine has any references
|
||||
*
|
||||
* @return Whether the spine has any references
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return spineReferences.isEmpty();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package me.ag2s.epublib.domain;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
|
||||
/**
|
||||
* A Section of a book.
|
||||
* Represents both an item in the package document and a item in the index.
|
||||
*
|
||||
* @author paul
|
||||
*/
|
||||
public class SpineReference extends ResourceReference implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = -7921609197351510248L;
|
||||
private boolean linear;//default = 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.
|
||||
* <p>
|
||||
* It's an optional property that readers may also ignore.
|
||||
*
|
||||
* <blockquote>primary or auxiliary is useful for Reading Systems which
|
||||
* opt to present auxiliary content differently than primary content.
|
||||
* For example, a Reading System might opt to render auxiliary content in
|
||||
* a popup window apart from the main window which presents the primary
|
||||
* content. (For an example of the types of content that may be considered
|
||||
* auxiliary, refer to the example below and the subsequent discussion.)</blockquote>
|
||||
*
|
||||
* @return whether the section is Primary or Auxiliary.
|
||||
* @see <a href="http://www.idpf.org/epub/20/spec/OPF_2.0.1_draft.htm#Section2.4">OPF Spine specification</a>
|
||||
*/
|
||||
public boolean isLinear() {
|
||||
return linear;
|
||||
}
|
||||
|
||||
public void setLinear(boolean linear) {
|
||||
this.linear = linear;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
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.
|
||||
*
|
||||
* @author paul
|
||||
* @see TableOfContents
|
||||
*/
|
||||
public class TOCReference extends TitledResourceReference
|
||||
implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 5787958246077042456L;
|
||||
private List<TOCReference> children;
|
||||
private static final Comparator<TOCReference> COMPARATOR_BY_TITLE_IGNORE_CASE = (tocReference1, tocReference2) -> String.CASE_INSENSITIVE_ORDER.compare(tocReference1.getTitle(), tocReference2.getTitle());
|
||||
|
||||
@Deprecated
|
||||
public TOCReference() {
|
||||
this(null, null, null);
|
||||
}
|
||||
|
||||
public TOCReference(String name, Resource resource) {
|
||||
this(name, resource, null);
|
||||
}
|
||||
|
||||
public TOCReference(String name, Resource resource, String fragmentId) {
|
||||
this(name, resource, fragmentId, new ArrayList<>());
|
||||
}
|
||||
|
||||
public TOCReference(String title, Resource resource, String fragmentId,
|
||||
List<TOCReference> children) {
|
||||
super(resource, title, fragmentId);
|
||||
this.children = children;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static Comparator<TOCReference> getComparatorByTitleIgnoreCase() {
|
||||
return COMPARATOR_BY_TITLE_IGNORE_CASE;
|
||||
}
|
||||
|
||||
public List<TOCReference> getChildren() {
|
||||
return children;
|
||||
}
|
||||
|
||||
public TOCReference addChildSection(TOCReference childSection) {
|
||||
this.children.add(childSection);
|
||||
return childSection;
|
||||
}
|
||||
|
||||
public void setChildren(List<TOCReference> children) {
|
||||
this.children = children;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,264 @@
|
||||
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.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* See the spine for the complete list of sections in the order in which they should be read.
|
||||
*
|
||||
* @author paul
|
||||
* @see Spine
|
||||
*/
|
||||
public class TableOfContents implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = -3147391239966275152L;
|
||||
|
||||
public static final String DEFAULT_PATH_SEPARATOR = "/";
|
||||
|
||||
private List<TOCReference> tocReferences;
|
||||
|
||||
public TableOfContents() {
|
||||
this(new ArrayList<>());
|
||||
}
|
||||
|
||||
public TableOfContents(List<TOCReference> tocReferences) {
|
||||
this.tocReferences = tocReferences;
|
||||
}
|
||||
|
||||
public List<TOCReference> getTocReferences() {
|
||||
return tocReferences;
|
||||
}
|
||||
|
||||
public void setTocReferences(List<TOCReference> tocReferences) {
|
||||
this.tocReferences = tocReferences;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls addTOCReferenceAtLocation after splitting the path using the DEFAULT_PATH_SEPARATOR.
|
||||
*
|
||||
* @return the new TOCReference
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
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 resource
|
||||
* @param path path
|
||||
* @param pathSeparator 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 title
|
||||
* @param tocReferences tocReferences
|
||||
* @return null if not found.
|
||||
*/
|
||||
private static TOCReference findTocReferenceByTitle(String title,
|
||||
List<TOCReference> tocReferences) {
|
||||
for (TOCReference tocReference : tocReferences) {
|
||||
if (title.equals(tocReference.getTitle())) {
|
||||
return tocReference;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given Resources to the TableOfContents at the location specified by the pathElements.
|
||||
* <p>
|
||||
* Example:
|
||||
* Calling this method with a Resource and new String[] {"chapter1", "paragraph1"} will result in the following:
|
||||
* <ul>
|
||||
* <li>a TOCReference with the title "chapter1" at the root level.<br/>
|
||||
* If this TOCReference did not yet exist it will have been created and does not point to any resource</li>
|
||||
* <li>A TOCReference that has the title "paragraph1". This TOCReference will be the child of TOCReference "chapter1" and
|
||||
* will point to the given Resource</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param resource resource
|
||||
* @param pathElements pathElements
|
||||
* @return the new TOCReference
|
||||
*/
|
||||
public TOCReference addSection(Resource resource, String[] pathElements) {
|
||||
if (pathElements == null || pathElements.length == 0) {
|
||||
return null;
|
||||
}
|
||||
TOCReference result = null;
|
||||
List<TOCReference> currentTocReferences = this.tocReferences;
|
||||
for (String currentTitle : pathElements) {
|
||||
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.
|
||||
* <p>
|
||||
* Example:
|
||||
* Calling this method with a Resource and new int[] {0, 0} will result in the following:
|
||||
* <ul>
|
||||
* <li>a TOCReference at the root level.<br/>
|
||||
* If this TOCReference did not yet exist it will have been created with a title of "" and does not point to any resource</li>
|
||||
* <li>A TOCReference that points to the given resource and is a child of the previously created TOCReference.<br/>
|
||||
* If this TOCReference didn't exist yet it will be created and have a title of ""</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param resource resource
|
||||
* @param pathElements pathElements
|
||||
* @return the new TOCReference
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public TOCReference addSection(Resource resource, int[] pathElements,
|
||||
String sectionTitlePrefix, String sectionNumberSeparator) {
|
||||
if (pathElements == null || pathElements.length == 0) {
|
||||
return null;
|
||||
}
|
||||
TOCReference result = null;
|
||||
List<TOCReference> currentTocReferences = this.tocReferences;
|
||||
for (int i = 0; i < pathElements.length; i++) {
|
||||
int currentIndex = pathElements[i];
|
||||
if (currentIndex > 0 && currentIndex < (currentTocReferences.size()
|
||||
- 1)) {
|
||||
result = currentTocReferences.get(currentIndex);
|
||||
} else {
|
||||
result = null;
|
||||
}
|
||||
if (result == null) {
|
||||
paddTOCReferences(currentTocReferences, pathElements, i,
|
||||
sectionTitlePrefix, sectionNumberSeparator);
|
||||
result = currentTocReferences.get(currentIndex);
|
||||
}
|
||||
currentTocReferences = result.getChildren();
|
||||
}
|
||||
result.setResource(resource);
|
||||
return result;
|
||||
}
|
||||
|
||||
private void paddTOCReferences(List<TOCReference> currentTocReferences,
|
||||
int[] pathElements, int pathPos, String sectionPrefix,
|
||||
String sectionNumberSeparator) {
|
||||
for (int i = currentTocReferences.size(); i <= pathElements[pathPos]; i++) {
|
||||
String sectionTitle = createSectionTitle(pathElements, pathPos, i,
|
||||
sectionPrefix,
|
||||
sectionNumberSeparator);
|
||||
currentTocReferences.add(new TOCReference(sectionTitle, null));
|
||||
}
|
||||
}
|
||||
|
||||
private String createSectionTitle(int[] pathElements, int pathPos,
|
||||
int lastPos,
|
||||
String sectionPrefix, String sectionNumberSeparator) {
|
||||
StringBuilder title = new StringBuilder(sectionPrefix);
|
||||
for (int i = 0; i < pathPos; i++) {
|
||||
if (i > 0) {
|
||||
title.append(sectionNumberSeparator);
|
||||
}
|
||||
title.append(pathElements[i] + 1);
|
||||
}
|
||||
if (pathPos > 0) {
|
||||
title.append(sectionNumberSeparator);
|
||||
}
|
||||
title.append(lastPos + 1);
|
||||
return title.toString();
|
||||
}
|
||||
|
||||
public TOCReference addTOCReference(TOCReference tocReference) {
|
||||
if (tocReferences == null) {
|
||||
tocReferences = new ArrayList<>();
|
||||
}
|
||||
tocReferences.add(tocReference);
|
||||
return tocReference;
|
||||
}
|
||||
|
||||
/**
|
||||
* All unique references (unique by href) in the order in which they are referenced to in the table of contents.
|
||||
*
|
||||
* @return All unique references (unique by href) in the order in which they are referenced to in the table of contents.
|
||||
*/
|
||||
public List<Resource> getAllUniqueResources() {
|
||||
Set<String> uniqueHrefs = new HashSet<>();
|
||||
List<Resource> result = new ArrayList<>();
|
||||
getAllUniqueResources(uniqueHrefs, result, tocReferences);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void getAllUniqueResources(
|
||||
Set<String> uniqueHrefs,
|
||||
List<Resource> result,
|
||||
List<TOCReference> tocReferences
|
||||
) {
|
||||
for (TOCReference tocReference : tocReferences) {
|
||||
Resource resource = tocReference.getResource();
|
||||
if (resource != null && !uniqueHrefs.contains(resource.getHref())) {
|
||||
uniqueHrefs.add(resource.getHref());
|
||||
result.add(resource);
|
||||
}
|
||||
getAllUniqueResources(uniqueHrefs, result, tocReference.getChildren());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The total number of references in this table of contents.
|
||||
*
|
||||
* @return The total number of references in this table of contents.
|
||||
*/
|
||||
public int size() {
|
||||
return getTotalSize(tocReferences);
|
||||
}
|
||||
|
||||
private static int getTotalSize(Collection<TOCReference> tocReferences) {
|
||||
int result = tocReferences.size();
|
||||
for (TOCReference tocReference : tocReferences) {
|
||||
result += getTotalSize(tocReference.getChildren());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* The maximum depth of the reference tree
|
||||
*
|
||||
* @return The maximum depth of the reference tree
|
||||
*/
|
||||
public int calculateDepth() {
|
||||
return calculateDepth(tocReferences, 0);
|
||||
}
|
||||
|
||||
private int calculateDepth(List<TOCReference> tocReferences,
|
||||
int currentDepth) {
|
||||
int maxChildDepth = 0;
|
||||
for (TOCReference tocReference : tocReferences) {
|
||||
int childDepth = calculateDepth(tocReference.getChildren(), 1);
|
||||
if (childDepth > maxChildDepth) {
|
||||
maxChildDepth = childDepth;
|
||||
}
|
||||
}
|
||||
return currentDepth + maxChildDepth;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package me.ag2s.epublib.domain;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import me.ag2s.epublib.Constants;
|
||||
import me.ag2s.epublib.util.StringUtil;
|
||||
|
||||
public class TitledResourceReference extends ResourceReference
|
||||
implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 3918155020095190080L;
|
||||
private String fragmentId;
|
||||
private String title;
|
||||
|
||||
/**
|
||||
* 这会使title为null
|
||||
*
|
||||
* @param resource resource
|
||||
*/
|
||||
@Deprecated
|
||||
@SuppressWarnings("unused")
|
||||
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为null时不设置标题
|
||||
if (this.resource != null && this.title != null) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user