1extern crate alloc;
12use alloc::boxed::Box;
13use alloc::format;
14use alloc::string::String;
15use alloc::string::ToString;
16use alloc::vec::Vec;
17
18use alloc::collections::BTreeMap;
19
20use crate::error::{Error, Result};
21
22pub const NS_TT: &str = "http://www.w3.org/ns/ttml";
26pub const NS_TTP: &str = "http://www.w3.org/ns/ttml#parameter";
28pub const NS_TTS: &str = "http://www.w3.org/ns/ttml#styling";
30pub const NS_TTA: &str = "http://www.w3.org/ns/ttml#audio";
32pub const NS_TTM: &str = "http://www.w3.org/ns/ttml#metadata";
34pub const NS_TT_PROFILE: &str = "http://www.w3.org/ns/ttml/profile/";
36pub const NS_TT_FEATURE: &str = "http://www.w3.org/ns/ttml/feature/";
38pub const NS_ITTS: &str = "http://www.w3.org/ns/ttml/profile/imsc1#styling";
40pub const NS_ITTP: &str = "http://www.w3.org/ns/ttml/profile/imsc1#parameter";
42pub const NS_ITTM: &str = "http://www.w3.org/ns/ttml/profile/imsc1#metadata";
44pub const NS_EBUTTS: &str = "urn:ebu:tt:style";
46pub const NS_EBUTTM: &str = "urn:ebu:tt:metadata";
48pub const NS_SMPTE: &str = "http://www.smpte-ra.org/schemas/2052-1/2010/smpte-tt";
50pub const NS_XML: &str = "http://www.w3.org/XML/1998/namespace";
52
53pub const IMSC11_TEXT_PROFILE: &str = "http://www.w3.org/ns/ttml/profile/imsc1.1/text";
57pub const IMSC11_IMAGE_PROFILE: &str = "http://www.w3.org/ns/ttml/profile/imsc1.1/image";
59pub const IMSC1_TEXT_PROFILE: &str = "http://www.w3.org/ns/ttml/profile/imsc1/text";
61pub const IMSC1_IMAGE_PROFILE: &str = "http://www.w3.org/ns/ttml/profile/imsc1/image";
63
64#[derive(Debug, Clone, PartialEq)]
70#[non_exhaustive]
71pub struct Document {
72 pub tt: TtElement,
74 pub xml_declaration: Option<XmlDeclaration>,
76}
77
78#[derive(Debug, Clone, PartialEq)]
80#[non_exhaustive]
81pub struct XmlDeclaration {
82 pub version: String,
84 pub encoding: String,
86}
87
88impl Document {
89 pub fn new() -> Self {
95 Document {
96 tt: TtElement::default(),
97 xml_declaration: None,
98 }
99 }
100
101 pub fn parse_str(xml: &str) -> Result<Self> {
103 let doc = roxmltree::Document::parse(xml).map_err(|e| Error::XmlParse(e.to_string()))?;
104
105 let root = doc.root_element();
106
107 let tt_node = root
110 .children()
111 .find(|n| n.is_element() && n.tag_name().name() == "tt")
112 .or_else(|| {
113 if root.tag_name().name() == "tt" {
114 Some(root)
115 } else {
116 None
117 }
118 })
119 .ok_or_else(|| Error::NotTtmlRoot(root.tag_name().name().to_string()))?;
120
121 let tt_ns = tt_node.tag_name().namespace();
122 if tt_ns != Some(NS_TT) {
123 return Err(Error::NotTtmlRoot(format!(
124 "namespace {:?}",
125 tt_ns.unwrap_or("(none)")
126 )));
127 }
128
129 let tt = parse_tt_element(tt_node)?;
130
131 Ok(Document {
132 tt,
133 xml_declaration: None,
134 })
135 }
136
137 pub fn to_xml(&self) -> String {
139 let mut buf = String::new();
140 buf.push_str(r#"<?xml version="1.0" encoding="UTF-8"?>"#);
141 buf.push('\n');
142 serialize_tt_element(&self.tt, &mut buf, 0);
143 if !buf.ends_with('\n') {
145 buf.push('\n');
146 }
147 buf
148 }
149
150 pub fn time_context(&self) -> crate::time::TimeContext {
152 self.tt.time_context()
153 }
154}
155
156impl Default for Document {
157 fn default() -> Self {
158 Self::new()
159 }
160}
161
162#[derive(Debug, Clone, PartialEq, Default)]
166#[non_exhaustive]
167pub struct TtElement {
168 pub xml_lang: Option<String>,
170 pub xml_id: Option<String>,
172 pub xml_space: Option<XmlSpace>,
174 pub ttp_time_base: Option<String>,
176 pub ttp_frame_rate: Option<String>,
178 pub ttp_frame_rate_multiplier: Option<String>,
180 pub ttp_tick_rate: Option<String>,
182 pub ttp_sub_frame_rate: Option<String>,
184 pub ttp_drop_mode: Option<String>,
186 pub ttp_marker_mode: Option<String>,
188 pub ttp_clock_mode: Option<String>,
190 pub ttp_cell_resolution: Option<String>,
192 pub ttp_pixel_aspect_ratio: Option<String>,
194 pub ttp_display_aspect_ratio: Option<String>,
196 pub ttp_profile: Option<String>,
198 pub ttp_content_profiles: Option<String>,
200 pub ttp_content_profile_combination: Option<String>,
202 pub ttp_processor_profiles: Option<String>,
204 pub ttp_processor_profile_combination: Option<String>,
206 pub ttp_infer_processor_profile_method: Option<String>,
208 pub ttp_infer_processor_profile_source: Option<String>,
210 pub ttp_permit_feature_narrowing: Option<String>,
212 pub ttp_permit_feature_widening: Option<String>,
214 pub ttp_validation: Option<String>,
216 pub ttp_validation_action: Option<String>,
218 pub tts_extent: Option<String>,
220 pub ittp_active_area: Option<String>,
222 pub ittp_aspect_ratio: Option<String>,
224 pub ittp_progressively_decodable: Option<String>,
226 pub other_attributes: BTreeMap<(String, String), String>,
228 pub head: Option<HeadElement>,
230 pub body: Option<BodyElement>,
232 pub text: Option<String>,
234}
235
236impl TtElement {
237 pub fn time_context(&self) -> crate::time::TimeContext {
239 use crate::time::{ClockMode, DropMode, MarkerMode, TimeBase};
240
241 let time_base = match self.ttp_time_base.as_deref() {
242 Some("smpte") => TimeBase::Smpte,
243 Some("clock") => TimeBase::Clock,
244 _ => TimeBase::Media,
245 };
246
247 let frame_rate: u32 = self
248 .ttp_frame_rate
249 .as_deref()
250 .and_then(|s| s.parse().ok())
251 .unwrap_or(30);
252
253 let (frame_mult_num, frame_mult_den) = if let Some(s) = &self.ttp_frame_rate_multiplier {
254 let parts: Vec<&str> = s.split_whitespace().collect();
255 if parts.len() == 2 {
256 (parts[0].parse().unwrap_or(1), parts[1].parse().unwrap_or(1))
257 } else {
258 (1, 1)
259 }
260 } else {
261 (1, 1)
262 };
263
264 let sub_frame_rate: u32 = self
265 .ttp_sub_frame_rate
266 .as_deref()
267 .and_then(|s| s.parse().ok())
268 .unwrap_or(1);
269
270 let tick_rate: u32 = self
271 .ttp_tick_rate
272 .as_deref()
273 .and_then(|s| s.parse().ok())
274 .unwrap_or(frame_rate * sub_frame_rate);
275
276 let drop_mode = match self.ttp_drop_mode.as_deref() {
277 Some("dropNTSC") => DropMode::DropNtsc,
278 Some("dropPAL") => DropMode::DropPal,
279 _ => DropMode::NonDrop,
280 };
281
282 let marker_mode = match self.ttp_marker_mode.as_deref() {
283 Some("continuous") => MarkerMode::Continuous,
284 _ => MarkerMode::Discontinuous,
285 };
286
287 let clock_mode = match self.ttp_clock_mode.as_deref() {
288 Some("local") => ClockMode::Local,
289 Some("gps") => ClockMode::Gps,
290 _ => ClockMode::Utc,
291 };
292
293 crate::time::TimeContext {
294 time_base,
295 frame_rate,
296 frame_rate_multiplier_numerator: frame_mult_num,
297 frame_rate_multiplier_denominator: frame_mult_den,
298 sub_frame_rate,
299 tick_rate,
300 drop_mode,
301 marker_mode,
302 clock_mode,
303 }
304 }
305}
306
307#[derive(Debug, Clone, PartialEq, Default)]
309#[non_exhaustive]
310pub struct HeadElement {
311 pub xml_id: Option<String>,
313 pub xml_lang: Option<String>,
315 pub xml_space: Option<XmlSpace>,
317 pub metadata: Vec<MetadataChild>,
319 pub styling: Option<StylingElement>,
321 pub layout: Option<LayoutElement>,
323}
324
325#[derive(Debug, Clone, PartialEq, Default)]
327#[non_exhaustive]
328pub struct BodyElement {
329 pub xml_id: Option<String>,
331 pub xml_lang: Option<String>,
333 pub xml_space: Option<XmlSpace>,
335 pub begin: Option<String>,
337 pub dur: Option<String>,
339 pub end: Option<String>,
341 pub time_container: Option<String>,
343 pub region: Option<String>,
345 pub style: Option<String>,
347 pub animate: Option<String>,
349 pub condition: Option<String>,
351 pub style_attributes: StyleAttributes,
353 pub other_attributes: BTreeMap<(String, String), String>,
355 pub divs: Vec<DivElement>,
357 pub metadata: Vec<MetadataChild>,
359 pub animations: Vec<AnimationChild>,
361}
362
363#[derive(Debug, Clone, PartialEq, Default)]
365#[non_exhaustive]
366pub struct DivElement {
367 pub xml_id: Option<String>,
369 pub xml_lang: Option<String>,
371 pub xml_space: Option<XmlSpace>,
373 pub begin: Option<String>,
375 pub dur: Option<String>,
377 pub end: Option<String>,
379 pub time_container: Option<String>,
381 pub region: Option<String>,
383 pub style: Option<String>,
385 pub animate: Option<String>,
387 pub condition: Option<String>,
389 pub style_attributes: StyleAttributes,
391 pub other_attributes: BTreeMap<(String, String), String>,
393 pub smpte_background_image: Option<String>,
395 pub paragraphs: Vec<PElement>,
397 pub images: Vec<ImageElement>,
399 pub metadata: Vec<MetadataChild>,
401 pub animations: Vec<AnimationChild>,
403}
404
405#[derive(Debug, Clone, PartialEq, Default)]
407#[non_exhaustive]
408pub struct PElement {
409 pub xml_id: Option<String>,
411 pub xml_lang: Option<String>,
413 pub xml_space: Option<XmlSpace>,
415 pub begin: Option<String>,
417 pub dur: Option<String>,
419 pub end: Option<String>,
421 pub time_container: Option<String>,
423 pub region: Option<String>,
425 pub style: Option<String>,
427 pub animate: Option<String>,
429 pub condition: Option<String>,
431 pub style_attributes: StyleAttributes,
433 pub other_attributes: BTreeMap<(String, String), String>,
435 pub content: Vec<InlineContent>,
437 pub metadata: Vec<MetadataChild>,
439 pub animations: Vec<AnimationChild>,
441}
442
443#[derive(Debug, Clone, PartialEq, Default)]
445#[non_exhaustive]
446pub struct SpanElement {
447 pub xml_id: Option<String>,
449 pub xml_lang: Option<String>,
451 pub xml_space: Option<XmlSpace>,
453 pub begin: Option<String>,
455 pub dur: Option<String>,
457 pub end: Option<String>,
459 pub time_container: Option<String>,
461 pub region: Option<String>,
463 pub style: Option<String>,
465 pub animate: Option<String>,
467 pub condition: Option<String>,
469 pub style_attributes: StyleAttributes,
471 pub other_attributes: BTreeMap<(String, String), String>,
473 pub content: Vec<InlineContent>,
475 pub metadata: Vec<MetadataChild>,
477 pub animations: Vec<AnimationChild>,
479}
480
481#[derive(Debug, Clone, PartialEq, Default)]
483#[non_exhaustive]
484pub struct BrElement {
485 pub xml_id: Option<String>,
487 pub xml_lang: Option<String>,
489 pub xml_space: Option<XmlSpace>,
491 pub style: Option<String>,
493 pub condition: Option<String>,
495 pub style_attributes: StyleAttributes,
497 pub other_attributes: BTreeMap<(String, String), String>,
499}
500
501#[derive(Debug, Clone, PartialEq, Default)]
503#[non_exhaustive]
504pub struct SetElement {
505 pub xml_id: Option<String>,
507 pub xml_lang: Option<String>,
509 pub xml_space: Option<XmlSpace>,
511 pub begin: Option<String>,
513 pub dur: Option<String>,
515 pub end: Option<String>,
517 pub fill: Option<String>,
519 pub repeat_count: Option<String>,
521 pub condition: Option<String>,
523 pub style_attributes: StyleAttributes,
525 pub other_attributes: BTreeMap<(String, String), String>,
527}
528
529#[derive(Debug, Clone, PartialEq, Default)]
531#[non_exhaustive]
532pub struct ImageElement {
533 pub xml_id: Option<String>,
535 pub xml_lang: Option<String>,
537 pub xml_space: Option<XmlSpace>,
539 pub begin: Option<String>,
541 pub dur: Option<String>,
543 pub end: Option<String>,
545 pub time_container: Option<String>,
547 pub region: Option<String>,
549 pub style: Option<String>,
551 pub animate: Option<String>,
553 pub condition: Option<String>,
555 pub src: Option<String>,
557 pub type_: Option<String>,
559 pub tts_extent: Option<String>,
561 pub style_attributes: StyleAttributes,
563 pub other_attributes: BTreeMap<(String, String), String>,
565 pub metadata: Vec<MetadataChild>,
567}
568
569#[derive(Debug, Clone, PartialEq)]
571#[non_exhaustive]
572pub enum InlineContent {
573 Text(String),
575 Span(Box<SpanElement>),
577 Br(Box<BrElement>),
579}
580
581#[derive(Debug, Clone, PartialEq)]
583#[non_exhaustive]
584pub enum AnimationChild {
585 Set(SetElement),
587}
588
589#[derive(Debug, Clone, PartialEq)]
591#[non_exhaustive]
592pub enum MetadataChild {
593 Metadata(MetadataElement),
595 TtmTitle(TtmTextElement),
597 TtmDesc(TtmTextElement),
599 TtmCopyright(TtmTextElement),
601 TtmAgent(TtmAgentElement),
603 TtmItem(TtmItemElement),
605 TtmName(TtmNameElement),
607 EbuttmDocumentMetadata(EbuttmElement),
609 EbuttmConformsToStandard(EbuttmTextElement),
611 IttmAltText(IttmAltTextElement),
613}
614
615#[derive(Debug, Clone, PartialEq, Default)]
617#[non_exhaustive]
618pub struct MetadataElement {
619 pub xml_id: Option<String>,
621 pub xml_lang: Option<String>,
623 pub xml_space: Option<XmlSpace>,
625 pub condition: Option<String>,
627 pub children: Vec<MetadataChild>,
629}
630
631#[derive(Debug, Clone, PartialEq, Default)]
633#[non_exhaustive]
634pub struct TtmTextElement {
635 pub xml_id: Option<String>,
637 pub xml_lang: Option<String>,
639 pub xml_space: Option<XmlSpace>,
641 pub condition: Option<String>,
643 pub text: String,
645}
646
647#[derive(Debug, Clone, PartialEq, Default)]
649#[non_exhaustive]
650pub struct TtmAgentElement {
651 pub xml_id: Option<String>,
653 pub xml_lang: Option<String>,
655 pub xml_space: Option<XmlSpace>,
657 pub condition: Option<String>,
659 pub type_: Option<String>,
661 pub names: Vec<TtmNameElement>,
663}
664
665#[derive(Debug, Clone, PartialEq, Default)]
667#[non_exhaustive]
668pub struct TtmNameElement {
669 pub xml_id: Option<String>,
671 pub xml_lang: Option<String>,
673 pub xml_space: Option<XmlSpace>,
675 pub condition: Option<String>,
677 pub type_: Option<String>,
679 pub text: String,
681}
682
683#[derive(Debug, Clone, PartialEq, Default)]
685#[non_exhaustive]
686pub struct TtmItemElement {
687 pub xml_id: Option<String>,
689 pub xml_lang: Option<String>,
691 pub xml_space: Option<XmlSpace>,
693 pub condition: Option<String>,
695 pub name: Option<String>,
697 pub text: Option<String>,
699 pub items: Vec<TtmItemElement>,
701}
702
703#[derive(Debug, Clone, PartialEq, Default)]
705#[non_exhaustive]
706pub struct EbuttmElement {
707 pub children: Vec<MetadataChild>,
709}
710
711#[derive(Debug, Clone, PartialEq, Default)]
713#[non_exhaustive]
714pub struct EbuttmTextElement {
715 pub text: String,
717}
718
719#[derive(Debug, Clone, PartialEq, Default)]
721#[non_exhaustive]
722pub struct IttmAltTextElement {
723 pub xml_id: Option<String>,
725 pub xml_lang: Option<String>,
727 pub xml_space: Option<XmlSpace>,
729 pub text: String,
731}
732
733#[derive(Debug, Clone, PartialEq, Default)]
737#[non_exhaustive]
738pub struct LayoutElement {
739 pub xml_id: Option<String>,
741 pub xml_lang: Option<String>,
743 pub xml_space: Option<XmlSpace>,
745 pub regions: Vec<RegionElement>,
747}
748
749#[derive(Debug, Clone, PartialEq, Default)]
751#[non_exhaustive]
752pub struct RegionElement {
753 pub xml_id: Option<String>,
755 pub xml_lang: Option<String>,
757 pub xml_space: Option<XmlSpace>,
759 pub begin: Option<String>,
761 pub dur: Option<String>,
763 pub end: Option<String>,
765 pub time_container: Option<String>,
767 pub style: Option<String>,
769 pub animate: Option<String>,
771 pub condition: Option<String>,
773 pub ttm_role: Option<String>,
775 pub style_attributes: StyleAttributes,
777 pub other_attributes: BTreeMap<(String, String), String>,
779}
780
781#[derive(Debug, Clone, PartialEq, Default)]
785#[non_exhaustive]
786pub struct StylingElement {
787 pub xml_id: Option<String>,
789 pub xml_lang: Option<String>,
791 pub xml_space: Option<XmlSpace>,
793 pub initials: Vec<InitialElement>,
795 pub styles: Vec<StyleElement>,
797}
798
799#[derive(Debug, Clone, PartialEq, Default)]
801#[non_exhaustive]
802pub struct InitialElement {
803 pub xml_id: Option<String>,
805 pub xml_lang: Option<String>,
807 pub xml_space: Option<XmlSpace>,
809 pub condition: Option<String>,
811 pub style_attributes: StyleAttributes,
813 pub other_attributes: BTreeMap<(String, String), String>,
815}
816
817#[derive(Debug, Clone, PartialEq, Default)]
819#[non_exhaustive]
820pub struct StyleElement {
821 pub xml_id: Option<String>,
823 pub xml_lang: Option<String>,
825 pub xml_space: Option<XmlSpace>,
827 pub condition: Option<String>,
829 pub style: Option<String>,
831 pub style_attributes: StyleAttributes,
833 pub other_attributes: BTreeMap<(String, String), String>,
835}
836
837#[derive(Debug, Clone, PartialEq, Default)]
844#[non_exhaustive]
845pub struct StyleAttributes {
846 pub tts_background_color: Option<String>,
848 pub tts_background_clip: Option<String>,
850 pub tts_background_extent: Option<String>,
852 pub tts_background_image: Option<String>,
854 pub tts_background_origin: Option<String>,
856 pub tts_background_position: Option<String>,
858 pub tts_background_repeat: Option<String>,
860 pub tts_border: Option<String>,
862 pub tts_bpd: Option<String>,
864 pub tts_color: Option<String>,
866 pub tts_direction: Option<String>,
868 pub tts_disparity: Option<String>,
870 pub tts_display: Option<String>,
872 pub tts_display_align: Option<String>,
874 pub tts_extent: Option<String>,
876 pub tts_font_family: Option<String>,
878 pub tts_font_kerning: Option<String>,
880 pub tts_font_selection_strategy: Option<String>,
882 pub tts_font_shear: Option<String>,
884 pub tts_font_size: Option<String>,
886 pub tts_font_style: Option<String>,
888 pub tts_font_variant: Option<String>,
890 pub tts_font_weight: Option<String>,
892 pub tts_ipd: Option<String>,
894 pub tts_letter_spacing: Option<String>,
896 pub tts_line_height: Option<String>,
898 pub tts_line_shear: Option<String>,
900 pub tts_luminance_gain: Option<String>,
902 pub tts_opacity: Option<String>,
904 pub tts_origin: Option<String>,
906 pub tts_overflow: Option<String>,
908 pub tts_padding: Option<String>,
910 pub tts_position: Option<String>,
912 pub tts_ruby: Option<String>,
914 pub tts_ruby_align: Option<String>,
916 pub tts_ruby_position: Option<String>,
918 pub tts_ruby_reserve: Option<String>,
920 pub tts_shear: Option<String>,
922 pub tts_show_background: Option<String>,
924 pub tts_text_align: Option<String>,
926 pub tts_text_combine: Option<String>,
928 pub tts_text_decoration: Option<String>,
930 pub tts_text_emphasis: Option<String>,
932 pub tts_text_orientation: Option<String>,
934 pub tts_text_outline: Option<String>,
936 pub tts_text_shadow: Option<String>,
938 pub tts_unicode_bidi: Option<String>,
940 pub tts_visibility: Option<String>,
942 pub tts_wrap_option: Option<String>,
944 pub tts_writing_mode: Option<String>,
946 pub tts_z_index: Option<String>,
948 pub tta_gain: Option<String>,
950 pub tta_pan: Option<String>,
952 pub tta_pitch: Option<String>,
954 pub tta_speak: Option<String>,
956 pub itts_forced_display: Option<String>,
958 pub itts_fill_line_gap: Option<String>,
960 pub ebutts_line_padding: Option<String>,
962 pub ebutts_multi_row_align: Option<String>,
964}
965
966#[derive(Debug, Clone, Copy, PartialEq, Eq)]
968#[non_exhaustive]
969pub enum XmlSpace {
970 Default,
972 Preserve,
974}
975
976impl XmlSpace {
977 pub fn name(&self) -> &'static str {
979 match self {
980 XmlSpace::Default => "default",
981 XmlSpace::Preserve => "preserve",
982 }
983 }
984}
985
986broadcast_common::impl_spec_display!(XmlSpace);
987
988fn attribute_value<'a>(node: &roxmltree::Node<'a, 'a>, ns: &str, local: &str) -> Option<&'a str> {
994 for attr in node.attributes() {
996 if attr.namespace() == Some(ns) && attr.name() == local {
997 return Some(attr.value());
998 }
999 }
1000 None
1001}
1002
1003fn other_attributes(node: &roxmltree::Node<'_, '_>) -> BTreeMap<(String, String), String> {
1005 let known_nses = &[
1006 NS_TT,
1007 NS_TTP,
1008 NS_TTS,
1009 NS_TTA,
1010 NS_TTM,
1011 NS_TT_PROFILE,
1012 NS_ITTS,
1013 NS_ITTP,
1014 NS_ITTM,
1015 NS_EBUTTS,
1016 NS_EBUTTM,
1017 NS_SMPTE,
1018 NS_XML,
1019 "", ];
1021 let mut map = BTreeMap::new();
1022 for attr in node.attributes() {
1023 let ns = attr.namespace().unwrap_or("");
1024 let local = attr.name();
1025 if !known_nses.contains(&ns) {
1026 map.insert(
1027 (ns.to_string(), local.to_string()),
1028 attr.value().to_string(),
1029 );
1030 }
1031 }
1032 map
1033}
1034
1035fn parse_tt_element(node: roxmltree::Node<'_, '_>) -> Result<TtElement> {
1039 let mut head = None;
1040 let mut body = None;
1041
1042 for child in node.children() {
1043 if !child.is_element() {
1044 continue;
1045 }
1046 let name = child.tag_name().name();
1047 let ns = child.tag_name().namespace();
1048
1049 match (name, ns) {
1050 ("head", Some(NS_TT)) => {
1051 head = Some(parse_head_element(child)?);
1052 }
1053 ("body", Some(NS_TT)) => {
1054 body = Some(parse_body_element(child)?);
1055 }
1056 _ => {
1057 }
1059 }
1060 }
1061
1062 let _ = parse_style_attributes(node);
1064
1065 Ok(TtElement {
1066 xml_lang: attribute_value(&node, NS_XML, "lang").map(|s| s.to_string()),
1067 xml_id: attribute_value(&node, NS_XML, "id").map(|s| s.to_string()),
1068 xml_space: parse_xml_space(attribute_value(&node, NS_XML, "space")),
1069 ttp_time_base: attribute_value(&node, NS_TTP, "timeBase").map(|s| s.to_string()),
1070 ttp_frame_rate: attribute_value(&node, NS_TTP, "frameRate").map(|s| s.to_string()),
1071 ttp_frame_rate_multiplier: attribute_value(&node, NS_TTP, "frameRateMultiplier")
1072 .map(|s| s.to_string()),
1073 ttp_tick_rate: attribute_value(&node, NS_TTP, "tickRate").map(|s| s.to_string()),
1074 ttp_sub_frame_rate: attribute_value(&node, NS_TTP, "subFrameRate").map(|s| s.to_string()),
1075 ttp_drop_mode: attribute_value(&node, NS_TTP, "dropMode").map(|s| s.to_string()),
1076 ttp_marker_mode: attribute_value(&node, NS_TTP, "markerMode").map(|s| s.to_string()),
1077 ttp_clock_mode: attribute_value(&node, NS_TTP, "clockMode").map(|s| s.to_string()),
1078 ttp_cell_resolution: attribute_value(&node, NS_TTP, "cellResolution")
1079 .map(|s| s.to_string()),
1080 ttp_pixel_aspect_ratio: attribute_value(&node, NS_TTP, "pixelAspectRatio")
1081 .map(|s| s.to_string()),
1082 ttp_display_aspect_ratio: attribute_value(&node, NS_TTP, "displayAspectRatio")
1083 .map(|s| s.to_string()),
1084 ttp_profile: attribute_value(&node, NS_TTP, "profile").map(|s| s.to_string()),
1085 ttp_content_profiles: attribute_value(&node, NS_TTP, "contentProfiles")
1086 .map(|s| s.to_string()),
1087 ttp_content_profile_combination: attribute_value(
1088 &node,
1089 NS_TTP,
1090 "contentProfileCombination",
1091 )
1092 .map(|s| s.to_string()),
1093 ttp_processor_profiles: attribute_value(&node, NS_TTP, "processorProfiles")
1094 .map(|s| s.to_string()),
1095 ttp_processor_profile_combination: attribute_value(
1096 &node,
1097 NS_TTP,
1098 "processorProfileCombination",
1099 )
1100 .map(|s| s.to_string()),
1101 ttp_infer_processor_profile_method: attribute_value(
1102 &node,
1103 NS_TTP,
1104 "inferProcessorProfileMethod",
1105 )
1106 .map(|s| s.to_string()),
1107 ttp_infer_processor_profile_source: attribute_value(
1108 &node,
1109 NS_TTP,
1110 "inferProcessorProfileSource",
1111 )
1112 .map(|s| s.to_string()),
1113 ttp_permit_feature_narrowing: attribute_value(&node, NS_TTP, "permitFeatureNarrowing")
1114 .map(|s| s.to_string()),
1115 ttp_permit_feature_widening: attribute_value(&node, NS_TTP, "permitFeatureWidening")
1116 .map(|s| s.to_string()),
1117 ttp_validation: attribute_value(&node, NS_TTP, "validation").map(|s| s.to_string()),
1118 ttp_validation_action: attribute_value(&node, NS_TTP, "validationAction")
1119 .map(|s| s.to_string()),
1120 tts_extent: attribute_value(&node, NS_TTS, "extent").map(|s| s.to_string()),
1121 ittp_active_area: attribute_value(&node, NS_ITTP, "activeArea").map(|s| s.to_string()),
1122 ittp_aspect_ratio: attribute_value(&node, NS_ITTP, "aspectRatio").map(|s| s.to_string()),
1123 ittp_progressively_decodable: attribute_value(&node, NS_ITTP, "progressivelyDecodable")
1124 .map(|s| s.to_string()),
1125 other_attributes: other_attributes(&node),
1126 head,
1127 body,
1128 text: node.text().map(|s| s.to_string()),
1129 })
1130}
1131
1132fn parse_head_element(node: roxmltree::Node<'_, '_>) -> Result<HeadElement> {
1133 let mut metadata = Vec::new();
1134 let mut styling = None;
1135 let mut layout = None;
1136
1137 for child in node.children() {
1139 if !child.is_element() {
1140 continue;
1141 }
1142 let name = child.tag_name().name();
1143 let ns = child.tag_name().namespace();
1144
1145 match (name, ns) {
1146 ("metadata", Some(NS_TT)) => {
1147 metadata.push(MetadataChild::Metadata(parse_metadata_element(child)?));
1148 }
1149 ("title", Some(NS_TTM)) => {
1150 metadata.push(MetadataChild::TtmTitle(parse_ttm_text(child)?));
1151 }
1152 ("desc", Some(NS_TTM)) => {
1153 metadata.push(MetadataChild::TtmDesc(parse_ttm_text(child)?));
1154 }
1155 ("copyright", Some(NS_TTM)) => {
1156 metadata.push(MetadataChild::TtmCopyright(parse_ttm_text(child)?));
1157 }
1158 ("agent", Some(NS_TTM)) => {
1159 metadata.push(MetadataChild::TtmAgent(parse_ttm_agent(child)?));
1160 }
1161 ("item", Some(NS_TTM)) => {
1162 metadata.push(MetadataChild::TtmItem(parse_ttm_item(child)?));
1163 }
1164 ("name", Some(NS_TTM)) => {
1165 metadata.push(MetadataChild::TtmName(parse_ttm_name(child)?));
1166 }
1167 ("documentMetadata", Some(NS_EBUTTM)) => {
1168 metadata.push(MetadataChild::EbuttmDocumentMetadata(parse_ebuttm_element(
1169 child,
1170 )?));
1171 }
1172 ("conformsToStandard", Some(NS_EBUTTM)) => {
1173 metadata.push(MetadataChild::EbuttmConformsToStandard(parse_ebuttm_text(
1174 child,
1175 )?));
1176 }
1177 ("altText", Some(NS_ITTM)) => {
1178 metadata.push(MetadataChild::IttmAltText(parse_ittm_alt_text(child)?));
1179 }
1180 ("styling", Some(NS_TT)) => {
1181 styling = Some(parse_styling_element(child)?);
1182 }
1183 ("layout", Some(NS_TT)) => {
1184 layout = Some(parse_layout_element(child)?);
1185 }
1186 _ => {}
1187 }
1188 }
1189
1190 Ok(HeadElement {
1191 xml_id: attribute_value(&node, NS_XML, "id").map(|s| s.to_string()),
1192 xml_lang: attribute_value(&node, NS_XML, "lang").map(|s| s.to_string()),
1193 xml_space: parse_xml_space(attribute_value(&node, NS_XML, "space")),
1194 metadata,
1195 styling,
1196 layout,
1197 })
1198}
1199
1200fn parse_body_element(node: roxmltree::Node<'_, '_>) -> Result<BodyElement> {
1201 let mut divs = Vec::new();
1202 let mut metadata = Vec::new();
1203 let mut animations = Vec::new();
1204
1205 for child in node.children() {
1206 if !child.is_element() {
1207 continue;
1208 }
1209 let name = child.tag_name().name();
1210 let ns = child.tag_name().namespace();
1211
1212 match (name, ns) {
1213 ("div", Some(NS_TT)) => {
1214 divs.push(parse_div_element(child)?);
1215 }
1216 ("metadata", Some(NS_TT)) => {
1217 metadata.push(MetadataChild::Metadata(parse_metadata_element(child)?));
1218 }
1219 ("title", Some(NS_TTM)) => {
1220 metadata.push(MetadataChild::TtmTitle(parse_ttm_text(child)?));
1221 }
1222 ("desc", Some(NS_TTM)) => {
1223 metadata.push(MetadataChild::TtmDesc(parse_ttm_text(child)?));
1224 }
1225 ("copyright", Some(NS_TTM)) => {
1226 metadata.push(MetadataChild::TtmCopyright(parse_ttm_text(child)?));
1227 }
1228 ("documentMetadata", Some(NS_EBUTTM)) => {
1229 metadata.push(MetadataChild::EbuttmDocumentMetadata(parse_ebuttm_element(
1230 child,
1231 )?));
1232 }
1233 ("conformsToStandard", Some(NS_EBUTTM)) => {
1234 metadata.push(MetadataChild::EbuttmConformsToStandard(parse_ebuttm_text(
1235 child,
1236 )?));
1237 }
1238 ("altText", Some(NS_ITTM)) => {
1239 metadata.push(MetadataChild::IttmAltText(parse_ittm_alt_text(child)?));
1240 }
1241 ("set", Some(NS_TT)) => {
1242 animations.push(AnimationChild::Set(parse_set_element(child)?));
1243 }
1244 _ => {}
1245 }
1246 }
1247
1248 let style_attrs = parse_style_attributes(node);
1249
1250 Ok(BodyElement {
1251 xml_id: attribute_value(&node, NS_XML, "id").map(|s| s.to_string()),
1252 xml_lang: attribute_value(&node, NS_XML, "lang").map(|s| s.to_string()),
1253 xml_space: parse_xml_space(attribute_value(&node, NS_XML, "space")),
1254 begin: node.attribute("begin").map(|s| s.to_string()),
1255 dur: node.attribute("dur").map(|s| s.to_string()),
1256 end: node.attribute("end").map(|s| s.to_string()),
1257 time_container: node.attribute("timeContainer").map(|s| s.to_string()),
1258 region: node.attribute("region").map(|s| s.to_string()),
1259 style: node.attribute("style").map(|s| s.to_string()),
1260 animate: node.attribute("animate").map(|s| s.to_string()),
1261 condition: node.attribute("condition").map(|s| s.to_string()),
1262 style_attributes: style_attrs,
1263 other_attributes: other_attributes(&node),
1264 divs,
1265 metadata,
1266 animations,
1267 })
1268}
1269
1270fn parse_div_element(node: roxmltree::Node<'_, '_>) -> Result<DivElement> {
1271 let mut paragraphs = Vec::new();
1272 let mut images = Vec::new();
1273 let mut metadata = Vec::new();
1274 let mut animations = Vec::new();
1275
1276 for child in node.children() {
1277 if !child.is_element() {
1278 continue;
1279 }
1280 let name = child.tag_name().name();
1281 let ns = child.tag_name().namespace();
1282
1283 match (name, ns) {
1284 ("p", Some(NS_TT)) => {
1285 paragraphs.push(parse_p_element(child)?);
1286 }
1287 ("image", Some(NS_TT)) => {
1288 images.push(parse_image_element(child)?);
1289 }
1290 ("metadata", Some(NS_TT)) => {
1291 metadata.push(MetadataChild::Metadata(parse_metadata_element(child)?));
1292 }
1293 ("altText", Some(NS_ITTM)) => {
1294 metadata.push(MetadataChild::IttmAltText(parse_ittm_alt_text(child)?));
1295 }
1296 ("set", Some(NS_TT)) => {
1297 animations.push(AnimationChild::Set(parse_set_element(child)?));
1298 }
1299 _ => {}
1300 }
1301 }
1302
1303 let style_attrs = parse_style_attributes(node);
1304
1305 Ok(DivElement {
1306 xml_id: attribute_value(&node, NS_XML, "id").map(|s| s.to_string()),
1307 xml_lang: attribute_value(&node, NS_XML, "lang").map(|s| s.to_string()),
1308 xml_space: parse_xml_space(attribute_value(&node, NS_XML, "space")),
1309 begin: node.attribute("begin").map(|s| s.to_string()),
1310 dur: node.attribute("dur").map(|s| s.to_string()),
1311 end: node.attribute("end").map(|s| s.to_string()),
1312 time_container: node.attribute("timeContainer").map(|s| s.to_string()),
1313 region: node.attribute("region").map(|s| s.to_string()),
1314 style: node.attribute("style").map(|s| s.to_string()),
1315 animate: node.attribute("animate").map(|s| s.to_string()),
1316 condition: node.attribute("condition").map(|s| s.to_string()),
1317 style_attributes: style_attrs,
1318 other_attributes: other_attributes(&node),
1319 smpte_background_image: attribute_value(&node, NS_SMPTE, "backgroundImage")
1320 .map(|s| s.to_string()),
1321 paragraphs,
1322 images,
1323 metadata,
1324 animations,
1325 })
1326}
1327
1328fn parse_p_element(node: roxmltree::Node<'_, '_>) -> Result<PElement> {
1329 let mut content: Vec<InlineContent> = Vec::new();
1330 let mut metadata = Vec::new();
1331 let mut animations = Vec::new();
1332
1333 for child in node.children() {
1334 if child.is_text() {
1335 let text = child.text().unwrap_or("");
1336 if !text.is_empty() {
1337 if let Some(last) = content.last_mut()
1338 && let InlineContent::Text(t) = last
1339 {
1340 t.push_str(text);
1341 continue;
1342 }
1343 content.push(InlineContent::Text(text.to_string()));
1344 }
1345 } else if child.is_element() {
1346 let name = child.tag_name().name();
1347 let ns = child.tag_name().namespace();
1348
1349 match (name, ns) {
1350 ("span", Some(NS_TT)) => {
1351 content.push(InlineContent::Span(Box::new(parse_span_element(child)?)));
1352 }
1353 ("br", Some(NS_TT)) => {
1354 content.push(InlineContent::Br(Box::new(parse_br_element(child)?)));
1355 }
1356 ("metadata", Some(NS_TT)) => {
1357 metadata.push(MetadataChild::Metadata(parse_metadata_element(child)?));
1358 }
1359 ("set", Some(NS_TT)) => {
1360 animations.push(AnimationChild::Set(parse_set_element(child)?));
1361 }
1362 _ => {}
1363 }
1364 }
1365 }
1366
1367 let style_attrs = parse_style_attributes(node);
1368
1369 Ok(PElement {
1370 xml_id: attribute_value(&node, NS_XML, "id").map(|s| s.to_string()),
1371 xml_lang: attribute_value(&node, NS_XML, "lang").map(|s| s.to_string()),
1372 xml_space: parse_xml_space(attribute_value(&node, NS_XML, "space")),
1373 begin: node.attribute("begin").map(|s| s.to_string()),
1374 dur: node.attribute("dur").map(|s| s.to_string()),
1375 end: node.attribute("end").map(|s| s.to_string()),
1376 time_container: node.attribute("timeContainer").map(|s| s.to_string()),
1377 region: node.attribute("region").map(|s| s.to_string()),
1378 style: node.attribute("style").map(|s| s.to_string()),
1379 animate: node.attribute("animate").map(|s| s.to_string()),
1380 condition: node.attribute("condition").map(|s| s.to_string()),
1381 style_attributes: style_attrs,
1382 other_attributes: other_attributes(&node),
1383 content,
1384 metadata,
1385 animations,
1386 })
1387}
1388
1389fn parse_span_element(node: roxmltree::Node<'_, '_>) -> Result<SpanElement> {
1390 let mut content: Vec<InlineContent> = Vec::new();
1391 let mut metadata = Vec::new();
1392 let mut animations = Vec::new();
1393
1394 for child in node.children() {
1395 if child.is_text() {
1396 let text = child.text().unwrap_or("");
1397 if !text.is_empty() {
1398 if let Some(last) = content.last_mut()
1399 && let InlineContent::Text(t) = last
1400 {
1401 t.push_str(text);
1402 continue;
1403 }
1404 content.push(InlineContent::Text(text.to_string()));
1405 }
1406 } else if child.is_element() {
1407 let name = child.tag_name().name();
1408 let ns = child.tag_name().namespace();
1409
1410 match (name, ns) {
1411 ("span", Some(NS_TT)) => {
1412 content.push(InlineContent::Span(Box::new(parse_span_element(child)?)));
1413 }
1414 ("br", Some(NS_TT)) => {
1415 content.push(InlineContent::Br(Box::new(parse_br_element(child)?)));
1416 }
1417 ("metadata", Some(NS_TT)) => {
1418 metadata.push(MetadataChild::Metadata(parse_metadata_element(child)?));
1419 }
1420 ("set", Some(NS_TT)) => {
1421 animations.push(AnimationChild::Set(parse_set_element(child)?));
1422 }
1423 _ => {}
1424 }
1425 }
1426 }
1427
1428 let style_attrs = parse_style_attributes(node);
1429
1430 Ok(SpanElement {
1431 xml_id: attribute_value(&node, NS_XML, "id").map(|s| s.to_string()),
1432 xml_lang: attribute_value(&node, NS_XML, "lang").map(|s| s.to_string()),
1433 xml_space: parse_xml_space(attribute_value(&node, NS_XML, "space")),
1434 begin: node.attribute("begin").map(|s| s.to_string()),
1435 dur: node.attribute("dur").map(|s| s.to_string()),
1436 end: node.attribute("end").map(|s| s.to_string()),
1437 time_container: node.attribute("timeContainer").map(|s| s.to_string()),
1438 region: node.attribute("region").map(|s| s.to_string()),
1439 style: node.attribute("style").map(|s| s.to_string()),
1440 animate: node.attribute("animate").map(|s| s.to_string()),
1441 condition: node.attribute("condition").map(|s| s.to_string()),
1442 style_attributes: style_attrs,
1443 other_attributes: other_attributes(&node),
1444 content,
1445 metadata,
1446 animations,
1447 })
1448}
1449
1450fn parse_br_element(node: roxmltree::Node<'_, '_>) -> Result<BrElement> {
1451 let style_attrs = parse_style_attributes(node);
1452 Ok(BrElement {
1453 xml_id: attribute_value(&node, NS_XML, "id").map(|s| s.to_string()),
1454 xml_lang: attribute_value(&node, NS_XML, "lang").map(|s| s.to_string()),
1455 xml_space: parse_xml_space(attribute_value(&node, NS_XML, "space")),
1456 style: node.attribute("style").map(|s| s.to_string()),
1457 condition: node.attribute("condition").map(|s| s.to_string()),
1458 style_attributes: style_attrs,
1459 other_attributes: other_attributes(&node),
1460 })
1461}
1462
1463fn parse_set_element(node: roxmltree::Node<'_, '_>) -> Result<SetElement> {
1464 let style_attrs = parse_style_attributes(node);
1465 Ok(SetElement {
1466 xml_id: attribute_value(&node, NS_XML, "id").map(|s| s.to_string()),
1467 xml_lang: attribute_value(&node, NS_XML, "lang").map(|s| s.to_string()),
1468 xml_space: parse_xml_space(attribute_value(&node, NS_XML, "space")),
1469 begin: node.attribute("begin").map(|s| s.to_string()),
1470 dur: node.attribute("dur").map(|s| s.to_string()),
1471 end: node.attribute("end").map(|s| s.to_string()),
1472 fill: node.attribute("fill").map(|s| s.to_string()),
1473 repeat_count: node.attribute("repeatCount").map(|s| s.to_string()),
1474 condition: node.attribute("condition").map(|s| s.to_string()),
1475 style_attributes: style_attrs,
1476 other_attributes: other_attributes(&node),
1477 })
1478}
1479
1480fn parse_image_element(node: roxmltree::Node<'_, '_>) -> Result<ImageElement> {
1481 let mut metadata = Vec::new();
1482
1483 for child in node.children() {
1484 if !child.is_element() {
1485 continue;
1486 }
1487 let name = child.tag_name().name();
1488 let ns = child.tag_name().namespace();
1489
1490 match (name, ns) {
1491 ("metadata", Some(NS_TT)) => {
1492 metadata.push(MetadataChild::Metadata(parse_metadata_element(child)?));
1493 }
1494 ("altText", Some(NS_ITTM)) => {
1495 metadata.push(MetadataChild::IttmAltText(parse_ittm_alt_text(child)?));
1496 }
1497 _ => {}
1498 }
1499 }
1500
1501 let style_attrs = parse_style_attributes(node);
1502
1503 Ok(ImageElement {
1504 xml_id: attribute_value(&node, NS_XML, "id").map(|s| s.to_string()),
1505 xml_lang: attribute_value(&node, NS_XML, "lang").map(|s| s.to_string()),
1506 xml_space: parse_xml_space(attribute_value(&node, NS_XML, "space")),
1507 begin: node.attribute("begin").map(|s| s.to_string()),
1508 dur: node.attribute("dur").map(|s| s.to_string()),
1509 end: node.attribute("end").map(|s| s.to_string()),
1510 time_container: node.attribute("timeContainer").map(|s| s.to_string()),
1511 region: node.attribute("region").map(|s| s.to_string()),
1512 style: node.attribute("style").map(|s| s.to_string()),
1513 animate: node.attribute("animate").map(|s| s.to_string()),
1514 condition: node.attribute("condition").map(|s| s.to_string()),
1515 src: node.attribute("src").map(|s| s.to_string()),
1516 type_: node.attribute("type").map(|s| s.to_string()),
1517 tts_extent: attribute_value(&node, NS_TTS, "extent").map(|s| s.to_string()),
1518 style_attributes: style_attrs,
1519 other_attributes: other_attributes(&node),
1520 metadata,
1521 })
1522}
1523
1524fn parse_metadata_element(node: roxmltree::Node<'_, '_>) -> Result<MetadataElement> {
1525 let mut children = Vec::new();
1526
1527 for child in node.children() {
1528 if !child.is_element() {
1529 continue;
1530 }
1531 let name = child.tag_name().name();
1532 let ns = child.tag_name().namespace();
1533
1534 match (name, ns) {
1535 ("metadata", Some(NS_TT)) => {
1536 children.push(MetadataChild::Metadata(parse_metadata_element(child)?));
1537 }
1538 ("title", Some(NS_TTM)) => {
1539 children.push(MetadataChild::TtmTitle(parse_ttm_text(child)?));
1540 }
1541 ("desc", Some(NS_TTM)) => {
1542 children.push(MetadataChild::TtmDesc(parse_ttm_text(child)?));
1543 }
1544 ("copyright", Some(NS_TTM)) => {
1545 children.push(MetadataChild::TtmCopyright(parse_ttm_text(child)?));
1546 }
1547 ("agent", Some(NS_TTM)) => {
1548 children.push(MetadataChild::TtmAgent(parse_ttm_agent(child)?));
1549 }
1550 ("item", Some(NS_TTM)) => {
1551 children.push(MetadataChild::TtmItem(parse_ttm_item(child)?));
1552 }
1553 ("name", Some(NS_TTM)) => {
1554 children.push(MetadataChild::TtmName(parse_ttm_name(child)?));
1555 }
1556 ("documentMetadata", Some(NS_EBUTTM)) => {
1557 children.push(MetadataChild::EbuttmDocumentMetadata(parse_ebuttm_element(
1558 child,
1559 )?));
1560 }
1561 ("conformsToStandard", Some(NS_EBUTTM)) => {
1562 children.push(MetadataChild::EbuttmConformsToStandard(parse_ebuttm_text(
1563 child,
1564 )?));
1565 }
1566 ("altText", Some(NS_ITTM)) => {
1567 children.push(MetadataChild::IttmAltText(parse_ittm_alt_text(child)?));
1568 }
1569 _ => {}
1570 }
1571 }
1572
1573 Ok(MetadataElement {
1574 xml_id: attribute_value(&node, NS_XML, "id").map(|s| s.to_string()),
1575 xml_lang: attribute_value(&node, NS_XML, "lang").map(|s| s.to_string()),
1576 xml_space: parse_xml_space(attribute_value(&node, NS_XML, "space")),
1577 condition: node.attribute("condition").map(|s| s.to_string()),
1578 children,
1579 })
1580}
1581
1582fn parse_ttm_text(node: roxmltree::Node<'_, '_>) -> Result<TtmTextElement> {
1583 let text = node.text().unwrap_or("").to_string();
1584 Ok(TtmTextElement {
1585 xml_id: attribute_value(&node, NS_XML, "id").map(|s| s.to_string()),
1586 xml_lang: attribute_value(&node, NS_XML, "lang").map(|s| s.to_string()),
1587 xml_space: parse_xml_space(attribute_value(&node, NS_XML, "space")),
1588 condition: node.attribute("condition").map(|s| s.to_string()),
1589 text,
1590 })
1591}
1592
1593fn parse_ttm_agent(node: roxmltree::Node<'_, '_>) -> Result<TtmAgentElement> {
1594 let mut names = Vec::new();
1595 for child in node.children() {
1596 if child.is_element()
1597 && child.tag_name().name() == "name"
1598 && child.tag_name().namespace() == Some(NS_TTM)
1599 {
1600 names.push(parse_ttm_name(child)?);
1601 }
1602 }
1603 Ok(TtmAgentElement {
1604 xml_id: attribute_value(&node, NS_XML, "id").map(|s| s.to_string()),
1605 xml_lang: attribute_value(&node, NS_XML, "lang").map(|s| s.to_string()),
1606 xml_space: parse_xml_space(attribute_value(&node, NS_XML, "space")),
1607 condition: node.attribute("condition").map(|s| s.to_string()),
1608 type_: node.attribute("type").map(|s| s.to_string()),
1609 names,
1610 })
1611}
1612
1613fn parse_ttm_name(node: roxmltree::Node<'_, '_>) -> Result<TtmNameElement> {
1614 let text = node.text().unwrap_or("").to_string();
1615 Ok(TtmNameElement {
1616 xml_id: attribute_value(&node, NS_XML, "id").map(|s| s.to_string()),
1617 xml_lang: attribute_value(&node, NS_XML, "lang").map(|s| s.to_string()),
1618 xml_space: parse_xml_space(attribute_value(&node, NS_XML, "space")),
1619 condition: node.attribute("condition").map(|s| s.to_string()),
1620 type_: node.attribute("type").map(|s| s.to_string()),
1621 text,
1622 })
1623}
1624
1625fn parse_ttm_item(node: roxmltree::Node<'_, '_>) -> Result<TtmItemElement> {
1626 let mut items = Vec::new();
1627 let mut text_parts = String::new();
1628
1629 for child in node.children() {
1630 if child.is_text() {
1631 if let Some(t) = child.text() {
1632 text_parts.push_str(t);
1633 }
1634 } else if child.is_element()
1635 && child.tag_name().name() == "item"
1636 && child.tag_name().namespace() == Some(NS_TTM)
1637 {
1638 items.push(parse_ttm_item(child)?);
1639 }
1640 }
1641
1642 Ok(TtmItemElement {
1643 xml_id: attribute_value(&node, NS_XML, "id").map(|s| s.to_string()),
1644 xml_lang: attribute_value(&node, NS_XML, "lang").map(|s| s.to_string()),
1645 xml_space: parse_xml_space(attribute_value(&node, NS_XML, "space")),
1646 condition: node.attribute("condition").map(|s| s.to_string()),
1647 name: node.attribute("name").map(|s| s.to_string()),
1648 text: if text_parts.is_empty() {
1649 None
1650 } else {
1651 Some(text_parts)
1652 },
1653 items,
1654 })
1655}
1656
1657fn parse_ittm_alt_text(node: roxmltree::Node<'_, '_>) -> Result<IttmAltTextElement> {
1658 let text = node.text().unwrap_or("").to_string();
1659 Ok(IttmAltTextElement {
1660 xml_id: attribute_value(&node, NS_XML, "id").map(|s| s.to_string()),
1661 xml_lang: attribute_value(&node, NS_XML, "lang").map(|s| s.to_string()),
1662 xml_space: parse_xml_space(attribute_value(&node, NS_XML, "space")),
1663 text,
1664 })
1665}
1666
1667fn parse_ebuttm_element(node: roxmltree::Node<'_, '_>) -> Result<EbuttmElement> {
1668 let mut children = Vec::new();
1669 for child in node.children() {
1670 if !child.is_element() {
1671 continue;
1672 }
1673 let name = child.tag_name().name();
1674 let ns = child.tag_name().namespace();
1675
1676 if name == "conformsToStandard" && ns == Some(NS_EBUTTM) {
1677 children.push(MetadataChild::EbuttmConformsToStandard(parse_ebuttm_text(
1678 child,
1679 )?));
1680 }
1681 }
1682 Ok(EbuttmElement { children })
1683}
1684
1685fn parse_ebuttm_text(node: roxmltree::Node<'_, '_>) -> Result<EbuttmTextElement> {
1686 let text = node.text().unwrap_or("").to_string();
1687 Ok(EbuttmTextElement { text })
1688}
1689
1690fn parse_styling_element(node: roxmltree::Node<'_, '_>) -> Result<StylingElement> {
1691 let mut initials = Vec::new();
1692 let mut styles = Vec::new();
1693
1694 for child in node.children() {
1695 if !child.is_element() {
1696 continue;
1697 }
1698 let name = child.tag_name().name();
1699 let ns = child.tag_name().namespace();
1700
1701 match (name, ns) {
1702 ("initial", Some(NS_TT)) => {
1703 initials.push(parse_initial_element(child)?);
1704 }
1705 ("style", Some(NS_TT)) => {
1706 styles.push(parse_style_element(child)?);
1707 }
1708 _ => {}
1709 }
1710 }
1711
1712 Ok(StylingElement {
1713 xml_id: attribute_value(&node, NS_XML, "id").map(|s| s.to_string()),
1714 xml_lang: attribute_value(&node, NS_XML, "lang").map(|s| s.to_string()),
1715 xml_space: parse_xml_space(attribute_value(&node, NS_XML, "space")),
1716 initials,
1717 styles,
1718 })
1719}
1720
1721fn parse_initial_element(node: roxmltree::Node<'_, '_>) -> Result<InitialElement> {
1722 Ok(InitialElement {
1723 xml_id: attribute_value(&node, NS_XML, "id").map(|s| s.to_string()),
1724 xml_lang: attribute_value(&node, NS_XML, "lang").map(|s| s.to_string()),
1725 xml_space: parse_xml_space(attribute_value(&node, NS_XML, "space")),
1726 condition: node.attribute("condition").map(|s| s.to_string()),
1727 style_attributes: parse_style_attributes(node),
1728 other_attributes: other_attributes(&node),
1729 })
1730}
1731
1732fn parse_style_element(node: roxmltree::Node<'_, '_>) -> Result<StyleElement> {
1733 Ok(StyleElement {
1734 xml_id: attribute_value(&node, NS_XML, "id").map(|s| s.to_string()),
1735 xml_lang: attribute_value(&node, NS_XML, "lang").map(|s| s.to_string()),
1736 xml_space: parse_xml_space(attribute_value(&node, NS_XML, "space")),
1737 condition: node.attribute("condition").map(|s| s.to_string()),
1738 style: node.attribute("style").map(|s| s.to_string()),
1739 style_attributes: parse_style_attributes(node),
1740 other_attributes: other_attributes(&node),
1741 })
1742}
1743
1744fn parse_layout_element(node: roxmltree::Node<'_, '_>) -> Result<LayoutElement> {
1745 let mut regions = Vec::new();
1746 for child in node.children() {
1747 if child.is_element()
1748 && child.tag_name().name() == "region"
1749 && child.tag_name().namespace() == Some(NS_TT)
1750 {
1751 regions.push(parse_region_element(child)?);
1752 }
1753 }
1754
1755 Ok(LayoutElement {
1756 xml_id: attribute_value(&node, NS_XML, "id").map(|s| s.to_string()),
1757 xml_lang: attribute_value(&node, NS_XML, "lang").map(|s| s.to_string()),
1758 xml_space: parse_xml_space(attribute_value(&node, NS_XML, "space")),
1759 regions,
1760 })
1761}
1762
1763fn parse_region_element(node: roxmltree::Node<'_, '_>) -> Result<RegionElement> {
1764 let style_attrs = parse_style_attributes(node);
1765
1766 Ok(RegionElement {
1767 xml_id: attribute_value(&node, NS_XML, "id").map(|s| s.to_string()),
1768 xml_lang: attribute_value(&node, NS_XML, "lang").map(|s| s.to_string()),
1769 xml_space: parse_xml_space(attribute_value(&node, NS_XML, "space")),
1770 begin: node.attribute("begin").map(|s| s.to_string()),
1771 dur: node.attribute("dur").map(|s| s.to_string()),
1772 end: node.attribute("end").map(|s| s.to_string()),
1773 time_container: node.attribute("timeContainer").map(|s| s.to_string()),
1774 style: node.attribute("style").map(|s| s.to_string()),
1775 animate: node.attribute("animate").map(|s| s.to_string()),
1776 condition: node.attribute("condition").map(|s| s.to_string()),
1777 ttm_role: attribute_value(&node, NS_TTM, "role").map(|s| s.to_string()),
1778 style_attributes: style_attrs,
1779 other_attributes: other_attributes(&node),
1780 })
1781}
1782
1783fn parse_xml_space(value: Option<&str>) -> Option<XmlSpace> {
1784 match value {
1785 Some("preserve") => Some(XmlSpace::Preserve),
1786 Some("default") => Some(XmlSpace::Default),
1787 _ => None,
1788 }
1789}
1790
1791fn serialize_tt_element(tt: &TtElement, buf: &mut String, indent: usize) {
1795 let ind = " ".repeat(indent);
1796 buf.push_str(&ind);
1797 buf.push_str("<tt");
1798 buf.push_str(r#" xmlns="http://www.w3.org/ns/ttml""#);
1800 buf.push_str(r#" xmlns:tt="http://www.w3.org/ns/ttml""#);
1801 buf.push_str(r#" xmlns:ttp="http://www.w3.org/ns/ttml#parameter""#);
1802 buf.push_str(r#" xmlns:tts="http://www.w3.org/ns/ttml#styling""#);
1803 buf.push_str(r#" xmlns:ttm="http://www.w3.org/ns/ttml#metadata""#);
1804
1805 if tt_ns_needed(tt, NS_ITTS) {
1807 buf.push_str(r#" xmlns:itts="http://www.w3.org/ns/ttml/profile/imsc1#styling""#);
1808 }
1809 if tt_ns_needed(tt, NS_ITTP) {
1810 buf.push_str(r#" xmlns:ittp="http://www.w3.org/ns/ttml/profile/imsc1#parameter""#);
1811 }
1812 if tt_ns_needed(tt, NS_ITTM) {
1813 buf.push_str(r#" xmlns:ittm="http://www.w3.org/ns/ttml/profile/imsc1#metadata""#);
1814 }
1815 if tt_ns_needed(tt, NS_EBUTTM) {
1816 buf.push_str(r#" xmlns:ebuttm="urn:ebu:tt:metadata""#);
1817 }
1818 if tt_ns_needed(tt, NS_EBUTTS) {
1819 buf.push_str(r#" xmlns:ebutts="urn:ebu:tt:style""#);
1820 }
1821 if tt_ns_needed(tt, NS_SMPTE) {
1822 buf.push_str(r#" xmlns:smpte="http://www.smpte-ra.org/schemas/2052-1/2010/smpte-tt""#);
1823 }
1824 if tt_ns_needed(tt, NS_TTA) {
1825 buf.push_str(r#" xmlns:tta="http://www.w3.org/ns/ttml#audio""#);
1826 }
1827
1828 if let Some(ref lang) = tt.xml_lang {
1830 buf.push_str(&format!(r#" xml:lang="{}""#, xml_escape(lang)));
1831 }
1832
1833 serialize_opt_attr(buf, "ttp:timeBase", &tt.ttp_time_base);
1835 serialize_opt_attr(buf, "ttp:frameRate", &tt.ttp_frame_rate);
1836 serialize_opt_attr(
1837 buf,
1838 "ttp:frameRateMultiplier",
1839 &tt.ttp_frame_rate_multiplier,
1840 );
1841 serialize_opt_attr(buf, "ttp:tickRate", &tt.ttp_tick_rate);
1842 serialize_opt_attr(buf, "ttp:subFrameRate", &tt.ttp_sub_frame_rate);
1843 serialize_opt_attr(buf, "ttp:dropMode", &tt.ttp_drop_mode);
1844 serialize_opt_attr(buf, "ttp:markerMode", &tt.ttp_marker_mode);
1845 serialize_opt_attr(buf, "ttp:clockMode", &tt.ttp_clock_mode);
1846 serialize_opt_attr(buf, "ttp:cellResolution", &tt.ttp_cell_resolution);
1847 serialize_opt_attr(buf, "ttp:pixelAspectRatio", &tt.ttp_pixel_aspect_ratio);
1848 serialize_opt_attr(buf, "ttp:displayAspectRatio", &tt.ttp_display_aspect_ratio);
1849 serialize_opt_attr(buf, "ttp:profile", &tt.ttp_profile);
1850 serialize_opt_attr(buf, "ttp:contentProfiles", &tt.ttp_content_profiles);
1851 serialize_opt_attr(
1852 buf,
1853 "ttp:contentProfileCombination",
1854 &tt.ttp_content_profile_combination,
1855 );
1856 serialize_opt_attr(buf, "ttp:processorProfiles", &tt.ttp_processor_profiles);
1857 serialize_opt_attr(
1858 buf,
1859 "ttp:processorProfileCombination",
1860 &tt.ttp_processor_profile_combination,
1861 );
1862 serialize_opt_attr(
1863 buf,
1864 "ttp:permitFeatureNarrowing",
1865 &tt.ttp_permit_feature_narrowing,
1866 );
1867 serialize_opt_attr(
1868 buf,
1869 "ttp:permitFeatureWidening",
1870 &tt.ttp_permit_feature_widening,
1871 );
1872 serialize_opt_attr(buf, "ttp:validation", &tt.ttp_validation);
1873 serialize_opt_attr(buf, "ttp:validationAction", &tt.ttp_validation_action);
1874
1875 serialize_opt_attr(buf, "tts:extent", &tt.tts_extent);
1877
1878 serialize_opt_attr(buf, "ittp:activeArea", &tt.ittp_active_area);
1880 serialize_opt_attr(buf, "ittp:aspectRatio", &tt.ittp_aspect_ratio);
1881 serialize_opt_attr(
1882 buf,
1883 "ittp:progressivelyDecodable",
1884 &tt.ittp_progressively_decodable,
1885 );
1886
1887 serialize_opt_attr(buf, "xml:id", &tt.xml_id);
1889
1890 buf.push_str(">\n");
1891
1892 if let Some(ref head) = tt.head {
1894 serialize_head_element(head, buf, indent + 1);
1895 }
1896
1897 if let Some(ref body) = tt.body {
1899 serialize_body_element(body, buf, indent + 1);
1900 }
1901
1902 buf.push_str(&format!("{}</tt>\n", ind));
1903}
1904
1905fn tt_ns_needed(tt: &TtElement, ns: &str) -> bool {
1906 for (attr_ns, _) in tt.other_attributes.keys() {
1908 if attr_ns == ns {
1909 return true;
1910 }
1911 }
1912 if ns == NS_ITTP
1914 && (tt.ittp_active_area.is_some()
1915 || tt.ittp_aspect_ratio.is_some()
1916 || tt.ittp_progressively_decodable.is_some())
1917 {
1918 return true;
1919 }
1920 let body_needed = if let Some(ref body) = tt.body {
1922 body_ns_needed(body, ns)
1923 } else {
1924 false
1925 };
1926 let head_needed = if let Some(ref head) = tt.head {
1927 head_ns_needed(head, ns)
1928 } else {
1929 false
1930 };
1931 body_needed || head_needed
1932}
1933
1934fn body_ns_needed(body: &BodyElement, ns: &str) -> bool {
1935 for (an, _) in body.other_attributes.keys() {
1936 if an == ns {
1937 return true;
1938 }
1939 }
1940 if style_ns_needed(&body.style_attributes, ns) {
1941 return true;
1942 }
1943 for div in &body.divs {
1944 if div_ns_needed(div, ns) {
1945 return true;
1946 }
1947 }
1948 for meta in &body.metadata {
1949 if meta_ns_needed(meta, ns) {
1950 return true;
1951 }
1952 }
1953 false
1954}
1955
1956fn head_ns_needed(head: &HeadElement, ns: &str) -> bool {
1957 for meta in &head.metadata {
1958 if meta_ns_needed(meta, ns) {
1959 return true;
1960 }
1961 }
1962 false
1963}
1964
1965fn div_ns_needed(div: &DivElement, ns: &str) -> bool {
1966 if div.smpte_background_image.is_some() && ns == NS_SMPTE {
1967 return true;
1968 }
1969 for (an, _) in div.other_attributes.keys() {
1970 if an == ns {
1971 return true;
1972 }
1973 }
1974 if style_ns_needed(&div.style_attributes, ns) {
1975 return true;
1976 }
1977 for p in &div.paragraphs {
1978 if style_ns_needed(&p.style_attributes, ns) {
1979 return true;
1980 }
1981 for (an, _) in p.other_attributes.keys() {
1982 if an == ns {
1983 return true;
1984 }
1985 }
1986 for item in &p.content {
1987 if inline_ns_needed(item, ns) {
1988 return true;
1989 }
1990 }
1991 }
1992 for meta in &div.metadata {
1993 if meta_ns_needed(meta, ns) {
1994 return true;
1995 }
1996 }
1997 for img in &div.images {
1998 if img.tts_extent.is_some() && ns == NS_TTS {
1999 return true;
2000 }
2001 if style_ns_needed(&img.style_attributes, ns) {
2002 return true;
2003 }
2004 for meta in &img.metadata {
2005 if meta_ns_needed(meta, ns) {
2006 return true;
2007 }
2008 }
2009 }
2010 false
2011}
2012
2013fn inline_ns_needed(item: &InlineContent, ns: &str) -> bool {
2014 match item {
2015 InlineContent::Text(_) => false,
2016 InlineContent::Span(span) => {
2017 if style_ns_needed(&span.style_attributes, ns) {
2018 return true;
2019 }
2020 for (an, _) in span.other_attributes.keys() {
2021 if an == ns {
2022 return true;
2023 }
2024 }
2025 for child in &span.content {
2026 if inline_ns_needed(child, ns) {
2027 return true;
2028 }
2029 }
2030 false
2031 }
2032 InlineContent::Br(_) => false,
2033 }
2034}
2035
2036fn meta_ns_needed(meta: &MetadataChild, ns: &str) -> bool {
2037 match meta {
2038 MetadataChild::Metadata(m) => {
2039 for c in &m.children {
2040 if meta_ns_needed(c, ns) {
2041 return true;
2042 }
2043 }
2044 false
2045 }
2046 MetadataChild::EbuttmDocumentMetadata(eb) => {
2047 if ns == NS_EBUTTM {
2048 return true;
2049 }
2050 for c in &eb.children {
2051 if meta_ns_needed(c, ns) {
2052 return true;
2053 }
2054 }
2055 false
2056 }
2057 MetadataChild::EbuttmConformsToStandard(_) => ns == NS_EBUTTM,
2058 MetadataChild::IttmAltText(_) => ns == NS_ITTM,
2059 _ => false,
2060 }
2061}
2062
2063fn style_ns_needed(attrs: &StyleAttributes, ns: &str) -> bool {
2064 match ns {
2065 NS_TTS => {
2066 attrs.tts_background_color.is_some()
2067 || attrs.tts_color.is_some()
2068 || attrs.tts_extent.is_some()
2069 || attrs.tts_origin.is_some()
2070 || attrs.tts_font_size.is_some()
2071 || attrs.tts_font_family.is_some()
2072 || attrs.tts_font_style.is_some()
2073 || attrs.tts_font_weight.is_some()
2074 || attrs.tts_display_align.is_some()
2075 || attrs.tts_text_align.is_some()
2076 || attrs.tts_text_emphasis.is_some()
2077 || attrs.tts_text_shadow.is_some()
2078 || attrs.tts_ruby.is_some()
2079 || attrs.tts_ruby_align.is_some()
2080 || attrs.tts_ruby_position.is_some()
2081 || attrs.tts_ruby_reserve.is_some()
2082 || attrs.tts_line_height.is_some()
2083 || attrs.tts_opacity.is_some()
2084 || attrs.tts_position.is_some()
2085 || attrs.tts_visibility.is_some()
2086 || attrs.tts_display.is_some()
2087 || attrs.tts_writing_mode.is_some()
2088 || attrs.tts_show_background.is_some()
2089 || attrs.tts_overflow.is_some()
2090 || attrs.tts_z_index.is_some()
2091 || attrs.tts_padding.is_some()
2092 || attrs.tts_luminance_gain.is_some()
2093 || attrs.tts_direction.is_some()
2094 || attrs.tts_unicode_bidi.is_some()
2095 || attrs.tts_wrap_option.is_some()
2096 || attrs.tts_text_combine.is_some()
2097 || attrs.tts_text_decoration.is_some()
2098 || attrs.tts_text_orientation.is_some()
2099 || attrs.tts_text_outline.is_some()
2100 || attrs.tts_shear.is_some()
2101 || attrs.tts_background_clip.is_some()
2102 || attrs.tts_background_extent.is_some()
2103 || attrs.tts_background_image.is_some()
2104 || attrs.tts_background_origin.is_some()
2105 || attrs.tts_background_position.is_some()
2106 || attrs.tts_background_repeat.is_some()
2107 || attrs.tts_border.is_some()
2108 || attrs.tts_bpd.is_some()
2109 || attrs.tts_disparity.is_some()
2110 || attrs.tts_font_kerning.is_some()
2111 || attrs.tts_font_selection_strategy.is_some()
2112 || attrs.tts_font_shear.is_some()
2113 || attrs.tts_font_variant.is_some()
2114 || attrs.tts_ipd.is_some()
2115 || attrs.tts_letter_spacing.is_some()
2116 || attrs.tts_line_shear.is_some()
2117 }
2118 NS_ITTS => attrs.itts_forced_display.is_some() || attrs.itts_fill_line_gap.is_some(),
2119 NS_EBUTTS => attrs.ebutts_line_padding.is_some() || attrs.ebutts_multi_row_align.is_some(),
2120 NS_TTA => {
2121 attrs.tta_gain.is_some()
2122 || attrs.tta_pan.is_some()
2123 || attrs.tta_pitch.is_some()
2124 || attrs.tta_speak.is_some()
2125 }
2126 _ => false,
2127 }
2128}
2129
2130fn serialize_opt_attr(buf: &mut String, name: &str, value: &Option<String>) {
2131 if let Some(v) = value
2132 && !v.is_empty()
2133 {
2134 buf.push_str(&format!(r#" {}="{}""#, name, xml_escape(v)));
2135 }
2136}
2137
2138fn serialize_head_element(head: &HeadElement, buf: &mut String, indent: usize) {
2139 let ind = " ".repeat(indent);
2140 buf.push_str(&format!("{}<head>\n", ind));
2141
2142 for meta in &head.metadata {
2144 serialize_metadata_child(meta, buf, indent + 1);
2145 }
2146
2147 if let Some(ref styling) = head.styling {
2149 serialize_styling_element(styling, buf, indent + 1);
2150 }
2151
2152 if let Some(ref layout) = head.layout {
2154 serialize_layout_element(layout, buf, indent + 1);
2155 }
2156
2157 buf.push_str(&format!("{}</head>\n", ind));
2158}
2159
2160fn serialize_body_element(body: &BodyElement, buf: &mut String, indent: usize) {
2161 let ind = " ".repeat(indent);
2162 buf.push_str(&format!("{}<body", ind));
2163
2164 serialize_common_timing_attrs(
2165 buf,
2166 body.begin.as_deref(),
2167 body.dur.as_deref(),
2168 body.end.as_deref(),
2169 body.time_container.as_deref(),
2170 );
2171 serialize_opt_attr(buf, "region", &body.region);
2172 serialize_opt_attr(buf, "style", &body.style);
2173 serialize_opt_attr(buf, "animate", &body.animate);
2174 serialize_opt_attr(buf, "condition", &body.condition);
2175 serialize_style_attrs(&body.style_attributes, buf);
2176 serialize_opt_attr(buf, "xml:id", &body.xml_id);
2177 serialize_opt_attr(buf, "xml:lang", &body.xml_lang);
2178
2179 if body.divs.is_empty() && body.metadata.is_empty() && body.animations.is_empty() {
2180 buf.push_str("/>\n");
2181 } else {
2182 buf.push_str(">\n");
2183
2184 for meta in &body.metadata {
2185 serialize_metadata_child(meta, buf, indent + 1);
2186 }
2187 for anim in &body.animations {
2188 serialize_animation_child(anim, buf, indent + 1);
2189 }
2190 for div in &body.divs {
2191 serialize_div_element(div, buf, indent + 1);
2192 }
2193
2194 buf.push_str(&format!("{}</body>\n", ind));
2195 }
2196}
2197
2198fn serialize_div_element(div: &DivElement, buf: &mut String, indent: usize) {
2199 let ind = " ".repeat(indent);
2200 buf.push_str(&format!("{}<div", ind));
2201
2202 serialize_common_timing_attrs(
2203 buf,
2204 div.begin.as_deref(),
2205 div.dur.as_deref(),
2206 div.end.as_deref(),
2207 div.time_container.as_deref(),
2208 );
2209 serialize_opt_attr(buf, "region", &div.region);
2210 serialize_opt_attr(buf, "style", &div.style);
2211 serialize_opt_attr(buf, "animate", &div.animate);
2212 serialize_opt_attr(buf, "condition", &div.condition);
2213 serialize_style_attrs(&div.style_attributes, buf);
2214 serialize_opt_attr(buf, "smpte:backgroundImage", &div.smpte_background_image);
2215 serialize_opt_attr(buf, "xml:id", &div.xml_id);
2216 serialize_opt_attr(buf, "xml:lang", &div.xml_lang);
2217
2218 let has_children = !div.paragraphs.is_empty()
2219 || !div.images.is_empty()
2220 || !div.metadata.is_empty()
2221 || !div.animations.is_empty();
2222 if !has_children {
2223 buf.push_str("/>\n");
2224 } else {
2225 buf.push_str(">\n");
2226
2227 for meta in &div.metadata {
2228 serialize_metadata_child(meta, buf, indent + 1);
2229 }
2230 for anim in &div.animations {
2231 serialize_animation_child(anim, buf, indent + 1);
2232 }
2233 for p in &div.paragraphs {
2234 serialize_p_element(p, buf, indent + 1);
2235 }
2236 for img in &div.images {
2237 serialize_image_element(img, buf, indent + 1);
2238 }
2239
2240 buf.push_str(&format!("{}</div>\n", ind));
2241 }
2242}
2243
2244fn serialize_p_element(p: &PElement, buf: &mut String, indent: usize) {
2245 let ind = " ".repeat(indent);
2246 buf.push_str(&format!("{}<p", ind));
2247
2248 serialize_common_timing_attrs(
2249 buf,
2250 p.begin.as_deref(),
2251 p.dur.as_deref(),
2252 p.end.as_deref(),
2253 p.time_container.as_deref(),
2254 );
2255 serialize_opt_attr(buf, "region", &p.region);
2256 serialize_opt_attr(buf, "style", &p.style);
2257 serialize_opt_attr(buf, "animate", &p.animate);
2258 serialize_opt_attr(buf, "condition", &p.condition);
2259 serialize_style_attrs(&p.style_attributes, buf);
2260 serialize_opt_attr(buf, "xml:id", &p.xml_id);
2261 serialize_opt_attr(buf, "xml:lang", &p.xml_lang);
2262
2263 let has_children = !p.content.is_empty() || !p.metadata.is_empty() || !p.animations.is_empty();
2264 if !has_children {
2265 buf.push_str("/>\n");
2266 } else {
2267 buf.push('>');
2268 for meta in &p.metadata {
2269 buf.push('\n');
2270 serialize_metadata_child(meta, buf, indent + 1);
2271 }
2272 for anim in &p.animations {
2273 buf.push('\n');
2274 serialize_animation_child(anim, buf, indent + 1);
2275 }
2276 for item in &p.content {
2277 serialize_inline_content(item, buf);
2278 }
2279 buf.push_str("</p>\n");
2280 }
2281}
2282
2283fn serialize_inline_content(content: &InlineContent, buf: &mut String) {
2284 match content {
2285 InlineContent::Text(text) => {
2286 buf.push_str(&xml_escape(text));
2287 }
2288 InlineContent::Span(span) => {
2289 buf.push_str("<span");
2290 serialize_common_timing_attrs(
2291 buf,
2292 span.begin.as_deref(),
2293 span.dur.as_deref(),
2294 span.end.as_deref(),
2295 span.time_container.as_deref(),
2296 );
2297 serialize_opt_attr(buf, "region", &span.region);
2298 serialize_opt_attr(buf, "style", &span.style);
2299 serialize_opt_attr(buf, "animate", &span.animate);
2300 serialize_style_attrs(&span.style_attributes, buf);
2301 serialize_opt_attr(buf, "xml:id", &span.xml_id);
2302 serialize_opt_attr(buf, "xml:lang", &span.xml_lang);
2303
2304 if span.content.is_empty() {
2305 buf.push_str("/>");
2306 } else {
2307 buf.push('>');
2308 for item in &span.content {
2309 serialize_inline_content(item, buf);
2310 }
2311 buf.push_str("</span>");
2312 }
2313 }
2314 InlineContent::Br(_br) => {
2315 buf.push_str("<br/>");
2316 }
2317 }
2318}
2319
2320fn serialize_image_element(image: &ImageElement, buf: &mut String, indent: usize) {
2321 let ind = " ".repeat(indent);
2322 buf.push_str(&format!("{}<image", ind));
2323
2324 serialize_common_timing_attrs(
2325 buf,
2326 image.begin.as_deref(),
2327 image.dur.as_deref(),
2328 image.end.as_deref(),
2329 image.time_container.as_deref(),
2330 );
2331 serialize_opt_attr(buf, "region", &image.region);
2332 serialize_opt_attr(buf, "style", &image.style);
2333 serialize_opt_attr(buf, "animate", &image.animate);
2334 serialize_opt_attr(buf, "condition", &image.condition);
2335 serialize_opt_attr(buf, "src", &image.src);
2336 serialize_opt_attr(buf, "type", &image.type_);
2337 serialize_opt_attr(buf, "tts:extent", &image.tts_extent);
2339 serialize_style_attrs_skip_extent(&image.style_attributes, buf);
2341 serialize_opt_attr(buf, "xml:id", &image.xml_id);
2342 serialize_opt_attr(buf, "xml:lang", &image.xml_lang);
2343
2344 if image.metadata.is_empty() {
2345 buf.push_str("/>\n");
2346 } else {
2347 buf.push_str(">\n");
2348 for meta in &image.metadata {
2349 serialize_metadata_child(meta, buf, indent + 1);
2350 }
2351 buf.push_str(&format!("{}</image>\n", ind));
2352 }
2353}
2354
2355fn serialize_metadata_child(child: &MetadataChild, buf: &mut String, indent: usize) {
2356 let ind = " ".repeat(indent);
2357 match child {
2358 MetadataChild::Metadata(m) => {
2359 buf.push_str(&format!("{}<metadata>\n", ind));
2360 for c in &m.children {
2361 serialize_metadata_child(c, buf, indent + 1);
2362 }
2363 buf.push_str(&format!("{}</metadata>\n", ind));
2364 }
2365 MetadataChild::TtmTitle(t) => {
2366 buf.push_str(&format!(
2367 "{}<ttm:title>{}</ttm:title>\n",
2368 ind,
2369 xml_escape(&t.text)
2370 ));
2371 }
2372 MetadataChild::TtmDesc(t) => {
2373 buf.push_str(&format!(
2374 "{}<ttm:desc>{}</ttm:desc>\n",
2375 ind,
2376 xml_escape(&t.text)
2377 ));
2378 }
2379 MetadataChild::TtmCopyright(t) => {
2380 buf.push_str(&format!(
2381 "{}<ttm:copyright>{}</ttm:copyright>\n",
2382 ind,
2383 xml_escape(&t.text)
2384 ));
2385 }
2386 MetadataChild::TtmAgent(a) => {
2387 buf.push_str(&format!("{}<ttm:agent", ind));
2388 serialize_opt_attr(buf, "type", &a.type_);
2389 buf.push_str(">\n");
2390 for name in &a.names {
2391 buf.push_str(&format!(
2392 "{}<ttm:name>{}</ttm:name>\n",
2393 " ".repeat(indent + 1),
2394 xml_escape(&name.text)
2395 ));
2396 }
2397 buf.push_str(&format!("{}</ttm:agent>\n", ind));
2398 }
2399 MetadataChild::TtmItem(item) => {
2400 serialize_ttm_item(item, buf, indent);
2401 }
2402 MetadataChild::TtmName(n) => {
2403 buf.push_str(&format!(
2404 "{}<ttm:name>{}</ttm:name>\n",
2405 ind,
2406 xml_escape(&n.text)
2407 ));
2408 }
2409 MetadataChild::EbuttmDocumentMetadata(eb) => {
2410 buf.push_str(&format!("{}<ebuttm:documentMetadata>\n", ind));
2411 for c in &eb.children {
2412 serialize_metadata_child(c, buf, indent + 1);
2413 }
2414 buf.push_str(&format!("{}</ebuttm:documentMetadata>\n", ind));
2415 }
2416 MetadataChild::EbuttmConformsToStandard(cs) => {
2417 buf.push_str(&format!(
2418 "{}<ebuttm:conformsToStandard>{}</ebuttm:conformsToStandard>\n",
2419 ind,
2420 xml_escape(&cs.text)
2421 ));
2422 }
2423 MetadataChild::IttmAltText(alt) => {
2424 buf.push_str(&format!(
2425 "{}<ittm:altText>{}</ittm:altText>\n",
2426 ind,
2427 xml_escape(&alt.text)
2428 ));
2429 }
2430 }
2431}
2432
2433fn serialize_ttm_item(item: &TtmItemElement, buf: &mut String, indent: usize) {
2434 let ind = " ".repeat(indent);
2435 buf.push_str(&format!("{}<ttm:item", ind));
2436 serialize_opt_attr(buf, "name", &item.name);
2437 if item.items.is_empty() && item.text.is_none() {
2438 buf.push_str("/>\n");
2439 } else if item.items.is_empty() {
2440 buf.push('>');
2441 if let Some(ref t) = item.text {
2442 buf.push_str(&xml_escape(t));
2443 }
2444 buf.push_str("</ttm:item>\n");
2445 } else {
2446 buf.push_str(">\n");
2447 for i in &item.items {
2448 serialize_ttm_item(i, buf, indent + 1);
2449 }
2450 buf.push_str(&format!("{}</ttm:item>\n", ind));
2451 }
2452}
2453
2454fn serialize_animation_child(anim: &AnimationChild, buf: &mut String, indent: usize) {
2455 let ind = " ".repeat(indent);
2456 match anim {
2457 AnimationChild::Set(set) => {
2458 buf.push_str(&format!("{}<set", ind));
2459 serialize_common_timing_attrs(
2460 buf,
2461 set.begin.as_deref(),
2462 set.dur.as_deref(),
2463 set.end.as_deref(),
2464 None,
2465 );
2466 serialize_opt_attr(buf, "fill", &set.fill);
2467 serialize_opt_attr(buf, "repeatCount", &set.repeat_count);
2468 serialize_style_attrs(&set.style_attributes, buf);
2469 buf.push_str("/>\n");
2470 }
2471 }
2472}
2473
2474fn serialize_styling_element(styling: &StylingElement, buf: &mut String, indent: usize) {
2475 let ind = " ".repeat(indent);
2476 buf.push_str(&format!("{}<styling>\n", ind));
2477 for init in &styling.initials {
2478 buf.push_str(&format!("{}<initial", " ".repeat(indent + 1)));
2479 serialize_style_attrs(&init.style_attributes, buf);
2480 buf.push_str("/>\n");
2481 }
2482 for style in &styling.styles {
2483 buf.push_str(&format!("{}<style", " ".repeat(indent + 1)));
2484 serialize_opt_attr(buf, "xml:id", &style.xml_id);
2485 serialize_opt_attr(buf, "style", &style.style);
2486 serialize_style_attrs(&style.style_attributes, buf);
2487 buf.push_str("/>\n");
2488 }
2489 buf.push_str(&format!("{}</styling>\n", ind));
2490}
2491
2492fn serialize_layout_element(layout: &LayoutElement, buf: &mut String, indent: usize) {
2493 let ind = " ".repeat(indent);
2494 buf.push_str(&format!("{}<layout>\n", ind));
2495 for region in &layout.regions {
2496 serialize_region_element(region, buf, indent + 1);
2497 }
2498 buf.push_str(&format!("{}</layout>\n", ind));
2499}
2500
2501fn serialize_region_element(region: &RegionElement, buf: &mut String, indent: usize) {
2502 let ind = " ".repeat(indent);
2503 buf.push_str(&format!("{}<region", ind));
2504
2505 serialize_common_timing_attrs(
2506 buf,
2507 region.begin.as_deref(),
2508 region.dur.as_deref(),
2509 region.end.as_deref(),
2510 region.time_container.as_deref(),
2511 );
2512 serialize_opt_attr(buf, "style", ®ion.style);
2513 serialize_opt_attr(buf, "animate", ®ion.animate);
2514 serialize_opt_attr(buf, "condition", ®ion.condition);
2515 serialize_opt_attr(buf, "ttm:role", ®ion.ttm_role);
2516 serialize_style_attrs(®ion.style_attributes, buf);
2517 serialize_opt_attr(buf, "xml:id", ®ion.xml_id);
2518 serialize_opt_attr(buf, "xml:lang", ®ion.xml_lang);
2519
2520 buf.push_str("/>\n");
2521}
2522
2523fn serialize_common_timing_attrs(
2524 buf: &mut String,
2525 begin: Option<&str>,
2526 dur: Option<&str>,
2527 end: Option<&str>,
2528 time_container: Option<&str>,
2529) {
2530 if let Some(b) = begin {
2531 buf.push_str(&format!(r#" begin="{}""#, xml_escape(b)));
2532 }
2533 if let Some(d) = dur {
2534 buf.push_str(&format!(r#" dur="{}""#, xml_escape(d)));
2535 }
2536 if let Some(e) = end {
2537 buf.push_str(&format!(r#" end="{}""#, xml_escape(e)));
2538 }
2539 if let Some(tc) = time_container {
2540 buf.push_str(&format!(r#" timeContainer="{}""#, xml_escape(tc)));
2541 }
2542}
2543
2544fn serialize_style_attrs_skip_extent(attrs: &StyleAttributes, buf: &mut String) {
2547 serialize_opt_attr(buf, "tts:backgroundColor", &attrs.tts_background_color);
2548 serialize_opt_attr(buf, "tts:backgroundClip", &attrs.tts_background_clip);
2549 serialize_opt_attr(buf, "tts:backgroundExtent", &attrs.tts_background_extent);
2550 serialize_opt_attr(buf, "tts:backgroundImage", &attrs.tts_background_image);
2551 serialize_opt_attr(buf, "tts:backgroundOrigin", &attrs.tts_background_origin);
2552 serialize_opt_attr(
2553 buf,
2554 "tts:backgroundPosition",
2555 &attrs.tts_background_position,
2556 );
2557 serialize_opt_attr(buf, "tts:backgroundRepeat", &attrs.tts_background_repeat);
2558 serialize_opt_attr(buf, "tts:border", &attrs.tts_border);
2559 serialize_opt_attr(buf, "tts:bpd", &attrs.tts_bpd);
2560 serialize_opt_attr(buf, "tts:color", &attrs.tts_color);
2561 serialize_opt_attr(buf, "tts:direction", &attrs.tts_direction);
2562 serialize_opt_attr(buf, "tts:disparity", &attrs.tts_disparity);
2563 serialize_opt_attr(buf, "tts:display", &attrs.tts_display);
2564 serialize_opt_attr(buf, "tts:displayAlign", &attrs.tts_display_align);
2565 serialize_opt_attr(buf, "tts:fontFamily", &attrs.tts_font_family);
2566 serialize_opt_attr(buf, "tts:fontKerning", &attrs.tts_font_kerning);
2567 serialize_opt_attr(
2568 buf,
2569 "tts:fontSelectionStrategy",
2570 &attrs.tts_font_selection_strategy,
2571 );
2572 serialize_opt_attr(buf, "tts:fontShear", &attrs.tts_font_shear);
2573 serialize_opt_attr(buf, "tts:fontSize", &attrs.tts_font_size);
2574 serialize_opt_attr(buf, "tts:fontStyle", &attrs.tts_font_style);
2575 serialize_opt_attr(buf, "tts:fontVariant", &attrs.tts_font_variant);
2576 serialize_opt_attr(buf, "tts:fontWeight", &attrs.tts_font_weight);
2577 serialize_opt_attr(buf, "tts:ipd", &attrs.tts_ipd);
2578 serialize_opt_attr(buf, "tts:letterSpacing", &attrs.tts_letter_spacing);
2579 serialize_opt_attr(buf, "tts:lineHeight", &attrs.tts_line_height);
2580 serialize_opt_attr(buf, "tts:lineShear", &attrs.tts_line_shear);
2581 serialize_opt_attr(buf, "tts:luminanceGain", &attrs.tts_luminance_gain);
2582 serialize_opt_attr(buf, "tts:opacity", &attrs.tts_opacity);
2583 serialize_opt_attr(buf, "tts:origin", &attrs.tts_origin);
2584 serialize_opt_attr(buf, "tts:overflow", &attrs.tts_overflow);
2585 serialize_opt_attr(buf, "tts:padding", &attrs.tts_padding);
2586 serialize_opt_attr(buf, "tts:position", &attrs.tts_position);
2587 serialize_opt_attr(buf, "tts:ruby", &attrs.tts_ruby);
2588 serialize_opt_attr(buf, "tts:rubyAlign", &attrs.tts_ruby_align);
2589 serialize_opt_attr(buf, "tts:rubyPosition", &attrs.tts_ruby_position);
2590 serialize_opt_attr(buf, "tts:rubyReserve", &attrs.tts_ruby_reserve);
2591 serialize_opt_attr(buf, "tts:shear", &attrs.tts_shear);
2592 serialize_opt_attr(buf, "tts:showBackground", &attrs.tts_show_background);
2593 serialize_opt_attr(buf, "tts:textAlign", &attrs.tts_text_align);
2594 serialize_opt_attr(buf, "tts:textCombine", &attrs.tts_text_combine);
2595 serialize_opt_attr(buf, "tts:textDecoration", &attrs.tts_text_decoration);
2596 serialize_opt_attr(buf, "tts:textEmphasis", &attrs.tts_text_emphasis);
2597 serialize_opt_attr(buf, "tts:textOrientation", &attrs.tts_text_orientation);
2598 serialize_opt_attr(buf, "tts:textOutline", &attrs.tts_text_outline);
2599 serialize_opt_attr(buf, "tts:textShadow", &attrs.tts_text_shadow);
2600 serialize_opt_attr(buf, "tts:unicodeBidi", &attrs.tts_unicode_bidi);
2601 serialize_opt_attr(buf, "tts:visibility", &attrs.tts_visibility);
2602 serialize_opt_attr(buf, "tts:wrapOption", &attrs.tts_wrap_option);
2603 serialize_opt_attr(buf, "tts:writingMode", &attrs.tts_writing_mode);
2604 serialize_opt_attr(buf, "tts:zIndex", &attrs.tts_z_index);
2605 serialize_opt_attr(buf, "tta:gain", &attrs.tta_gain);
2606 serialize_opt_attr(buf, "tta:pan", &attrs.tta_pan);
2607 serialize_opt_attr(buf, "tta:pitch", &attrs.tta_pitch);
2608 serialize_opt_attr(buf, "tta:speak", &attrs.tta_speak);
2609 serialize_opt_attr(buf, "itts:forcedDisplay", &attrs.itts_forced_display);
2610 serialize_opt_attr(buf, "itts:fillLineGap", &attrs.itts_fill_line_gap);
2611 serialize_opt_attr(buf, "ebutts:linePadding", &attrs.ebutts_line_padding);
2612 serialize_opt_attr(buf, "ebutts:multiRowAlign", &attrs.ebutts_multi_row_align);
2613}
2614
2615#[allow(clippy::too_many_lines)]
2616fn serialize_style_attrs(attrs: &StyleAttributes, buf: &mut String) {
2617 serialize_opt_attr(buf, "tts:backgroundColor", &attrs.tts_background_color);
2618 serialize_opt_attr(buf, "tts:backgroundClip", &attrs.tts_background_clip);
2619 serialize_opt_attr(buf, "tts:backgroundExtent", &attrs.tts_background_extent);
2620 serialize_opt_attr(buf, "tts:backgroundImage", &attrs.tts_background_image);
2621 serialize_opt_attr(buf, "tts:backgroundOrigin", &attrs.tts_background_origin);
2622 serialize_opt_attr(
2623 buf,
2624 "tts:backgroundPosition",
2625 &attrs.tts_background_position,
2626 );
2627 serialize_opt_attr(buf, "tts:backgroundRepeat", &attrs.tts_background_repeat);
2628 serialize_opt_attr(buf, "tts:border", &attrs.tts_border);
2629 serialize_opt_attr(buf, "tts:bpd", &attrs.tts_bpd);
2630 serialize_opt_attr(buf, "tts:color", &attrs.tts_color);
2631 serialize_opt_attr(buf, "tts:direction", &attrs.tts_direction);
2632 serialize_opt_attr(buf, "tts:disparity", &attrs.tts_disparity);
2633 serialize_opt_attr(buf, "tts:display", &attrs.tts_display);
2634 serialize_opt_attr(buf, "tts:displayAlign", &attrs.tts_display_align);
2635 serialize_opt_attr(buf, "tts:extent", &attrs.tts_extent);
2636 serialize_opt_attr(buf, "tts:fontFamily", &attrs.tts_font_family);
2637 serialize_opt_attr(buf, "tts:fontKerning", &attrs.tts_font_kerning);
2638 serialize_opt_attr(
2639 buf,
2640 "tts:fontSelectionStrategy",
2641 &attrs.tts_font_selection_strategy,
2642 );
2643 serialize_opt_attr(buf, "tts:fontShear", &attrs.tts_font_shear);
2644 serialize_opt_attr(buf, "tts:fontSize", &attrs.tts_font_size);
2645 serialize_opt_attr(buf, "tts:fontStyle", &attrs.tts_font_style);
2646 serialize_opt_attr(buf, "tts:fontVariant", &attrs.tts_font_variant);
2647 serialize_opt_attr(buf, "tts:fontWeight", &attrs.tts_font_weight);
2648 serialize_opt_attr(buf, "tts:ipd", &attrs.tts_ipd);
2649 serialize_opt_attr(buf, "tts:letterSpacing", &attrs.tts_letter_spacing);
2650 serialize_opt_attr(buf, "tts:lineHeight", &attrs.tts_line_height);
2651 serialize_opt_attr(buf, "tts:lineShear", &attrs.tts_line_shear);
2652 serialize_opt_attr(buf, "tts:luminanceGain", &attrs.tts_luminance_gain);
2653 serialize_opt_attr(buf, "tts:opacity", &attrs.tts_opacity);
2654 serialize_opt_attr(buf, "tts:origin", &attrs.tts_origin);
2655 serialize_opt_attr(buf, "tts:overflow", &attrs.tts_overflow);
2656 serialize_opt_attr(buf, "tts:padding", &attrs.tts_padding);
2657 serialize_opt_attr(buf, "tts:position", &attrs.tts_position);
2658 serialize_opt_attr(buf, "tts:ruby", &attrs.tts_ruby);
2659 serialize_opt_attr(buf, "tts:rubyAlign", &attrs.tts_ruby_align);
2660 serialize_opt_attr(buf, "tts:rubyPosition", &attrs.tts_ruby_position);
2661 serialize_opt_attr(buf, "tts:rubyReserve", &attrs.tts_ruby_reserve);
2662 serialize_opt_attr(buf, "tts:shear", &attrs.tts_shear);
2663 serialize_opt_attr(buf, "tts:showBackground", &attrs.tts_show_background);
2664 serialize_opt_attr(buf, "tts:textAlign", &attrs.tts_text_align);
2665 serialize_opt_attr(buf, "tts:textCombine", &attrs.tts_text_combine);
2666 serialize_opt_attr(buf, "tts:textDecoration", &attrs.tts_text_decoration);
2667 serialize_opt_attr(buf, "tts:textEmphasis", &attrs.tts_text_emphasis);
2668 serialize_opt_attr(buf, "tts:textOrientation", &attrs.tts_text_orientation);
2669 serialize_opt_attr(buf, "tts:textOutline", &attrs.tts_text_outline);
2670 serialize_opt_attr(buf, "tts:textShadow", &attrs.tts_text_shadow);
2671 serialize_opt_attr(buf, "tts:unicodeBidi", &attrs.tts_unicode_bidi);
2672 serialize_opt_attr(buf, "tts:visibility", &attrs.tts_visibility);
2673 serialize_opt_attr(buf, "tts:wrapOption", &attrs.tts_wrap_option);
2674 serialize_opt_attr(buf, "tts:writingMode", &attrs.tts_writing_mode);
2675 serialize_opt_attr(buf, "tts:zIndex", &attrs.tts_z_index);
2676 serialize_opt_attr(buf, "tta:gain", &attrs.tta_gain);
2677 serialize_opt_attr(buf, "tta:pan", &attrs.tta_pan);
2678 serialize_opt_attr(buf, "tta:pitch", &attrs.tta_pitch);
2679 serialize_opt_attr(buf, "tta:speak", &attrs.tta_speak);
2680 serialize_opt_attr(buf, "itts:forcedDisplay", &attrs.itts_forced_display);
2681 serialize_opt_attr(buf, "itts:fillLineGap", &attrs.itts_fill_line_gap);
2682 serialize_opt_attr(buf, "ebutts:linePadding", &attrs.ebutts_line_padding);
2683 serialize_opt_attr(buf, "ebutts:multiRowAlign", &attrs.ebutts_multi_row_align);
2684}
2685
2686fn xml_escape(s: &str) -> String {
2688 s.replace('&', "&")
2689 .replace('<', "<")
2690 .replace('>', ">")
2691 .replace('"', """)
2692 .replace('\'', "'")
2693}
2694
2695#[allow(clippy::too_many_lines)]
2699fn parse_style_attributes(node: roxmltree::Node<'_, '_>) -> StyleAttributes {
2700 StyleAttributes {
2701 tts_background_color: attribute_value(&node, NS_TTS, "backgroundColor")
2702 .map(|s| s.to_string()),
2703 tts_background_clip: attribute_value(&node, NS_TTS, "backgroundClip")
2704 .map(|s| s.to_string()),
2705 tts_background_extent: attribute_value(&node, NS_TTS, "backgroundExtent")
2706 .map(|s| s.to_string()),
2707 tts_background_image: attribute_value(&node, NS_TTS, "backgroundImage")
2708 .map(|s| s.to_string()),
2709 tts_background_origin: attribute_value(&node, NS_TTS, "backgroundOrigin")
2710 .map(|s| s.to_string()),
2711 tts_background_position: attribute_value(&node, NS_TTS, "backgroundPosition")
2712 .map(|s| s.to_string()),
2713 tts_background_repeat: attribute_value(&node, NS_TTS, "backgroundRepeat")
2714 .map(|s| s.to_string()),
2715 tts_border: attribute_value(&node, NS_TTS, "border").map(|s| s.to_string()),
2716 tts_bpd: attribute_value(&node, NS_TTS, "bpd").map(|s| s.to_string()),
2717 tts_color: attribute_value(&node, NS_TTS, "color").map(|s| s.to_string()),
2718 tts_direction: attribute_value(&node, NS_TTS, "direction").map(|s| s.to_string()),
2719 tts_disparity: attribute_value(&node, NS_TTS, "disparity").map(|s| s.to_string()),
2720 tts_display: attribute_value(&node, NS_TTS, "display").map(|s| s.to_string()),
2721 tts_display_align: attribute_value(&node, NS_TTS, "displayAlign").map(|s| s.to_string()),
2722 tts_extent: attribute_value(&node, NS_TTS, "extent").map(|s| s.to_string()),
2723 tts_font_family: attribute_value(&node, NS_TTS, "fontFamily").map(|s| s.to_string()),
2724 tts_font_kerning: attribute_value(&node, NS_TTS, "fontKerning").map(|s| s.to_string()),
2725 tts_font_selection_strategy: attribute_value(&node, NS_TTS, "fontSelectionStrategy")
2726 .map(|s| s.to_string()),
2727 tts_font_shear: attribute_value(&node, NS_TTS, "fontShear").map(|s| s.to_string()),
2728 tts_font_size: attribute_value(&node, NS_TTS, "fontSize").map(|s| s.to_string()),
2729 tts_font_style: attribute_value(&node, NS_TTS, "fontStyle").map(|s| s.to_string()),
2730 tts_font_variant: attribute_value(&node, NS_TTS, "fontVariant").map(|s| s.to_string()),
2731 tts_font_weight: attribute_value(&node, NS_TTS, "fontWeight").map(|s| s.to_string()),
2732 tts_ipd: attribute_value(&node, NS_TTS, "ipd").map(|s| s.to_string()),
2733 tts_letter_spacing: attribute_value(&node, NS_TTS, "letterSpacing").map(|s| s.to_string()),
2734 tts_line_height: attribute_value(&node, NS_TTS, "lineHeight").map(|s| s.to_string()),
2735 tts_line_shear: attribute_value(&node, NS_TTS, "lineShear").map(|s| s.to_string()),
2736 tts_luminance_gain: attribute_value(&node, NS_TTS, "luminanceGain").map(|s| s.to_string()),
2737 tts_opacity: attribute_value(&node, NS_TTS, "opacity").map(|s| s.to_string()),
2738 tts_origin: attribute_value(&node, NS_TTS, "origin").map(|s| s.to_string()),
2739 tts_overflow: attribute_value(&node, NS_TTS, "overflow").map(|s| s.to_string()),
2740 tts_padding: attribute_value(&node, NS_TTS, "padding").map(|s| s.to_string()),
2741 tts_position: attribute_value(&node, NS_TTS, "position").map(|s| s.to_string()),
2742 tts_ruby: attribute_value(&node, NS_TTS, "ruby").map(|s| s.to_string()),
2743 tts_ruby_align: attribute_value(&node, NS_TTS, "rubyAlign").map(|s| s.to_string()),
2744 tts_ruby_position: attribute_value(&node, NS_TTS, "rubyPosition").map(|s| s.to_string()),
2745 tts_ruby_reserve: attribute_value(&node, NS_TTS, "rubyReserve").map(|s| s.to_string()),
2746 tts_shear: attribute_value(&node, NS_TTS, "shear").map(|s| s.to_string()),
2747 tts_show_background: attribute_value(&node, NS_TTS, "showBackground")
2748 .map(|s| s.to_string()),
2749 tts_text_align: attribute_value(&node, NS_TTS, "textAlign").map(|s| s.to_string()),
2750 tts_text_combine: attribute_value(&node, NS_TTS, "textCombine").map(|s| s.to_string()),
2751 tts_text_decoration: attribute_value(&node, NS_TTS, "textDecoration")
2752 .map(|s| s.to_string()),
2753 tts_text_emphasis: attribute_value(&node, NS_TTS, "textEmphasis").map(|s| s.to_string()),
2754 tts_text_orientation: attribute_value(&node, NS_TTS, "textOrientation")
2755 .map(|s| s.to_string()),
2756 tts_text_outline: attribute_value(&node, NS_TTS, "textOutline").map(|s| s.to_string()),
2757 tts_text_shadow: attribute_value(&node, NS_TTS, "textShadow").map(|s| s.to_string()),
2758 tts_unicode_bidi: attribute_value(&node, NS_TTS, "unicodeBidi").map(|s| s.to_string()),
2759 tts_visibility: attribute_value(&node, NS_TTS, "visibility").map(|s| s.to_string()),
2760 tts_wrap_option: attribute_value(&node, NS_TTS, "wrapOption").map(|s| s.to_string()),
2761 tts_writing_mode: attribute_value(&node, NS_TTS, "writingMode").map(|s| s.to_string()),
2762 tts_z_index: attribute_value(&node, NS_TTS, "zIndex").map(|s| s.to_string()),
2763 tta_gain: attribute_value(&node, NS_TTA, "gain").map(|s| s.to_string()),
2764 tta_pan: attribute_value(&node, NS_TTA, "pan").map(|s| s.to_string()),
2765 tta_pitch: attribute_value(&node, NS_TTA, "pitch").map(|s| s.to_string()),
2766 tta_speak: attribute_value(&node, NS_TTA, "speak").map(|s| s.to_string()),
2767 itts_forced_display: attribute_value(&node, NS_ITTS, "forcedDisplay")
2768 .map(|s| s.to_string()),
2769 itts_fill_line_gap: attribute_value(&node, NS_ITTS, "fillLineGap").map(|s| s.to_string()),
2770 ebutts_line_padding: attribute_value(&node, NS_EBUTTS, "linePadding")
2771 .map(|s| s.to_string()),
2772 ebutts_multi_row_align: attribute_value(&node, NS_EBUTTS, "multiRowAlign")
2773 .map(|s| s.to_string()),
2774 }
2775}