1use std::path::PathBuf;
18
19#[cfg(feature = "builder")]
20use crate::{
21 error::{EpubBuilderError, EpubError},
22 utils::ELEMENT_IN_DC_NAMESPACE,
23};
24
25#[derive(Debug, PartialEq, Eq)]
29pub enum EpubVersion {
30 Version2_0,
31 Version3_0,
32}
33
34#[derive(Debug, Clone)]
59pub struct MetadataItem {
60 pub id: Option<String>,
65
66 pub property: String,
72
73 pub value: String,
75
76 pub lang: Option<String>,
78
79 pub refined: Vec<MetadataRefinement>,
87}
88
89#[cfg(feature = "builder")]
90impl MetadataItem {
91 pub fn new(property: &str, value: &str) -> Self {
99 Self {
100 id: None,
101 property: property.to_string(),
102 value: value.to_string(),
103 lang: None,
104 refined: vec![],
105 }
106 }
107
108 pub fn with_id(&mut self, id: &str) -> &mut Self {
115 self.id = Some(id.to_string());
116 self
117 }
118
119 pub fn with_lang(&mut self, lang: &str) -> &mut Self {
126 self.lang = Some(lang.to_string());
127 self
128 }
129
130 pub fn append_refinement(&mut self, refine: MetadataRefinement) -> &mut Self {
140 if self.id.is_some() {
141 self.refined.push(refine);
142 } else {
143 }
145
146 self
147 }
148
149 pub fn build(&self) -> Self {
153 Self { ..self.clone() }
154 }
155
156 pub(crate) fn attributes(&self) -> Vec<(&str, &str)> {
158 let mut attributes = Vec::new();
159
160 if !ELEMENT_IN_DC_NAMESPACE.contains(&self.property.as_str()) {
161 attributes.push(("property", self.property.as_str()));
162 }
163
164 if let Some(id) = &self.id {
165 attributes.push(("id", id.as_str()));
166 };
167
168 if let Some(lang) = &self.lang {
169 attributes.push(("lang", lang.as_str()));
170 };
171
172 attributes
173 }
174}
175
176#[derive(Debug, Clone)]
200pub struct MetadataRefinement {
201 pub refines: String,
202
203 pub property: String,
208
209 pub value: String,
211
212 pub lang: Option<String>,
214
215 pub scheme: Option<String>,
220}
221
222#[cfg(feature = "builder")]
223impl MetadataRefinement {
224 pub fn new(refines: &str, property: &str, value: &str) -> Self {
233 Self {
234 refines: refines.to_string(),
235 property: property.to_string(),
236 value: value.to_string(),
237 lang: None,
238 scheme: None,
239 }
240 }
241
242 pub fn with_lang(&mut self, lang: &str) -> &mut Self {
249 self.lang = Some(lang.to_string());
250 self
251 }
252
253 pub fn with_scheme(&mut self, scheme: &str) -> &mut Self {
260 self.scheme = Some(scheme.to_string());
261 self
262 }
263
264 pub fn build(&self) -> Self {
268 Self { ..self.clone() }
269 }
270
271 pub(crate) fn attributes(&self) -> Vec<(&str, &str)> {
273 let mut attributes = Vec::new();
274
275 attributes.push(("refines", self.refines.as_str()));
276 attributes.push(("property", self.property.as_str()));
277
278 if let Some(lang) = &self.lang {
279 attributes.push(("lang", lang.as_str()));
280 };
281
282 if let Some(scheme) = &self.scheme {
283 attributes.push(("scheme", scheme.as_str()));
284 };
285
286 attributes
287 }
288}
289
290#[derive(Debug)]
299pub struct MetadataLinkItem {
300 pub href: String,
302
303 pub rel: String,
305
306 pub hreflang: Option<String>,
308
309 pub id: Option<String>,
313
314 pub mime: Option<String>,
316
317 pub properties: Option<String>,
322
323 pub refines: Option<String>,
328}
329
330#[derive(Debug, Clone)]
361pub struct ManifestItem {
362 pub id: String,
364
365 pub path: PathBuf,
371
372 pub mime: String,
374
375 pub properties: Option<String>,
381
382 pub fallback: Option<String>,
393}
394
395#[cfg(feature = "builder")]
396impl ManifestItem {
397 pub fn new(id: &str, path: &str) -> Result<Self, EpubError> {
408 if path.starts_with("../") {
409 return Err(
410 EpubBuilderError::IllegalManifestPath { manifest_id: id.to_string() }.into(),
411 );
412 }
413
414 Ok(Self {
415 id: id.to_string(),
416 path: PathBuf::from(path),
417 mime: String::new(),
418 properties: None,
419 fallback: None,
420 })
421 }
422
423 pub(crate) fn set_mime(self, mime: &str) -> Self {
425 Self {
426 id: self.id,
427 path: self.path,
428 mime: mime.to_string(),
429 properties: self.properties,
430 fallback: self.fallback,
431 }
432 }
433
434 pub fn append_property(&mut self, property: &str) -> &mut Self {
441 let new_properties = if let Some(properties) = &self.properties {
442 format!("{} {}", properties, property)
443 } else {
444 property.to_string()
445 };
446
447 self.properties = Some(new_properties);
448 self
449 }
450
451 pub fn with_fallback(&mut self, fallback: &str) -> &mut Self {
458 self.fallback = Some(fallback.to_string());
459 self
460 }
461
462 pub fn build(&self) -> Self {
466 Self { ..self.clone() }
467 }
468
469 pub fn attributes(&self) -> Vec<(&str, &str)> {
471 let mut attributes = Vec::new();
472
473 attributes.push(("id", self.id.as_str()));
474 attributes.push(("href", self.path.to_str().unwrap()));
475 attributes.push(("media-type", self.mime.as_str()));
476
477 if let Some(properties) = &self.properties {
478 attributes.push(("properties", properties.as_str()));
479 }
480
481 if let Some(fallback) = &self.fallback {
482 attributes.push(("fallback", fallback.as_str()));
483 }
484
485 attributes
486 }
487}
488
489#[derive(Debug, Clone)]
516pub struct SpineItem {
517 pub idref: String,
523
524 pub id: Option<String>,
526
527 pub properties: Option<String>,
533
534 pub linear: bool,
544}
545
546#[cfg(feature = "builder")]
547impl SpineItem {
548 pub fn new(idref: &str) -> Self {
557 Self {
558 idref: idref.to_string(),
559 id: None,
560 properties: None,
561 linear: true,
562 }
563 }
564
565 pub fn with_id(&mut self, id: &str) -> &mut Self {
572 self.id = Some(id.to_string());
573 self
574 }
575
576 pub fn append_property(&mut self, property: &str) -> &mut Self {
583 let new_properties = if let Some(properties) = &self.properties {
584 format!("{} {}", properties, property)
585 } else {
586 property.to_string()
587 };
588
589 self.properties = Some(new_properties);
590 self
591 }
592
593 pub fn set_linear(&mut self, linear: bool) -> &mut Self {
600 self.linear = linear;
601 self
602 }
603
604 pub fn build(&self) -> Self {
608 Self { ..self.clone() }
609 }
610
611 pub(crate) fn attributes(&self) -> Vec<(&str, &str)> {
613 let mut attributes = Vec::new();
614
615 attributes.push(("idref", self.idref.as_str()));
616 attributes.push(("linear", if self.linear { "yes" } else { "no" }));
617
618 if let Some(id) = &self.id {
619 attributes.push(("id", id.as_str()));
620 }
621
622 if let Some(properties) = &self.properties {
623 attributes.push(("properties", properties.as_str()));
624 }
625
626 attributes
627 }
628}
629
630#[derive(Debug, Clone)]
636pub struct EncryptionData {
637 pub method: String,
644
645 pub data: String,
650}
651
652#[derive(Debug, Eq, Clone)]
677pub struct NavPoint {
678 pub label: String,
682
683 pub content: Option<PathBuf>,
688
689 pub children: Vec<NavPoint>,
691
692 pub play_order: Option<usize>,
697}
698
699#[cfg(feature = "builder")]
700impl NavPoint {
701 pub fn new(label: &str) -> Self {
708 Self {
709 label: label.to_string(),
710 content: None,
711 children: vec![],
712 play_order: None,
713 }
714 }
715
716 pub fn with_content(&mut self, content: &str) -> &mut Self {
723 self.content = Some(PathBuf::from(content));
724 self
725 }
726
727 pub fn append_child(&mut self, child: NavPoint) -> &mut Self {
734 self.children.push(child);
735 self
736 }
737
738 pub fn set_children(&mut self, children: Vec<NavPoint>) -> &mut Self {
745 self.children = children;
746 self
747 }
748
749 pub fn build(&self) -> Self {
753 Self { ..self.clone() }
754 }
755}
756
757impl Ord for NavPoint {
758 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
759 self.play_order.cmp(&other.play_order)
760 }
761}
762
763impl PartialOrd for NavPoint {
764 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
765 Some(self.cmp(other))
766 }
767}
768
769impl PartialEq for NavPoint {
770 fn eq(&self, other: &Self) -> bool {
771 self.play_order == other.play_order
772 }
773}
774
775#[cfg(feature = "content_builder")]
780#[derive(Debug, Clone, Eq, PartialEq)]
781pub struct Footnote {
782 pub locate: usize,
783 pub content: String,
784}
785
786#[cfg(feature = "content_builder")]
787impl Ord for Footnote {
788 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
789 self.locate.cmp(&other.locate)
790 }
791}
792
793#[cfg(feature = "content_builder")]
794impl PartialOrd for Footnote {
795 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
796 Some(self.cmp(other))
797 }
798}
799
800#[cfg(feature = "content_builder")]
802pub enum BlockType {
803 Text,
804 Quote,
805 Title,
806 Image,
807 Audio,
808 Video,
809 MathML,
810}
811
812#[cfg(test)]
813mod tests {
814 mod navpoint_tests {
815 use std::path::PathBuf;
816
817 use crate::types::NavPoint;
818
819 #[test]
821 fn test_navpoint_partial_eq() {
822 let nav1 = NavPoint {
823 label: "Chapter 1".to_string(),
824 content: Some(PathBuf::from("chapter1.html")),
825 children: vec![],
826 play_order: Some(1),
827 };
828
829 let nav2 = NavPoint {
830 label: "Chapter 1".to_string(),
831 content: Some(PathBuf::from("chapter2.html")),
832 children: vec![],
833 play_order: Some(1),
834 };
835
836 let nav3 = NavPoint {
837 label: "Chapter 2".to_string(),
838 content: Some(PathBuf::from("chapter1.html")),
839 children: vec![],
840 play_order: Some(2),
841 };
842
843 assert_eq!(nav1, nav2); assert_ne!(nav1, nav3); }
846
847 #[test]
849 fn test_navpoint_ord() {
850 let nav1 = NavPoint {
851 label: "Chapter 1".to_string(),
852 content: Some(PathBuf::from("chapter1.html")),
853 children: vec![],
854 play_order: Some(1),
855 };
856
857 let nav2 = NavPoint {
858 label: "Chapter 2".to_string(),
859 content: Some(PathBuf::from("chapter2.html")),
860 children: vec![],
861 play_order: Some(2),
862 };
863
864 let nav3 = NavPoint {
865 label: "Chapter 3".to_string(),
866 content: Some(PathBuf::from("chapter3.html")),
867 children: vec![],
868 play_order: Some(3),
869 };
870
871 assert!(nav1 < nav2);
873 assert!(nav2 > nav1);
874 assert!(nav1 == nav1);
875
876 assert_eq!(nav1.partial_cmp(&nav2), Some(std::cmp::Ordering::Less));
878 assert_eq!(nav2.partial_cmp(&nav1), Some(std::cmp::Ordering::Greater));
879 assert_eq!(nav1.partial_cmp(&nav1), Some(std::cmp::Ordering::Equal));
880
881 let mut nav_points = vec![nav2.clone(), nav3.clone(), nav1.clone()];
883 nav_points.sort();
884 assert_eq!(nav_points, vec![nav1, nav2, nav3]);
885 }
886
887 #[test]
889 fn test_navpoint_ord_with_none_play_order() {
890 let nav_with_order = NavPoint {
891 label: "Chapter 1".to_string(),
892 content: Some(PathBuf::from("chapter1.html")),
893 children: vec![],
894 play_order: Some(1),
895 };
896
897 let nav_without_order = NavPoint {
898 label: "Preface".to_string(),
899 content: Some(PathBuf::from("preface.html")),
900 children: vec![],
901 play_order: None,
902 };
903
904 assert!(nav_without_order < nav_with_order);
905 assert!(nav_with_order > nav_without_order);
906
907 let nav_without_order2 = NavPoint {
908 label: "Introduction".to_string(),
909 content: Some(PathBuf::from("intro.html")),
910 children: vec![],
911 play_order: None,
912 };
913
914 assert!(nav_without_order == nav_without_order2);
915 }
916
917 #[test]
919 fn test_navpoint_with_children() {
920 let child1 = NavPoint {
921 label: "Section 1.1".to_string(),
922 content: Some(PathBuf::from("section1_1.html")),
923 children: vec![],
924 play_order: Some(1),
925 };
926
927 let child2 = NavPoint {
928 label: "Section 1.2".to_string(),
929 content: Some(PathBuf::from("section1_2.html")),
930 children: vec![],
931 play_order: Some(2),
932 };
933
934 let parent1 = NavPoint {
935 label: "Chapter 1".to_string(),
936 content: Some(PathBuf::from("chapter1.html")),
937 children: vec![child1.clone(), child2.clone()],
938 play_order: Some(1),
939 };
940
941 let parent2 = NavPoint {
942 label: "Chapter 1".to_string(),
943 content: Some(PathBuf::from("chapter1.html")),
944 children: vec![child1.clone(), child2.clone()],
945 play_order: Some(1),
946 };
947
948 assert!(parent1 == parent2);
949
950 let parent3 = NavPoint {
951 label: "Chapter 2".to_string(),
952 content: Some(PathBuf::from("chapter2.html")),
953 children: vec![child1.clone(), child2.clone()],
954 play_order: Some(2),
955 };
956
957 assert!(parent1 != parent3);
958 assert!(parent1 < parent3);
959 }
960
961 #[test]
963 fn test_navpoint_with_none_content() {
964 let nav1 = NavPoint {
965 label: "Chapter 1".to_string(),
966 content: None,
967 children: vec![],
968 play_order: Some(1),
969 };
970
971 let nav2 = NavPoint {
972 label: "Chapter 1".to_string(),
973 content: None,
974 children: vec![],
975 play_order: Some(1),
976 };
977
978 assert!(nav1 == nav2);
979 }
980 }
981
982 #[cfg(feature = "builder")]
983 mod builder_tests {
984 mod metadata_item {
985 use crate::types::{MetadataItem, MetadataRefinement};
986
987 #[test]
988 fn test_metadata_item_new() {
989 let metadata_item = MetadataItem::new("title", "EPUB Test Book");
990
991 assert_eq!(metadata_item.property, "title");
992 assert_eq!(metadata_item.value, "EPUB Test Book");
993 assert_eq!(metadata_item.id, None);
994 assert_eq!(metadata_item.lang, None);
995 assert_eq!(metadata_item.refined.len(), 0);
996 }
997
998 #[test]
999 fn test_metadata_item_with_id() {
1000 let mut metadata_item = MetadataItem::new("creator", "John Doe");
1001 metadata_item.with_id("creator-1");
1002
1003 assert_eq!(metadata_item.property, "creator");
1004 assert_eq!(metadata_item.value, "John Doe");
1005 assert_eq!(metadata_item.id, Some("creator-1".to_string()));
1006 assert_eq!(metadata_item.lang, None);
1007 assert_eq!(metadata_item.refined.len(), 0);
1008 }
1009
1010 #[test]
1011 fn test_metadata_item_with_lang() {
1012 let mut metadata_item = MetadataItem::new("title", "测试书籍");
1013 metadata_item.with_lang("zh-CN");
1014
1015 assert_eq!(metadata_item.property, "title");
1016 assert_eq!(metadata_item.value, "测试书籍");
1017 assert_eq!(metadata_item.id, None);
1018 assert_eq!(metadata_item.lang, Some("zh-CN".to_string()));
1019 assert_eq!(metadata_item.refined.len(), 0);
1020 }
1021
1022 #[test]
1023 fn test_metadata_item_append_refinement() {
1024 let mut metadata_item = MetadataItem::new("creator", "John Doe");
1025 metadata_item.with_id("creator-1"); let refinement = MetadataRefinement::new("creator-1", "role", "author");
1028 metadata_item.append_refinement(refinement);
1029
1030 assert_eq!(metadata_item.refined.len(), 1);
1031 assert_eq!(metadata_item.refined[0].refines, "creator-1");
1032 assert_eq!(metadata_item.refined[0].property, "role");
1033 assert_eq!(metadata_item.refined[0].value, "author");
1034 }
1035
1036 #[test]
1037 fn test_metadata_item_append_refinement_without_id() {
1038 let mut metadata_item = MetadataItem::new("title", "Test Book");
1039 let refinement = MetadataRefinement::new("title", "title-type", "main");
1042 metadata_item.append_refinement(refinement);
1043
1044 assert_eq!(metadata_item.refined.len(), 0);
1046 }
1047
1048 #[test]
1049 fn test_metadata_item_build() {
1050 let mut metadata_item = MetadataItem::new("identifier", "urn:isbn:1234567890");
1051 metadata_item.with_id("pub-id").with_lang("en");
1052
1053 let built = metadata_item.build();
1054
1055 assert_eq!(built.property, "identifier");
1056 assert_eq!(built.value, "urn:isbn:1234567890");
1057 assert_eq!(built.id, Some("pub-id".to_string()));
1058 assert_eq!(built.lang, Some("en".to_string()));
1059 assert_eq!(built.refined.len(), 0);
1060 }
1061
1062 #[test]
1063 fn test_metadata_item_builder_chaining() {
1064 let mut metadata_item = MetadataItem::new("title", "EPUB 3.3 Guide");
1065 metadata_item.with_id("title").with_lang("en");
1066
1067 let refinement = MetadataRefinement::new("title", "title-type", "main");
1068 metadata_item.append_refinement(refinement);
1069
1070 let built = metadata_item.build();
1071
1072 assert_eq!(built.property, "title");
1073 assert_eq!(built.value, "EPUB 3.3 Guide");
1074 assert_eq!(built.id, Some("title".to_string()));
1075 assert_eq!(built.lang, Some("en".to_string()));
1076 assert_eq!(built.refined.len(), 1);
1077 }
1078
1079 #[test]
1080 fn test_metadata_item_attributes_dc_namespace() {
1081 let mut metadata_item = MetadataItem::new("title", "Test Book");
1082 metadata_item.with_id("title-id");
1083
1084 let attributes = metadata_item.attributes();
1085
1086 assert!(!attributes.iter().any(|(k, _)| k == &"property"));
1088 assert!(
1089 attributes
1090 .iter()
1091 .any(|(k, v)| k == &"id" && v == &"title-id")
1092 );
1093 }
1094
1095 #[test]
1096 fn test_metadata_item_attributes_non_dc_namespace() {
1097 let mut metadata_item = MetadataItem::new("meta", "value");
1098 metadata_item.with_id("meta-id");
1099
1100 let attributes = metadata_item.attributes();
1101
1102 assert!(attributes.iter().any(|(k, _)| k == &"property"));
1104 assert!(
1105 attributes
1106 .iter()
1107 .any(|(k, v)| k == &"id" && v == &"meta-id")
1108 );
1109 }
1110
1111 #[test]
1112 fn test_metadata_item_attributes_with_lang() {
1113 let mut metadata_item = MetadataItem::new("title", "Test Book");
1114 metadata_item.with_id("title-id").with_lang("en");
1115
1116 let attributes = metadata_item.attributes();
1117
1118 assert!(
1119 attributes
1120 .iter()
1121 .any(|(k, v)| k == &"id" && v == &"title-id")
1122 );
1123 assert!(attributes.iter().any(|(k, v)| k == &"lang" && v == &"en"));
1124 }
1125 }
1126
1127 mod metadata_refinement {
1128 use crate::types::MetadataRefinement;
1129
1130 #[test]
1131 fn test_metadata_refinement_new() {
1132 let refinement = MetadataRefinement::new("title", "title-type", "main");
1133
1134 assert_eq!(refinement.refines, "title");
1135 assert_eq!(refinement.property, "title-type");
1136 assert_eq!(refinement.value, "main");
1137 assert_eq!(refinement.lang, None);
1138 assert_eq!(refinement.scheme, None);
1139 }
1140
1141 #[test]
1142 fn test_metadata_refinement_with_lang() {
1143 let mut refinement = MetadataRefinement::new("creator", "role", "author");
1144 refinement.with_lang("en");
1145
1146 assert_eq!(refinement.refines, "creator");
1147 assert_eq!(refinement.property, "role");
1148 assert_eq!(refinement.value, "author");
1149 assert_eq!(refinement.lang, Some("en".to_string()));
1150 assert_eq!(refinement.scheme, None);
1151 }
1152
1153 #[test]
1154 fn test_metadata_refinement_with_scheme() {
1155 let mut refinement = MetadataRefinement::new("creator", "role", "author");
1156 refinement.with_scheme("marc:relators");
1157
1158 assert_eq!(refinement.refines, "creator");
1159 assert_eq!(refinement.property, "role");
1160 assert_eq!(refinement.value, "author");
1161 assert_eq!(refinement.lang, None);
1162 assert_eq!(refinement.scheme, Some("marc:relators".to_string()));
1163 }
1164
1165 #[test]
1166 fn test_metadata_refinement_build() {
1167 let mut refinement = MetadataRefinement::new("title", "alternate-script", "テスト");
1168 refinement.with_lang("ja").with_scheme("iso-15924");
1169
1170 let built = refinement.build();
1171
1172 assert_eq!(built.refines, "title");
1173 assert_eq!(built.property, "alternate-script");
1174 assert_eq!(built.value, "テスト");
1175 assert_eq!(built.lang, Some("ja".to_string()));
1176 assert_eq!(built.scheme, Some("iso-15924".to_string()));
1177 }
1178
1179 #[test]
1180 fn test_metadata_refinement_builder_chaining() {
1181 let mut refinement = MetadataRefinement::new("creator", "file-as", "Doe, John");
1182 refinement.with_lang("en").with_scheme("dcterms");
1183
1184 let built = refinement.build();
1185
1186 assert_eq!(built.refines, "creator");
1187 assert_eq!(built.property, "file-as");
1188 assert_eq!(built.value, "Doe, John");
1189 assert_eq!(built.lang, Some("en".to_string()));
1190 assert_eq!(built.scheme, Some("dcterms".to_string()));
1191 }
1192
1193 #[test]
1194 fn test_metadata_refinement_attributes() {
1195 let mut refinement = MetadataRefinement::new("title", "title-type", "main");
1196 refinement.with_lang("en").with_scheme("onix:codelist5");
1197
1198 let attributes = refinement.attributes();
1199
1200 assert!(
1201 attributes
1202 .iter()
1203 .any(|(k, v)| k == &"refines" && v == &"title")
1204 );
1205 assert!(
1206 attributes
1207 .iter()
1208 .any(|(k, v)| k == &"property" && v == &"title-type")
1209 );
1210 assert!(attributes.iter().any(|(k, v)| k == &"lang" && v == &"en"));
1211 assert!(
1212 attributes
1213 .iter()
1214 .any(|(k, v)| k == &"scheme" && v == &"onix:codelist5")
1215 );
1216 }
1217
1218 #[test]
1219 fn test_metadata_refinement_attributes_optional_fields() {
1220 let refinement = MetadataRefinement::new("creator", "role", "author");
1221 let attributes = refinement.attributes();
1222
1223 assert!(
1224 attributes
1225 .iter()
1226 .any(|(k, v)| k == &"refines" && v == &"creator")
1227 );
1228 assert!(
1229 attributes
1230 .iter()
1231 .any(|(k, v)| k == &"property" && v == &"role")
1232 );
1233
1234 assert!(!attributes.iter().any(|(k, _)| k == &"lang"));
1236 assert!(!attributes.iter().any(|(k, _)| k == &"scheme"));
1237 }
1238 }
1239
1240 mod manifest_item {
1241 use std::path::PathBuf;
1242
1243 use crate::types::ManifestItem;
1244
1245 #[test]
1246 fn test_manifest_item_new() {
1247 let manifest_item = ManifestItem::new("cover", "images/cover.jpg");
1248 assert!(manifest_item.is_ok());
1249
1250 let manifest_item = manifest_item.unwrap();
1251 assert_eq!(manifest_item.id, "cover");
1252 assert_eq!(manifest_item.path, PathBuf::from("images/cover.jpg"));
1253 assert_eq!(manifest_item.mime, "");
1254 assert_eq!(manifest_item.properties, None);
1255 assert_eq!(manifest_item.fallback, None);
1256 }
1257
1258 #[test]
1259 fn test_manifest_item_append_property() {
1260 let manifest_item = ManifestItem::new("nav", "nav.xhtml");
1261 assert!(manifest_item.is_ok());
1262
1263 let mut manifest_item = manifest_item.unwrap();
1264 manifest_item.append_property("nav");
1265
1266 assert_eq!(manifest_item.id, "nav");
1267 assert_eq!(manifest_item.path, PathBuf::from("nav.xhtml"));
1268 assert_eq!(manifest_item.mime, "");
1269 assert_eq!(manifest_item.properties, Some("nav".to_string()));
1270 assert_eq!(manifest_item.fallback, None);
1271 }
1272
1273 #[test]
1274 fn test_manifest_item_append_multiple_properties() {
1275 let manifest_item = ManifestItem::new("content", "content.xhtml");
1276 assert!(manifest_item.is_ok());
1277
1278 let mut manifest_item = manifest_item.unwrap();
1279 manifest_item
1280 .append_property("nav")
1281 .append_property("scripted")
1282 .append_property("svg");
1283
1284 assert_eq!(
1285 manifest_item.properties,
1286 Some("nav scripted svg".to_string())
1287 );
1288 }
1289
1290 #[test]
1291 fn test_manifest_item_with_fallback() {
1292 let manifest_item = ManifestItem::new("image", "image.tiff");
1293 assert!(manifest_item.is_ok());
1294
1295 let mut manifest_item = manifest_item.unwrap();
1296 manifest_item.with_fallback("image-fallback");
1297
1298 assert_eq!(manifest_item.id, "image");
1299 assert_eq!(manifest_item.path, PathBuf::from("image.tiff"));
1300 assert_eq!(manifest_item.mime, "");
1301 assert_eq!(manifest_item.properties, None);
1302 assert_eq!(manifest_item.fallback, Some("image-fallback".to_string()));
1303 }
1304
1305 #[test]
1306 fn test_manifest_item_set_mime() {
1307 let manifest_item = ManifestItem::new("style", "style.css");
1308 assert!(manifest_item.is_ok());
1309
1310 let manifest_item = manifest_item.unwrap();
1311 let updated_item = manifest_item.set_mime("text/css");
1312
1313 assert_eq!(updated_item.id, "style");
1314 assert_eq!(updated_item.path, PathBuf::from("style.css"));
1315 assert_eq!(updated_item.mime, "text/css");
1316 assert_eq!(updated_item.properties, None);
1317 assert_eq!(updated_item.fallback, None);
1318 }
1319
1320 #[test]
1321 fn test_manifest_item_build() {
1322 let manifest_item = ManifestItem::new("cover", "images/cover.jpg");
1323 assert!(manifest_item.is_ok());
1324
1325 let mut manifest_item = manifest_item.unwrap();
1326 manifest_item
1327 .append_property("cover-image")
1328 .with_fallback("cover-fallback");
1329
1330 let built = manifest_item.build();
1331
1332 assert_eq!(built.id, "cover");
1333 assert_eq!(built.path, PathBuf::from("images/cover.jpg"));
1334 assert_eq!(built.mime, "");
1335 assert_eq!(built.properties, Some("cover-image".to_string()));
1336 assert_eq!(built.fallback, Some("cover-fallback".to_string()));
1337 }
1338
1339 #[test]
1340 fn test_manifest_item_builder_chaining() {
1341 let manifest_item = ManifestItem::new("content", "content.xhtml");
1342 assert!(manifest_item.is_ok());
1343
1344 let mut manifest_item = manifest_item.unwrap();
1345 manifest_item
1346 .append_property("scripted")
1347 .append_property("svg")
1348 .with_fallback("fallback-content");
1349
1350 let built = manifest_item.build();
1351
1352 assert_eq!(built.id, "content");
1353 assert_eq!(built.path, PathBuf::from("content.xhtml"));
1354 assert_eq!(built.mime, "");
1355 assert_eq!(built.properties, Some("scripted svg".to_string()));
1356 assert_eq!(built.fallback, Some("fallback-content".to_string()));
1357 }
1358
1359 #[test]
1360 fn test_manifest_item_attributes() {
1361 let manifest_item = ManifestItem::new("nav", "nav.xhtml");
1362 assert!(manifest_item.is_ok());
1363
1364 let mut manifest_item = manifest_item.unwrap();
1365 manifest_item
1366 .append_property("nav")
1367 .with_fallback("fallback-nav");
1368
1369 let manifest_item = manifest_item.set_mime("application/xhtml+xml");
1371 let attributes = manifest_item.attributes();
1372
1373 assert!(attributes.contains(&("id", "nav")));
1375 assert!(attributes.contains(&("href", "nav.xhtml")));
1376 assert!(attributes.contains(&("media-type", "application/xhtml+xml")));
1377 assert!(attributes.contains(&("properties", "nav")));
1378 assert!(attributes.contains(&("fallback", "fallback-nav")));
1379 }
1380
1381 #[test]
1382 fn test_manifest_item_attributes_optional_fields() {
1383 let manifest_item = ManifestItem::new("simple", "simple.xhtml");
1384 assert!(manifest_item.is_ok());
1385
1386 let manifest_item = manifest_item.unwrap();
1387 let manifest_item = manifest_item.set_mime("application/xhtml+xml");
1388 let attributes = manifest_item.attributes();
1389
1390 assert!(attributes.contains(&("id", "simple")));
1392 assert!(attributes.contains(&("href", "simple.xhtml")));
1393 assert!(attributes.contains(&("media-type", "application/xhtml+xml")));
1394
1395 assert!(!attributes.iter().any(|(k, _)| k == &"properties"));
1397 assert!(!attributes.iter().any(|(k, _)| k == &"fallback"));
1398 }
1399
1400 #[test]
1401 fn test_manifest_item_path_handling() {
1402 let manifest_item = ManifestItem::new("test", "../images/test.png");
1403 assert!(manifest_item.is_err());
1404
1405 let err = manifest_item.unwrap_err();
1406 assert_eq!(
1407 err.to_string(),
1408 "Epub builder error: A manifest with id 'test' should not use a relative path starting with '../'."
1409 );
1410 }
1411 }
1412
1413 mod spine_item {
1414 use crate::types::SpineItem;
1415
1416 #[test]
1417 fn test_spine_item_new() {
1418 let spine_item = SpineItem::new("content_001");
1419
1420 assert_eq!(spine_item.idref, "content_001");
1421 assert_eq!(spine_item.id, None);
1422 assert_eq!(spine_item.properties, None);
1423 assert_eq!(spine_item.linear, true);
1424 }
1425
1426 #[test]
1427 fn test_spine_item_with_id() {
1428 let mut spine_item = SpineItem::new("content_001");
1429 spine_item.with_id("spine1");
1430
1431 assert_eq!(spine_item.idref, "content_001");
1432 assert_eq!(spine_item.id, Some("spine1".to_string()));
1433 assert_eq!(spine_item.properties, None);
1434 assert_eq!(spine_item.linear, true);
1435 }
1436
1437 #[test]
1438 fn test_spine_item_append_property() {
1439 let mut spine_item = SpineItem::new("content_001");
1440 spine_item.append_property("page-spread-left");
1441
1442 assert_eq!(spine_item.idref, "content_001");
1443 assert_eq!(spine_item.id, None);
1444 assert_eq!(spine_item.properties, Some("page-spread-left".to_string()));
1445 assert_eq!(spine_item.linear, true);
1446 }
1447
1448 #[test]
1449 fn test_spine_item_append_multiple_properties() {
1450 let mut spine_item = SpineItem::new("content_001");
1451 spine_item
1452 .append_property("page-spread-left")
1453 .append_property("rendition:layout-pre-paginated");
1454
1455 assert_eq!(
1456 spine_item.properties,
1457 Some("page-spread-left rendition:layout-pre-paginated".to_string())
1458 );
1459 }
1460
1461 #[test]
1462 fn test_spine_item_set_linear() {
1463 let mut spine_item = SpineItem::new("content_001");
1464 spine_item.set_linear(false);
1465
1466 assert_eq!(spine_item.idref, "content_001");
1467 assert_eq!(spine_item.id, None);
1468 assert_eq!(spine_item.properties, None);
1469 assert_eq!(spine_item.linear, false);
1470 }
1471
1472 #[test]
1473 fn test_spine_item_build() {
1474 let mut spine_item = SpineItem::new("content_001");
1475 spine_item
1476 .with_id("spine1")
1477 .append_property("page-spread-left")
1478 .set_linear(false);
1479
1480 let built = spine_item.build();
1481
1482 assert_eq!(built.idref, "content_001");
1483 assert_eq!(built.id, Some("spine1".to_string()));
1484 assert_eq!(built.properties, Some("page-spread-left".to_string()));
1485 assert_eq!(built.linear, false);
1486 }
1487
1488 #[test]
1489 fn test_spine_item_builder_chaining() {
1490 let mut spine_item = SpineItem::new("content_001");
1491 spine_item
1492 .with_id("spine1")
1493 .append_property("page-spread-left")
1494 .set_linear(false);
1495
1496 let built = spine_item.build();
1497
1498 assert_eq!(built.idref, "content_001");
1499 assert_eq!(built.id, Some("spine1".to_string()));
1500 assert_eq!(built.properties, Some("page-spread-left".to_string()));
1501 assert_eq!(built.linear, false);
1502 }
1503
1504 #[test]
1505 fn test_spine_item_attributes() {
1506 let mut spine_item = SpineItem::new("content_001");
1507 spine_item
1508 .with_id("spine1")
1509 .append_property("page-spread-left")
1510 .set_linear(false);
1511
1512 let attributes = spine_item.attributes();
1513
1514 assert!(attributes.contains(&("idref", "content_001")));
1516 assert!(attributes.contains(&("id", "spine1")));
1517 assert!(attributes.contains(&("properties", "page-spread-left")));
1518 assert!(attributes.contains(&("linear", "no"))); }
1520
1521 #[test]
1522 fn test_spine_item_attributes_linear_yes() {
1523 let spine_item = SpineItem::new("content_001");
1524 let attributes = spine_item.attributes();
1525
1526 assert!(attributes.contains(&("linear", "yes")));
1528 }
1529
1530 #[test]
1531 fn test_spine_item_attributes_optional_fields() {
1532 let spine_item = SpineItem::new("content_001");
1533 let attributes = spine_item.attributes();
1534
1535 assert!(attributes.contains(&("idref", "content_001")));
1537 assert!(attributes.contains(&("linear", "yes")));
1538
1539 assert!(!attributes.iter().any(|(k, _)| k == &"id"));
1541 assert!(!attributes.iter().any(|(k, _)| k == &"properties"));
1542 }
1543 }
1544
1545 mod navpoint {
1546
1547 use std::path::PathBuf;
1548
1549 use crate::types::NavPoint;
1550
1551 #[test]
1552 fn test_navpoint_new() {
1553 let navpoint = NavPoint::new("Test Chapter");
1554
1555 assert_eq!(navpoint.label, "Test Chapter");
1556 assert_eq!(navpoint.content, None);
1557 assert_eq!(navpoint.children.len(), 0);
1558 }
1559
1560 #[test]
1561 fn test_navpoint_with_content() {
1562 let mut navpoint = NavPoint::new("Test Chapter");
1563 navpoint.with_content("chapter1.html");
1564
1565 assert_eq!(navpoint.label, "Test Chapter");
1566 assert_eq!(navpoint.content, Some(PathBuf::from("chapter1.html")));
1567 assert_eq!(navpoint.children.len(), 0);
1568 }
1569
1570 #[test]
1571 fn test_navpoint_append_child() {
1572 let mut parent = NavPoint::new("Parent Chapter");
1573
1574 let mut child1 = NavPoint::new("Child Section 1");
1575 child1.with_content("section1.html");
1576
1577 let mut child2 = NavPoint::new("Child Section 2");
1578 child2.with_content("section2.html");
1579
1580 parent.append_child(child1.build());
1581 parent.append_child(child2.build());
1582
1583 assert_eq!(parent.children.len(), 2);
1584 assert_eq!(parent.children[0].label, "Child Section 1");
1585 assert_eq!(parent.children[1].label, "Child Section 2");
1586 }
1587
1588 #[test]
1589 fn test_navpoint_set_children() {
1590 let mut navpoint = NavPoint::new("Main Chapter");
1591 let children = vec![NavPoint::new("Section 1"), NavPoint::new("Section 2")];
1592
1593 navpoint.set_children(children);
1594
1595 assert_eq!(navpoint.children.len(), 2);
1596 assert_eq!(navpoint.children[0].label, "Section 1");
1597 assert_eq!(navpoint.children[1].label, "Section 2");
1598 }
1599
1600 #[test]
1601 fn test_navpoint_build() {
1602 let mut navpoint = NavPoint::new("Complete Chapter");
1603 navpoint.with_content("complete.html");
1604
1605 let child = NavPoint::new("Sub Section");
1606 navpoint.append_child(child.build());
1607
1608 let built = navpoint.build();
1609
1610 assert_eq!(built.label, "Complete Chapter");
1611 assert_eq!(built.content, Some(PathBuf::from("complete.html")));
1612 assert_eq!(built.children.len(), 1);
1613 assert_eq!(built.children[0].label, "Sub Section");
1614 }
1615
1616 #[test]
1617 fn test_navpoint_builder_chaining() {
1618 let mut navpoint = NavPoint::new("Chained Chapter");
1619
1620 navpoint
1621 .with_content("chained.html")
1622 .append_child(NavPoint::new("Child 1").build())
1623 .append_child(NavPoint::new("Child 2").build());
1624
1625 let built = navpoint.build();
1626
1627 assert_eq!(built.label, "Chained Chapter");
1628 assert_eq!(built.content, Some(PathBuf::from("chained.html")));
1629 assert_eq!(built.children.len(), 2);
1630 }
1631
1632 #[test]
1633 fn test_navpoint_empty_children() {
1634 let navpoint = NavPoint::new("No Children Chapter");
1635 let built = navpoint.build();
1636
1637 assert_eq!(built.children.len(), 0);
1638 }
1639
1640 #[test]
1641 fn test_navpoint_complex_hierarchy() {
1642 let mut root = NavPoint::new("Book");
1643
1644 let mut chapter1 = NavPoint::new("Chapter 1");
1645 chapter1
1646 .with_content("chapter1.html")
1647 .append_child(
1648 NavPoint::new("Section 1.1")
1649 .with_content("sec1_1.html")
1650 .build(),
1651 )
1652 .append_child(
1653 NavPoint::new("Section 1.2")
1654 .with_content("sec1_2.html")
1655 .build(),
1656 );
1657
1658 let mut chapter2 = NavPoint::new("Chapter 2");
1659 chapter2.with_content("chapter2.html").append_child(
1660 NavPoint::new("Section 2.1")
1661 .with_content("sec2_1.html")
1662 .build(),
1663 );
1664
1665 root.append_child(chapter1.build())
1666 .append_child(chapter2.build());
1667
1668 let book = root.build();
1669
1670 assert_eq!(book.label, "Book");
1671 assert_eq!(book.children.len(), 2);
1672
1673 let ch1 = &book.children[0];
1674 assert_eq!(ch1.label, "Chapter 1");
1675 assert_eq!(ch1.children.len(), 2);
1676
1677 let ch2 = &book.children[1];
1678 assert_eq!(ch2.label, "Chapter 2");
1679 assert_eq!(ch2.children.len(), 1);
1680 }
1681 }
1682 }
1683
1684 mod footnote_tests {
1685 use crate::types::Footnote;
1686
1687 #[test]
1688 fn test_footnote_basic_creation() {
1689 let footnote = Footnote {
1690 locate: 100,
1691 content: "Sample footnote".to_string(),
1692 };
1693
1694 assert_eq!(footnote.locate, 100);
1695 assert_eq!(footnote.content, "Sample footnote");
1696 }
1697
1698 #[test]
1699 fn test_footnote_equality() {
1700 let footnote1 = Footnote {
1701 locate: 100,
1702 content: "First note".to_string(),
1703 };
1704
1705 let footnote2 = Footnote {
1706 locate: 100,
1707 content: "First note".to_string(),
1708 };
1709
1710 let footnote3 = Footnote {
1711 locate: 100,
1712 content: "Different note".to_string(),
1713 };
1714
1715 let footnote4 = Footnote {
1716 locate: 200,
1717 content: "First note".to_string(),
1718 };
1719
1720 assert_eq!(footnote1, footnote2);
1721 assert_ne!(footnote1, footnote3);
1722 assert_ne!(footnote1, footnote4);
1723 }
1724
1725 #[test]
1726 fn test_footnote_ordering() {
1727 let footnote1 = Footnote {
1728 locate: 100,
1729 content: "First".to_string(),
1730 };
1731
1732 let footnote2 = Footnote {
1733 locate: 200,
1734 content: "Second".to_string(),
1735 };
1736
1737 let footnote3 = Footnote {
1738 locate: 150,
1739 content: "Middle".to_string(),
1740 };
1741
1742 assert!(footnote1 < footnote2);
1743 assert!(footnote2 > footnote1);
1744 assert!(footnote1 < footnote3);
1745 assert!(footnote3 < footnote2);
1746 assert_eq!(footnote1.cmp(&footnote1), std::cmp::Ordering::Equal);
1747 }
1748
1749 #[test]
1750 fn test_footnote_sorting() {
1751 let mut footnotes = vec![
1752 Footnote {
1753 locate: 300,
1754 content: "Third note".to_string(),
1755 },
1756 Footnote {
1757 locate: 100,
1758 content: "First note".to_string(),
1759 },
1760 Footnote {
1761 locate: 200,
1762 content: "Second note".to_string(),
1763 },
1764 ];
1765
1766 footnotes.sort();
1767
1768 assert_eq!(footnotes[0].locate, 100);
1769 assert_eq!(footnotes[1].locate, 200);
1770 assert_eq!(footnotes[2].locate, 300);
1771
1772 assert_eq!(footnotes[0].content, "First note");
1773 assert_eq!(footnotes[1].content, "Second note");
1774 assert_eq!(footnotes[2].content, "Third note");
1775 }
1776 }
1777}