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