Skip to content

Commit 972a6ac

Browse files
committed
use VecMap/VecSet to keep order in attributes and classes
This changes some text results as attributes are now kept in original order, and classes are shown as a set (not list).
1 parent e0b056c commit 972a6ac

11 files changed

Lines changed: 280 additions & 30 deletions

src/dom/element.rs

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
use super::node::Node;
22
#[cfg(feature = "source-span")]
33
use super::span::SourceSpan;
4-
use serde::{Serialize, Serializer};
5-
use std::collections::{BTreeMap, HashMap};
4+
use crate::VecSet;
5+
use crate::dom::vecmap::VecMap;
6+
use serde::Serialize;
67
use std::default::Default;
7-
use std::result::Result;
88

99
/// Normal: `<div></div>` or Void: `<meta/>`and `<meta>`
1010
#[derive(Debug, Clone, Serialize, PartialEq)]
@@ -17,7 +17,7 @@ pub enum ElementVariant {
1717
Void,
1818
}
1919

20-
pub type Attributes = HashMap<String, Option<String>>;
20+
pub type Attributes = VecMap<String, Option<String>>;
2121

2222
/// Most of the parsed html nodes are elements, except for text
2323
#[derive(Debug, Clone, Serialize, PartialEq)]
@@ -34,13 +34,12 @@ pub struct Element {
3434
pub variant: ElementVariant,
3535

3636
/// All of the elements attributes, except id and class
37-
#[serde(skip_serializing_if = "HashMap::is_empty")]
38-
#[serde(serialize_with = "ordered_map")]
37+
#[serde(skip_serializing_if = "VecMap::is_empty")]
3938
pub attributes: Attributes,
4039

4140
/// All of the elements classes
42-
#[serde(skip_serializing_if = "Vec::is_empty")]
43-
pub classes: Vec<String>,
41+
#[serde(skip_serializing_if = "VecSet::is_empty")]
42+
pub classes: VecSet<String>,
4443

4544
/// All of the elements child nodes
4645
#[serde(skip_serializing_if = "Vec::is_empty")]
@@ -58,16 +57,11 @@ impl Default for Element {
5857
id: None,
5958
name: "".to_string(),
6059
variant: ElementVariant::Void,
61-
classes: vec![],
62-
attributes: HashMap::new(),
60+
classes: VecSet::default(),
61+
attributes: VecMap::default(),
6362
children: vec![],
6463
#[cfg(feature = "source-span")]
6564
source_span: SourceSpan::default(),
6665
}
6766
}
6867
}
69-
70-
fn ordered_map<S: Serializer>(value: &Attributes, serializer: S) -> Result<S::Ok, S::Error> {
71-
let ordered: BTreeMap<_, _> = value.iter().collect();
72-
ordered.serialize(serializer)
73-
}

src/dom/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ pub mod formatting;
1212
pub mod node;
1313
#[cfg(feature = "source-span")]
1414
pub mod span;
15+
pub mod vecmap;
16+
pub mod vecset;
1517

1618
#[cfg(feature = "source-span")]
1719
use crate::dom::span::SourceSpan;
@@ -291,7 +293,7 @@ impl Dom {
291293
if let Some(classes) = attr_value {
292294
let classes = classes.split_whitespace().collect::<Vec<_>>();
293295
for class in classes {
294-
element.classes.push(class.to_string());
296+
element.classes.insert(class.to_string());
295297
}
296298
}
297299
}

src/dom/vecmap.rs

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
use serde::{Serialize, Serializer};
2+
use std::borrow::Borrow;
3+
use std::fmt::{Debug, Formatter};
4+
use std::iter::{FromIterator, Map};
5+
use std::mem;
6+
use std::ops::Index;
7+
8+
#[derive(Clone, Default, PartialEq, Eq)]
9+
pub struct VecMap<K, V>(pub Vec<(K, V)>);
10+
11+
impl<K, V> VecMap<K, V> {
12+
pub fn is_empty(&self) -> bool {
13+
self.0.is_empty()
14+
}
15+
pub fn iter(&self) -> Iter<'_, K, V> {
16+
Iter(self.0.iter().map(|kv| (&kv.0, &kv.1)))
17+
}
18+
19+
fn position<Q>(&self, key: &Q) -> Option<usize>
20+
where
21+
K: Eq + Borrow<Q>,
22+
Q: Eq + ?Sized,
23+
{
24+
self.0.iter().position(|(k, _)| k.borrow() == key)
25+
}
26+
27+
pub fn contains_key<Q>(&self, key: &Q) -> bool
28+
where
29+
K: Eq + Borrow<Q>,
30+
Q: Eq + ?Sized,
31+
{
32+
self.position(key).is_some()
33+
}
34+
35+
pub fn insert(&mut self, key: K, mut value: V) -> Option<V>
36+
where
37+
K: Eq,
38+
{
39+
match self.position(&key) {
40+
None => {
41+
self.0.push((key, value));
42+
None
43+
}
44+
Some(i) => {
45+
mem::swap(&mut value, &mut unsafe { self.0.get_unchecked_mut(i) }.1);
46+
Some(value)
47+
}
48+
}
49+
}
50+
51+
pub fn get<Q>(&self, key: &Q) -> Option<&V>
52+
where
53+
K: Eq + Borrow<Q>,
54+
Q: Eq + ?Sized,
55+
{
56+
self.position(key)
57+
.map(|i| &unsafe { self.0.get_unchecked(i) }.1)
58+
}
59+
60+
pub fn get_mut<Q>(&mut self, key: &Q) -> Option<&mut V>
61+
where
62+
K: Eq + Borrow<Q>,
63+
Q: Eq + ?Sized,
64+
{
65+
self.position(key)
66+
.map(move |i| &mut unsafe { self.0.get_unchecked_mut(i) }.1)
67+
}
68+
69+
pub fn remove<Q>(&mut self, key: &Q) -> Option<V>
70+
where
71+
K: Eq + Borrow<Q>,
72+
Q: Eq + ?Sized,
73+
{
74+
self.position(key).map(|i| self.0.remove(i).1)
75+
}
76+
}
77+
78+
impl<K, V> Debug for VecMap<K, V>
79+
where
80+
K: Debug,
81+
V: Debug,
82+
{
83+
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
84+
f.debug_map().entries(self.iter()).finish()
85+
}
86+
}
87+
88+
#[allow(clippy::type_complexity)]
89+
pub struct Iter<'a, K, V>(pub Map<std::slice::Iter<'a, (K, V)>, fn(&'a (K, V)) -> (&K, &V)>);
90+
91+
impl<'a, K, V> Iterator for Iter<'a, K, V> {
92+
type Item = (&'a K, &'a V);
93+
94+
fn next(&mut self) -> Option<Self::Item> {
95+
self.0.next()
96+
}
97+
}
98+
99+
impl<K, V> FromIterator<(K, V)> for VecMap<K, V> {
100+
fn from_iter<T: IntoIterator<Item = (K, V)>>(iter: T) -> Self {
101+
Self(Vec::from_iter(iter))
102+
}
103+
}
104+
105+
pub struct IntoIter<K, V>(pub std::vec::IntoIter<(K, V)>);
106+
107+
impl<K, V> IntoIterator for VecMap<K, V> {
108+
type Item = (K, V);
109+
type IntoIter = IntoIter<K, V>;
110+
111+
fn into_iter(self) -> Self::IntoIter {
112+
IntoIter(self.0.into_iter())
113+
}
114+
}
115+
116+
impl<K, V> Iterator for IntoIter<K, V> {
117+
type Item = (K, V);
118+
119+
fn next(&mut self) -> Option<Self::Item> {
120+
self.0.next()
121+
}
122+
}
123+
124+
impl<K, Q, V> Index<&Q> for VecMap<K, V>
125+
where
126+
K: Eq + Borrow<Q>,
127+
Q: Eq + ?Sized,
128+
{
129+
type Output = V;
130+
131+
fn index(&self, index: &Q) -> &Self::Output {
132+
self.get(index).expect("no entry found for key")
133+
}
134+
}
135+
136+
impl<K, V> Serialize for VecMap<K, V>
137+
where
138+
K: Serialize,
139+
V: Serialize,
140+
{
141+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
142+
where
143+
S: Serializer,
144+
{
145+
serializer.collect_map(self.iter())
146+
}
147+
}

src/dom/vecset.rs

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
use crate::VecMap;
2+
use serde::{Serialize, Serializer};
3+
use std::borrow::Borrow;
4+
use std::fmt::{Debug, Formatter};
5+
use std::iter::{FromIterator, Map};
6+
7+
#[derive(Clone, Default, PartialEq, Eq)]
8+
pub struct VecSet<K>(pub VecMap<K, ()>);
9+
10+
impl<K> VecSet<K> {
11+
pub fn is_empty(&self) -> bool {
12+
self.0.is_empty()
13+
}
14+
15+
pub fn iter(&self) -> Iter<'_, K> {
16+
Iter(self.0.iter().map(|(k, _)| k))
17+
}
18+
19+
pub fn contains_key<Q>(&self, key: &Q) -> bool
20+
where
21+
K: Eq + Borrow<Q>,
22+
Q: Eq + ?Sized,
23+
{
24+
self.0.contains_key(key)
25+
}
26+
27+
pub fn insert(&mut self, key: K) -> bool
28+
where
29+
K: Eq,
30+
{
31+
self.0.insert(key, ()).is_none()
32+
}
33+
34+
pub fn remove<Q>(&mut self, key: &Q) -> bool
35+
where
36+
K: Eq + Borrow<Q>,
37+
Q: Eq + ?Sized,
38+
{
39+
self.0.remove(key).is_some()
40+
}
41+
}
42+
43+
impl<K> Debug for VecSet<K>
44+
where
45+
K: Debug,
46+
{
47+
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
48+
f.debug_set().entries(self.0.iter()).finish()
49+
}
50+
}
51+
52+
impl<K> FromIterator<K> for VecSet<K> {
53+
fn from_iter<T: IntoIterator<Item = K>>(iter: T) -> Self {
54+
Self(VecMap::from_iter(iter.into_iter().map(|k| (k, ()))))
55+
}
56+
}
57+
58+
#[allow(clippy::type_complexity)]
59+
pub struct Iter<'a, K>(pub Map<crate::dom::vecmap::Iter<'a, K, ()>, fn((&'a K, &'a ())) -> &'a K>);
60+
61+
impl<'a, K> Iterator for Iter<'a, K> {
62+
type Item = &'a K;
63+
64+
fn next(&mut self) -> Option<Self::Item> {
65+
self.0.next()
66+
}
67+
}
68+
69+
#[allow(clippy::type_complexity)]
70+
pub struct IntoIter<K>(pub Map<crate::dom::vecmap::IntoIter<K, ()>, fn((K, ())) -> K>);
71+
72+
impl<K> IntoIterator for VecSet<K> {
73+
type Item = K;
74+
type IntoIter = IntoIter<K>;
75+
76+
fn into_iter(self) -> Self::IntoIter {
77+
IntoIter(self.0.into_iter().map(|(k, _)| k))
78+
}
79+
}
80+
81+
impl<K> Iterator for IntoIter<K> {
82+
type Item = K;
83+
84+
fn next(&mut self) -> Option<Self::Item> {
85+
self.0.next()
86+
}
87+
}
88+
89+
impl<K> Serialize for VecSet<K>
90+
where
91+
K: Serialize,
92+
{
93+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
94+
where
95+
S: Serializer,
96+
{
97+
serializer.collect_seq(self.0.iter().map(|(k, _)| k))
98+
}
99+
}

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,5 +108,7 @@ pub use crate::dom::element::{Element, ElementVariant};
108108
pub use crate::dom::node::Node;
109109
#[cfg(feature = "source-span")]
110110
pub use crate::dom::span::SourceSpan;
111+
pub use crate::dom::vecmap::VecMap;
112+
pub use crate::dom::vecset::VecSet;
111113
pub use crate::error::Error;
112114
pub use crate::error::Result;

tests/snapshots/element_attributes__it_can_parse_multiple_attributes_double_quote.snap

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
---
22
source: tests/element_attributes.rs
3+
assertion_line: 50
34
expression: dom
45
---
56
{
@@ -9,9 +10,9 @@ expression: dom
910
"name": "div",
1011
"variant": "normal",
1112
"attributes": {
12-
"ape": "oh",
1313
"cat": "mjau",
14-
"dog": "woff"
14+
"dog": "woff",
15+
"ape": "oh"
1516
}
1617
}
1718
]

tests/snapshots/element_attributes__it_can_parse_multiple_attributes_no_quote.snap

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
---
22
source: tests/element_attributes.rs
3+
assertion_line: 57
34
expression: dom
45
---
56
{
@@ -9,9 +10,9 @@ expression: dom
910
"name": "div",
1011
"variant": "normal",
1112
"attributes": {
12-
"ape": "oh",
1313
"cat": "mjau",
14-
"dog": "woff"
14+
"dog": "woff",
15+
"ape": "oh"
1516
}
1617
}
1718
]

tests/snapshots/element_attributes__it_can_parse_multiple_attributes_single_quote.snap

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
---
22
source: tests/element_attributes.rs
3+
assertion_line: 36
34
expression: dom
45
---
56
{
@@ -9,9 +10,9 @@ expression: dom
910
"name": "div",
1011
"variant": "normal",
1112
"attributes": {
12-
"ape": "oh",
1313
"cat": "mjau",
14-
"dog": "woff"
14+
"dog": "woff",
15+
"ape": "oh"
1516
}
1617
}
1718
]

0 commit comments

Comments
 (0)