1use crate::{
2 HasSpan, Parser, Span,
3 attributes::{ElementAttribute, element_attribute::ParseShorthand},
4 content::{Content, SubstitutionStep},
5 internal::{debug::DebugSliceReference, opaque_iter::opaque_slice_iter},
6 span::MatchedItem,
7 strings::CowStr,
8 warnings::{MatchAndWarnings, Warning, WarningType},
9};
10
11opaque_slice_iter! {
12 pub struct ElementAttributes<'a> yielding ElementAttribute<'a>;
15}
16
17#[derive(Clone, Eq, PartialEq, Default)]
25pub struct Attrlist<'src> {
26 attributes: Vec<ElementAttribute<'src>>,
27 anchor: Option<CowStr<'src>>,
28 source: Span<'src>,
29}
30
31impl<'src> Attrlist<'src> {
32 pub(crate) fn parse(
37 source: Span<'src>,
38 parser: &Parser,
39 attrlist_context: AttrlistContext,
40 ) -> MatchAndWarnings<'src, MatchedItem<'src, Self>> {
41 let mut attributes: Vec<ElementAttribute> = vec![];
42 let mut parse_shorthand_items = true;
43 let mut warnings: Vec<Warning<'src>> = vec![];
44
45 let source_cow = if source.contains('{') && source.contains('}') {
47 let mut content = Content::from(source);
48 SubstitutionStep::AttributeReferences.apply(&mut content, parser, None);
49 CowStr::from(content.rendered.to_string())
50 } else {
51 CowStr::from(source.data())
52 };
53
54 if source_cow.starts_with('[') && source_cow.ends_with(']') {
55 let anchor = source_cow[1..source_cow.len() - 1].to_owned();
56
57 return MatchAndWarnings {
58 item: MatchedItem {
59 item: Self {
60 attributes,
61 anchor: Some(CowStr::from(anchor)),
62 source,
63 },
64 after: source.discard_all(),
65 },
66 warnings,
67 };
68 }
69
70 let mut index = 0;
71
72 let mut entry_number = 0usize;
77
78 let after_index = loop {
79 entry_number += 1;
80
81 let (mut attr, new_index, warning_types) = ElementAttribute::parse(
82 &source_cow,
83 index,
84 parser,
85 ParseShorthand(parse_shorthand_items),
86 attrlist_context,
87 );
88
89 for warning_type in warning_types {
94 warnings.push(Warning {
95 source,
96 warning: warning_type,
97 origin: None,
98 });
99 }
100
101 parse_shorthand_items = false;
108
109 let mut after = Span::new(source_cow.as_ref()).discard(new_index);
110
111 if attr.name().is_none()
117 && attr.value().is_empty()
118 && !attr.value_is_quoted()
119 && after.is_empty()
120 && attributes.is_empty()
121 {
122 break index;
123 }
124
125 if attr.name().is_some() {
126 if attr.value() != "None" {
130 attributes.push(attr);
131 }
132 } else if !attr.value().is_empty() || attr.value_is_quoted() {
133 attr.set_positional_index(entry_number);
137 attributes.push(attr);
138 }
139
140 after = after.take_whitespace().after;
145
146 match after.take_prefix(",") {
147 Some(comma) => {
148 after = comma.after.take_whitespace().after;
149
150 if after.starts_with(',') {
151 warnings.push(Warning {
152 source,
153 warning: WarningType::EmptyAttributeValue,
154 origin: None,
155 });
156
157 entry_number += 1;
160 after = after.discard(1);
161 index = after.byte_offset();
162 continue;
163 }
164
165 index = after.byte_offset();
166 }
167 None => {
168 break after.byte_offset();
169 }
170 }
171 };
172
173 if after_index < source_cow.len() {
174 warnings.push(Warning {
175 source,
176 warning: WarningType::MissingCommaAfterQuotedAttributeValue,
177 origin: None,
178 });
179 }
180
181 MatchAndWarnings {
182 item: MatchedItem {
183 item: Self {
184 attributes,
185 anchor: None,
186 source,
187 },
188 after: source.discard_all(),
189 },
190 warnings,
191 }
192 }
193
194 pub(crate) fn source_with_language(language: Span<'src>) -> Self {
209 Self {
210 attributes: vec![
211 ElementAttribute::synthesized_source_style(),
212 ElementAttribute::positional_from_span(language),
213 ],
214 anchor: None,
215 source: language,
216 }
217 }
218
219 pub(crate) fn merge_block_attribute_line(&mut self, later: Attrlist<'src>) {
248 let fold_roles =
253 self.named_attribute("role").is_some() || later.named_attribute("role").is_some();
254
255 let resolved_roles: Option<Vec<String>> = if fold_roles {
256 let current: Vec<String> = self.roles().iter().map(|r| r.to_string()).collect();
260
261 let later_formal: Option<Vec<String>> = later
266 .named_attribute("role")
267 .map(|attr| split_role_value(attr.value()).map(str::to_string).collect());
268
269 let later_shorthand: Vec<String> = later
270 .nth_attribute(1)
271 .map(|attr| attr.roles().iter().map(|r| r.to_string()).collect())
272 .unwrap_or_default();
273
274 let mut resolved = later_formal.unwrap_or(current);
275 resolved.extend(later_shorthand);
276
277 if let Some(existing) = self.nth_positional_mut(1) {
280 *existing = existing.without_shorthand_roles();
281 }
282
283 Some(resolved)
284 } else {
285 None
286 };
287
288 let Attrlist {
289 attributes: later_attributes,
290 anchor: later_anchor,
291 source: _,
292 } = later;
293
294 if later_anchor.is_some() {
295 self.anchor = later_anchor;
296 }
297
298 for attr in later_attributes {
299 if fold_roles && attr.name_str() == Some("role") {
303 continue;
304 }
305
306 match attr.positional_index() {
314 None => {
317 if let Some(existing) = self
318 .attributes
319 .iter_mut()
320 .find(|a| a.name_str() == attr.name_str())
321 {
322 *existing = attr;
323 } else {
324 self.attributes.push(attr);
325 }
326 }
327
328 Some(1) => {
333 let attr = if fold_roles {
334 attr.without_shorthand_roles()
335 } else {
336 attr
337 };
338
339 if let Some(existing) = self.nth_positional_mut(1) {
340 *existing = ElementAttribute::merge_block_style_shorthand(existing, &attr);
341 } else {
342 self.attributes.push(attr);
343 }
344 }
345
346 Some(position) => {
349 if let Some(existing) = self.nth_positional_mut(position) {
350 *existing = attr;
351 } else {
352 self.attributes.push(attr);
353 }
354 }
355 }
356 }
357
358 if let Some(resolved) = resolved_roles {
360 self.set_role_attribute(resolved);
361 }
362 }
363
364 fn set_role_attribute(&mut self, roles: Vec<String>) {
370 if roles.is_empty() {
371 self.attributes.retain(|a| a.name_str() != Some("role"));
372 return;
373 }
374
375 let attr = ElementAttribute::synthesized_role(roles.join(" "));
376 if let Some(existing) = self
377 .attributes
378 .iter_mut()
379 .find(|a| a.name_str() == Some("role"))
380 {
381 *existing = attr;
382 } else {
383 self.attributes.push(attr);
384 }
385 }
386
387 fn nth_positional_mut(&mut self, n: usize) -> Option<&mut ElementAttribute<'src>> {
397 debug_assert!(n >= 1, "nth_positional_mut requires a 1-based position");
398
399 self.attributes
400 .iter_mut()
401 .find(|attr| attr.positional_index() == Some(n))
402 }
403
404 pub fn attributes(&'src self) -> ElementAttributes<'src> {
407 ElementAttributes::new(&self.attributes)
408 }
409
410 pub fn anchor(&'src self) -> Option<&'src str> {
412 self.anchor.as_deref()
413 }
414
415 pub(crate) fn title_attribute(&self) -> Option<(&str, bool)> {
427 self.attributes
428 .iter()
429 .find(|attr| attr.name_str() == Some("title"))
430 .map(|attr| (attr.value_str(), attr.value_is_substituted()))
431 }
432
433 pub fn named_attribute(&'src self, name: &str) -> Option<&'src ElementAttribute<'src>> {
435 self.attributes.iter().find(|attr| {
436 if let Some(attr_name) = attr.name() {
437 attr_name == name
438 } else {
439 false
440 }
441 })
442 }
443
444 pub fn nth_attribute(&'src self, n: usize) -> Option<&'src ElementAttribute<'src>> {
454 if n == 0 {
455 None
456 } else {
457 self.attributes
458 .iter()
459 .find(|attr| attr.positional_index() == Some(n))
460 }
461 }
462
463 pub fn named_or_positional_attribute(
471 &'src self,
472 name: &str,
473 index: usize,
474 ) -> Option<&'src ElementAttribute<'src>> {
475 self.named_attribute(name)
476 .or_else(|| self.nth_attribute(index))
477 }
478
479 pub fn id(&'src self) -> Option<&'src str> {
510 self.anchor().or_else(|| {
511 self.nth_attribute(1)
512 .and_then(|attr1| attr1.id())
513 .or_else(|| self.named_attribute("id").map(|attr| attr.value()))
514 })
515 }
516
517 pub fn roles(&'src self) -> Vec<&'src str> {
539 let mut roles = self
540 .nth_attribute(1)
541 .map(|attr1| attr1.roles())
542 .unwrap_or_default();
543
544 if let Some(role_attr) = self.named_attribute("role") {
545 roles.extend(split_role_value(role_attr.value()));
546 }
547
548 roles
549 }
550
551 pub fn options(&'src self) -> Vec<&'src str> {
615 let mut options = self
616 .nth_attribute(1)
617 .map(|attr1| attr1.options())
618 .unwrap_or_default();
619
620 if let Some(option_attr) = self.named_attribute("opts") {
621 options.append(&mut split_options(option_attr.value()));
622 }
623
624 if let Some(option_attr) = self.named_attribute("options") {
625 options.append(&mut split_options(option_attr.value()));
626 }
627
628 options
629 }
630
631 pub fn has_option<N: AsRef<str>>(&'src self, name: N) -> bool {
637 let options = self.options();
639 let name = name.as_ref();
640 options.contains(&name)
641 }
642
643 pub fn block_style(&'src self) -> Option<&'src str> {
645 self.nth_attribute(1).and_then(|a| a.block_style())
646 }
647}
648
649impl<'src> HasSpan<'src> for Attrlist<'src> {
650 fn span(&self) -> Span<'src> {
651 self.source
652 }
653}
654
655impl std::fmt::Debug for Attrlist<'_> {
656 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
657 f.debug_struct("Attrlist")
658 .field("attributes", &DebugSliceReference(&self.attributes))
659 .field("anchor", &self.anchor)
660 .field("source", &self.source)
661 .finish()
662 }
663}
664
665fn split_options(value: &str) -> Vec<&str> {
670 value
671 .split(',')
672 .map(str::trim)
673 .filter(|opt| !opt.is_empty())
674 .collect()
675}
676
677fn split_role_value(value: &str) -> impl Iterator<Item = &str> {
685 value.split(' ').filter(|role| !role.is_empty())
686}
687
688#[derive(Clone, Copy, Debug, Eq, PartialEq)]
690pub(crate) enum AttrlistContext {
691 Block,
692 Inline,
693}
694
695#[cfg(test)]
696mod tests {
697 #![allow(clippy::unwrap_used)]
698
699 use crate::{attributes::AttrlistContext, tests::prelude::*};
700
701 #[test]
702 fn impl_clone() {
703 let p = Parser::default();
705 let b1 = crate::attributes::Attrlist::parse(
706 crate::Span::new("abc"),
707 &p,
708 AttrlistContext::Inline,
709 )
710 .unwrap_if_no_warnings();
711
712 let b2 = b1.item.clone();
713 assert_eq!(b1.item, b2);
714 }
715
716 #[test]
717 fn impl_default() {
718 let attrlist = crate::attributes::Attrlist::default();
719
720 assert_eq!(
721 attrlist,
722 Attrlist {
723 attributes: &[],
724 anchor: None,
725 source: Span {
726 data: "",
727 line: 1,
728 col: 1,
729 offset: 0
730 }
731 }
732 );
733
734 assert!(attrlist.named_attribute("foo").is_none());
735
736 assert!(attrlist.nth_attribute(0).is_none());
737 assert!(attrlist.nth_attribute(1).is_none());
738 assert!(attrlist.nth_attribute(42).is_none());
739
740 assert!(attrlist.named_or_positional_attribute("foo", 0).is_none());
741 assert!(attrlist.named_or_positional_attribute("foo", 1).is_none());
742 assert!(attrlist.named_or_positional_attribute("foo", 42).is_none());
743
744 assert!(attrlist.id().is_none());
745 assert!(attrlist.roles().is_empty());
746 assert!(attrlist.block_style().is_none());
747
748 assert_eq!(
749 attrlist.span(),
750 Span {
751 data: "",
752 line: 1,
753 col: 1,
754 offset: 0,
755 }
756 );
757 }
758
759 #[test]
760 fn empty_source() {
761 let p = Parser::default();
762
763 let mi =
764 crate::attributes::Attrlist::parse(crate::Span::default(), &p, AttrlistContext::Inline)
765 .unwrap_if_no_warnings();
766
767 assert_eq!(
768 mi.item,
769 Attrlist {
770 attributes: &[],
771 anchor: None,
772 source: Span {
773 data: "",
774 line: 1,
775 col: 1,
776 offset: 0
777 }
778 }
779 );
780
781 assert!(mi.item.named_attribute("foo").is_none());
782
783 assert!(mi.item.nth_attribute(0).is_none());
784 assert!(mi.item.nth_attribute(1).is_none());
785 assert!(mi.item.nth_attribute(42).is_none());
786
787 assert!(mi.item.named_or_positional_attribute("foo", 0).is_none());
788 assert!(mi.item.named_or_positional_attribute("foo", 1).is_none());
789 assert!(mi.item.named_or_positional_attribute("foo", 42).is_none());
790
791 assert!(mi.item.id().is_none());
792 assert!(mi.item.roles().is_empty());
793 assert!(mi.item.block_style().is_none());
794
795 assert_eq!(
796 mi.item.span(),
797 Span {
798 data: "",
799 line: 1,
800 col: 1,
801 offset: 0,
802 }
803 );
804
805 assert_eq!(
806 mi.after,
807 Span {
808 data: "",
809 line: 1,
810 col: 1,
811 offset: 0
812 }
813 );
814 }
815
816 #[test]
817 fn empty_positional_attributes() {
818 let p = Parser::default();
819
820 let mi = crate::attributes::Attrlist::parse(
821 crate::Span::new(",300,400"),
822 &p,
823 AttrlistContext::Inline,
824 )
825 .unwrap_if_no_warnings();
826
827 assert_eq!(
831 mi.item,
832 Attrlist {
833 attributes: &[
834 ElementAttribute {
835 name: None,
836 shorthand_items: &[],
837 value: "300"
838 },
839 ElementAttribute {
840 name: None,
841 shorthand_items: &[],
842 value: "400"
843 }
844 ],
845 anchor: None,
846 source: Span {
847 data: ",300,400",
848 line: 1,
849 col: 1,
850 offset: 0
851 }
852 }
853 );
854
855 assert!(mi.item.named_attribute("foo").is_none());
856 assert!(mi.item.nth_attribute(0).is_none());
857 assert!(mi.item.named_or_positional_attribute("foo", 0).is_none());
858
859 assert!(mi.item.id().is_none());
860 assert!(mi.item.roles().is_empty());
861 assert!(mi.item.block_style().is_none());
862
863 assert!(mi.item.nth_attribute(1).is_none());
865 assert!(mi.item.named_or_positional_attribute("alt", 1).is_none());
866
867 assert_eq!(
868 mi.item.nth_attribute(2).unwrap(),
869 ElementAttribute {
870 name: None,
871 shorthand_items: &[],
872 value: "300"
873 }
874 );
875
876 assert_eq!(
877 mi.item.named_or_positional_attribute("width", 2).unwrap(),
878 ElementAttribute {
879 name: None,
880 shorthand_items: &[],
881 value: "300"
882 }
883 );
884
885 assert_eq!(
886 mi.item.nth_attribute(3).unwrap(),
887 ElementAttribute {
888 name: None,
889 shorthand_items: &[],
890 value: "400"
891 }
892 );
893
894 assert_eq!(
895 mi.item.named_or_positional_attribute("height", 3).unwrap(),
896 ElementAttribute {
897 name: None,
898 shorthand_items: &[],
899 value: "400"
900 }
901 );
902
903 assert!(mi.item.nth_attribute(4).is_none());
904 assert!(mi.item.named_or_positional_attribute("height", 4).is_none());
905 assert!(mi.item.nth_attribute(42).is_none());
906
907 assert_eq!(
908 mi.item.span(),
909 Span {
910 data: ",300,400",
911 line: 1,
912 col: 1,
913 offset: 0,
914 }
915 );
916
917 assert_eq!(
918 mi.after,
919 Span {
920 data: "",
921 line: 1,
922 col: 9,
923 offset: 8
924 }
925 );
926 }
927
928 #[test]
929 fn only_positional_attributes() {
930 let p = Parser::default();
931
932 let mi = crate::attributes::Attrlist::parse(
933 crate::Span::new("Sunset,300,400"),
934 &p,
935 AttrlistContext::Inline,
936 )
937 .unwrap_if_no_warnings();
938
939 assert_eq!(
940 mi.item,
941 Attrlist {
942 attributes: &[
943 ElementAttribute {
944 name: None,
945 shorthand_items: &["Sunset"],
946 value: "Sunset"
947 },
948 ElementAttribute {
949 name: None,
950 shorthand_items: &[],
951 value: "300"
952 },
953 ElementAttribute {
954 name: None,
955 shorthand_items: &[],
956 value: "400"
957 }
958 ],
959 anchor: None,
960 source: Span {
961 data: "Sunset,300,400",
962 line: 1,
963 col: 1,
964 offset: 0
965 }
966 }
967 );
968
969 assert!(mi.item.named_attribute("foo").is_none());
970 assert!(mi.item.nth_attribute(0).is_none());
971 assert!(mi.item.named_or_positional_attribute("foo", 0).is_none());
972
973 assert!(mi.item.id().is_none());
974 assert!(mi.item.roles().is_empty());
975 assert_eq!(mi.item.block_style().unwrap(), "Sunset");
976
977 assert_eq!(
978 mi.item.nth_attribute(1).unwrap(),
979 ElementAttribute {
980 name: None,
981 shorthand_items: &["Sunset"],
982 value: "Sunset"
983 }
984 );
985
986 assert_eq!(
987 mi.item.named_or_positional_attribute("alt", 1).unwrap(),
988 ElementAttribute {
989 name: None,
990 shorthand_items: &["Sunset"],
991 value: "Sunset"
992 }
993 );
994
995 assert_eq!(
996 mi.item.nth_attribute(2).unwrap(),
997 ElementAttribute {
998 name: None,
999 shorthand_items: &[],
1000 value: "300"
1001 }
1002 );
1003
1004 assert_eq!(
1005 mi.item.named_or_positional_attribute("width", 2).unwrap(),
1006 ElementAttribute {
1007 name: None,
1008 shorthand_items: &[],
1009 value: "300"
1010 }
1011 );
1012
1013 assert_eq!(
1014 mi.item.nth_attribute(3).unwrap(),
1015 ElementAttribute {
1016 name: None,
1017 shorthand_items: &[],
1018 value: "400"
1019 }
1020 );
1021
1022 assert_eq!(
1023 mi.item.named_or_positional_attribute("height", 3).unwrap(),
1024 ElementAttribute {
1025 name: None,
1026 shorthand_items: &[],
1027 value: "400"
1028 }
1029 );
1030
1031 assert!(mi.item.nth_attribute(4).is_none());
1032 assert!(mi.item.named_or_positional_attribute("height", 4).is_none());
1033 assert!(mi.item.nth_attribute(42).is_none());
1034
1035 assert_eq!(
1036 mi.item.span(),
1037 Span {
1038 data: "Sunset,300,400",
1039 line: 1,
1040 col: 1,
1041 offset: 0,
1042 }
1043 );
1044
1045 assert_eq!(
1046 mi.after,
1047 Span {
1048 data: "",
1049 line: 1,
1050 col: 15,
1051 offset: 14
1052 }
1053 );
1054 }
1055
1056 #[test]
1057 fn trim_trailing_space() {
1058 let p = Parser::default();
1059
1060 let mi = crate::attributes::Attrlist::parse(
1061 crate::Span::new("Sunset ,300 , 400"),
1062 &p,
1063 AttrlistContext::Inline,
1064 )
1065 .unwrap_if_no_warnings();
1066
1067 assert_eq!(
1068 mi.item,
1069 Attrlist {
1070 attributes: &[
1071 ElementAttribute {
1072 name: None,
1073 shorthand_items: &["Sunset"],
1074 value: "Sunset"
1075 },
1076 ElementAttribute {
1077 name: None,
1078 shorthand_items: &[],
1079 value: "300"
1080 },
1081 ElementAttribute {
1082 name: None,
1083 shorthand_items: &[],
1084 value: "400"
1085 }
1086 ],
1087 anchor: None,
1088 source: Span {
1089 data: "Sunset ,300 , 400",
1090 line: 1,
1091 col: 1,
1092 offset: 0
1093 }
1094 }
1095 );
1096
1097 assert!(mi.item.named_attribute("foo").is_none());
1098 assert!(mi.item.nth_attribute(0).is_none());
1099 assert!(mi.item.named_or_positional_attribute("foo", 0).is_none());
1100
1101 assert!(mi.item.id().is_none());
1102 assert!(mi.item.roles().is_empty());
1103 assert_eq!(mi.item.block_style().unwrap(), "Sunset");
1104
1105 assert_eq!(
1106 mi.item.nth_attribute(1).unwrap(),
1107 ElementAttribute {
1108 name: None,
1109 shorthand_items: &["Sunset"],
1110 value: "Sunset"
1111 }
1112 );
1113
1114 assert_eq!(
1115 mi.item.named_or_positional_attribute("alt", 1).unwrap(),
1116 ElementAttribute {
1117 name: None,
1118 shorthand_items: &["Sunset"],
1119 value: "Sunset"
1120 }
1121 );
1122
1123 assert_eq!(
1124 mi.item.nth_attribute(2).unwrap(),
1125 ElementAttribute {
1126 name: None,
1127 shorthand_items: &[],
1128 value: "300"
1129 }
1130 );
1131
1132 assert_eq!(
1133 mi.item.named_or_positional_attribute("width", 2).unwrap(),
1134 ElementAttribute {
1135 name: None,
1136 shorthand_items: &[],
1137 value: "300"
1138 }
1139 );
1140
1141 assert_eq!(
1142 mi.item.nth_attribute(3).unwrap(),
1143 ElementAttribute {
1144 name: None,
1145 shorthand_items: &[],
1146 value: "400"
1147 }
1148 );
1149
1150 assert_eq!(
1151 mi.item.named_or_positional_attribute("height", 3).unwrap(),
1152 ElementAttribute {
1153 name: None,
1154 shorthand_items: &[],
1155 value: "400"
1156 }
1157 );
1158
1159 assert!(mi.item.nth_attribute(4).is_none());
1160 assert!(mi.item.named_or_positional_attribute("height", 4).is_none());
1161 assert!(mi.item.nth_attribute(42).is_none());
1162
1163 assert_eq!(
1164 mi.item.span(),
1165 Span {
1166 data: "Sunset ,300 , 400",
1167 line: 1,
1168 col: 1,
1169 offset: 0,
1170 }
1171 );
1172
1173 assert_eq!(
1174 mi.after,
1175 Span {
1176 data: "",
1177 line: 1,
1178 col: 18,
1179 offset: 17
1180 }
1181 );
1182 }
1183
1184 #[test]
1185 fn only_named_attributes() {
1186 let p = Parser::default();
1187
1188 let mi = crate::attributes::Attrlist::parse(
1189 crate::Span::new("alt=Sunset,width=300,height=400"),
1190 &p,
1191 AttrlistContext::Inline,
1192 )
1193 .unwrap_if_no_warnings();
1194
1195 assert_eq!(
1196 mi.item,
1197 Attrlist {
1198 attributes: &[
1199 ElementAttribute {
1200 name: Some("alt"),
1201 shorthand_items: &[],
1202 value: "Sunset"
1203 },
1204 ElementAttribute {
1205 name: Some("width"),
1206 shorthand_items: &[],
1207 value: "300"
1208 },
1209 ElementAttribute {
1210 name: Some("height"),
1211 shorthand_items: &[],
1212 value: "400"
1213 }
1214 ],
1215 anchor: None,
1216 source: Span {
1217 data: "alt=Sunset,width=300,height=400",
1218 line: 1,
1219 col: 1,
1220 offset: 0
1221 }
1222 }
1223 );
1224
1225 assert!(mi.item.named_attribute("foo").is_none());
1226 assert!(mi.item.named_or_positional_attribute("foo", 0).is_none());
1227
1228 assert_eq!(
1229 mi.item.named_attribute("alt").unwrap(),
1230 ElementAttribute {
1231 name: Some("alt"),
1232 shorthand_items: &[],
1233 value: "Sunset"
1234 }
1235 );
1236
1237 assert_eq!(
1238 mi.item.named_or_positional_attribute("alt", 1).unwrap(),
1239 ElementAttribute {
1240 name: Some("alt"),
1241 shorthand_items: &[],
1242 value: "Sunset"
1243 }
1244 );
1245
1246 assert_eq!(
1247 mi.item.named_attribute("width").unwrap(),
1248 ElementAttribute {
1249 name: Some("width"),
1250 shorthand_items: &[],
1251 value: "300"
1252 }
1253 );
1254
1255 assert_eq!(
1256 mi.item.named_or_positional_attribute("width", 2).unwrap(),
1257 ElementAttribute {
1258 name: Some("width"),
1259 shorthand_items: &[],
1260 value: "300"
1261 }
1262 );
1263
1264 assert_eq!(
1265 mi.item.named_attribute("height").unwrap(),
1266 ElementAttribute {
1267 name: Some("height"),
1268 shorthand_items: &[],
1269 value: "400"
1270 }
1271 );
1272
1273 assert_eq!(
1274 mi.item.named_or_positional_attribute("height", 3).unwrap(),
1275 ElementAttribute {
1276 name: Some("height"),
1277 shorthand_items: &[],
1278 value: "400"
1279 }
1280 );
1281
1282 assert!(mi.item.nth_attribute(0).is_none());
1283 assert!(mi.item.nth_attribute(1).is_none());
1284 assert!(mi.item.nth_attribute(2).is_none());
1285 assert!(mi.item.nth_attribute(3).is_none());
1286 assert!(mi.item.nth_attribute(4).is_none());
1287 assert!(mi.item.nth_attribute(42).is_none());
1288
1289 assert!(mi.item.id().is_none());
1290 assert!(mi.item.roles().is_empty());
1291 assert!(mi.item.block_style().is_none());
1292
1293 assert_eq!(
1294 mi.item.span(),
1295 Span {
1296 data: "alt=Sunset,width=300,height=400",
1297 line: 1,
1298 col: 1,
1299 offset: 0
1300 }
1301 );
1302
1303 assert_eq!(
1304 mi.after,
1305 Span {
1306 data: "",
1307 line: 1,
1308 col: 32,
1309 offset: 31
1310 }
1311 );
1312 }
1313
1314 #[test]
1315 fn ignore_named_attribute_with_none_value() {
1316 let p = Parser::default();
1317 let mi = crate::attributes::Attrlist::parse(
1318 crate::Span::new("alt=Sunset,width=None,height=400"),
1319 &p,
1320 AttrlistContext::Inline,
1321 )
1322 .unwrap_if_no_warnings();
1323
1324 assert_eq!(
1325 mi.item,
1326 Attrlist {
1327 attributes: &[
1328 ElementAttribute {
1329 name: Some("alt"),
1330 shorthand_items: &[],
1331 value: "Sunset"
1332 },
1333 ElementAttribute {
1334 name: Some("height"),
1335 shorthand_items: &[],
1336 value: "400"
1337 }
1338 ],
1339 anchor: None,
1340 source: Span {
1341 data: "alt=Sunset,width=None,height=400",
1342 line: 1,
1343 col: 1,
1344 offset: 0
1345 }
1346 }
1347 );
1348
1349 assert!(mi.item.named_attribute("foo").is_none());
1350 assert!(mi.item.named_or_positional_attribute("foo", 0).is_none());
1351
1352 assert_eq!(
1353 mi.item.named_attribute("alt").unwrap(),
1354 ElementAttribute {
1355 name: Some("alt"),
1356 shorthand_items: &[],
1357 value: "Sunset"
1358 }
1359 );
1360
1361 assert_eq!(
1362 mi.item.named_or_positional_attribute("alt", 1).unwrap(),
1363 ElementAttribute {
1364 name: Some("alt"),
1365 shorthand_items: &[],
1366 value: "Sunset"
1367 }
1368 );
1369
1370 assert!(mi.item.named_attribute("width").is_none());
1371 assert!(mi.item.named_or_positional_attribute("width", 2).is_none());
1372
1373 assert_eq!(
1374 mi.item.named_attribute("height").unwrap(),
1375 ElementAttribute {
1376 name: Some("height"),
1377 shorthand_items: &[],
1378 value: "400"
1379 }
1380 );
1381
1382 assert_eq!(
1383 mi.item.named_or_positional_attribute("height", 2).unwrap(),
1384 ElementAttribute {
1385 name: Some("height"),
1386 shorthand_items: &[],
1387 value: "400"
1388 }
1389 );
1390
1391 assert!(mi.item.nth_attribute(0).is_none());
1392 assert!(mi.item.nth_attribute(1).is_none());
1393 assert!(mi.item.nth_attribute(2).is_none());
1394 assert!(mi.item.nth_attribute(3).is_none());
1395 assert!(mi.item.nth_attribute(4).is_none());
1396 assert!(mi.item.nth_attribute(42).is_none());
1397
1398 assert!(mi.item.id().is_none());
1399 assert!(mi.item.roles().is_empty());
1400 assert!(mi.item.block_style().is_none());
1401
1402 assert_eq!(
1403 mi.item.span(),
1404 Span {
1405 data: "alt=Sunset,width=None,height=400",
1406 line: 1,
1407 col: 1,
1408 offset: 0
1409 }
1410 );
1411
1412 assert_eq!(
1413 mi.after,
1414 Span {
1415 data: "",
1416 line: 1,
1417 col: 33,
1418 offset: 32
1419 }
1420 );
1421 }
1422
1423 #[test]
1424 fn err_unparsed_remainder_after_value() {
1425 let p = Parser::default();
1426
1427 let maw = crate::attributes::Attrlist::parse(
1428 crate::Span::new("alt=\"Sunset\"width=300"),
1429 &p,
1430 AttrlistContext::Inline,
1431 );
1432
1433 let mi = maw.item.clone();
1434
1435 assert_eq!(
1436 mi.item,
1437 Attrlist {
1438 attributes: &[ElementAttribute {
1439 name: Some("alt"),
1440 shorthand_items: &[],
1441 value: "Sunset"
1442 }],
1443 anchor: None,
1444 source: Span {
1445 data: "alt=\"Sunset\"width=300",
1446 line: 1,
1447 col: 1,
1448 offset: 0
1449 }
1450 }
1451 );
1452
1453 assert_eq!(
1454 mi.after,
1455 Span {
1456 data: "",
1457 line: 1,
1458 col: 22,
1459 offset: 21
1460 }
1461 );
1462
1463 assert_eq!(
1464 maw.warnings,
1465 vec![Warning {
1466 source: Span {
1467 data: "alt=\"Sunset\"width=300",
1468 line: 1,
1469 col: 1,
1470 offset: 0,
1471 },
1472 warning: WarningType::MissingCommaAfterQuotedAttributeValue,
1473 }]
1474 );
1475 }
1476
1477 #[test]
1478 fn propagates_error_from_element_attribute() {
1479 let p = Parser::default();
1480
1481 let maw = crate::attributes::Attrlist::parse(
1482 crate::Span::new("foo%#id"),
1483 &p,
1484 AttrlistContext::Inline,
1485 );
1486
1487 let mi = maw.item.clone();
1488
1489 assert_eq!(
1490 mi.item,
1491 Attrlist {
1492 attributes: &[ElementAttribute {
1493 name: None,
1494 shorthand_items: &["foo", "#id"],
1495 value: "foo%#id"
1496 }],
1497 anchor: None,
1498 source: Span {
1499 data: "foo%#id",
1500 line: 1,
1501 col: 1,
1502 offset: 0
1503 }
1504 }
1505 );
1506
1507 assert_eq!(
1508 mi.after,
1509 Span {
1510 data: "",
1511 line: 1,
1512 col: 8,
1513 offset: 7
1514 }
1515 );
1516
1517 assert_eq!(
1518 maw.warnings,
1519 vec![Warning {
1520 source: Span {
1521 data: "foo%#id",
1522 line: 1,
1523 col: 1,
1524 offset: 0,
1525 },
1526 warning: WarningType::EmptyShorthandName,
1527 }]
1528 );
1529 }
1530
1531 #[test]
1532 fn merge_block_attribute_line_anchor_later_wins() {
1533 let p = Parser::default();
1534
1535 let mut first = crate::attributes::Attrlist::parse(
1536 crate::Span::new("[id1]"),
1537 &p,
1538 AttrlistContext::Block,
1539 )
1540 .unwrap_if_no_warnings()
1541 .item;
1542
1543 let later = crate::attributes::Attrlist::parse(
1544 crate::Span::new("[id2]"),
1545 &p,
1546 AttrlistContext::Block,
1547 )
1548 .unwrap_if_no_warnings()
1549 .item;
1550
1551 assert_eq!(first.anchor(), Some("id1"));
1552 first.merge_block_attribute_line(later);
1553 assert_eq!(first.anchor(), Some("id2"));
1554 }
1555
1556 #[test]
1557 fn merge_block_attribute_line_anchor_retained_when_later_has_none() {
1558 let p = Parser::default();
1559
1560 let mut first = crate::attributes::Attrlist::parse(
1561 crate::Span::new("[id1]"),
1562 &p,
1563 AttrlistContext::Block,
1564 )
1565 .unwrap_if_no_warnings()
1566 .item;
1567
1568 let later = crate::attributes::Attrlist::parse(
1569 crate::Span::new("foo=bar"),
1570 &p,
1571 AttrlistContext::Block,
1572 )
1573 .unwrap_if_no_warnings()
1574 .item;
1575
1576 first.merge_block_attribute_line(later);
1577 assert_eq!(first.anchor(), Some("id1"));
1578 assert_eq!(first.named_attribute("foo").unwrap().value(), "bar");
1579 }
1580
1581 #[test]
1582 fn merge_block_attribute_line_positions_account_for_named_entries() {
1583 let p = Parser::default();
1589
1590 let mut first = crate::attributes::Attrlist::parse(
1591 crate::Span::new("quote,Author1"),
1592 &p,
1593 AttrlistContext::Block,
1594 )
1595 .unwrap_if_no_warnings()
1596 .item;
1597
1598 let later = crate::attributes::Attrlist::parse(
1599 crate::Span::new("width=300,Author2,Extra"),
1600 &p,
1601 AttrlistContext::Block,
1602 )
1603 .unwrap_if_no_warnings()
1604 .item;
1605
1606 first.merge_block_attribute_line(later);
1607
1608 assert_eq!(first.nth_attribute(1).unwrap().value(), "quote");
1609 assert_eq!(first.nth_attribute(2).unwrap().value(), "Author2");
1610 assert_eq!(first.nth_attribute(3).unwrap().value(), "Extra");
1611 assert_eq!(first.named_attribute("width").unwrap().value(), "300");
1612 }
1613
1614 #[test]
1615 fn anchor_syntax() {
1616 let p = Parser::default();
1617
1618 let maw = crate::attributes::Attrlist::parse(
1619 crate::Span::new("[notice]"),
1620 &p,
1621 AttrlistContext::Inline,
1622 );
1623
1624 let mi = maw.item.clone();
1625
1626 assert_eq!(
1627 mi.item,
1628 Attrlist {
1629 attributes: &[],
1630 anchor: Some("notice"),
1631 source: Span {
1632 data: "[notice]",
1633 line: 1,
1634 col: 1,
1635 offset: 0
1636 }
1637 }
1638 );
1639
1640 assert_eq!(
1641 mi.after,
1642 Span {
1643 data: "",
1644 line: 1,
1645 col: 9,
1646 offset: 8
1647 }
1648 );
1649
1650 assert!(maw.warnings.is_empty());
1651 }
1652
1653 mod id {
1654 use crate::{attributes::AttrlistContext, tests::prelude::*};
1655
1656 #[test]
1657 fn via_shorthand_syntax() {
1658 let p = Parser::default();
1659
1660 let mi = crate::attributes::Attrlist::parse(
1661 crate::Span::new("#goals"),
1662 &p,
1663 AttrlistContext::Inline,
1664 )
1665 .unwrap_if_no_warnings();
1666
1667 assert_eq!(
1668 mi.item,
1669 Attrlist {
1670 attributes: &[ElementAttribute {
1671 name: None,
1672 shorthand_items: &["#goals"],
1673 value: "#goals"
1674 }],
1675 anchor: None,
1676 source: Span {
1677 data: "#goals",
1678 line: 1,
1679 col: 1,
1680 offset: 0
1681 }
1682 }
1683 );
1684
1685 assert!(mi.item.named_attribute("foo").is_none());
1686 assert!(mi.item.named_or_positional_attribute("foo", 0).is_none());
1687 assert_eq!(mi.item.id().unwrap(), "goals");
1688 assert!(mi.item.roles().is_empty());
1689 assert!(mi.item.block_style().is_none());
1690
1691 assert_eq!(
1692 mi.item.span(),
1693 Span {
1694 data: "#goals",
1695 line: 1,
1696 col: 1,
1697 offset: 0
1698 }
1699 );
1700
1701 assert_eq!(
1702 mi.after,
1703 Span {
1704 data: "",
1705 line: 1,
1706 col: 7,
1707 offset: 6
1708 }
1709 );
1710 }
1711
1712 #[test]
1713 fn via_named_attribute() {
1714 let p = Parser::default();
1715
1716 let mi = crate::attributes::Attrlist::parse(
1717 crate::Span::new("foo=bar,id=goals"),
1718 &p,
1719 AttrlistContext::Inline,
1720 )
1721 .unwrap_if_no_warnings();
1722
1723 assert_eq!(
1724 mi.item,
1725 Attrlist {
1726 attributes: &[
1727 ElementAttribute {
1728 name: Some("foo"),
1729 shorthand_items: &[],
1730 value: "bar"
1731 },
1732 ElementAttribute {
1733 name: Some("id"),
1734 shorthand_items: &[],
1735 value: "goals"
1736 },
1737 ],
1738 anchor: None,
1739 source: Span {
1740 data: "foo=bar,id=goals",
1741 line: 1,
1742 col: 1,
1743 offset: 0
1744 }
1745 }
1746 );
1747
1748 assert_eq!(
1749 mi.item.named_attribute("foo").unwrap(),
1750 ElementAttribute {
1751 name: Some("foo"),
1752 shorthand_items: &[],
1753 value: "bar"
1754 }
1755 );
1756
1757 assert_eq!(
1758 mi.item.named_attribute("id").unwrap(),
1759 ElementAttribute {
1760 name: Some("id"),
1761 shorthand_items: &[],
1762 value: "goals"
1763 }
1764 );
1765
1766 assert_eq!(mi.item.id().unwrap(), "goals");
1767 assert!(mi.item.roles().is_empty());
1768 assert!(mi.item.block_style().is_none());
1769
1770 assert_eq!(
1771 mi.after,
1772 Span {
1773 data: "",
1774 line: 1,
1775 col: 17,
1776 offset: 16
1777 }
1778 );
1779 }
1780
1781 #[test]
1782 fn via_block_anchor_syntax() {
1783 let p = Parser::default();
1784
1785 let mi = crate::attributes::Attrlist::parse(
1786 crate::Span::new("[goals]"),
1787 &p,
1788 AttrlistContext::Inline,
1789 )
1790 .unwrap_if_no_warnings();
1791
1792 assert_eq!(
1793 mi.item,
1794 Attrlist {
1795 attributes: &[],
1796 anchor: Some("goals"),
1797 source: Span {
1798 data: "[goals]",
1799 line: 1,
1800 col: 1,
1801 offset: 0
1802 }
1803 }
1804 );
1805
1806 assert_eq!(mi.item.id().unwrap(), "goals");
1807
1808 assert_eq!(
1809 mi.after,
1810 Span {
1811 data: "",
1812 line: 1,
1813 col: 8,
1814 offset: 7
1815 }
1816 );
1817 }
1818
1819 #[test]
1820 fn shorthand_only_first_attribute() {
1821 let p = Parser::default();
1822
1823 let mi = crate::attributes::Attrlist::parse(
1824 crate::Span::new("foo,blah#goals"),
1825 &p,
1826 AttrlistContext::Inline,
1827 )
1828 .unwrap_if_no_warnings();
1829
1830 assert_eq!(
1831 mi.item,
1832 Attrlist {
1833 attributes: &[
1834 ElementAttribute {
1835 name: None,
1836 shorthand_items: &["foo"],
1837 value: "foo"
1838 },
1839 ElementAttribute {
1840 name: None,
1841 shorthand_items: &[],
1842 value: "blah#goals"
1843 },
1844 ],
1845 anchor: None,
1846 source: Span {
1847 data: "foo,blah#goals",
1848 line: 1,
1849 col: 1,
1850 offset: 0
1851 }
1852 }
1853 );
1854
1855 assert!(mi.item.id().is_none());
1856 assert!(mi.item.roles().is_empty());
1857 assert_eq!(mi.item.block_style().unwrap(), "foo");
1858
1859 assert_eq!(
1860 mi.after,
1861 Span {
1862 data: "",
1863 line: 1,
1864 col: 15,
1865 offset: 14
1866 }
1867 );
1868 }
1869 }
1870
1871 mod roles {
1872 use crate::{attributes::AttrlistContext, tests::prelude::*};
1873
1874 #[test]
1875 fn via_shorthand_syntax() {
1876 let p = Parser::default();
1877
1878 let mi = crate::attributes::Attrlist::parse(
1879 crate::Span::new(".rolename"),
1880 &p,
1881 AttrlistContext::Inline,
1882 )
1883 .unwrap_if_no_warnings();
1884
1885 assert_eq!(
1886 mi.item,
1887 Attrlist {
1888 attributes: &[ElementAttribute {
1889 name: None,
1890 shorthand_items: &[".rolename"],
1891 value: ".rolename"
1892 }],
1893 anchor: None,
1894 source: Span {
1895 data: ".rolename",
1896 line: 1,
1897 col: 1,
1898 offset: 0
1899 }
1900 }
1901 );
1902
1903 assert!(mi.item.named_attribute("foo").is_none());
1904 assert!(mi.item.named_or_positional_attribute("foo", 0).is_none());
1905
1906 let roles = mi.item.roles();
1907 let mut roles = roles.iter();
1908 assert_eq!(roles.next().unwrap(), &"rolename");
1909 assert!(roles.next().is_none());
1910
1911 assert!(mi.item.block_style().is_none());
1912
1913 assert_eq!(
1914 mi.item.span(),
1915 Span {
1916 data: ".rolename",
1917 line: 1,
1918 col: 1,
1919 offset: 0
1920 }
1921 );
1922
1923 assert_eq!(
1924 mi.after,
1925 Span {
1926 data: "",
1927 line: 1,
1928 col: 10,
1929 offset: 9
1930 }
1931 );
1932 }
1933
1934 #[test]
1935 fn via_shorthand_syntax_trim_trailing_whitespace() {
1936 let p = Parser::default();
1937
1938 let mi = crate::attributes::Attrlist::parse(
1939 crate::Span::new(".rolename "),
1940 &p,
1941 AttrlistContext::Inline,
1942 )
1943 .unwrap_if_no_warnings();
1944
1945 assert_eq!(
1946 mi.item,
1947 Attrlist {
1948 attributes: &[ElementAttribute {
1949 name: None,
1950 shorthand_items: &[".rolename"],
1951 value: ".rolename"
1952 }],
1953 anchor: None,
1954 source: Span {
1955 data: ".rolename ",
1956 line: 1,
1957 col: 1,
1958 offset: 0
1959 }
1960 }
1961 );
1962
1963 assert!(mi.item.named_attribute("foo").is_none());
1964 assert!(mi.item.named_or_positional_attribute("foo", 0).is_none());
1965
1966 let roles = mi.item.roles();
1967 let mut roles = roles.iter();
1968
1969 assert_eq!(roles.next().unwrap(), &"rolename");
1970 assert!(roles.next().is_none());
1971
1972 assert!(mi.item.block_style().is_none());
1973
1974 assert_eq!(
1975 mi.item.span(),
1976 Span {
1977 data: ".rolename ",
1978 line: 1,
1979 col: 1,
1980 offset: 0
1981 }
1982 );
1983
1984 assert_eq!(
1985 mi.after,
1986 Span {
1987 data: "",
1988 line: 1,
1989 col: 11,
1990 offset: 10
1991 }
1992 );
1993 }
1994
1995 #[test]
1996 fn multiple_roles_via_shorthand_syntax() {
1997 let p = Parser::default();
1998
1999 let mi = crate::attributes::Attrlist::parse(
2000 crate::Span::new(".role1.role2.role3"),
2001 &p,
2002 AttrlistContext::Inline,
2003 )
2004 .unwrap_if_no_warnings();
2005
2006 assert_eq!(
2007 mi.item,
2008 Attrlist {
2009 attributes: &[ElementAttribute {
2010 name: None,
2011 shorthand_items: &[".role1", ".role2", ".role3"],
2012 value: ".role1.role2.role3"
2013 }],
2014 anchor: None,
2015 source: Span {
2016 data: ".role1.role2.role3",
2017 line: 1,
2018 col: 1,
2019 offset: 0
2020 }
2021 }
2022 );
2023
2024 assert!(mi.item.named_attribute("foo").is_none());
2025 assert!(mi.item.named_or_positional_attribute("foo", 0).is_none());
2026
2027 let roles = mi.item.roles();
2028 let mut roles = roles.iter();
2029 assert_eq!(roles.next().unwrap(), &"role1");
2030 assert_eq!(roles.next().unwrap(), &"role2");
2031 assert_eq!(roles.next().unwrap(), &"role3");
2032 assert!(roles.next().is_none());
2033
2034 assert!(mi.item.block_style().is_none());
2035
2036 assert_eq!(
2037 mi.item.span(),
2038 Span {
2039 data: ".role1.role2.role3",
2040 line: 1,
2041 col: 1,
2042 offset: 0
2043 }
2044 );
2045
2046 assert_eq!(
2047 mi.after,
2048 Span {
2049 data: "",
2050 line: 1,
2051 col: 19,
2052 offset: 18
2053 }
2054 );
2055 }
2056
2057 #[test]
2058 fn multiple_roles_via_shorthand_syntax_trim_whitespace() {
2059 let p = Parser::default();
2060
2061 let mi = crate::attributes::Attrlist::parse(
2062 crate::Span::new(".role1 .role2 .role3 "),
2063 &p,
2064 AttrlistContext::Inline,
2065 )
2066 .unwrap_if_no_warnings();
2067
2068 assert_eq!(
2069 mi.item,
2070 Attrlist {
2071 attributes: &[ElementAttribute {
2072 name: None,
2073 shorthand_items: &[".role1", ".role2", ".role3"],
2074 value: ".role1 .role2 .role3"
2075 }],
2076 anchor: None,
2077 source: Span {
2078 data: ".role1 .role2 .role3 ",
2079 line: 1,
2080 col: 1,
2081 offset: 0
2082 }
2083 }
2084 );
2085
2086 assert!(mi.item.named_attribute("foo").is_none());
2087 assert!(mi.item.named_or_positional_attribute("foo", 0).is_none());
2088
2089 let roles = mi.item.roles();
2090 let mut roles = roles.iter();
2091 assert_eq!(roles.next().unwrap(), &"role1");
2092 assert_eq!(roles.next().unwrap(), &"role2");
2093 assert_eq!(roles.next().unwrap(), &"role3");
2094 assert!(roles.next().is_none());
2095
2096 assert!(mi.item.block_style().is_none());
2097
2098 assert_eq!(
2099 mi.item.span(),
2100 Span {
2101 data: ".role1 .role2 .role3 ",
2102 line: 1,
2103 col: 1,
2104 offset: 0
2105 }
2106 );
2107
2108 assert_eq!(
2109 mi.after,
2110 Span {
2111 data: "",
2112 line: 1,
2113 col: 22,
2114 offset: 21
2115 }
2116 );
2117 }
2118
2119 #[test]
2120 fn via_named_attribute() {
2121 let p = Parser::default();
2122
2123 let mi = crate::attributes::Attrlist::parse(
2124 crate::Span::new("foo=bar,role=role1"),
2125 &p,
2126 AttrlistContext::Inline,
2127 )
2128 .unwrap_if_no_warnings();
2129
2130 assert_eq!(
2131 mi.item,
2132 Attrlist {
2133 attributes: &[
2134 ElementAttribute {
2135 name: Some("foo"),
2136 shorthand_items: &[],
2137 value: "bar"
2138 },
2139 ElementAttribute {
2140 name: Some("role"),
2141 shorthand_items: &[],
2142 value: "role1"
2143 },
2144 ],
2145 anchor: None,
2146 source: Span {
2147 data: "foo=bar,role=role1",
2148 line: 1,
2149 col: 1,
2150 offset: 0
2151 }
2152 }
2153 );
2154
2155 assert_eq!(
2156 mi.item.named_attribute("foo").unwrap(),
2157 ElementAttribute {
2158 name: Some("foo"),
2159 shorthand_items: &[],
2160 value: "bar"
2161 }
2162 );
2163
2164 assert_eq!(
2165 mi.item.named_attribute("role").unwrap(),
2166 ElementAttribute {
2167 name: Some("role"),
2168 shorthand_items: &[],
2169 value: "role1"
2170 }
2171 );
2172
2173 let roles = mi.item.roles();
2174 let mut roles = roles.iter();
2175 assert_eq!(roles.next().unwrap(), &"role1");
2176 assert!(roles.next().is_none());
2177
2178 assert!(mi.item.block_style().is_none());
2179
2180 assert_eq!(
2181 mi.after,
2182 Span {
2183 data: "",
2184 line: 1,
2185 col: 19,
2186 offset: 18
2187 }
2188 );
2189 }
2190
2191 #[test]
2192 fn multiple_roles_via_named_attribute() {
2193 let p = Parser::default();
2194
2195 let mi = crate::attributes::Attrlist::parse(
2196 crate::Span::new("foo=bar,role=role1 role2 role3 "),
2197 &p,
2198 AttrlistContext::Inline,
2199 )
2200 .unwrap_if_no_warnings();
2201
2202 assert_eq!(
2203 mi.item,
2204 Attrlist {
2205 attributes: &[
2206 ElementAttribute {
2207 name: Some("foo"),
2208 shorthand_items: &[],
2209 value: "bar"
2210 },
2211 ElementAttribute {
2212 name: Some("role"),
2213 shorthand_items: &[],
2214 value: "role1 role2 role3"
2215 },
2216 ],
2217 anchor: None,
2218 source: Span {
2219 data: "foo=bar,role=role1 role2 role3 ",
2220 line: 1,
2221 col: 1,
2222 offset: 0
2223 }
2224 }
2225 );
2226
2227 assert_eq!(
2228 mi.item.named_attribute("foo").unwrap(),
2229 ElementAttribute {
2230 name: Some("foo"),
2231 shorthand_items: &[],
2232 value: "bar"
2233 }
2234 );
2235
2236 assert_eq!(
2237 mi.item.named_attribute("role").unwrap(),
2238 ElementAttribute {
2239 name: Some("role"),
2240 shorthand_items: &[],
2241 value: "role1 role2 role3"
2242 }
2243 );
2244
2245 let roles = mi.item.roles();
2246 let mut roles = roles.iter();
2247 assert_eq!(roles.next().unwrap(), &"role1");
2248 assert_eq!(roles.next().unwrap(), &"role2");
2249 assert_eq!(roles.next().unwrap(), &"role3");
2250 assert!(roles.next().is_none());
2251
2252 assert!(mi.item.block_style().is_none());
2253
2254 assert_eq!(
2255 mi.after,
2256 Span {
2257 data: "",
2258 line: 1,
2259 col: 34,
2260 offset: 33
2261 }
2262 );
2263 }
2264
2265 #[test]
2266 fn shorthand_role_and_named_attribute_role() {
2267 let p = Parser::default();
2268
2269 let mi = crate::attributes::Attrlist::parse(
2270 crate::Span::new("#foo.sh1.sh2,role=na1 na2 na3 "),
2271 &p,
2272 AttrlistContext::Inline,
2273 )
2274 .unwrap_if_no_warnings();
2275
2276 assert_eq!(
2277 mi.item,
2278 Attrlist {
2279 attributes: &[
2280 ElementAttribute {
2281 name: None,
2282 shorthand_items: &["#foo", ".sh1", ".sh2"],
2283 value: "#foo.sh1.sh2"
2284 },
2285 ElementAttribute {
2286 name: Some("role"),
2287 shorthand_items: &[],
2288 value: "na1 na2 na3"
2289 },
2290 ],
2291 anchor: None,
2292 source: Span {
2293 data: "#foo.sh1.sh2,role=na1 na2 na3 ",
2294 line: 1,
2295 col: 1,
2296 offset: 0
2297 }
2298 }
2299 );
2300
2301 assert!(mi.item.named_attribute("foo").is_none());
2302
2303 assert_eq!(
2304 mi.item.named_attribute("role").unwrap(),
2305 ElementAttribute {
2306 name: Some("role"),
2307 shorthand_items: &[],
2308 value: "na1 na2 na3"
2309 }
2310 );
2311
2312 let roles = mi.item.roles();
2313 let mut roles = roles.iter();
2314 assert_eq!(roles.next().unwrap(), &"sh1");
2315 assert_eq!(roles.next().unwrap(), &"sh2");
2316 assert_eq!(roles.next().unwrap(), &"na1");
2317 assert_eq!(roles.next().unwrap(), &"na2");
2318 assert_eq!(roles.next().unwrap(), &"na3");
2319 assert!(roles.next().is_none());
2320
2321 assert!(mi.item.block_style().is_none());
2322
2323 assert_eq!(
2324 mi.after,
2325 Span {
2326 data: "",
2327 line: 1,
2328 col: 33,
2329 offset: 32
2330 }
2331 );
2332 }
2333
2334 #[test]
2335 fn shorthand_only_first_attribute() {
2336 let p = Parser::default();
2337
2338 let mi = crate::attributes::Attrlist::parse(
2339 crate::Span::new("foo,blah.rolename"),
2340 &p,
2341 AttrlistContext::Inline,
2342 )
2343 .unwrap_if_no_warnings();
2344
2345 assert_eq!(
2346 mi.item,
2347 Attrlist {
2348 attributes: &[
2349 ElementAttribute {
2350 name: None,
2351 shorthand_items: &["foo"],
2352 value: "foo"
2353 },
2354 ElementAttribute {
2355 name: None,
2356 shorthand_items: &[],
2357 value: "blah.rolename"
2358 },
2359 ],
2360 anchor: None,
2361 source: Span {
2362 data: "foo,blah.rolename",
2363 line: 1,
2364 col: 1,
2365 offset: 0
2366 }
2367 }
2368 );
2369
2370 let roles = mi.item.roles();
2371 assert_eq!(roles.iter().len(), 0);
2372
2373 assert_eq!(mi.item.block_style().unwrap(), "foo");
2374
2375 assert_eq!(
2376 mi.after,
2377 Span {
2378 data: "",
2379 line: 1,
2380 col: 18,
2381 offset: 17
2382 }
2383 );
2384 }
2385 }
2386
2387 mod options {
2388 use crate::{attributes::AttrlistContext, tests::prelude::*};
2389
2390 #[test]
2391 fn via_shorthand_syntax() {
2392 let p = Parser::default();
2393
2394 let mi = crate::attributes::Attrlist::parse(
2395 crate::Span::new("%option"),
2396 &p,
2397 AttrlistContext::Inline,
2398 )
2399 .unwrap_if_no_warnings();
2400
2401 assert_eq!(
2402 mi.item,
2403 Attrlist {
2404 attributes: &[ElementAttribute {
2405 name: None,
2406 shorthand_items: &["%option"],
2407 value: "%option"
2408 }],
2409 anchor: None,
2410 source: Span {
2411 data: "%option",
2412 line: 1,
2413 col: 1,
2414 offset: 0
2415 }
2416 }
2417 );
2418
2419 assert!(mi.item.named_attribute("foo").is_none());
2420 assert!(mi.item.named_or_positional_attribute("foo", 0).is_none());
2421
2422 let options = mi.item.options();
2423 let mut options = options.iter();
2424 assert_eq!(options.next().unwrap(), &"option",);
2425 assert!(options.next().is_none());
2426
2427 assert!(mi.item.has_option("option"));
2428 assert!(!mi.item.has_option("option1"));
2429
2430 assert_eq!(
2431 mi.item.span(),
2432 Span {
2433 data: "%option",
2434 line: 1,
2435 col: 1,
2436 offset: 0
2437 }
2438 );
2439
2440 assert_eq!(
2441 mi.after,
2442 Span {
2443 data: "",
2444 line: 1,
2445 col: 8,
2446 offset: 7
2447 }
2448 );
2449 }
2450
2451 #[test]
2452 fn multiple_options_via_shorthand_syntax() {
2453 let p = Parser::default();
2454
2455 let mi = crate::attributes::Attrlist::parse(
2456 crate::Span::new("%option1%option2%option3"),
2457 &p,
2458 AttrlistContext::Inline,
2459 )
2460 .unwrap_if_no_warnings();
2461
2462 assert_eq!(
2463 mi.item,
2464 Attrlist {
2465 attributes: &[ElementAttribute {
2466 name: None,
2467 shorthand_items: &["%option1", "%option2", "%option3",],
2468 value: "%option1%option2%option3"
2469 }],
2470 anchor: None,
2471 source: Span {
2472 data: "%option1%option2%option3",
2473 line: 1,
2474 col: 1,
2475 offset: 0
2476 }
2477 }
2478 );
2479
2480 assert!(mi.item.named_attribute("foo").is_none());
2481 assert!(mi.item.named_or_positional_attribute("foo", 0).is_none());
2482
2483 let options = mi.item.options();
2484 let mut options = options.iter();
2485 assert_eq!(options.next().unwrap(), &"option1");
2486 assert_eq!(options.next().unwrap(), &"option2");
2487 assert_eq!(options.next().unwrap(), &"option3");
2488 assert!(options.next().is_none());
2489
2490 assert!(mi.item.has_option("option1"));
2491 assert!(mi.item.has_option("option2"));
2492 assert!(mi.item.has_option("option3"));
2493 assert!(!mi.item.has_option("option4"));
2494
2495 assert_eq!(
2496 mi.item.span(),
2497 Span {
2498 data: "%option1%option2%option3",
2499 line: 1,
2500 col: 1,
2501 offset: 0
2502 }
2503 );
2504
2505 assert_eq!(
2506 mi.after,
2507 Span {
2508 data: "",
2509 line: 1,
2510 col: 25,
2511 offset: 24
2512 }
2513 );
2514 }
2515
2516 #[test]
2517 fn via_options_attribute() {
2518 let p = Parser::default();
2519
2520 let mi = crate::attributes::Attrlist::parse(
2521 crate::Span::new("foo=bar,options=option1"),
2522 &p,
2523 AttrlistContext::Inline,
2524 )
2525 .unwrap_if_no_warnings();
2526
2527 assert_eq!(
2528 mi.item,
2529 Attrlist {
2530 attributes: &[
2531 ElementAttribute {
2532 name: Some("foo"),
2533 shorthand_items: &[],
2534 value: "bar"
2535 },
2536 ElementAttribute {
2537 name: Some("options"),
2538 shorthand_items: &[],
2539 value: "option1"
2540 },
2541 ],
2542 anchor: None,
2543 source: Span {
2544 data: "foo=bar,options=option1",
2545 line: 1,
2546 col: 1,
2547 offset: 0
2548 }
2549 }
2550 );
2551
2552 assert_eq!(
2553 mi.item.named_attribute("foo").unwrap(),
2554 ElementAttribute {
2555 name: Some("foo"),
2556 shorthand_items: &[],
2557 value: "bar"
2558 }
2559 );
2560
2561 assert_eq!(
2562 mi.item.named_attribute("options").unwrap(),
2563 ElementAttribute {
2564 name: Some("options"),
2565 shorthand_items: &[],
2566 value: "option1"
2567 }
2568 );
2569
2570 let options = mi.item.options();
2571 let mut options = options.iter();
2572 assert_eq!(options.next().unwrap(), &"option1");
2573 assert!(options.next().is_none());
2574
2575 assert!(mi.item.has_option("option1"));
2576 assert!(!mi.item.has_option("option2"));
2577
2578 assert_eq!(
2579 mi.after,
2580 Span {
2581 data: "",
2582 line: 1,
2583 col: 24,
2584 offset: 23
2585 }
2586 );
2587 }
2588
2589 #[test]
2590 fn via_opts_attribute() {
2591 let p = Parser::default();
2592
2593 let mi = crate::attributes::Attrlist::parse(
2594 crate::Span::new("foo=bar,opts=option1"),
2595 &p,
2596 AttrlistContext::Inline,
2597 )
2598 .unwrap_if_no_warnings();
2599
2600 assert_eq!(
2601 mi.item,
2602 Attrlist {
2603 attributes: &[
2604 ElementAttribute {
2605 name: Some("foo"),
2606 shorthand_items: &[],
2607 value: "bar"
2608 },
2609 ElementAttribute {
2610 name: Some("opts"),
2611 shorthand_items: &[],
2612 value: "option1"
2613 },
2614 ],
2615 anchor: None,
2616 source: Span {
2617 data: "foo=bar,opts=option1",
2618 line: 1,
2619 col: 1,
2620 offset: 0
2621 }
2622 }
2623 );
2624
2625 assert_eq!(
2626 mi.item.named_attribute("foo").unwrap(),
2627 ElementAttribute {
2628 name: Some("foo"),
2629 shorthand_items: &[],
2630 value: "bar"
2631 }
2632 );
2633
2634 assert_eq!(
2635 mi.item.named_attribute("opts").unwrap(),
2636 ElementAttribute {
2637 name: Some("opts"),
2638 shorthand_items: &[],
2639 value: "option1"
2640 }
2641 );
2642
2643 let options = mi.item.options();
2644 let mut options = options.iter();
2645 assert_eq!(options.next().unwrap(), &"option1");
2646 assert!(options.next().is_none());
2647
2648 assert!(!mi.item.has_option("option"));
2649 assert!(mi.item.has_option("option1"));
2650 assert!(!mi.item.has_option("option2"));
2651
2652 assert_eq!(
2653 mi.after,
2654 Span {
2655 data: "",
2656 line: 1,
2657 col: 21,
2658 offset: 20
2659 }
2660 );
2661 }
2662
2663 #[test]
2664 fn multiple_options_via_named_attribute() {
2665 let p = Parser::default();
2666
2667 let mi = crate::attributes::Attrlist::parse(
2668 crate::Span::new("foo=bar,options=\"option1,option2,option3\""),
2669 &p,
2670 AttrlistContext::Inline,
2671 )
2672 .unwrap_if_no_warnings();
2673
2674 assert_eq!(
2675 mi.item,
2676 Attrlist {
2677 attributes: &[
2678 ElementAttribute {
2679 name: Some("foo"),
2680 shorthand_items: &[],
2681 value: "bar"
2682 },
2683 ElementAttribute {
2684 name: Some("options"),
2685 shorthand_items: &[],
2686 value: "option1,option2,option3"
2687 },
2688 ],
2689 anchor: None,
2690 source: Span {
2691 data: "foo=bar,options=\"option1,option2,option3\"",
2692 line: 1,
2693 col: 1,
2694 offset: 0
2695 }
2696 }
2697 );
2698
2699 assert_eq!(
2700 mi.item.named_attribute("foo").unwrap(),
2701 ElementAttribute {
2702 name: Some("foo"),
2703 shorthand_items: &[],
2704 value: "bar"
2705 }
2706 );
2707
2708 assert_eq!(
2709 mi.item.named_attribute("options").unwrap(),
2710 ElementAttribute {
2711 name: Some("options"),
2712 shorthand_items: &[],
2713 value: "option1,option2,option3"
2714 }
2715 );
2716
2717 let options = mi.item.options();
2718 let mut options = options.iter();
2719 assert_eq!(options.next().unwrap(), &"option1");
2720 assert_eq!(options.next().unwrap(), &"option2");
2721 assert_eq!(options.next().unwrap(), &"option3");
2722 assert!(options.next().is_none());
2723
2724 assert!(mi.item.has_option("option1"));
2725 assert!(mi.item.has_option("option2"));
2726 assert!(mi.item.has_option("option3"));
2727 assert!(!mi.item.has_option("option4"));
2728
2729 assert_eq!(
2730 mi.after,
2731 Span {
2732 data: "",
2733 line: 1,
2734 col: 42,
2735 offset: 41
2736 }
2737 );
2738 }
2739
2740 #[test]
2741 fn shorthand_option_and_named_attribute_option() {
2742 let p = Parser::default();
2743
2744 let mi = crate::attributes::Attrlist::parse(
2745 crate::Span::new("#foo%sh1%sh2,options=\"na1,na2,na3\""),
2746 &p,
2747 AttrlistContext::Inline,
2748 )
2749 .unwrap_if_no_warnings();
2750
2751 assert_eq!(
2752 mi.item,
2753 Attrlist {
2754 attributes: &[
2755 ElementAttribute {
2756 name: None,
2757 shorthand_items: &["#foo", "%sh1", "%sh2"],
2758 value: "#foo%sh1%sh2"
2759 },
2760 ElementAttribute {
2761 name: Some("options"),
2762 shorthand_items: &[],
2763 value: "na1,na2,na3"
2764 },
2765 ],
2766 anchor: None,
2767 source: Span {
2768 data: "#foo%sh1%sh2,options=\"na1,na2,na3\"",
2769 line: 1,
2770 col: 1,
2771 offset: 0
2772 }
2773 }
2774 );
2775
2776 assert!(mi.item.named_attribute("foo").is_none(),);
2777
2778 assert_eq!(
2779 mi.item.named_attribute("options").unwrap(),
2780 ElementAttribute {
2781 name: Some("options"),
2782 shorthand_items: &[],
2783 value: "na1,na2,na3"
2784 }
2785 );
2786
2787 let options = mi.item.options();
2788 let mut options = options.iter();
2789 assert_eq!(options.next().unwrap(), &"sh1");
2790 assert_eq!(options.next().unwrap(), &"sh2");
2791 assert_eq!(options.next().unwrap(), &"na1");
2792 assert_eq!(options.next().unwrap(), &"na2");
2793 assert_eq!(options.next().unwrap(), &"na3");
2794 assert!(options.next().is_none(),);
2795
2796 assert!(mi.item.has_option("sh1"));
2797 assert!(mi.item.has_option("sh2"));
2798 assert!(!mi.item.has_option("sh3"));
2799 assert!(mi.item.has_option("na1"));
2800 assert!(mi.item.has_option("na2"));
2801 assert!(mi.item.has_option("na3"));
2802 assert!(!mi.item.has_option("na4"));
2803
2804 assert_eq!(
2805 mi.after,
2806 Span {
2807 data: "",
2808 line: 1,
2809 col: 35,
2810 offset: 34
2811 }
2812 );
2813 }
2814
2815 #[test]
2816 fn shorthand_only_first_attribute() {
2817 let p = Parser::default();
2818
2819 let mi = crate::attributes::Attrlist::parse(
2820 crate::Span::new("foo,blah%option"),
2821 &p,
2822 AttrlistContext::Inline,
2823 )
2824 .unwrap_if_no_warnings();
2825
2826 assert_eq!(
2827 mi.item,
2828 Attrlist {
2829 attributes: &[
2830 ElementAttribute {
2831 name: None,
2832 shorthand_items: &["foo"],
2833 value: "foo"
2834 },
2835 ElementAttribute {
2836 name: None,
2837 shorthand_items: &[],
2838 value: "blah%option"
2839 },
2840 ],
2841 anchor: None,
2842 source: Span {
2843 data: "foo,blah%option",
2844 line: 1,
2845 col: 1,
2846 offset: 0
2847 }
2848 }
2849 );
2850
2851 let options = mi.item.options();
2852 assert_eq!(options.iter().len(), 0);
2853
2854 assert!(!mi.item.has_option("option"));
2855
2856 assert_eq!(
2857 mi.after,
2858 Span {
2859 data: "",
2860 line: 1,
2861 col: 16,
2862 offset: 15
2863 }
2864 );
2865 }
2866 }
2867
2868 #[test]
2869 fn block_style() {
2870 let p = Parser::default();
2871
2872 let mi = crate::attributes::Attrlist::parse(
2873 crate::Span::new("blah#goals"),
2874 &p,
2875 AttrlistContext::Inline,
2876 )
2877 .unwrap_if_no_warnings();
2878
2879 let attrlist = mi.item;
2880 assert_eq!(attrlist.block_style().unwrap(), "blah");
2881 }
2882
2883 #[test]
2884 fn err_double_comma() {
2885 let p = Parser::default();
2886
2887 let maw = crate::attributes::Attrlist::parse(
2888 crate::Span::new("alt=Sunset,width=300,,height=400"),
2889 &p,
2890 AttrlistContext::Inline,
2891 );
2892
2893 let mi = maw.item.clone();
2894
2895 assert_eq!(
2896 mi.item,
2897 Attrlist {
2898 attributes: &[
2899 ElementAttribute {
2900 name: Some("alt"),
2901 shorthand_items: &[],
2902 value: "Sunset"
2903 },
2904 ElementAttribute {
2905 name: Some("width"),
2906 shorthand_items: &[],
2907 value: "300"
2908 },
2909 ElementAttribute {
2910 name: Some("height"),
2911 shorthand_items: &[],
2912 value: "400"
2913 },
2914 ],
2915 anchor: None,
2916 source: Span {
2917 data: "alt=Sunset,width=300,,height=400",
2918 line: 1,
2919 col: 1,
2920 offset: 0,
2921 }
2922 }
2923 );
2924
2925 assert_eq!(
2926 mi.after,
2927 Span {
2928 data: "",
2929 line: 1,
2930 col: 33,
2931 offset: 32,
2932 }
2933 );
2934
2935 assert_eq!(
2936 maw.warnings,
2937 vec![Warning {
2938 source: Span {
2939 data: "alt=Sunset,width=300,,height=400",
2940 line: 1,
2941 col: 1,
2942 offset: 0,
2943 },
2944 warning: WarningType::EmptyAttributeValue,
2945 }]
2946 );
2947 }
2948
2949 #[test]
2950 fn applies_attribute_substitution_before_parsing() {
2951 let p = Parser::default().with_intrinsic_attribute(
2952 "sunset_dimensions",
2953 "300,400",
2954 ModificationContext::Anywhere,
2955 );
2956
2957 let mi = crate::attributes::Attrlist::parse(
2958 crate::Span::new("Sunset,{sunset_dimensions}"),
2959 &p,
2960 AttrlistContext::Inline,
2961 )
2962 .unwrap_if_no_warnings();
2963
2964 assert_eq!(
2965 mi.item,
2966 Attrlist {
2967 attributes: &[
2968 ElementAttribute {
2969 name: None,
2970 shorthand_items: &["Sunset"],
2971 value: "Sunset"
2972 },
2973 ElementAttribute {
2974 name: None,
2975 shorthand_items: &[],
2976 value: "300"
2977 },
2978 ElementAttribute {
2979 name: None,
2980 shorthand_items: &[],
2981 value: "400"
2982 }
2983 ],
2984 anchor: None,
2985 source: Span {
2986 data: "Sunset,{sunset_dimensions}",
2987 line: 1,
2988 col: 1,
2989 offset: 0
2990 }
2991 }
2992 );
2993
2994 assert!(mi.item.named_attribute("foo").is_none());
2995 assert!(mi.item.nth_attribute(0).is_none());
2996 assert!(mi.item.named_or_positional_attribute("foo", 0).is_none());
2997
2998 assert!(mi.item.id().is_none());
2999 assert!(mi.item.roles().is_empty());
3000 assert_eq!(mi.item.block_style().unwrap(), "Sunset");
3001
3002 assert_eq!(
3003 mi.item.nth_attribute(1).unwrap(),
3004 ElementAttribute {
3005 name: None,
3006 shorthand_items: &["Sunset"],
3007 value: "Sunset"
3008 }
3009 );
3010
3011 assert_eq!(
3012 mi.item.named_or_positional_attribute("alt", 1).unwrap(),
3013 ElementAttribute {
3014 name: None,
3015 shorthand_items: &["Sunset"],
3016 value: "Sunset"
3017 }
3018 );
3019
3020 assert_eq!(
3021 mi.item.nth_attribute(2).unwrap(),
3022 ElementAttribute {
3023 name: None,
3024 shorthand_items: &[],
3025 value: "300"
3026 }
3027 );
3028
3029 assert_eq!(
3030 mi.item.named_or_positional_attribute("width", 2).unwrap(),
3031 ElementAttribute {
3032 name: None,
3033 shorthand_items: &[],
3034 value: "300"
3035 }
3036 );
3037
3038 assert_eq!(
3039 mi.item.nth_attribute(3).unwrap(),
3040 ElementAttribute {
3041 name: None,
3042 shorthand_items: &[],
3043 value: "400"
3044 }
3045 );
3046
3047 assert_eq!(
3048 mi.item.named_or_positional_attribute("height", 3).unwrap(),
3049 ElementAttribute {
3050 name: None,
3051 shorthand_items: &[],
3052 value: "400"
3053 }
3054 );
3055
3056 assert!(mi.item.nth_attribute(4).is_none());
3057 assert!(mi.item.named_or_positional_attribute("height", 4).is_none());
3058 assert!(mi.item.nth_attribute(42).is_none());
3059
3060 assert_eq!(
3061 mi.item.span(),
3062 Span {
3063 data: "Sunset,{sunset_dimensions}",
3064 line: 1,
3065 col: 1,
3066 offset: 0,
3067 }
3068 );
3069
3070 assert_eq!(
3071 mi.after,
3072 Span {
3073 data: "",
3074 line: 1,
3075 col: 27,
3076 offset: 26,
3077 }
3078 );
3079 }
3080
3081 #[test]
3082 fn ignores_unknown_attribute_when_applying_attribution_substitution() {
3083 let p = Parser::default().with_intrinsic_attribute(
3084 "sunset_dimensions",
3085 "300,400",
3086 ModificationContext::Anywhere,
3087 );
3088
3089 let mi = crate::attributes::Attrlist::parse(
3090 crate::Span::new("Sunset,{not_sunset_dimensions}"),
3091 &p,
3092 AttrlistContext::Inline,
3093 )
3094 .unwrap_if_no_warnings();
3095
3096 assert_eq!(
3097 mi.item,
3098 Attrlist {
3099 attributes: &[
3100 ElementAttribute {
3101 name: None,
3102 shorthand_items: &["Sunset"],
3103 value: "Sunset"
3104 },
3105 ElementAttribute {
3106 name: None,
3107 shorthand_items: &[],
3108 value: "{not_sunset_dimensions}"
3109 },
3110 ],
3111 anchor: None,
3112 source: Span {
3113 data: "Sunset,{not_sunset_dimensions}",
3114 line: 1,
3115 col: 1,
3116 offset: 0
3117 }
3118 }
3119 );
3120
3121 assert!(mi.item.named_attribute("foo").is_none());
3122 assert!(mi.item.nth_attribute(0).is_none());
3123 assert!(mi.item.named_or_positional_attribute("foo", 0).is_none());
3124
3125 assert!(mi.item.id().is_none());
3126 assert!(mi.item.roles().is_empty());
3127 assert_eq!(mi.item.block_style().unwrap(), "Sunset");
3128
3129 assert_eq!(
3130 mi.item.nth_attribute(1).unwrap(),
3131 ElementAttribute {
3132 name: None,
3133 shorthand_items: &["Sunset"],
3134 value: "Sunset"
3135 }
3136 );
3137
3138 assert_eq!(
3139 mi.item.named_or_positional_attribute("alt", 1).unwrap(),
3140 ElementAttribute {
3141 name: None,
3142 shorthand_items: &["Sunset"],
3143 value: "Sunset"
3144 }
3145 );
3146
3147 assert_eq!(
3148 mi.item.nth_attribute(2).unwrap(),
3149 ElementAttribute {
3150 name: None,
3151 shorthand_items: &[],
3152 value: "{not_sunset_dimensions}"
3153 }
3154 );
3155
3156 assert_eq!(
3157 mi.item.named_or_positional_attribute("width", 2).unwrap(),
3158 ElementAttribute {
3159 name: None,
3160 shorthand_items: &[],
3161 value: "{not_sunset_dimensions}"
3162 }
3163 );
3164
3165 assert!(mi.item.nth_attribute(3).is_none());
3166 assert!(mi.item.named_or_positional_attribute("height", 3).is_none());
3167 assert!(mi.item.nth_attribute(42).is_none());
3168
3169 assert_eq!(
3170 mi.item.span(),
3171 Span {
3172 data: "Sunset,{not_sunset_dimensions}",
3173 line: 1,
3174 col: 1,
3175 offset: 0,
3176 }
3177 );
3178
3179 assert_eq!(
3180 mi.after,
3181 Span {
3182 data: "",
3183 line: 1,
3184 col: 31,
3185 offset: 30,
3186 }
3187 );
3188 }
3189
3190 #[test]
3191 fn impl_debug() {
3192 let p = Parser::default();
3193
3194 let mi = crate::attributes::Attrlist::parse(
3195 crate::Span::new("Sunset,300,400"),
3196 &p,
3197 AttrlistContext::Inline,
3198 )
3199 .unwrap_if_no_warnings();
3200
3201 let attrlist = mi.item;
3202
3203 assert_eq!(
3204 format!("{attrlist:#?}"),
3205 r#"Attrlist {
3206 attributes: &[
3207 ElementAttribute {
3208 name: None,
3209 value: "Sunset",
3210 shorthand_item_indices: [
3211 0,
3212 ],
3213 },
3214 ElementAttribute {
3215 name: None,
3216 value: "300",
3217 shorthand_item_indices: [],
3218 },
3219 ElementAttribute {
3220 name: None,
3221 value: "400",
3222 shorthand_item_indices: [],
3223 },
3224 ],
3225 anchor: None,
3226 source: Span {
3227 data: "Sunset,300,400",
3228 line: 1,
3229 col: 1,
3230 offset: 0,
3231 },
3232}"#
3233 );
3234 }
3235}