NCXDocumentV2.java 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. package me.ag2s.epublib.epub;
  2. import android.util.Log;
  3. import org.w3c.dom.Document;
  4. import org.w3c.dom.Element;
  5. import org.w3c.dom.Node;
  6. import org.w3c.dom.NodeList;
  7. import org.xmlpull.v1.XmlSerializer;
  8. import java.io.ByteArrayOutputStream;
  9. import java.io.IOException;
  10. import java.io.UnsupportedEncodingException;
  11. import java.net.URLDecoder;
  12. import java.util.ArrayList;
  13. import java.util.List;
  14. import java.util.zip.ZipEntry;
  15. import java.util.zip.ZipOutputStream;
  16. import me.ag2s.epublib.Constants;
  17. import me.ag2s.epublib.domain.Author;
  18. import me.ag2s.epublib.domain.EpubBook;
  19. import me.ag2s.epublib.domain.Identifier;
  20. import me.ag2s.epublib.domain.MediaTypes;
  21. import me.ag2s.epublib.domain.Resource;
  22. import me.ag2s.epublib.domain.TOCReference;
  23. import me.ag2s.epublib.domain.TableOfContents;
  24. import me.ag2s.epublib.util.ResourceUtil;
  25. import me.ag2s.epublib.util.StringUtil;
  26. /**
  27. * Writes the ncx document as defined by namespace http://www.daisy.org/z3986/2005/ncx/
  28. *
  29. * @author paul
  30. */
  31. public class NCXDocumentV2 {
  32. public static final String NAMESPACE_NCX = "http://www.daisy.org/z3986/2005/ncx/";
  33. @SuppressWarnings("unused")
  34. public static final String PREFIX_NCX = "ncx";
  35. public static final String NCX_ITEM_ID = "ncx";
  36. public static final String DEFAULT_NCX_HREF = "toc.ncx";
  37. public static final String PREFIX_DTB = "dtb";
  38. private static final String TAG = NCXDocumentV2.class.getName();
  39. private interface NCXTags {
  40. String ncx = "ncx";
  41. String meta = "meta";
  42. String navPoint = "navPoint";
  43. String navMap = "navMap";
  44. String navLabel = "navLabel";
  45. String content = "content";
  46. String text = "text";
  47. String docTitle = "docTitle";
  48. String docAuthor = "docAuthor";
  49. String head = "head";
  50. }
  51. private interface NCXAttributes {
  52. String src = "src";
  53. String name = "name";
  54. String content = "content";
  55. String id = "id";
  56. String playOrder = "playOrder";
  57. String clazz = "class";
  58. String version = "version";
  59. }
  60. private interface NCXAttributeValues {
  61. String chapter = "chapter";
  62. String version = "2005-1";
  63. }
  64. @SuppressWarnings("unused")
  65. public static Resource read(EpubBook book, EpubReader epubReader) {
  66. Resource ncxResource = null;
  67. if (book.getSpine().getTocResource() == null) {
  68. Log.e(TAG, "Book does not contain a table of contents file");
  69. return null;
  70. }
  71. try {
  72. ncxResource = book.getSpine().getTocResource();
  73. if (ncxResource == null) {
  74. return null;
  75. }
  76. Log.d(TAG, ncxResource.getHref());
  77. Document ncxDocument = ResourceUtil.getAsDocument(ncxResource);
  78. Element navMapElement = DOMUtil
  79. .getFirstElementByTagNameNS(ncxDocument.getDocumentElement(),
  80. NAMESPACE_NCX, NCXTags.navMap);
  81. if (navMapElement == null) {
  82. return null;
  83. }
  84. TableOfContents tableOfContents = new TableOfContents(
  85. readTOCReferences(navMapElement.getChildNodes(), book));
  86. book.setTableOfContents(tableOfContents);
  87. } catch (Exception e) {
  88. Log.e(TAG, e.getMessage(), e);
  89. }
  90. return ncxResource;
  91. }
  92. static List<TOCReference> readTOCReferences(NodeList navpoints,
  93. EpubBook book) {
  94. if (navpoints == null) {
  95. return new ArrayList<>();
  96. }
  97. List<TOCReference> result = new ArrayList<>(
  98. navpoints.getLength());
  99. for (int i = 0; i < navpoints.getLength(); i++) {
  100. Node node = navpoints.item(i);
  101. if (node.getNodeType() != Document.ELEMENT_NODE) {
  102. continue;
  103. }
  104. if (!(node.getLocalName().equals(NCXTags.navPoint))) {
  105. continue;
  106. }
  107. TOCReference tocReference = readTOCReference((Element) node, book);
  108. result.add(tocReference);
  109. }
  110. return result;
  111. }
  112. static TOCReference readTOCReference(Element navpointElement, EpubBook book) {
  113. String label = readNavLabel(navpointElement);
  114. //Log.d(TAG,"label:"+label);
  115. String tocResourceRoot = StringUtil
  116. .substringBeforeLast(book.getSpine().getTocResource().getHref(), '/');
  117. if (tocResourceRoot.length() == book.getSpine().getTocResource().getHref()
  118. .length()) {
  119. tocResourceRoot = "";
  120. } else {
  121. tocResourceRoot = tocResourceRoot + "/";
  122. }
  123. String reference = StringUtil
  124. .collapsePathDots(tocResourceRoot + readNavReference(navpointElement));
  125. String href = StringUtil
  126. .substringBefore(reference, Constants.FRAGMENT_SEPARATOR_CHAR);
  127. String fragmentId = StringUtil
  128. .substringAfter(reference, Constants.FRAGMENT_SEPARATOR_CHAR);
  129. Resource resource = book.getResources().getByHref(href);
  130. if (resource == null) {
  131. Log.e(TAG, "Resource with href " + href + " in NCX document not found");
  132. }
  133. Log.v(TAG, "label:" + label);
  134. Log.v(TAG, "href:" + href);
  135. Log.v(TAG, "fragmentId:" + fragmentId);
  136. TOCReference result = new TOCReference(label, resource, fragmentId);
  137. List<TOCReference> childTOCReferences = readTOCReferences(
  138. navpointElement.getChildNodes(), book);
  139. result.setChildren(childTOCReferences);
  140. return result;
  141. }
  142. private static String readNavReference(Element navpointElement) {
  143. Element contentElement = DOMUtil
  144. .getFirstElementByTagNameNS(navpointElement, NAMESPACE_NCX,
  145. NCXTags.content);
  146. if (contentElement == null) {
  147. return null;
  148. }
  149. String result = DOMUtil
  150. .getAttribute(contentElement, NAMESPACE_NCX, NCXAttributes.src);
  151. try {
  152. result = URLDecoder.decode(result, Constants.CHARACTER_ENCODING);
  153. } catch (UnsupportedEncodingException e) {
  154. Log.e(TAG, e.getMessage());
  155. }
  156. return result;
  157. }
  158. private static String readNavLabel(Element navpointElement) {
  159. //Log.d(TAG,navpointElement.getTagName());
  160. Element navLabel = DOMUtil
  161. .getFirstElementByTagNameNS(navpointElement, NAMESPACE_NCX,
  162. NCXTags.navLabel);
  163. assert navLabel != null;
  164. return DOMUtil.getTextChildrenContent(DOMUtil
  165. .getFirstElementByTagNameNS(navLabel, NAMESPACE_NCX, NCXTags.text));
  166. }
  167. @SuppressWarnings("unused")
  168. public static void write(EpubWriter epubWriter, EpubBook book,
  169. ZipOutputStream resultStream) throws IOException {
  170. resultStream
  171. .putNextEntry(new ZipEntry(book.getSpine().getTocResource().getHref()));
  172. XmlSerializer out = EpubProcessorSupport.createXmlSerializer(resultStream);
  173. write(out, book);
  174. out.flush();
  175. }
  176. /**
  177. * Generates a resource containing an xml document containing the table of contents of the book in ncx format.
  178. *
  179. * @param xmlSerializer the serializer used
  180. * @param book the book to serialize
  181. * @throws IOException IOException
  182. * @throws IllegalStateException IllegalStateException
  183. * @throws IllegalArgumentException IllegalArgumentException
  184. */
  185. public static void write(XmlSerializer xmlSerializer, EpubBook book)
  186. throws IllegalArgumentException, IllegalStateException, IOException {
  187. write(xmlSerializer, book.getMetadata().getIdentifiers(), book.getTitle(),
  188. book.getMetadata().getAuthors(), book.getTableOfContents());
  189. }
  190. public static Resource createNCXResource(EpubBook book)
  191. throws IllegalArgumentException, IllegalStateException, IOException {
  192. return createNCXResource(book.getMetadata().getIdentifiers(),
  193. book.getTitle(), book.getMetadata().getAuthors(),
  194. book.getTableOfContents());
  195. }
  196. public static Resource createNCXResource(List<Identifier> identifiers,
  197. String title, List<Author> authors, TableOfContents tableOfContents)
  198. throws IllegalArgumentException, IllegalStateException, IOException {
  199. ByteArrayOutputStream data = new ByteArrayOutputStream();
  200. XmlSerializer out = EpubProcessorSupport.createXmlSerializer(data);
  201. write(out, identifiers, title, authors, tableOfContents);
  202. return new Resource(NCX_ITEM_ID, data.toByteArray(),
  203. DEFAULT_NCX_HREF, MediaTypes.NCX);
  204. }
  205. public static void write(XmlSerializer serializer,
  206. List<Identifier> identifiers, String title, List<Author> authors,
  207. TableOfContents tableOfContents)
  208. throws IllegalArgumentException, IllegalStateException, IOException {
  209. serializer.startDocument(Constants.CHARACTER_ENCODING, false);
  210. serializer.setPrefix(EpubWriter.EMPTY_NAMESPACE_PREFIX, NAMESPACE_NCX);
  211. serializer.startTag(NAMESPACE_NCX, NCXTags.ncx);
  212. // serializer.writeNamespace("ncx", NAMESPACE_NCX);
  213. // serializer.attribute("xmlns", NAMESPACE_NCX);
  214. serializer
  215. .attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, NCXAttributes.version,
  216. NCXAttributeValues.version);
  217. serializer.startTag(NAMESPACE_NCX, NCXTags.head);
  218. for (Identifier identifier : identifiers) {
  219. writeMetaElement(identifier.getScheme(), identifier.getValue(),
  220. serializer);
  221. }
  222. writeMetaElement("generator", Constants.EPUB_GENERATOR_NAME, serializer);
  223. writeMetaElement("depth", String.valueOf(tableOfContents.calculateDepth()),
  224. serializer);
  225. writeMetaElement("totalPageCount", "0", serializer);
  226. writeMetaElement("maxPageNumber", "0", serializer);
  227. serializer.endTag(NAMESPACE_NCX, "head");
  228. serializer.startTag(NAMESPACE_NCX, NCXTags.docTitle);
  229. serializer.startTag(NAMESPACE_NCX, NCXTags.text);
  230. // write the first title
  231. serializer.text(StringUtil.defaultIfNull(title));
  232. serializer.endTag(NAMESPACE_NCX, NCXTags.text);
  233. serializer.endTag(NAMESPACE_NCX, NCXTags.docTitle);
  234. for (Author author : authors) {
  235. serializer.startTag(NAMESPACE_NCX, NCXTags.docAuthor);
  236. serializer.startTag(NAMESPACE_NCX, NCXTags.text);
  237. serializer.text(author.getLastname() + ", " + author.getFirstname());
  238. serializer.endTag(NAMESPACE_NCX, NCXTags.text);
  239. serializer.endTag(NAMESPACE_NCX, NCXTags.docAuthor);
  240. }
  241. serializer.startTag(NAMESPACE_NCX, NCXTags.navMap);
  242. writeNavPoints(tableOfContents.getTocReferences(), 1, serializer);
  243. serializer.endTag(NAMESPACE_NCX, NCXTags.navMap);
  244. serializer.endTag(NAMESPACE_NCX, "ncx");
  245. serializer.endDocument();
  246. }
  247. private static void writeMetaElement(String dtbName, String content,
  248. XmlSerializer serializer)
  249. throws IllegalArgumentException, IllegalStateException, IOException {
  250. serializer.startTag(NAMESPACE_NCX, NCXTags.meta);
  251. serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, NCXAttributes.name,
  252. PREFIX_DTB + ":" + dtbName);
  253. serializer
  254. .attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, NCXAttributes.content,
  255. content);
  256. serializer.endTag(NAMESPACE_NCX, NCXTags.meta);
  257. }
  258. private static int writeNavPoints(List<TOCReference> tocReferences,
  259. int playOrder,
  260. XmlSerializer serializer)
  261. throws IllegalArgumentException, IllegalStateException, IOException {
  262. for (TOCReference tocReference : tocReferences) {
  263. if (tocReference.getResource() == null) {
  264. playOrder = writeNavPoints(tocReference.getChildren(), playOrder,
  265. serializer);
  266. continue;
  267. }
  268. writeNavPointStart(tocReference, playOrder, serializer);
  269. playOrder++;
  270. if (!tocReference.getChildren().isEmpty()) {
  271. playOrder = writeNavPoints(tocReference.getChildren(), playOrder,
  272. serializer);
  273. }
  274. writeNavPointEnd(tocReference, serializer);
  275. }
  276. return playOrder;
  277. }
  278. private static void writeNavPointStart(TOCReference tocReference,
  279. int playOrder, XmlSerializer serializer)
  280. throws IllegalArgumentException, IllegalStateException, IOException {
  281. serializer.startTag(NAMESPACE_NCX, NCXTags.navPoint);
  282. serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, NCXAttributes.id,
  283. "navPoint-" + playOrder);
  284. serializer
  285. .attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, NCXAttributes.playOrder,
  286. String.valueOf(playOrder));
  287. serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, NCXAttributes.clazz,
  288. NCXAttributeValues.chapter);
  289. serializer.startTag(NAMESPACE_NCX, NCXTags.navLabel);
  290. serializer.startTag(NAMESPACE_NCX, NCXTags.text);
  291. serializer.text(tocReference.getTitle());
  292. serializer.endTag(NAMESPACE_NCX, NCXTags.text);
  293. serializer.endTag(NAMESPACE_NCX, NCXTags.navLabel);
  294. serializer.startTag(NAMESPACE_NCX, NCXTags.content);
  295. serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, NCXAttributes.src,
  296. tocReference.getCompleteHref());
  297. serializer.endTag(NAMESPACE_NCX, NCXTags.content);
  298. }
  299. @SuppressWarnings("unused")
  300. private static void writeNavPointEnd(TOCReference tocReference,
  301. XmlSerializer serializer)
  302. throws IllegalArgumentException, IllegalStateException, IOException {
  303. serializer.endTag(NAMESPACE_NCX, NCXTags.navPoint);
  304. }
  305. }