1use crate::{
2 Parser, Span,
3 attributes::AttrlistContext,
4 content::{Content, SubstitutionGroup},
5 span::MatchedItem,
6 strings::CowStr,
7 warnings::WarningType,
8};
9
10#[derive(Clone, Debug, Eq, PartialEq)]
18pub struct ElementAttribute<'src> {
19 name: Option<CowStr<'src>>,
20 value: CowStr<'src>,
21 shorthand_item_indices: Vec<usize>,
22}
23
24impl<'src> ElementAttribute<'src> {
25 pub(crate) fn parse(
26 source_text: &CowStr<'src>,
27 start_index: usize,
28 parser: &Parser,
29 mut parse_shorthand: ParseShorthand,
30 attrlist_context: AttrlistContext,
31 ) -> (Self, usize, Vec<WarningType>) {
32 let mut warnings: Vec<WarningType> = vec![];
33
34 let (name, value, shorthand_item_indices, offset) = {
35 let mut source = Span::new(source_text.as_ref());
36 source = source.discard(start_index);
37
38 let (name, after): (Option<Span<'_>>, Span) = match source.take_attr_name() {
39 Some(name) => {
40 let space = name.after.take_whitespace_with_newline();
41 match space.after.take_prefix("=") {
42 Some(equals) => {
43 let space = equals.after.take_whitespace_with_newline();
44 if space.after.is_empty() || space.after.starts_with(',') {
45 (None, source)
47 } else {
48 (Some(name.item), space.after)
49 }
50 }
51 None => (None, source),
52 }
53 }
54 None => (None, source),
55 };
56
57 let after = after.take_whitespace_with_newline().after;
58 let first_char = after.data().chars().next();
59
60 let value = match first_char {
61 Some('\'') | Some('"') => match after.take_quoted_string() {
62 Some(v) => {
63 parse_shorthand = ParseShorthand(false);
64 v
65 }
66 None => {
67 warnings.push(WarningType::AttributeValueMissingTerminatingQuote);
68 after.take_while(|c| c != ',').trim_item_trailing_spaces()
69 }
70 },
71 _ => after.take_while(|c| c != ',').trim_item_trailing_spaces(),
72 };
73
74 let after = value.after;
75 let mut value = cowstr_from_source_and_span(source_text, &value.item);
76
77 if let Some(first) = first_char
78 && (first == '\'' || first == '\"')
79 {
80 let escaped_quote = format!("\\{first}");
81 let mut new_value = value.replace(&escaped_quote, &first.to_string());
82
83 if first == '\'' && attrlist_context == AttrlistContext::Block {
84 let span = Span::new(&new_value);
85 let mut content = Content::from(span);
86 SubstitutionGroup::Normal.apply(&mut content, parser, None);
87
88 if content.rendered.as_ref() != new_value {
89 new_value = content.rendered.to_string();
90 }
91 }
92
93 if new_value != *value {
94 value = CowStr::from(new_value);
95 }
96 }
97
98 let shorthand_item_indices = if name.is_none() && parse_shorthand.0 {
99 parse_shorthand_items(&value, &mut warnings)
100 } else {
101 vec![]
102 };
103
104 let name = name.map(|name| cowstr_from_source_and_span(source_text, &name));
105
106 (name, value, shorthand_item_indices, after.byte_offset())
107 };
108
109 (
110 Self {
111 name,
112 value,
113 shorthand_item_indices,
114 },
115 offset,
116 warnings,
117 )
118 }
119
120 pub fn name(&'src self) -> Option<&'src str> {
122 if let Some(ref name) = self.name {
123 Some(name.as_ref())
124 } else {
125 None
126 }
127 }
128
129 pub fn shorthand_items(&'src self) -> Vec<&'src str> {
135 let mut result = vec![];
136 let value = self.value.as_ref();
137
138 let mut iter = self.shorthand_item_indices.iter().peekable();
139
140 loop {
141 let Some(curr) = iter.next() else { break };
142 let mut next_item = if let Some(next) = iter.peek() {
143 &value[*curr..**next]
144 } else {
145 &value[*curr..]
146 };
147
148 if next_item == "#" || next_item == "." || next_item == "%" {
149 continue;
150 }
151
152 next_item = next_item.trim_end();
153
154 if !next_item.is_empty() {
155 result.push(next_item);
156 }
157 }
158
159 result
160 }
161
162 pub fn block_style(&'src self) -> Option<&'src str> {
164 self.shorthand_items()
165 .first()
166 .filter(|v| !v.chars().any(is_shorthand_delimiter))
167 .cloned()
168 }
169
170 pub fn id(&'src self) -> Option<&'src str> {
204 self.shorthand_items()
205 .iter()
206 .find(|v| v.starts_with('#'))
207 .map(|v| &v[1..])
208 }
209
210 pub fn roles(&'src self) -> Vec<&'src str> {
232 self.shorthand_items()
233 .iter()
234 .filter(|span| span.starts_with('.'))
235 .map(|span| &span[1..])
236 .collect()
237 }
238
239 pub fn options(&'src self) -> Vec<&'src str> {
304 self.shorthand_items()
305 .iter()
306 .filter(|v| v.starts_with('%'))
307 .map(|v| &v[1..])
308 .collect()
309 }
310
311 pub fn value(&'src self) -> &'src str {
316 self.value.as_ref()
317 }
318}
319
320fn parse_shorthand_items(source: &str, warnings: &mut Vec<WarningType>) -> Vec<usize> {
321 let mut shorthand_item_indices: Vec<usize> = vec![];
322 let mut span = Span::new(source);
323
324 if let Some(block_style_pr) = span.split_at_match_non_empty(is_shorthand_delimiter) {
326 shorthand_item_indices.push(block_style_pr.item.discard_whitespace().byte_offset());
327
328 span = block_style_pr.after;
329 }
330
331 while !span.is_empty() {
332 let after_delimiter = span.discard(1);
334
335 match after_delimiter.position(is_shorthand_delimiter) {
336 None => {
337 if after_delimiter.is_empty() {
338 warnings.push(WarningType::EmptyShorthandItem);
339 shorthand_item_indices.push(span.byte_offset());
340 span = after_delimiter;
341 } else {
342 shorthand_item_indices.push(span.byte_offset());
343 span = span.discard_all();
344 }
345 }
346
347 Some(0) => {
348 shorthand_item_indices.push(span.byte_offset());
349 warnings.push(WarningType::EmptyShorthandItem);
350 span = after_delimiter;
351 }
352
353 Some(index) => {
354 let mi: MatchedItem<Span> = span.into_parse_result(index + 1);
355 shorthand_item_indices.push(span.byte_offset());
356 span = mi.after;
357 }
358 }
359 }
360
361 shorthand_item_indices
362}
363
364fn is_shorthand_delimiter(c: char) -> bool {
365 c == '#' || c == '%' || c == '.'
366}
367
368#[derive(Clone, Debug)]
369pub(crate) struct ParseShorthand(pub bool);
370
371fn cowstr_from_source_and_span<'src>(source: &CowStr<'src>, span: &Span<'_>) -> CowStr<'src> {
372 if let CowStr::Borrowed(source) = source {
373 let borrowed: Span<'src> = Span::new(source)
374 .discard(span.byte_offset())
375 .slice_to(..span.len());
376
377 CowStr::Borrowed(borrowed.data())
378 } else {
379 CowStr::from(span.data().to_string())
380 }
381}
382
383#[cfg(test)]
384mod tests {
385 #![allow(clippy::unwrap_used)]
386 use pretty_assertions_sorted::assert_eq;
387
388 use crate::{
389 Parser,
390 attributes::{AttrlistContext, element_attribute::ParseShorthand},
391 strings::CowStr,
392 tests::prelude::*,
393 };
394
395 #[test]
396 fn impl_clone() {
397 let p = Parser::default();
399
400 let b1 = crate::attributes::ElementAttribute::parse(
401 &CowStr::from("abc"),
402 0,
403 &p,
404 ParseShorthand(false),
405 AttrlistContext::Inline,
406 )
407 .0;
408
409 let b2 = b1.clone();
410
411 assert_eq!(b1, b2);
412 }
413
414 #[test]
415 fn empty_source() {
416 let p = Parser::default();
417
418 let (element_attr, offset, warning_types) = crate::attributes::ElementAttribute::parse(
419 &CowStr::from(""),
420 0,
421 &p,
422 ParseShorthand(false),
423 AttrlistContext::Inline,
424 );
425
426 assert!(warning_types.is_empty());
427
428 assert_eq!(
429 element_attr,
430 ElementAttribute {
431 name: None,
432 shorthand_items: &[],
433 value: "",
434 }
435 );
436
437 assert!(element_attr.name().is_none());
438 assert!(element_attr.block_style().is_none());
439 assert!(element_attr.id().is_none());
440 assert!(element_attr.roles().is_empty());
441 assert!(element_attr.options().is_empty());
442
443 assert_eq!(offset, 0);
444 }
445
446 #[test]
447 fn only_spaces() {
448 let p = Parser::default();
449
450 let (element_attr, offset, warning_types) = crate::attributes::ElementAttribute::parse(
451 &CowStr::from(" "),
452 0,
453 &p,
454 ParseShorthand(false),
455 AttrlistContext::Inline,
456 );
457
458 assert!(warning_types.is_empty());
459
460 assert_eq!(
461 element_attr,
462 ElementAttribute {
463 name: None,
464 shorthand_items: &[],
465 value: "",
466 }
467 );
468
469 assert!(element_attr.name().is_none());
470 assert!(element_attr.block_style().is_none());
471 assert!(element_attr.id().is_none());
472 assert!(element_attr.roles().is_empty());
473 assert!(element_attr.options().is_empty());
474
475 assert_eq!(offset, 3);
476 }
477
478 #[test]
479 fn unquoted_and_unnamed_value() {
480 let p = Parser::default();
481
482 let (element_attr, offset, warning_types) = crate::attributes::ElementAttribute::parse(
483 &CowStr::from("abc"),
484 0,
485 &p,
486 ParseShorthand(false),
487 AttrlistContext::Inline,
488 );
489
490 assert!(warning_types.is_empty());
491
492 assert_eq!(
493 element_attr,
494 ElementAttribute {
495 name: None,
496 shorthand_items: &[],
497 value: "abc",
498 }
499 );
500
501 assert!(element_attr.name().is_none());
502 assert!(element_attr.block_style().is_none());
503 assert!(element_attr.id().is_none());
504 assert!(element_attr.roles().is_empty());
505 assert!(element_attr.options().is_empty());
506
507 assert_eq!(offset, 3);
508 }
509
510 #[test]
511 fn unquoted_stops_at_comma() {
512 let p = Parser::default();
513
514 let (element_attr, offset, warning_types) = crate::attributes::ElementAttribute::parse(
515 &CowStr::from("abc,def"),
516 0,
517 &p,
518 ParseShorthand(false),
519 AttrlistContext::Inline,
520 );
521
522 assert!(warning_types.is_empty());
523
524 assert_eq!(
525 element_attr,
526 ElementAttribute {
527 name: None,
528 shorthand_items: &[],
529 value: "abc",
530 }
531 );
532
533 assert!(element_attr.name().is_none());
534 assert!(element_attr.block_style().is_none());
535 assert!(element_attr.id().is_none());
536 assert!(element_attr.roles().is_empty());
537 assert!(element_attr.options().is_empty());
538
539 assert_eq!(offset, 3);
540 }
541
542 mod quoted_string {
543 use pretty_assertions_sorted::assert_eq;
544
545 use crate::{
546 Parser,
547 attributes::{AttrlistContext, element_attribute::ParseShorthand},
548 parser::ModificationContext,
549 strings::CowStr,
550 tests::prelude::*,
551 warnings::WarningType,
552 };
553
554 #[test]
555 fn err_unterminated_double_quote() {
556 let p = Parser::default();
557
558 let (element_attr, offset, warning_types) = crate::attributes::ElementAttribute::parse(
559 &CowStr::from("\"xyz"),
560 0,
561 &p,
562 ParseShorthand(false),
563 AttrlistContext::Inline,
564 );
565
566 assert_eq!(
567 element_attr,
568 ElementAttribute {
569 name: None,
570 shorthand_items: &[],
571 value: "\"xyz"
572 }
573 );
574
575 assert!(element_attr.name().is_none());
576 assert!(element_attr.block_style().is_none());
577 assert!(element_attr.id().is_none());
578 assert!(element_attr.roles().is_empty());
579 assert!(element_attr.options().is_empty());
580
581 assert_eq!(offset, 4);
582
583 assert_eq!(
584 warning_types,
585 vec![WarningType::AttributeValueMissingTerminatingQuote]
586 );
587 }
588
589 #[test]
590 fn err_unterminated_double_quote_ends_at_comma() {
591 let p = Parser::default();
592
593 let (element_attr, offset, warning_types) = crate::attributes::ElementAttribute::parse(
594 &CowStr::from("\"xyz,abc"),
595 0,
596 &p,
597 ParseShorthand(false),
598 AttrlistContext::Inline,
599 );
600
601 assert_eq!(
602 element_attr,
603 ElementAttribute {
604 name: None,
605 shorthand_items: &[],
606 value: "\"xyz"
607 }
608 );
609
610 assert!(element_attr.name().is_none());
611 assert!(element_attr.block_style().is_none());
612 assert!(element_attr.id().is_none());
613 assert!(element_attr.roles().is_empty());
614 assert!(element_attr.options().is_empty());
615
616 assert_eq!(offset, 4);
617 assert_eq!(
618 warning_types,
619 vec![WarningType::AttributeValueMissingTerminatingQuote]
620 );
621 }
622
623 #[test]
624 fn double_quoted_string() {
625 let p = Parser::default();
626
627 let (element_attr, offset, warning_types) = crate::attributes::ElementAttribute::parse(
628 &CowStr::from("\"abc\"def"),
629 0,
630 &p,
631 ParseShorthand(false),
632 AttrlistContext::Inline,
633 );
634
635 assert!(warning_types.is_empty());
636
637 assert_eq!(
638 element_attr,
639 ElementAttribute {
640 name: None,
641 shorthand_items: &[],
642 value: "abc"
643 }
644 );
645
646 assert!(element_attr.name().is_none());
647 assert!(element_attr.block_style().is_none());
648 assert!(element_attr.id().is_none());
649 assert!(element_attr.roles().is_empty());
650 assert!(element_attr.options().is_empty());
651
652 assert_eq!(offset, 5);
653 }
654
655 #[test]
656 fn double_quoted_with_escape() {
657 let p = Parser::default();
658
659 let (element_attr, offset, warning_types) = crate::attributes::ElementAttribute::parse(
660 &CowStr::from("\"a\\\"bc\"def"),
661 0,
662 &p,
663 ParseShorthand(false),
664 AttrlistContext::Inline,
665 );
666
667 assert!(warning_types.is_empty());
668
669 assert_eq!(
670 element_attr,
671 ElementAttribute {
672 name: None,
673 shorthand_items: &[],
674 value: "a\"bc"
675 }
676 );
677
678 assert!(element_attr.name().is_none());
679 assert!(element_attr.block_style().is_none());
680 assert!(element_attr.id().is_none());
681 assert!(element_attr.roles().is_empty());
682 assert!(element_attr.options().is_empty());
683
684 assert_eq!(offset, 7);
685 }
686
687 #[test]
688 fn double_quoted_with_single_quote() {
689 let p = Parser::default();
690
691 let (element_attr, offset, warning_types) = crate::attributes::ElementAttribute::parse(
692 &CowStr::from("\"a'bc\"def"),
693 0,
694 &p,
695 ParseShorthand(false),
696 AttrlistContext::Inline,
697 );
698
699 assert!(warning_types.is_empty());
700
701 assert_eq!(
702 element_attr,
703 ElementAttribute {
704 name: None,
705 shorthand_items: &[],
706 value: "a'bc"
707 }
708 );
709
710 assert!(element_attr.name().is_none());
711 assert!(element_attr.block_style().is_none());
712 assert!(element_attr.id().is_none());
713 assert!(element_attr.roles().is_empty());
714 assert!(element_attr.options().is_empty());
715
716 assert_eq!(offset, 6);
717 }
718
719 #[test]
720 fn err_unterminated_single_quote() {
721 let p = Parser::default();
722
723 let (element_attr, offset, warning_types) = crate::attributes::ElementAttribute::parse(
724 &CowStr::from("\'xyz"),
725 0,
726 &p,
727 ParseShorthand(false),
728 AttrlistContext::Inline,
729 );
730
731 assert_eq!(
732 element_attr,
733 ElementAttribute {
734 name: None,
735 shorthand_items: &[],
736 value: "\'xyz"
737 }
738 );
739
740 assert!(element_attr.name().is_none());
741 assert!(element_attr.block_style().is_none());
742 assert!(element_attr.id().is_none());
743 assert!(element_attr.roles().is_empty());
744 assert!(element_attr.options().is_empty());
745
746 assert_eq!(offset, 4);
747
748 assert_eq!(
749 warning_types,
750 vec![WarningType::AttributeValueMissingTerminatingQuote]
751 );
752 }
753
754 #[test]
755 fn err_unterminated_single_quote_ends_at_comma() {
756 let p = Parser::default();
757
758 let (element_attr, offset, warning_types) = crate::attributes::ElementAttribute::parse(
759 &CowStr::from("\'xyz,abc"),
760 0,
761 &p,
762 ParseShorthand(false),
763 AttrlistContext::Inline,
764 );
765
766 assert_eq!(
767 element_attr,
768 ElementAttribute {
769 name: None,
770 shorthand_items: &[],
771 value: "\'xyz"
772 }
773 );
774
775 assert!(element_attr.name().is_none());
776 assert!(element_attr.block_style().is_none());
777 assert!(element_attr.id().is_none());
778 assert!(element_attr.roles().is_empty());
779 assert!(element_attr.options().is_empty());
780
781 assert_eq!(offset, 4);
782 assert_eq!(
783 warning_types,
784 vec![WarningType::AttributeValueMissingTerminatingQuote]
785 );
786 }
787
788 #[test]
789 fn single_quoted_string() {
790 let p = Parser::default();
791
792 let (element_attr, offset, warning_types) = crate::attributes::ElementAttribute::parse(
793 &CowStr::from("'abc'def"),
794 0,
795 &p,
796 ParseShorthand(false),
797 AttrlistContext::Inline,
798 );
799
800 assert!(warning_types.is_empty());
801
802 assert_eq!(
803 element_attr,
804 ElementAttribute {
805 name: None,
806 shorthand_items: &[],
807 value: "abc"
808 }
809 );
810
811 assert!(element_attr.name().is_none());
812 assert!(element_attr.block_style().is_none());
813 assert!(element_attr.id().is_none());
814 assert!(element_attr.roles().is_empty());
815 assert!(element_attr.options().is_empty());
816
817 assert_eq!(offset, 5);
818 }
819
820 #[test]
821 fn single_quoted_with_escape() {
822 let p = Parser::default();
823
824 let (element_attr, offset, warning_types) = crate::attributes::ElementAttribute::parse(
825 &CowStr::from("'a\\'bc'def"),
826 0,
827 &p,
828 ParseShorthand(false),
829 AttrlistContext::Inline,
830 );
831
832 assert!(warning_types.is_empty());
833
834 assert_eq!(
835 element_attr,
836 ElementAttribute {
837 name: None,
838 shorthand_items: &[],
839 value: "a'bc"
840 }
841 );
842
843 assert!(element_attr.name().is_none());
844 assert!(element_attr.block_style().is_none());
845 assert!(element_attr.id().is_none());
846 assert!(element_attr.roles().is_empty());
847 assert!(element_attr.options().is_empty());
848
849 assert_eq!(offset, 7);
850 }
851
852 #[test]
853 fn single_quoted_with_double_quote() {
854 let p = Parser::default();
855
856 let (element_attr, offset, warning_types) = crate::attributes::ElementAttribute::parse(
857 &CowStr::from("'a\"bc'def"),
858 0,
859 &p,
860 ParseShorthand(false),
861 AttrlistContext::Inline,
862 );
863
864 assert!(warning_types.is_empty());
865
866 assert_eq!(
867 element_attr,
868 ElementAttribute {
869 name: None,
870 shorthand_items: &[],
871 value: "a\"bc"
872 }
873 );
874
875 assert!(element_attr.name().is_none());
876 assert!(element_attr.block_style().is_none());
877 assert!(element_attr.id().is_none());
878 assert!(element_attr.roles().is_empty());
879 assert!(element_attr.options().is_empty());
880
881 assert_eq!(offset, 6);
882 }
883
884 #[test]
885 fn single_quoted_gets_substitions() {
886 let p = Parser::default().with_intrinsic_attribute(
887 "foo",
888 "bar",
889 ModificationContext::Anywhere,
890 );
891
892 let (element_attr, offset, warning_types) = crate::attributes::ElementAttribute::parse(
893 &CowStr::from("'*abc* def {foo}'"),
894 0,
895 &p,
896 ParseShorthand(false),
897 AttrlistContext::Block,
898 );
899
900 assert!(warning_types.is_empty());
901
902 assert_eq!(
903 element_attr,
904 ElementAttribute {
905 name: None,
906 shorthand_items: &[],
907 value: "<strong>abc</strong> def bar"
908 }
909 );
910
911 assert!(element_attr.name().is_none());
912 assert!(element_attr.block_style().is_none());
913 assert!(element_attr.id().is_none());
914 assert!(element_attr.roles().is_empty());
915 assert!(element_attr.options().is_empty());
916
917 assert_eq!(offset, 17);
918 }
919 }
920
921 mod named {
922 use pretty_assertions_sorted::assert_eq;
923
924 use crate::{
925 Parser,
926 attributes::{AttrlistContext, element_attribute::ParseShorthand},
927 strings::CowStr,
928 tests::prelude::*,
929 };
930
931 #[test]
932 fn simple_named_value() {
933 let p = Parser::default();
934
935 let (element_attr, offset, warning_types) = crate::attributes::ElementAttribute::parse(
936 &CowStr::from("abc=def"),
937 0,
938 &p,
939 ParseShorthand(false),
940 AttrlistContext::Inline,
941 );
942
943 assert!(warning_types.is_empty());
944
945 assert_eq!(
946 element_attr,
947 ElementAttribute {
948 name: Some("abc"),
949 shorthand_items: &[],
950 value: "def"
951 }
952 );
953
954 assert_eq!(element_attr.name().unwrap(), "abc");
955 assert!(element_attr.block_style().is_none());
956 assert!(element_attr.id().is_none());
957 assert!(element_attr.roles().is_empty());
958 assert!(element_attr.options().is_empty());
959
960 assert_eq!(offset, 7);
961 }
962
963 #[test]
964 fn ignores_spaces_around_equals() {
965 let p = Parser::default();
966
967 let (element_attr, offset, warning_types) = crate::attributes::ElementAttribute::parse(
968 &CowStr::from("abc = def"),
969 0,
970 &p,
971 ParseShorthand(false),
972 AttrlistContext::Inline,
973 );
974
975 assert!(warning_types.is_empty());
976
977 assert_eq!(
978 element_attr,
979 ElementAttribute {
980 name: Some("abc"),
981 shorthand_items: &[],
982 value: "def"
983 }
984 );
985
986 assert_eq!(element_attr.name().unwrap(), "abc");
987
988 assert_eq!(offset, 10);
989 }
990
991 #[test]
992 fn numeric_name() {
993 let p = Parser::default();
994
995 let (element_attr, offset, warning_types) = crate::attributes::ElementAttribute::parse(
996 &CowStr::from("94-x =def"),
997 0,
998 &p,
999 ParseShorthand(false),
1000 AttrlistContext::Inline,
1001 );
1002
1003 assert!(warning_types.is_empty());
1004
1005 assert_eq!(
1006 element_attr,
1007 ElementAttribute {
1008 name: Some("94-x"),
1009 shorthand_items: &[],
1010 value: "def"
1011 }
1012 );
1013
1014 assert_eq!(element_attr.name().unwrap(), "94-x");
1015 assert!(element_attr.block_style().is_none());
1016 assert!(element_attr.id().is_none());
1017 assert!(element_attr.roles().is_empty());
1018 assert!(element_attr.options().is_empty());
1019
1020 assert_eq!(offset, 9);
1021 }
1022
1023 #[test]
1024 fn quoted_value() {
1025 let p = Parser::default();
1026
1027 let (element_attr, offset, warning_types) = crate::attributes::ElementAttribute::parse(
1028 &CowStr::from("abc='def'g"),
1029 0,
1030 &p,
1031 ParseShorthand(false),
1032 AttrlistContext::Inline,
1033 );
1034
1035 assert!(warning_types.is_empty());
1036
1037 assert_eq!(
1038 element_attr,
1039 ElementAttribute {
1040 name: Some("abc"),
1041 shorthand_items: &[],
1042 value: "def"
1043 }
1044 );
1045
1046 assert_eq!(element_attr.name().unwrap(), "abc");
1047 assert!(element_attr.block_style().is_none());
1048 assert!(element_attr.id().is_none());
1049 assert!(element_attr.roles().is_empty());
1050 assert!(element_attr.options().is_empty());
1051
1052 assert_eq!(offset, 9);
1053 }
1054
1055 #[test]
1056 fn fallback_if_no_value() {
1057 let p = Parser::default();
1058
1059 let (element_attr, offset, warning_types) = crate::attributes::ElementAttribute::parse(
1060 &CowStr::from("abc="),
1061 0,
1062 &p,
1063 ParseShorthand(false),
1064 AttrlistContext::Inline,
1065 );
1066
1067 assert!(warning_types.is_empty());
1068
1069 assert_eq!(
1070 element_attr,
1071 ElementAttribute {
1072 name: None,
1073 shorthand_items: &[],
1074 value: "abc="
1075 }
1076 );
1077
1078 assert!(element_attr.name().is_none());
1079 assert!(element_attr.block_style().is_none());
1080 assert!(element_attr.id().is_none());
1081 assert!(element_attr.roles().is_empty());
1082 assert!(element_attr.options().is_empty());
1083
1084 assert_eq!(offset, 4);
1085 }
1086
1087 #[test]
1088 fn fallback_if_immediate_comma() {
1089 let p = Parser::default();
1090
1091 let (element_attr, offset, warning_types) = crate::attributes::ElementAttribute::parse(
1092 &CowStr::from("abc=,def"),
1093 0,
1094 &p,
1095 ParseShorthand(false),
1096 AttrlistContext::Inline,
1097 );
1098
1099 assert!(warning_types.is_empty());
1100
1101 assert_eq!(
1102 element_attr,
1103 ElementAttribute {
1104 name: None,
1105 shorthand_items: &[],
1106 value: "abc="
1107 }
1108 );
1109
1110 assert!(element_attr.name().is_none());
1111 assert!(element_attr.block_style().is_none());
1112 assert!(element_attr.id().is_none());
1113 assert!(element_attr.roles().is_empty());
1114 assert!(element_attr.options().is_empty());
1115
1116 assert_eq!(offset, 4);
1117 }
1118 }
1119
1120 mod parse_with_shorthand {
1121 use pretty_assertions_sorted::assert_eq;
1122
1123 use crate::{
1124 Parser,
1125 attributes::{AttrlistContext, element_attribute::ParseShorthand},
1126 strings::CowStr,
1127 tests::prelude::*,
1128 warnings::WarningType,
1129 };
1130
1131 #[test]
1132 fn block_style_only() {
1133 let p = Parser::default();
1134
1135 let (element_attr, offset, warning_types) = crate::attributes::ElementAttribute::parse(
1136 &CowStr::from("abc"),
1137 0,
1138 &p,
1139 ParseShorthand(true),
1140 AttrlistContext::Inline,
1141 );
1142
1143 assert!(warning_types.is_empty());
1144
1145 assert_eq!(
1146 element_attr,
1147 ElementAttribute {
1148 name: None,
1149 shorthand_items: &["abc"],
1150 value: "abc"
1151 }
1152 );
1153
1154 assert!(element_attr.name().is_none());
1155 assert_eq!(element_attr.shorthand_items(), vec!["abc"]);
1156 assert_eq!(element_attr.block_style().unwrap(), "abc");
1157 assert!(element_attr.id().is_none());
1158 assert!(element_attr.roles().is_empty());
1159 assert!(element_attr.options().is_empty());
1160
1161 assert_eq!(offset, 3);
1162 }
1163
1164 #[test]
1165 fn ignore_if_named_attribute() {
1166 let p = Parser::default();
1167
1168 let (element_attr, offset, warning_types) = crate::attributes::ElementAttribute::parse(
1169 &CowStr::from("name=block_style#id"),
1170 0,
1171 &p,
1172 ParseShorthand(true),
1173 AttrlistContext::Inline,
1174 );
1175
1176 assert!(warning_types.is_empty());
1177
1178 assert_eq!(
1179 element_attr,
1180 ElementAttribute {
1181 name: Some("name"),
1182 shorthand_items: &[],
1183 value: "block_style#id"
1184 }
1185 );
1186
1187 assert_eq!(element_attr.name().unwrap(), "name");
1188 assert!(element_attr.shorthand_items().is_empty());
1189 assert!(element_attr.block_style().is_none());
1190 assert!(element_attr.id().is_none());
1191 assert!(element_attr.roles().is_empty());
1192 assert!(element_attr.options().is_empty());
1193
1194 assert_eq!(offset, 19);
1195 }
1196
1197 #[test]
1198 fn error_empty_id() {
1199 let p = Parser::default();
1200
1201 let (element_attr, offset, warning_types) = crate::attributes::ElementAttribute::parse(
1202 &CowStr::from("abc#"),
1203 0,
1204 &p,
1205 ParseShorthand(true),
1206 AttrlistContext::Inline,
1207 );
1208
1209 assert_eq!(
1210 element_attr,
1211 ElementAttribute {
1212 name: None,
1213 shorthand_items: &["abc"],
1214 value: "abc#"
1215 }
1216 );
1217
1218 assert_eq!(offset, 4);
1219 assert_eq!(warning_types, vec![WarningType::EmptyShorthandItem]);
1220 }
1221
1222 #[test]
1223 fn error_duplicate_delimiter() {
1224 let p = Parser::default();
1225
1226 let (element_attr, offset, warning_types) = crate::attributes::ElementAttribute::parse(
1227 &CowStr::from("abc##id"),
1228 0,
1229 &p,
1230 ParseShorthand(true),
1231 AttrlistContext::Inline,
1232 );
1233
1234 assert_eq!(
1235 element_attr,
1236 ElementAttribute {
1237 name: None,
1238 shorthand_items: &["abc", "#id"],
1239 value: "abc##id"
1240 }
1241 );
1242
1243 assert_eq!(offset, 7);
1244 assert_eq!(warning_types, vec![WarningType::EmptyShorthandItem]);
1245 }
1246
1247 #[test]
1248 fn id_only() {
1249 let p = Parser::default();
1250
1251 let (element_attr, offset, warning_types) = crate::attributes::ElementAttribute::parse(
1252 &CowStr::from("#xyz"),
1253 0,
1254 &p,
1255 ParseShorthand(true),
1256 AttrlistContext::Inline,
1257 );
1258
1259 assert!(warning_types.is_empty());
1260
1261 assert_eq!(
1262 element_attr,
1263 ElementAttribute {
1264 name: None,
1265 shorthand_items: &["#xyz"],
1266 value: "#xyz"
1267 }
1268 );
1269
1270 assert!(element_attr.name().is_none());
1271 assert_eq!(element_attr.shorthand_items(), vec!["#xyz"]);
1272 assert!(element_attr.block_style().is_none());
1273 assert_eq!(element_attr.id().unwrap(), "xyz");
1274 assert!(element_attr.roles().is_empty());
1275 assert!(element_attr.options().is_empty());
1276
1277 assert_eq!(offset, 4);
1278 }
1279
1280 #[test]
1281 fn one_role_only() {
1282 let p = Parser::default();
1283
1284 let (element_attr, offset, warning_types) = crate::attributes::ElementAttribute::parse(
1285 &CowStr::from(".role1"),
1286 0,
1287 &p,
1288 ParseShorthand(true),
1289 AttrlistContext::Inline,
1290 );
1291
1292 assert!(warning_types.is_empty());
1293
1294 assert_eq!(
1295 element_attr,
1296 ElementAttribute {
1297 name: None,
1298 shorthand_items: &[".role1",],
1299 value: ".role1"
1300 }
1301 );
1302
1303 assert!(element_attr.name().is_none());
1304 assert_eq!(element_attr.shorthand_items(), vec![".role1"]);
1305 assert!(element_attr.block_style().is_none());
1306 assert!(element_attr.id().is_none());
1307 assert_eq!(element_attr.roles(), vec!("role1"));
1308 assert!(element_attr.options().is_empty());
1309
1310 assert_eq!(offset, 6);
1311 }
1312
1313 #[test]
1314 fn multiple_roles() {
1315 let p = Parser::default();
1316
1317 let (element_attr, offset, warning_types) = crate::attributes::ElementAttribute::parse(
1318 &CowStr::from(".role1.role2.role3"),
1319 0,
1320 &p,
1321 ParseShorthand(true),
1322 AttrlistContext::Inline,
1323 );
1324
1325 assert!(warning_types.is_empty());
1326
1327 assert_eq!(
1328 element_attr,
1329 ElementAttribute {
1330 name: None,
1331 shorthand_items: &[".role1", ".role2", ".role3"],
1332 value: ".role1.role2.role3"
1333 }
1334 );
1335
1336 assert!(element_attr.name().is_none());
1337
1338 assert_eq!(
1339 element_attr.shorthand_items(),
1340 vec![".role1", ".role2", ".role3"]
1341 );
1342
1343 assert!(element_attr.block_style().is_none());
1344 assert!(element_attr.id().is_none());
1345 assert_eq!(element_attr.roles(), vec!("role1", "role2", "role3",));
1346 assert!(element_attr.options().is_empty());
1347
1348 assert_eq!(offset, 18);
1349 }
1350
1351 #[test]
1352 fn one_option_only() {
1353 let p = Parser::default();
1354
1355 let (element_attr, offset, warning_types) = crate::attributes::ElementAttribute::parse(
1356 &CowStr::from("%option1"),
1357 0,
1358 &p,
1359 ParseShorthand(true),
1360 AttrlistContext::Inline,
1361 );
1362
1363 assert!(warning_types.is_empty());
1364
1365 assert_eq!(
1366 element_attr,
1367 ElementAttribute {
1368 name: None,
1369 shorthand_items: &["%option1"],
1370 value: "%option1"
1371 }
1372 );
1373
1374 assert!(element_attr.name().is_none());
1375 assert_eq!(element_attr.shorthand_items(), vec!["%option1"]);
1376 assert!(element_attr.block_style().is_none());
1377 assert!(element_attr.id().is_none());
1378 assert!(element_attr.roles().is_empty());
1379 assert_eq!(element_attr.options(), vec!("option1"));
1380
1381 assert_eq!(offset, 8);
1382 }
1383
1384 #[test]
1385 fn multiple_options() {
1386 let p = Parser::default();
1387
1388 let (element_attr, offset, warning_types) = crate::attributes::ElementAttribute::parse(
1389 &CowStr::from("%option1%option2%option3"),
1390 0,
1391 &p,
1392 ParseShorthand(true),
1393 AttrlistContext::Inline,
1394 );
1395
1396 assert!(warning_types.is_empty());
1397
1398 assert_eq!(
1399 element_attr,
1400 ElementAttribute {
1401 name: None,
1402 shorthand_items: &["%option1", "%option2", "%option3"],
1403 value: "%option1%option2%option3"
1404 }
1405 );
1406
1407 assert!(element_attr.name().is_none());
1408
1409 assert_eq!(
1410 element_attr.shorthand_items(),
1411 vec!["%option1", "%option2", "%option3"]
1412 );
1413
1414 assert!(element_attr.block_style().is_none());
1415 assert!(element_attr.id().is_none());
1416 assert!(element_attr.roles().is_empty());
1417 assert_eq!(
1418 element_attr.options(),
1419 vec!("option1", "option2", "option3")
1420 );
1421
1422 assert_eq!(offset, 24);
1423 }
1424
1425 #[test]
1426 fn block_style_and_id() {
1427 let p = Parser::default();
1428
1429 let (element_attr, offset, warning_types) = crate::attributes::ElementAttribute::parse(
1430 &CowStr::from("appendix#custom-id"),
1431 0,
1432 &p,
1433 ParseShorthand(true),
1434 AttrlistContext::Inline,
1435 );
1436
1437 assert!(warning_types.is_empty());
1438
1439 assert_eq!(
1440 element_attr,
1441 ElementAttribute {
1442 name: None,
1443 shorthand_items: &["appendix", "#custom-id"],
1444 value: "appendix#custom-id"
1445 }
1446 );
1447
1448 assert!(element_attr.name().is_none());
1449 assert_eq!(
1450 element_attr.shorthand_items(),
1451 vec!["appendix", "#custom-id"]
1452 );
1453 assert_eq!(element_attr.block_style().unwrap(), "appendix",);
1454 assert_eq!(element_attr.id().unwrap(), "custom-id",);
1455 assert!(element_attr.roles().is_empty());
1456 assert!(element_attr.options().is_empty());
1457
1458 assert_eq!(offset, 18);
1459 }
1460
1461 #[test]
1462 fn id_role_and_option() {
1463 let p = Parser::default();
1464
1465 let (element_attr, offset, warning_types) = crate::attributes::ElementAttribute::parse(
1466 &CowStr::from("#rules.prominent%incremental"),
1467 0,
1468 &p,
1469 ParseShorthand(true),
1470 AttrlistContext::Inline,
1471 );
1472
1473 assert!(warning_types.is_empty());
1474
1475 assert_eq!(
1476 element_attr,
1477 ElementAttribute {
1478 name: None,
1479 shorthand_items: &["#rules", ".prominent", "%incremental"],
1480 value: "#rules.prominent%incremental"
1481 }
1482 );
1483
1484 assert!(element_attr.name().is_none());
1485
1486 assert_eq!(
1487 element_attr.shorthand_items(),
1488 vec!["#rules", ".prominent", "%incremental"]
1489 );
1490
1491 assert!(element_attr.block_style().is_none());
1492 assert_eq!(element_attr.id().unwrap(), "rules");
1493 assert_eq!(element_attr.roles(), vec!("prominent"));
1494 assert_eq!(element_attr.options(), vec!("incremental"));
1495
1496 assert_eq!(offset, 28);
1497 }
1498 }
1499}