引言:由于公司存储电子书的格式是.epub。一本电子书加载的时候,如果电子书大的话,全部加载该电子书会非常的消耗时间和资源。非常的不合理。那么现在,将所有电子书按章切分。将拆分的电子书再上传至服务器,用户点击阅读电子书任何一章节,就加载该章节的数据。这里的具体逻辑不细写,主要细写如果切割电子书的过程。
准备这里我用到了Epublib这个jar包,详细资料参考下方
epublib github
epublib API
maven库搜索 epublib-core ,kxml2
pom文件引入依赖
<!-- https://mvnrepository.com/artifact/nl.siegmann.epublib/epublib-core --> <dependency> <groupId>nl.siegmann.epublib</groupId> <artifactId>epublib-core</artifactId> <version>3.1</version> </dependency> <!-- https://mvnrepository.com/artifact/net.sf.kxml/kxml2 --> <dependency> <groupId>net.sf.kxml</groupId> <artifactId>kxml2</artifactId> <version>2.3.0</version> </dependency> 分析 epub电子书结构.epub电子书是怎么的结构。我这边下载了一本。我用winrar解压看下。如下图
可以很清楚看见是由xhtml文件组成资源。关于content.opf ,toc.nxc等这些本来就是属于电子书的数据结构文件,可以自行网上查阅。
epublib中book数据结构epub电子书抽象成epublib中的book 。epublib 中book的数据结构是怎么样子的呢,这里稍微介绍一下,网上确实资料太少了。
Resource数据结构Resources类中又有一个HashMap,HashMap中的value为Resource,Resource的数据结构如下:
id,href相当于Resources中那个HashMap中的key,可以找到Resource
title 和 originalHref 表资源的名字和起始的href
mediaType 资源的数据类型 inputEncoding 编码格式
data 第一张图片上的.xhtml转string类型,然后再转成字节流的数据
MetadataBook里的Metadata相当于电子书的头部,可以获取电子书的基本数据,数据结构如下:
SpineBook中的骨架结构,用来链接Book资源文件,数据结构如下
Resource 与ResourceReference 为关联,SpineReference是ResourceReference的父类。
在设置好Resouces后一定要设置相应的Spine中的Resouce,不然电子书打开,会无法识别。
TableOfContentsBook中目录对应的数据结构。有目录的名字,目录对应的Resource,对应的数据结构如下:
实战Demo代码如下:
/** * @Description: 按章节切分 电子书 * @Author: ouyangkang * @CreateDate: 2018/9/28 17:41 * @Param [url] */ public static void segmentation(String url){ try { // 获取网络资源 URL urlResource = new URL(url) ; // 打开链接 HttpURLConnection conn = (HttpURLConnection) urlResource.openConnection(); // 建立链接 conn.connect(); // 读取电子书流 EpubReader epubReader = new EpubReader(); // 获取电子书 InputStream inputStream = conn.getInputStream(); if (inputStream == null){ return; } if (epubReader == null){ return; } Book book = epubReader.readEpub(inputStream); if (book == null){ return; } // 获取电子书目录 TableOfContents tableOfContents = book.getTableOfContents(); // 电子书章节封装资源 List<SpineReference> spineReferences = book.getSpine().getSpineReferences(); // 电子书 章节Id 集合 List<String> resourceIds = new ArrayList<>(16); // 获取所有资源href Set<String> hrefs = (Set<String>) book.getResources().getAllHrefs(); // css 资源文件 List<String> hrefCss = new ArrayList<>(); hrefs.stream().forEach(href -> { if (href.contains(".css")){ hrefCss.add(href); } }); spineReferences.stream().forEach(spineReference -> { // 获取章节Id resourceIds.add(spineReference.getResourceId()); }); // 电子书章节资源 Resources resources = new Resources(); // 写入电子书 EpubWriter epubWriter = new EpubWriter(); resourceIds.stream().forEach(resourceId -> { // 电子书章节 Book bookChapter = new Book(); //章节导航 Spine spine = new Spine(); if (hrefCss.size() > 0){ hrefCss.stream().forEach(href -> { resources.add(book.getResources().getByHref(href)); }); } nl.siegmann.epublib.domain.Resource resource = book.getResources().getById(resourceId); resources.add(resource); // 获取图片资源 try { String imageData = new String(resource.getData(),"UTF-8"); // 获取图片源 List<String> imagesHrefs = getImgSrc(imageData); //添加该章节下的图片资源 imagesHrefs.stream().forEach(imagesHref -> { nl.siegmann.epublib.domain.Resource resourceImage = book.getResources().getByHref(imagesHref); resources.add(resourceImage); }); //设置电子书资源 // 设置电子书导航文件 nl.siegmann.epublib.domain.Resource tocResource = book.getResources().getById("ncx"); if (tocResource != null){ // 发现有的电子书并不存在 toc.ncx 而是以一种toc.xhtml的文件存在 if (tocResource.getHref().contains("toc.ncx")) { spine.setTocResource(tocResource); } else { resources.add(tocResource); } } //添加章节资源 bookChapter.setResources(resources); // 添加该章节骨架 spine.addSpineReference(new SpineReference(book.getResources().getById(resourceId))); bookChapter.setSpine(spine); // 添加该书的所有目录 bookChapter.setTableOfContents(tableOfContents); //文件写入地址 String path = "D:\\ebook\\"+resourceId+".epub"; File file = new File(path); if (!file.getParentFile().exists()){ file.getParentFile().mkdir(); } epubWriter.write(bookChapter, new FileOutputStream(file)); } catch (IOException e) { e.printStackTrace(); }finally { conn.disconnect(); } }); }catch (Exception e){ e.printStackTrace(); } } /** * 获取img标签中的src值 * @param content * @return */ public static List<String> getImgSrc(String content){ List<String> list = new ArrayList<String>(); //目前img标签标示有3种表达式 //<img alt="" src="http://www.likecs.com/1.jpg"/> <img alt="" src="http://www.likecs.com/1.jpg"></img> <img alt="" src="http://www.likecs.com/1.jpg"> //开始匹配content中的<img />标签 Pattern p_img = Pattern.compile("<(img|IMG|image|IMAGE)(.*?)(/>|></img>|>|></image)"); Matcher m_img = p_img.matcher(content); boolean result_img = m_img.find(); if (result_img) { while (result_img) { //获取到匹配的<img />标签中的内容 String str_img = m_img.group(2); //开始匹配<img />标签中的src Pattern p_src = Pattern.compile("(src|SRC|href|HREF)=(\"|\')(.*?)(\"|\')"); Matcher m_src = p_src.matcher(str_img); if (m_src.find()) { String str_src = m_src.group(3); if (str_src.contains("Image")){ str_src = str_src.substring(str_src.indexOf("I"),str_src.length()); }else { str_src = str_src.substring(str_src.indexOf("i"),str_src.length()); } list.add(str_src); } //结束匹配<img />标签中的src //匹配content中是否存在下一个<img />标签,有则继续以上步骤匹配<img />标签中的src result_img = m_img.find(); } } return list; }