1use std::path::PathBuf;
28
29#[cfg(feature = "builder")]
30use crate::{
31 error::{EpubBuilderError, EpubError},
32 utils::ELEMENT_IN_DC_NAMESPACE,
33};
34
35#[derive(Debug, PartialEq, Eq)]
39pub enum EpubVersion {
40 Version2_0,
41 Version3_0,
42}
43
44#[derive(Debug, Clone)]
69pub struct MetadataItem {
70 pub id: Option<String>,
75
76 pub property: String,
82
83 pub value: String,
85
86 pub lang: Option<String>,
88
89 pub refined: Vec<MetadataRefinement>,
97}
98
99#[cfg(feature = "builder")]
100impl MetadataItem {
101 pub fn new(property: &str, value: &str) -> Self {
109 Self {
110 id: None,
111 property: property.to_string(),
112 value: value.to_string(),
113 lang: None,
114 refined: vec![],
115 }
116 }
117
118 pub fn with_id(&mut self, id: &str) -> &mut Self {
125 self.id = Some(id.to_string());
126 self
127 }
128
129 pub fn with_lang(&mut self, lang: &str) -> &mut Self {
136 self.lang = Some(lang.to_string());
137 self
138 }
139
140 pub fn append_refinement(&mut self, refine: MetadataRefinement) -> &mut Self {
150 if self.id.is_some() {
151 self.refined.push(refine);
152 } else {
153 }
155
156 self
157 }
158
159 pub fn build(&self) -> Self {
163 Self { ..self.clone() }
164 }
165
166 pub(crate) fn attributes(&self) -> Vec<(&str, &str)> {
168 let mut attributes = Vec::new();
169
170 if !ELEMENT_IN_DC_NAMESPACE.contains(&self.property.as_str()) {
171 attributes.push(("property", self.property.as_str()));
172 }
173
174 if let Some(id) = &self.id {
175 attributes.push(("id", id.as_str()));
176 };
177
178 if let Some(lang) = &self.lang {
179 attributes.push(("lang", lang.as_str()));
180 };
181
182 attributes
183 }
184}
185
186#[derive(Debug, Clone)]
210pub struct MetadataRefinement {
211 pub refines: String,
212
213 pub property: String,
218
219 pub value: String,
221
222 pub lang: Option<String>,
224
225 pub scheme: Option<String>,
230}
231
232#[cfg(feature = "builder")]
233impl MetadataRefinement {
234 pub fn new(refines: &str, property: &str, value: &str) -> Self {
243 Self {
244 refines: refines.to_string(),
245 property: property.to_string(),
246 value: value.to_string(),
247 lang: None,
248 scheme: None,
249 }
250 }
251
252 pub fn with_lang(&mut self, lang: &str) -> &mut Self {
259 self.lang = Some(lang.to_string());
260 self
261 }
262
263 pub fn with_scheme(&mut self, scheme: &str) -> &mut Self {
270 self.scheme = Some(scheme.to_string());
271 self
272 }
273
274 pub fn build(&self) -> Self {
278 Self { ..self.clone() }
279 }
280
281 pub(crate) fn attributes(&self) -> Vec<(&str, &str)> {
283 let mut attributes = Vec::new();
284
285 attributes.push(("refines", self.refines.as_str()));
286 attributes.push(("property", self.property.as_str()));
287
288 if let Some(lang) = &self.lang {
289 attributes.push(("lang", lang.as_str()));
290 };
291
292 if let Some(scheme) = &self.scheme {
293 attributes.push(("scheme", scheme.as_str()));
294 };
295
296 attributes
297 }
298}
299
300#[derive(Debug)]
309pub struct MetadataLinkItem {
310 pub href: String,
312
313 pub rel: String,
315
316 pub hreflang: Option<String>,
318
319 pub id: Option<String>,
323
324 pub mime: Option<String>,
326
327 pub properties: Option<String>,
332
333 pub refines: Option<String>,
338}
339
340#[derive(Debug, Clone)]
371pub struct ManifestItem {
372 pub id: String,
374
375 pub path: PathBuf,
381
382 pub mime: String,
384
385 pub properties: Option<String>,
391
392 pub fallback: Option<String>,
403}
404
405#[cfg(feature = "builder")]
406impl ManifestItem {
407 pub fn new(id: &str, path: &str) -> Result<Self, EpubError> {
418 if path.starts_with("../") {
419 return Err(EpubBuilderError::IllegalManifestPath {
420 manifest_id: id.to_string(),
421 }
422 .into());
423 }
424
425 Ok(Self {
426 id: id.to_string(),
427 path: PathBuf::from(path),
428 mime: String::new(),
429 properties: None,
430 fallback: None,
431 })
432 }
433
434 pub(crate) fn set_mime(self, mime: &str) -> Self {
436 Self {
437 id: self.id,
438 path: self.path,
439 mime: mime.to_string(),
440 properties: self.properties,
441 fallback: self.fallback,
442 }
443 }
444
445 pub fn append_property(&mut self, property: &str) -> &mut Self {
452 let new_properties = if let Some(properties) = &self.properties {
453 format!("{} {}", properties, property)
454 } else {
455 property.to_string()
456 };
457
458 self.properties = Some(new_properties);
459 self
460 }
461
462 pub fn with_fallback(&mut self, fallback: &str) -> &mut Self {
469 self.fallback = Some(fallback.to_string());
470 self
471 }
472
473 pub fn build(&self) -> Self {
477 Self { ..self.clone() }
478 }
479
480 pub fn attributes(&self) -> Vec<(&str, &str)> {
482 let mut attributes = Vec::new();
483
484 attributes.push(("id", self.id.as_str()));
485 attributes.push(("href", self.path.to_str().unwrap()));
486 attributes.push(("media-type", self.mime.as_str()));
487
488 if let Some(properties) = &self.properties {
489 attributes.push(("properties", properties.as_str()));
490 }
491
492 if let Some(fallback) = &self.fallback {
493 attributes.push(("fallback", fallback.as_str()));
494 }
495
496 attributes
497 }
498}
499
500#[derive(Debug, Clone)]
527pub struct SpineItem {
528 pub idref: String,
534
535 pub id: Option<String>,
537
538 pub properties: Option<String>,
544
545 pub linear: bool,
555}
556
557#[cfg(feature = "builder")]
558impl SpineItem {
559 pub fn new(idref: &str) -> Self {
568 Self {
569 idref: idref.to_string(),
570 id: None,
571 properties: None,
572 linear: true,
573 }
574 }
575
576 pub fn with_id(&mut self, id: &str) -> &mut Self {
583 self.id = Some(id.to_string());
584 self
585 }
586
587 pub fn append_property(&mut self, property: &str) -> &mut Self {
594 let new_properties = if let Some(properties) = &self.properties {
595 format!("{} {}", properties, property)
596 } else {
597 property.to_string()
598 };
599
600 self.properties = Some(new_properties);
601 self
602 }
603
604 pub fn set_linear(&mut self, linear: bool) -> &mut Self {
611 self.linear = linear;
612 self
613 }
614
615 pub fn build(&self) -> Self {
619 Self { ..self.clone() }
620 }
621
622 pub(crate) fn attributes(&self) -> Vec<(&str, &str)> {
624 let mut attributes = Vec::new();
625
626 attributes.push(("idref", self.idref.as_str()));
627 attributes.push(("linear", if self.linear { "yes" } else { "no" }));
628
629 if let Some(id) = &self.id {
630 attributes.push(("id", id.as_str()));
631 }
632
633 if let Some(properties) = &self.properties {
634 attributes.push(("properties", properties.as_str()));
635 }
636
637 attributes
638 }
639}
640
641#[derive(Debug, Clone)]
647pub struct EncryptionData {
648 pub method: String,
655
656 pub data: String,
661}
662
663#[derive(Debug, Eq, Clone)]
688pub struct NavPoint {
689 pub label: String,
693
694 pub content: Option<PathBuf>,
699
700 pub children: Vec<NavPoint>,
702
703 pub play_order: Option<usize>,
708}
709
710#[cfg(feature = "builder")]
711impl NavPoint {
712 pub fn new(label: &str) -> Self {
719 Self {
720 label: label.to_string(),
721 content: None,
722 children: vec![],
723 play_order: None,
724 }
725 }
726
727 pub fn with_content(&mut self, content: &str) -> &mut Self {
734 self.content = Some(PathBuf::from(content));
735 self
736 }
737
738 pub fn append_child(&mut self, child: NavPoint) -> &mut Self {
745 self.children.push(child);
746 self
747 }
748
749 pub fn set_children(&mut self, children: Vec<NavPoint>) -> &mut Self {
756 self.children = children;
757 self
758 }
759
760 pub fn build(&self) -> Self {
764 Self { ..self.clone() }
765 }
766}
767
768impl Ord for NavPoint {
769 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
770 self.play_order.cmp(&other.play_order)
771 }
772}
773
774impl PartialOrd for NavPoint {
775 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
776 Some(self.cmp(other))
777 }
778}
779
780impl PartialEq for NavPoint {
781 fn eq(&self, other: &Self) -> bool {
782 self.play_order == other.play_order
783 }
784}
785
786#[cfg(test)]
787mod tests {
788 mod navpoint_tests {
789 use std::path::PathBuf;
790
791 use crate::types::NavPoint;
792
793 #[test]
795 fn test_navpoint_partial_eq() {
796 let nav1 = NavPoint {
797 label: "Chapter 1".to_string(),
798 content: Some(PathBuf::from("chapter1.html")),
799 children: vec![],
800 play_order: Some(1),
801 };
802
803 let nav2 = NavPoint {
804 label: "Chapter 1".to_string(),
805 content: Some(PathBuf::from("chapter2.html")),
806 children: vec![],
807 play_order: Some(1),
808 };
809
810 let nav3 = NavPoint {
811 label: "Chapter 2".to_string(),
812 content: Some(PathBuf::from("chapter1.html")),
813 children: vec![],
814 play_order: Some(2),
815 };
816
817 assert_eq!(nav1, nav2); assert_ne!(nav1, nav3); }
820
821 #[test]
823 fn test_navpoint_ord() {
824 let nav1 = NavPoint {
825 label: "Chapter 1".to_string(),
826 content: Some(PathBuf::from("chapter1.html")),
827 children: vec![],
828 play_order: Some(1),
829 };
830
831 let nav2 = NavPoint {
832 label: "Chapter 2".to_string(),
833 content: Some(PathBuf::from("chapter2.html")),
834 children: vec![],
835 play_order: Some(2),
836 };
837
838 let nav3 = NavPoint {
839 label: "Chapter 3".to_string(),
840 content: Some(PathBuf::from("chapter3.html")),
841 children: vec![],
842 play_order: Some(3),
843 };
844
845 assert!(nav1 < nav2);
847 assert!(nav2 > nav1);
848 assert!(nav1 == nav1);
849
850 assert_eq!(nav1.partial_cmp(&nav2), Some(std::cmp::Ordering::Less));
852 assert_eq!(nav2.partial_cmp(&nav1), Some(std::cmp::Ordering::Greater));
853 assert_eq!(nav1.partial_cmp(&nav1), Some(std::cmp::Ordering::Equal));
854
855 let mut nav_points = vec![nav2.clone(), nav3.clone(), nav1.clone()];
857 nav_points.sort();
858 assert_eq!(nav_points, vec![nav1, nav2, nav3]);
859 }
860
861 #[test]
863 fn test_navpoint_ord_with_none_play_order() {
864 let nav_with_order = NavPoint {
865 label: "Chapter 1".to_string(),
866 content: Some(PathBuf::from("chapter1.html")),
867 children: vec![],
868 play_order: Some(1),
869 };
870
871 let nav_without_order = NavPoint {
872 label: "Preface".to_string(),
873 content: Some(PathBuf::from("preface.html")),
874 children: vec![],
875 play_order: None,
876 };
877
878 assert!(nav_without_order < nav_with_order);
879 assert!(nav_with_order > nav_without_order);
880
881 let nav_without_order2 = NavPoint {
882 label: "Introduction".to_string(),
883 content: Some(PathBuf::from("intro.html")),
884 children: vec![],
885 play_order: None,
886 };
887
888 assert!(nav_without_order == nav_without_order2);
889 }
890
891 #[test]
893 fn test_navpoint_with_children() {
894 let child1 = NavPoint {
895 label: "Section 1.1".to_string(),
896 content: Some(PathBuf::from("section1_1.html")),
897 children: vec![],
898 play_order: Some(1),
899 };
900
901 let child2 = NavPoint {
902 label: "Section 1.2".to_string(),
903 content: Some(PathBuf::from("section1_2.html")),
904 children: vec![],
905 play_order: Some(2),
906 };
907
908 let parent1 = NavPoint {
909 label: "Chapter 1".to_string(),
910 content: Some(PathBuf::from("chapter1.html")),
911 children: vec![child1.clone(), child2.clone()],
912 play_order: Some(1),
913 };
914
915 let parent2 = NavPoint {
916 label: "Chapter 1".to_string(),
917 content: Some(PathBuf::from("chapter1.html")),
918 children: vec![child1.clone(), child2.clone()],
919 play_order: Some(1),
920 };
921
922 assert!(parent1 == parent2);
923
924 let parent3 = NavPoint {
925 label: "Chapter 2".to_string(),
926 content: Some(PathBuf::from("chapter2.html")),
927 children: vec![child1.clone(), child2.clone()],
928 play_order: Some(2),
929 };
930
931 assert!(parent1 != parent3);
932 assert!(parent1 < parent3);
933 }
934
935 #[test]
937 fn test_navpoint_with_none_content() {
938 let nav1 = NavPoint {
939 label: "Chapter 1".to_string(),
940 content: None,
941 children: vec![],
942 play_order: Some(1),
943 };
944
945 let nav2 = NavPoint {
946 label: "Chapter 1".to_string(),
947 content: None,
948 children: vec![],
949 play_order: Some(1),
950 };
951
952 assert!(nav1 == nav2);
953 }
954 }
955
956 #[cfg(feature = "builder")]
957 mod builder_tests {
958 mod metadata_item {
959 use crate::types::{MetadataItem, MetadataRefinement};
960
961 #[test]
962 fn test_metadata_item_new() {
963 let metadata_item = MetadataItem::new("title", "EPUB Test Book");
964
965 assert_eq!(metadata_item.property, "title");
966 assert_eq!(metadata_item.value, "EPUB Test Book");
967 assert_eq!(metadata_item.id, None);
968 assert_eq!(metadata_item.lang, None);
969 assert_eq!(metadata_item.refined.len(), 0);
970 }
971
972 #[test]
973 fn test_metadata_item_with_id() {
974 let mut metadata_item = MetadataItem::new("creator", "John Doe");
975 metadata_item.with_id("creator-1");
976
977 assert_eq!(metadata_item.property, "creator");
978 assert_eq!(metadata_item.value, "John Doe");
979 assert_eq!(metadata_item.id, Some("creator-1".to_string()));
980 assert_eq!(metadata_item.lang, None);
981 assert_eq!(metadata_item.refined.len(), 0);
982 }
983
984 #[test]
985 fn test_metadata_item_with_lang() {
986 let mut metadata_item = MetadataItem::new("title", "测试书籍");
987 metadata_item.with_lang("zh-CN");
988
989 assert_eq!(metadata_item.property, "title");
990 assert_eq!(metadata_item.value, "测试书籍");
991 assert_eq!(metadata_item.id, None);
992 assert_eq!(metadata_item.lang, Some("zh-CN".to_string()));
993 assert_eq!(metadata_item.refined.len(), 0);
994 }
995
996 #[test]
997 fn test_metadata_item_append_refinement() {
998 let mut metadata_item = MetadataItem::new("creator", "John Doe");
999 metadata_item.with_id("creator-1"); let refinement = MetadataRefinement::new("creator-1", "role", "author");
1002 metadata_item.append_refinement(refinement);
1003
1004 assert_eq!(metadata_item.refined.len(), 1);
1005 assert_eq!(metadata_item.refined[0].refines, "creator-1");
1006 assert_eq!(metadata_item.refined[0].property, "role");
1007 assert_eq!(metadata_item.refined[0].value, "author");
1008 }
1009
1010 #[test]
1011 fn test_metadata_item_append_refinement_without_id() {
1012 let mut metadata_item = MetadataItem::new("title", "Test Book");
1013 let refinement = MetadataRefinement::new("title", "title-type", "main");
1016 metadata_item.append_refinement(refinement);
1017
1018 assert_eq!(metadata_item.refined.len(), 0);
1020 }
1021
1022 #[test]
1023 fn test_metadata_item_build() {
1024 let mut metadata_item = MetadataItem::new("identifier", "urn:isbn:1234567890");
1025 metadata_item.with_id("pub-id").with_lang("en");
1026
1027 let built = metadata_item.build();
1028
1029 assert_eq!(built.property, "identifier");
1030 assert_eq!(built.value, "urn:isbn:1234567890");
1031 assert_eq!(built.id, Some("pub-id".to_string()));
1032 assert_eq!(built.lang, Some("en".to_string()));
1033 assert_eq!(built.refined.len(), 0);
1034 }
1035
1036 #[test]
1037 fn test_metadata_item_builder_chaining() {
1038 let mut metadata_item = MetadataItem::new("title", "EPUB 3.3 Guide");
1039 metadata_item.with_id("title").with_lang("en");
1040
1041 let refinement = MetadataRefinement::new("title", "title-type", "main");
1042 metadata_item.append_refinement(refinement);
1043
1044 let built = metadata_item.build();
1045
1046 assert_eq!(built.property, "title");
1047 assert_eq!(built.value, "EPUB 3.3 Guide");
1048 assert_eq!(built.id, Some("title".to_string()));
1049 assert_eq!(built.lang, Some("en".to_string()));
1050 assert_eq!(built.refined.len(), 1);
1051 }
1052
1053 #[test]
1054 fn test_metadata_item_attributes_dc_namespace() {
1055 let mut metadata_item = MetadataItem::new("title", "Test Book");
1056 metadata_item.with_id("title-id");
1057
1058 let attributes = metadata_item.attributes();
1059
1060 assert!(!attributes.iter().any(|(k, _)| k == &"property"));
1062 assert!(
1063 attributes
1064 .iter()
1065 .any(|(k, v)| k == &"id" && v == &"title-id")
1066 );
1067 }
1068
1069 #[test]
1070 fn test_metadata_item_attributes_non_dc_namespace() {
1071 let mut metadata_item = MetadataItem::new("meta", "value");
1072 metadata_item.with_id("meta-id");
1073
1074 let attributes = metadata_item.attributes();
1075
1076 assert!(attributes.iter().any(|(k, _)| k == &"property"));
1078 assert!(
1079 attributes
1080 .iter()
1081 .any(|(k, v)| k == &"id" && v == &"meta-id")
1082 );
1083 }
1084
1085 #[test]
1086 fn test_metadata_item_attributes_with_lang() {
1087 let mut metadata_item = MetadataItem::new("title", "Test Book");
1088 metadata_item.with_id("title-id").with_lang("en");
1089
1090 let attributes = metadata_item.attributes();
1091
1092 assert!(
1093 attributes
1094 .iter()
1095 .any(|(k, v)| k == &"id" && v == &"title-id")
1096 );
1097 assert!(attributes.iter().any(|(k, v)| k == &"lang" && v == &"en"));
1098 }
1099 }
1100
1101 mod metadata_refinement {
1102 use crate::types::MetadataRefinement;
1103
1104 #[test]
1105 fn test_metadata_refinement_new() {
1106 let refinement = MetadataRefinement::new("title", "title-type", "main");
1107
1108 assert_eq!(refinement.refines, "title");
1109 assert_eq!(refinement.property, "title-type");
1110 assert_eq!(refinement.value, "main");
1111 assert_eq!(refinement.lang, None);
1112 assert_eq!(refinement.scheme, None);
1113 }
1114
1115 #[test]
1116 fn test_metadata_refinement_with_lang() {
1117 let mut refinement = MetadataRefinement::new("creator", "role", "author");
1118 refinement.with_lang("en");
1119
1120 assert_eq!(refinement.refines, "creator");
1121 assert_eq!(refinement.property, "role");
1122 assert_eq!(refinement.value, "author");
1123 assert_eq!(refinement.lang, Some("en".to_string()));
1124 assert_eq!(refinement.scheme, None);
1125 }
1126
1127 #[test]
1128 fn test_metadata_refinement_with_scheme() {
1129 let mut refinement = MetadataRefinement::new("creator", "role", "author");
1130 refinement.with_scheme("marc:relators");
1131
1132 assert_eq!(refinement.refines, "creator");
1133 assert_eq!(refinement.property, "role");
1134 assert_eq!(refinement.value, "author");
1135 assert_eq!(refinement.lang, None);
1136 assert_eq!(refinement.scheme, Some("marc:relators".to_string()));
1137 }
1138
1139 #[test]
1140 fn test_metadata_refinement_build() {
1141 let mut refinement = MetadataRefinement::new("title", "alternate-script", "テスト");
1142 refinement.with_lang("ja").with_scheme("iso-15924");
1143
1144 let built = refinement.build();
1145
1146 assert_eq!(built.refines, "title");
1147 assert_eq!(built.property, "alternate-script");
1148 assert_eq!(built.value, "テスト");
1149 assert_eq!(built.lang, Some("ja".to_string()));
1150 assert_eq!(built.scheme, Some("iso-15924".to_string()));
1151 }
1152
1153 #[test]
1154 fn test_metadata_refinement_builder_chaining() {
1155 let mut refinement = MetadataRefinement::new("creator", "file-as", "Doe, John");
1156 refinement.with_lang("en").with_scheme("dcterms");
1157
1158 let built = refinement.build();
1159
1160 assert_eq!(built.refines, "creator");
1161 assert_eq!(built.property, "file-as");
1162 assert_eq!(built.value, "Doe, John");
1163 assert_eq!(built.lang, Some("en".to_string()));
1164 assert_eq!(built.scheme, Some("dcterms".to_string()));
1165 }
1166
1167 #[test]
1168 fn test_metadata_refinement_attributes() {
1169 let mut refinement = MetadataRefinement::new("title", "title-type", "main");
1170 refinement.with_lang("en").with_scheme("onix:codelist5");
1171
1172 let attributes = refinement.attributes();
1173
1174 assert!(
1175 attributes
1176 .iter()
1177 .any(|(k, v)| k == &"refines" && v == &"title")
1178 );
1179 assert!(
1180 attributes
1181 .iter()
1182 .any(|(k, v)| k == &"property" && v == &"title-type")
1183 );
1184 assert!(attributes.iter().any(|(k, v)| k == &"lang" && v == &"en"));
1185 assert!(
1186 attributes
1187 .iter()
1188 .any(|(k, v)| k == &"scheme" && v == &"onix:codelist5")
1189 );
1190 }
1191
1192 #[test]
1193 fn test_metadata_refinement_attributes_optional_fields() {
1194 let refinement = MetadataRefinement::new("creator", "role", "author");
1195 let attributes = refinement.attributes();
1196
1197 assert!(
1198 attributes
1199 .iter()
1200 .any(|(k, v)| k == &"refines" && v == &"creator")
1201 );
1202 assert!(
1203 attributes
1204 .iter()
1205 .any(|(k, v)| k == &"property" && v == &"role")
1206 );
1207
1208 assert!(!attributes.iter().any(|(k, _)| k == &"lang"));
1210 assert!(!attributes.iter().any(|(k, _)| k == &"scheme"));
1211 }
1212 }
1213
1214 mod manifest_item {
1215 use std::path::PathBuf;
1216
1217 use crate::types::ManifestItem;
1218
1219 #[test]
1220 fn test_manifest_item_new() {
1221 let manifest_item = ManifestItem::new("cover", "images/cover.jpg");
1222 assert!(manifest_item.is_ok());
1223
1224 let manifest_item = manifest_item.unwrap();
1225 assert_eq!(manifest_item.id, "cover");
1226 assert_eq!(manifest_item.path, PathBuf::from("images/cover.jpg"));
1227 assert_eq!(manifest_item.mime, "");
1228 assert_eq!(manifest_item.properties, None);
1229 assert_eq!(manifest_item.fallback, None);
1230 }
1231
1232 #[test]
1233 fn test_manifest_item_append_property() {
1234 let manifest_item = ManifestItem::new("nav", "nav.xhtml");
1235 assert!(manifest_item.is_ok());
1236
1237 let mut manifest_item = manifest_item.unwrap();
1238 manifest_item.append_property("nav");
1239
1240 assert_eq!(manifest_item.id, "nav");
1241 assert_eq!(manifest_item.path, PathBuf::from("nav.xhtml"));
1242 assert_eq!(manifest_item.mime, "");
1243 assert_eq!(manifest_item.properties, Some("nav".to_string()));
1244 assert_eq!(manifest_item.fallback, None);
1245 }
1246
1247 #[test]
1248 fn test_manifest_item_append_multiple_properties() {
1249 let manifest_item = ManifestItem::new("content", "content.xhtml");
1250 assert!(manifest_item.is_ok());
1251
1252 let mut manifest_item = manifest_item.unwrap();
1253 manifest_item
1254 .append_property("nav")
1255 .append_property("scripted")
1256 .append_property("svg");
1257
1258 assert_eq!(
1259 manifest_item.properties,
1260 Some("nav scripted svg".to_string())
1261 );
1262 }
1263
1264 #[test]
1265 fn test_manifest_item_with_fallback() {
1266 let manifest_item = ManifestItem::new("image", "image.tiff");
1267 assert!(manifest_item.is_ok());
1268
1269 let mut manifest_item = manifest_item.unwrap();
1270 manifest_item.with_fallback("image-fallback");
1271
1272 assert_eq!(manifest_item.id, "image");
1273 assert_eq!(manifest_item.path, PathBuf::from("image.tiff"));
1274 assert_eq!(manifest_item.mime, "");
1275 assert_eq!(manifest_item.properties, None);
1276 assert_eq!(manifest_item.fallback, Some("image-fallback".to_string()));
1277 }
1278
1279 #[test]
1280 fn test_manifest_item_set_mime() {
1281 let manifest_item = ManifestItem::new("style", "style.css");
1282 assert!(manifest_item.is_ok());
1283
1284 let manifest_item = manifest_item.unwrap();
1285 let updated_item = manifest_item.set_mime("text/css");
1286
1287 assert_eq!(updated_item.id, "style");
1288 assert_eq!(updated_item.path, PathBuf::from("style.css"));
1289 assert_eq!(updated_item.mime, "text/css");
1290 assert_eq!(updated_item.properties, None);
1291 assert_eq!(updated_item.fallback, None);
1292 }
1293
1294 #[test]
1295 fn test_manifest_item_build() {
1296 let manifest_item = ManifestItem::new("cover", "images/cover.jpg");
1297 assert!(manifest_item.is_ok());
1298
1299 let mut manifest_item = manifest_item.unwrap();
1300 manifest_item
1301 .append_property("cover-image")
1302 .with_fallback("cover-fallback");
1303
1304 let built = manifest_item.build();
1305
1306 assert_eq!(built.id, "cover");
1307 assert_eq!(built.path, PathBuf::from("images/cover.jpg"));
1308 assert_eq!(built.mime, "");
1309 assert_eq!(built.properties, Some("cover-image".to_string()));
1310 assert_eq!(built.fallback, Some("cover-fallback".to_string()));
1311 }
1312
1313 #[test]
1314 fn test_manifest_item_builder_chaining() {
1315 let manifest_item = ManifestItem::new("content", "content.xhtml");
1316 assert!(manifest_item.is_ok());
1317
1318 let mut manifest_item = manifest_item.unwrap();
1319 manifest_item
1320 .append_property("scripted")
1321 .append_property("svg")
1322 .with_fallback("fallback-content");
1323
1324 let built = manifest_item.build();
1325
1326 assert_eq!(built.id, "content");
1327 assert_eq!(built.path, PathBuf::from("content.xhtml"));
1328 assert_eq!(built.mime, "");
1329 assert_eq!(built.properties, Some("scripted svg".to_string()));
1330 assert_eq!(built.fallback, Some("fallback-content".to_string()));
1331 }
1332
1333 #[test]
1334 fn test_manifest_item_attributes() {
1335 let manifest_item = ManifestItem::new("nav", "nav.xhtml");
1336 assert!(manifest_item.is_ok());
1337
1338 let mut manifest_item = manifest_item.unwrap();
1339 manifest_item
1340 .append_property("nav")
1341 .with_fallback("fallback-nav");
1342
1343 let manifest_item = manifest_item.set_mime("application/xhtml+xml");
1345 let attributes = manifest_item.attributes();
1346
1347 assert!(attributes.contains(&("id", "nav")));
1349 assert!(attributes.contains(&("href", "nav.xhtml")));
1350 assert!(attributes.contains(&("media-type", "application/xhtml+xml")));
1351 assert!(attributes.contains(&("properties", "nav")));
1352 assert!(attributes.contains(&("fallback", "fallback-nav")));
1353 }
1354
1355 #[test]
1356 fn test_manifest_item_attributes_optional_fields() {
1357 let manifest_item = ManifestItem::new("simple", "simple.xhtml");
1358 assert!(manifest_item.is_ok());
1359
1360 let manifest_item = manifest_item.unwrap();
1361 let manifest_item = manifest_item.set_mime("application/xhtml+xml");
1362 let attributes = manifest_item.attributes();
1363
1364 assert!(attributes.contains(&("id", "simple")));
1366 assert!(attributes.contains(&("href", "simple.xhtml")));
1367 assert!(attributes.contains(&("media-type", "application/xhtml+xml")));
1368
1369 assert!(!attributes.iter().any(|(k, _)| k == &"properties"));
1371 assert!(!attributes.iter().any(|(k, _)| k == &"fallback"));
1372 }
1373
1374 #[test]
1375 fn test_manifest_item_path_handling() {
1376 let manifest_item = ManifestItem::new("test", "../images/test.png");
1377 assert!(manifest_item.is_err());
1378
1379 let err = manifest_item.unwrap_err();
1380 assert_eq!(
1381 err.to_string(),
1382 "Epub builder error: A manifest with id 'test' should not use a relative path starting with '../'."
1383 );
1384 }
1385 }
1386
1387 mod spine_item {
1388 use crate::types::SpineItem;
1389
1390 #[test]
1391 fn test_spine_item_new() {
1392 let spine_item = SpineItem::new("content_001");
1393
1394 assert_eq!(spine_item.idref, "content_001");
1395 assert_eq!(spine_item.id, None);
1396 assert_eq!(spine_item.properties, None);
1397 assert_eq!(spine_item.linear, true);
1398 }
1399
1400 #[test]
1401 fn test_spine_item_with_id() {
1402 let mut spine_item = SpineItem::new("content_001");
1403 spine_item.with_id("spine1");
1404
1405 assert_eq!(spine_item.idref, "content_001");
1406 assert_eq!(spine_item.id, Some("spine1".to_string()));
1407 assert_eq!(spine_item.properties, None);
1408 assert_eq!(spine_item.linear, true);
1409 }
1410
1411 #[test]
1412 fn test_spine_item_append_property() {
1413 let mut spine_item = SpineItem::new("content_001");
1414 spine_item.append_property("page-spread-left");
1415
1416 assert_eq!(spine_item.idref, "content_001");
1417 assert_eq!(spine_item.id, None);
1418 assert_eq!(spine_item.properties, Some("page-spread-left".to_string()));
1419 assert_eq!(spine_item.linear, true);
1420 }
1421
1422 #[test]
1423 fn test_spine_item_append_multiple_properties() {
1424 let mut spine_item = SpineItem::new("content_001");
1425 spine_item
1426 .append_property("page-spread-left")
1427 .append_property("rendition:layout-pre-paginated");
1428
1429 assert_eq!(
1430 spine_item.properties,
1431 Some("page-spread-left rendition:layout-pre-paginated".to_string())
1432 );
1433 }
1434
1435 #[test]
1436 fn test_spine_item_set_linear() {
1437 let mut spine_item = SpineItem::new("content_001");
1438 spine_item.set_linear(false);
1439
1440 assert_eq!(spine_item.idref, "content_001");
1441 assert_eq!(spine_item.id, None);
1442 assert_eq!(spine_item.properties, None);
1443 assert_eq!(spine_item.linear, false);
1444 }
1445
1446 #[test]
1447 fn test_spine_item_build() {
1448 let mut spine_item = SpineItem::new("content_001");
1449 spine_item
1450 .with_id("spine1")
1451 .append_property("page-spread-left")
1452 .set_linear(false);
1453
1454 let built = spine_item.build();
1455
1456 assert_eq!(built.idref, "content_001");
1457 assert_eq!(built.id, Some("spine1".to_string()));
1458 assert_eq!(built.properties, Some("page-spread-left".to_string()));
1459 assert_eq!(built.linear, false);
1460 }
1461
1462 #[test]
1463 fn test_spine_item_builder_chaining() {
1464 let mut spine_item = SpineItem::new("content_001");
1465 spine_item
1466 .with_id("spine1")
1467 .append_property("page-spread-left")
1468 .set_linear(false);
1469
1470 let built = spine_item.build();
1471
1472 assert_eq!(built.idref, "content_001");
1473 assert_eq!(built.id, Some("spine1".to_string()));
1474 assert_eq!(built.properties, Some("page-spread-left".to_string()));
1475 assert_eq!(built.linear, false);
1476 }
1477
1478 #[test]
1479 fn test_spine_item_attributes() {
1480 let mut spine_item = SpineItem::new("content_001");
1481 spine_item
1482 .with_id("spine1")
1483 .append_property("page-spread-left")
1484 .set_linear(false);
1485
1486 let attributes = spine_item.attributes();
1487
1488 assert!(attributes.contains(&("idref", "content_001")));
1490 assert!(attributes.contains(&("id", "spine1")));
1491 assert!(attributes.contains(&("properties", "page-spread-left")));
1492 assert!(attributes.contains(&("linear", "no"))); }
1494
1495 #[test]
1496 fn test_spine_item_attributes_linear_yes() {
1497 let spine_item = SpineItem::new("content_001");
1498 let attributes = spine_item.attributes();
1499
1500 assert!(attributes.contains(&("linear", "yes")));
1502 }
1503
1504 #[test]
1505 fn test_spine_item_attributes_optional_fields() {
1506 let spine_item = SpineItem::new("content_001");
1507 let attributes = spine_item.attributes();
1508
1509 assert!(attributes.contains(&("idref", "content_001")));
1511 assert!(attributes.contains(&("linear", "yes")));
1512
1513 assert!(!attributes.iter().any(|(k, _)| k == &"id"));
1515 assert!(!attributes.iter().any(|(k, _)| k == &"properties"));
1516 }
1517 }
1518
1519 mod navpoint {
1520
1521 use std::path::PathBuf;
1522
1523 use crate::types::NavPoint;
1524
1525 #[test]
1526 fn test_navpoint_new() {
1527 let navpoint = NavPoint::new("Test Chapter");
1528
1529 assert_eq!(navpoint.label, "Test Chapter");
1530 assert_eq!(navpoint.content, None);
1531 assert_eq!(navpoint.children.len(), 0);
1532 }
1533
1534 #[test]
1535 fn test_navpoint_with_content() {
1536 let mut navpoint = NavPoint::new("Test Chapter");
1537 navpoint.with_content("chapter1.html");
1538
1539 assert_eq!(navpoint.label, "Test Chapter");
1540 assert_eq!(navpoint.content, Some(PathBuf::from("chapter1.html")));
1541 assert_eq!(navpoint.children.len(), 0);
1542 }
1543
1544 #[test]
1545 fn test_navpoint_append_child() {
1546 let mut parent = NavPoint::new("Parent Chapter");
1547
1548 let mut child1 = NavPoint::new("Child Section 1");
1549 child1.with_content("section1.html");
1550
1551 let mut child2 = NavPoint::new("Child Section 2");
1552 child2.with_content("section2.html");
1553
1554 parent.append_child(child1.build());
1555 parent.append_child(child2.build());
1556
1557 assert_eq!(parent.children.len(), 2);
1558 assert_eq!(parent.children[0].label, "Child Section 1");
1559 assert_eq!(parent.children[1].label, "Child Section 2");
1560 }
1561
1562 #[test]
1563 fn test_navpoint_set_children() {
1564 let mut navpoint = NavPoint::new("Main Chapter");
1565 let children = vec![NavPoint::new("Section 1"), NavPoint::new("Section 2")];
1566
1567 navpoint.set_children(children);
1568
1569 assert_eq!(navpoint.children.len(), 2);
1570 assert_eq!(navpoint.children[0].label, "Section 1");
1571 assert_eq!(navpoint.children[1].label, "Section 2");
1572 }
1573
1574 #[test]
1575 fn test_navpoint_build() {
1576 let mut navpoint = NavPoint::new("Complete Chapter");
1577 navpoint.with_content("complete.html");
1578
1579 let child = NavPoint::new("Sub Section");
1580 navpoint.append_child(child.build());
1581
1582 let built = navpoint.build();
1583
1584 assert_eq!(built.label, "Complete Chapter");
1585 assert_eq!(built.content, Some(PathBuf::from("complete.html")));
1586 assert_eq!(built.children.len(), 1);
1587 assert_eq!(built.children[0].label, "Sub Section");
1588 }
1589
1590 #[test]
1591 fn test_navpoint_builder_chaining() {
1592 let mut navpoint = NavPoint::new("Chained Chapter");
1593
1594 navpoint
1595 .with_content("chained.html")
1596 .append_child(NavPoint::new("Child 1").build())
1597 .append_child(NavPoint::new("Child 2").build());
1598
1599 let built = navpoint.build();
1600
1601 assert_eq!(built.label, "Chained Chapter");
1602 assert_eq!(built.content, Some(PathBuf::from("chained.html")));
1603 assert_eq!(built.children.len(), 2);
1604 }
1605
1606 #[test]
1607 fn test_navpoint_empty_children() {
1608 let navpoint = NavPoint::new("No Children Chapter");
1609 let built = navpoint.build();
1610
1611 assert_eq!(built.children.len(), 0);
1612 }
1613
1614 #[test]
1615 fn test_navpoint_complex_hierarchy() {
1616 let mut root = NavPoint::new("Book");
1617
1618 let mut chapter1 = NavPoint::new("Chapter 1");
1619 chapter1
1620 .with_content("chapter1.html")
1621 .append_child(
1622 NavPoint::new("Section 1.1")
1623 .with_content("sec1_1.html")
1624 .build(),
1625 )
1626 .append_child(
1627 NavPoint::new("Section 1.2")
1628 .with_content("sec1_2.html")
1629 .build(),
1630 );
1631
1632 let mut chapter2 = NavPoint::new("Chapter 2");
1633 chapter2.with_content("chapter2.html").append_child(
1634 NavPoint::new("Section 2.1")
1635 .with_content("sec2_1.html")
1636 .build(),
1637 );
1638
1639 root.append_child(chapter1.build())
1640 .append_child(chapter2.build());
1641
1642 let book = root.build();
1643
1644 assert_eq!(book.label, "Book");
1645 assert_eq!(book.children.len(), 2);
1646
1647 let ch1 = &book.children[0];
1648 assert_eq!(ch1.label, "Chapter 1");
1649 assert_eq!(ch1.children.len(), 2);
1650
1651 let ch2 = &book.children[1];
1652 assert_eq!(ch2.label, "Chapter 2");
1653 assert_eq!(ch2.children.len(), 1);
1654 }
1655 }
1656 }
1657}