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