diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 695d503..141d555 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -8,20 +8,21 @@ repository = { workspace = true } readme = { workspace = true } keywords = { workspace = true } rust-version = { workspace = true } + [dependencies] -zip = "6.0.0" +zip = "7.0.0" anyhow = "1.0.100" -quick-xml = { version = "0.38.3" } +quick-xml = { version = "0.38.4" } ab_glyph = { version = "0.2.32", optional = true } -imageproc = { version = "0.25.0", optional = true} -serde_json = { version = "1.0.145", optional = true } +imageproc = { version = "0.26.0", optional = true} +serde_json = { version = "1.0.149", optional = true } iepub-derive = { path = "../derive", version = "1.2.6" } serde = { version = "1.0.228", features = ["derive"], optional = true } -image = { version = "0.25.8", default-features = false, features = ["jpeg"], optional = true } +image = { version = "0.25.9", default-features = false, features = ["jpeg"], optional = true } md-5 = {version = "0.10.6", optional = true } [dev-dependencies] -reqwest = { version = "0.11", features = ["blocking"] } +reqwest = { version = "0.13.1", features = ["blocking"] } [features] no_nav=[] diff --git a/lib/src/adapter/core.rs b/lib/src/adapter/core.rs index 2ae5b10..8019507 100644 --- a/lib/src/adapter/core.rs +++ b/lib/src/adapter/core.rs @@ -674,7 +674,7 @@ pub mod concat { .with_title("1.1") .with_file_name("1/1.xhtml"), ); - let mut builder = EpubBuilder::new() + let builder = EpubBuilder::new() .custome_nav(true) .with_title("测试合并") .with_creator("作者") @@ -706,7 +706,7 @@ pub mod concat { .with_title("3.1") .with_file_name("2/1.xhtml"), ); - let mut builder = EpubBuilder::new() + let builder = EpubBuilder::new() .custome_nav(true) .with_title("测试合并2") .with_creator("作者2") @@ -730,11 +730,11 @@ pub mod concat { ); let mut book2 = builder.book().unwrap(); - let mut builder = EpubBuilder::default(); - let (mut builder, len, a_len) = + let builder = EpubBuilder::default(); + let (builder, len, a_len) = add_into_epub(builder, &mut book1, 0, 0, 0, None, &[]).unwrap(); - let (mut builder, len, a_len) = + let (builder, len, a_len) = add_into_epub(builder, &mut book2, len, a_len, 0, None, &[]).unwrap(); let b = builder.book().unwrap(); diff --git a/lib/src/epub/core.rs b/lib/src/epub/core.rs index c1b3ab6..dbd597e 100644 --- a/lib/src/epub/core.rs +++ b/lib/src/epub/core.rs @@ -3,6 +3,9 @@ use std::fmt::{Debug, Display}; use std::io::Write; use std::sync::{Arc, Mutex}; +use quick_xml::events::Event; +use quick_xml::Reader; + use super::common::{self}; use super::html::{get_html_info, to_html}; use crate::cache_struct; @@ -137,6 +140,17 @@ crate::cache_struct! { } } +crate::cache_struct! { + /** + * 标注 + */ + #[derive(Debug, Clone)] + pub struct Annotation{ + id:String, + text:String, + } +} + epub_base_field! { #[derive(Default, Clone)] pub struct EpubHtml { @@ -152,6 +166,7 @@ epub_base_field! { pub(crate) direction: Option, /// body 标签上的attribute pub(crate) body_attribute: Option>, + pub(crate) annotations: Option>, } } @@ -183,6 +198,78 @@ impl EpubHtml { self._data.as_deref() } + fn get_annotation(&mut self, data: &Vec) -> Vec { + let mut annotations = Vec::new(); + if let Ok(content) = String::from_utf8(data.to_vec()) { + let mut reader = quick_xml::reader::NsReader::from_str(&content); + reader.config_mut().trim_text(true); + let mut buf = Vec::new(); + let mut in_annotation = false; + let mut current_text = String::new(); + let mut data_id: Option = None; + loop { + if let Ok(event) = reader.read_event_into(&mut buf) { + match event { + Event::Start(e) => { + // 检测标注标签(如 ) + if e.name().as_ref() == b"span" { + if let Some(attr) = e.attributes().find(|a| { + a.as_ref() + .map(|a| a.key == quick_xml::name::QName(b"class")) + .unwrap_or(false) + }) { + if let Ok(attr) = attr { + let class_val = attr.unescape_value().unwrap_or_default(); + if class_val.contains("annotation") { + in_annotation = true; + // 提取元数据标识(如 data-id) + data_id = e + .attributes() + .find(|a| { + a.as_ref() + .map(|a| { + a.key + == quick_xml::name::QName( + b"data-id", + ) + }) + .unwrap_or(false) + }) + .and_then(|a| { + a.ok().map(|a| { + a.unescape_value() + .unwrap_or_default() + .to_string() + }) + }); + } + } + } + } + } + Event::Text(e) if in_annotation => { + current_text.push_str(&e.decode().unwrap_or_default()); + // 累积标注文本 + } + Event::End(e) if e.name().as_ref() == b"span" && in_annotation => { + if let Some(id) = data_id.take() { + annotations.push(Annotation { + id, + text: current_text.clone(), + }); + } + in_annotation = false; + } + Event::Eof => break, + _ => {} + } + } + buf.clear(); + } + } + annotations + } + pub(crate) fn read_data(&mut self, reader: &mut impl EpubReaderTrait) { let (id, origin) = if let Some(index) = self._file_name.find('#') { ( @@ -214,6 +301,7 @@ impl EpubHtml { if !title.is_empty() { self.set_title(&title); } + self.get_annotation(&content); self.set_data(content); if let Some(lang) = language { self.set_language(lang); @@ -273,6 +361,7 @@ impl EpubHtml { if !title.is_empty() { self.set_title(&title); } + self.get_annotation(&content); self.set_data(content); if let Some(lang) = language { self.set_language(lang); @@ -303,6 +392,10 @@ impl EpubHtml { self._data = None; } + pub fn annotation(&self) -> Option> { + self.annotations.clone() + } + pub fn format(&mut self) -> Option { self.data_mut(); Some(to_html(self, false, &None)) diff --git a/lib/src/epub/html.rs b/lib/src/epub/html.rs index 23be9d0..618bf62 100644 --- a/lib/src/epub/html.rs +++ b/lib/src/epub/html.rs @@ -1,7 +1,7 @@ use std::ops::Deref; use super::common; -use crate::{common::get_media_type, prelude::*}; +use crate::{common::get_media_type, epub::core::Annotation, prelude::*}; use quick_xml::events::Event; /// 生成html