1use crate::optimizations::transformer::Transform;
2use crate::optimizations::{if_some_has_important, none_or_has_important};
3use crate::structure::{Name, Parameters, Value};
4use nom::lib::std::fmt::Formatter;
5use std::fmt::Display;
6
7#[derive(Default, Debug, Clone)]
8pub struct MergeShortHand;
9
10impl Transform for MergeShortHand {
11 fn transform_parameters(&self, mut parameters: Parameters) -> Parameters {
12 let mut font = FontShortHand::default();
13 let mut list = ListShortHand::default();
14 let mut background = BackgroundShortHand::default();
15 let mut border = BorderShortHand::default();
16 let mut outline = OutlineShortHand::default();
17 let mut transition = TransitionShortHand::default();
18
19 parameters
20 .0
21 .iter()
22 .for_each(|(name, value): (&Name, &Value)| {
23 if !parameters.0.contains_key("font") {
24 font.add(name, value.clone());
25 }
26 if !parameters.0.contains_key("list-style") {
27 list.add(name, value.clone());
28 }
29 if !parameters.0.contains_key("background") {
30 background.add(name, value.clone());
31 }
32 if !parameters.0.contains_key("border") {
33 border.add(name, value.clone());
34 }
35 if !parameters.0.contains_key("outline") {
36 outline.add(name, value.clone());
37 }
38 if !parameters.0.contains_key("transition") {
39 transition.add(name, value.clone());
40 }
41 });
42
43 if font.is_maybe_shorted() {
44 parameters
45 .0
46 .insert(String::from("font"), font.to_string().trim().to_string());
47 parameters.0.swap_remove("font-style");
48 parameters.0.swap_remove("font-variant");
49 parameters.0.swap_remove("font-weight");
50 parameters.0.swap_remove("font-size");
51 parameters.0.swap_remove("line-height");
52 parameters.0.swap_remove("font-family");
53 }
54
55 if list.is_maybe_shorted() {
56 parameters.0.insert(
57 String::from("list-style"),
58 list.to_string().trim().to_string(),
59 );
60 parameters.0.swap_remove("list-style-type");
61 parameters.0.swap_remove("list-style-position");
62 parameters.0.swap_remove("list-style-image");
63 }
64
65 if background.is_maybe_shorted() {
66 parameters.0.insert(
67 String::from("background"),
68 background.to_string().trim().to_string(),
69 );
70 parameters.0.swap_remove("background-attachment");
71 parameters.0.swap_remove("background-color");
72 parameters.0.swap_remove("background-position");
73 parameters.0.swap_remove("background-repeat");
74 parameters.0.swap_remove("background-image");
75 }
76
77 if border.is_maybe_shorted() {
78 parameters.0.insert(
79 String::from("border"),
80 border.to_string().trim().to_string(),
81 );
82 parameters.0.swap_remove("border-width");
83 parameters.0.swap_remove("border-style");
84 parameters.0.swap_remove("border-color");
85 }
86
87 if outline.is_maybe_shorted() {
88 parameters.0.insert(
89 String::from("outline"),
90 outline.to_string().trim().to_string(),
91 );
92 parameters.0.swap_remove("outline-width");
93 parameters.0.swap_remove("outline-style");
94 parameters.0.swap_remove("outline-color");
95 }
96
97 if transition.is_maybe_shorted() {
98 parameters.0.insert(
99 String::from("transition"),
100 transition.to_string().trim().to_string(),
101 );
102 parameters.0.swap_remove("transition-property");
103 parameters.0.swap_remove("transition-duration");
104 parameters.0.swap_remove("transition-delay");
105 parameters.0.swap_remove("transition-timing-function");
106 }
107
108 parameters
109 }
110}
111
112#[derive(Debug, Default)]
113struct FontShortHand {
114 font_style: Option<Value>,
115 font_variant: Option<Value>,
116 font_weight: Option<Value>,
117 font_size: Option<Value>,
118 line_height: Option<Value>,
119 font_family: Option<Value>,
120}
121
122impl FontShortHand {
123 fn is_maybe_shorted(&self) -> bool {
124 self.font_size.is_some()
125 && self.font_family.is_some()
126 && (self.all_elements_has_important() || self.no_one_element_has_no_important())
127 }
128
129 fn all_elements_has_important(&self) -> bool {
130 if_some_has_important(self.font_style.as_ref())
131 && if_some_has_important(self.font_variant.as_ref())
132 && if_some_has_important(self.font_weight.as_ref())
133 && if_some_has_important(self.font_size.as_ref())
134 && if_some_has_important(self.line_height.as_ref())
135 && if_some_has_important(self.font_family.as_ref())
136 }
137
138 fn no_one_element_has_no_important(&self) -> bool {
139 !(none_or_has_important(self.font_style.as_ref())
140 || none_or_has_important(self.font_variant.as_ref())
141 || none_or_has_important(self.font_weight.as_ref())
142 || none_or_has_important(self.font_size.as_ref())
143 || none_or_has_important(self.line_height.as_ref())
144 || none_or_has_important(self.font_family.as_ref()))
145 }
146
147 fn add(&mut self, name: &Name, value: Value) {
148 match name.as_str() {
149 "font-style" => self.font_style = Some(value),
150 "font-variant" => self.font_variant = Some(value),
151 "font-weight" => self.font_weight = Some(value),
152 "font-size" => self.font_size = Some(value),
153 "line-height" => self.line_height = Some(value),
154 "font-family" => self.font_family = Some(value),
155 _ => {}
156 }
157 }
158}
159
160impl Display for FontShortHand {
161 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
162 if let Some(v) = &self.font_style {
163 write!(f, "{}", v.trim_end_matches("!important").trim())?;
164 }
165 if let Some(v) = &self.font_variant {
166 write!(f, " {}", v.trim_end_matches("!important").trim())?;
167 }
168 if let Some(v) = &self.font_weight {
169 write!(f, " {}", v.trim_end_matches("!important").trim())?;
170 }
171 if let Some(v) = &self.font_size {
172 write!(f, " {}", v.trim_end_matches("!important").trim())?;
173 }
174 if let Some(v) = &self.line_height {
175 write!(f, "/{}", v.trim_end_matches("!important").trim())?;
176 }
177 if let Some(v) = &self.font_family {
178 write!(f, " {}", v.trim_end_matches("!important").trim())?;
179 }
180 if self.all_elements_has_important() {
181 write!(f, "!important")?;
182 }
183 Ok(())
184 }
185}
186
187#[derive(Debug, Default)]
188struct ListShortHand {
189 list_style_type: Option<Value>,
190 list_style_position: Option<Value>,
191 list_style_image: Option<Value>,
192}
193
194impl ListShortHand {
195 fn is_maybe_shorted(&self) -> bool {
196 (self.list_style_type.is_some()
197 || self.list_style_position.is_some()
198 || self.list_style_image.is_some())
199 && (self.all_elements_has_important() || self.no_one_element_has_no_important())
200 }
201
202 fn all_elements_has_important(&self) -> bool {
203 if_some_has_important(self.list_style_type.as_ref())
204 && if_some_has_important(self.list_style_position.as_ref())
205 && if_some_has_important(self.list_style_image.as_ref())
206 }
207
208 fn no_one_element_has_no_important(&self) -> bool {
209 !(none_or_has_important(self.list_style_type.as_ref())
210 || none_or_has_important(self.list_style_position.as_ref())
211 || none_or_has_important(self.list_style_image.as_ref()))
212 }
213
214 fn add(&mut self, name: &Name, value: Value) {
215 match name.as_str() {
216 "list-style-type" => self.list_style_type = Some(value),
217 "list-style-position" => self.list_style_position = Some(value),
218 "list-style-image" => self.list_style_image = Some(value),
219 _ => {}
220 }
221 }
222}
223
224impl Display for ListShortHand {
225 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
226 if let Some(v) = &self.list_style_type {
227 write!(f, "{}", v.trim_end_matches("!important").trim())?;
228 }
229 if let Some(v) = &self.list_style_position {
230 write!(f, " {}", v.trim_end_matches("!important").trim())?;
231 }
232 if let Some(v) = &self.list_style_image {
233 write!(f, " {}", v.trim_end_matches("!important").trim())?;
234 }
235 if self.all_elements_has_important() {
236 write!(f, "!important")?;
237 }
238 Ok(())
239 }
240}
241
242#[derive(Debug, Default)]
243struct BackgroundShortHand {
244 background_color: Option<Value>,
245 background_image: Option<Value>,
246 background_repeat: Option<Value>,
247 background_attachment: Option<Value>,
248 background_position: Option<Value>,
249}
250
251impl BackgroundShortHand {
252 fn is_maybe_shorted(&self) -> bool {
253 (self.background_color.is_some()
254 || self.background_image.is_some()
255 || self.background_repeat.is_some()
256 || self.background_attachment.is_some()
257 || self.background_position.is_some())
258 && (self.all_elements_has_important() || self.no_one_element_has_no_important())
259 }
260
261 fn all_elements_has_important(&self) -> bool {
262 if_some_has_important(self.background_color.as_ref())
263 && if_some_has_important(self.background_image.as_ref())
264 && if_some_has_important(self.background_repeat.as_ref())
265 && if_some_has_important(self.background_attachment.as_ref())
266 && if_some_has_important(self.background_position.as_ref())
267 }
268
269 fn no_one_element_has_no_important(&self) -> bool {
270 !(none_or_has_important(self.background_color.as_ref())
271 || none_or_has_important(self.background_image.as_ref())
272 || none_or_has_important(self.background_repeat.as_ref())
273 || none_or_has_important(self.background_attachment.as_ref())
274 || none_or_has_important(self.background_position.as_ref()))
275 }
276
277 fn add(&mut self, name: &Name, value: Value) {
278 match name.as_str() {
279 "background-color" => self.background_color = Some(value),
280 "background-image" => self.background_image = Some(value),
281 "background-repeat" => self.background_repeat = Some(value),
282 "background-attachment" => self.background_attachment = Some(value),
283 "background-position" => self.background_position = Some(value),
284 _ => {}
285 }
286 }
287}
288
289impl Display for BackgroundShortHand {
290 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
291 if let Some(v) = &self.background_color {
292 write!(f, " {}", v.trim_end_matches("!important").trim())?;
293 }
294 if let Some(v) = &self.background_image {
295 write!(f, " {}", v.trim_end_matches("!important").trim())?;
296 }
297 if let Some(v) = &self.background_repeat {
298 write!(f, " {}", v.trim_end_matches("!important").trim())?;
299 }
300 if let Some(v) = &self.background_attachment {
301 write!(f, "{}", v.trim_end_matches("!important").trim())?;
302 }
303 if let Some(v) = &self.background_position {
304 write!(f, " {}", v.trim_end_matches("!important").trim())?;
305 }
306 if self.all_elements_has_important() {
307 write!(f, "!important")?;
308 }
309
310 Ok(())
311 }
312}
313
314#[derive(Debug, Default)]
315struct BorderShortHand {
316 border_width: Option<Value>,
317 border_style: Option<Value>,
318 border_color: Option<Value>,
319}
320
321impl BorderShortHand {
322 fn is_maybe_shorted(&self) -> bool {
323 (self.border_width.is_some() || self.border_style.is_some() || self.border_color.is_some())
324 && (self.all_elements_has_important() || self.no_one_element_has_no_important())
325 }
326
327 fn all_elements_has_important(&self) -> bool {
328 if_some_has_important(self.border_width.as_ref())
329 && if_some_has_important(self.border_style.as_ref())
330 && if_some_has_important(self.border_color.as_ref())
331 }
332
333 fn no_one_element_has_no_important(&self) -> bool {
334 !(none_or_has_important(self.border_width.as_ref())
335 || none_or_has_important(self.border_style.as_ref())
336 || none_or_has_important(self.border_color.as_ref()))
337 }
338
339 fn add(&mut self, name: &Name, value: Value) {
340 match name.as_str() {
341 "border-width" => self.border_width = Some(value),
342 "border-style" => self.border_style = Some(value),
343 "border-color" => self.border_color = Some(value),
344 _ => {}
345 }
346 }
347}
348
349impl Display for BorderShortHand {
350 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
351 if let Some(v) = &self.border_width {
352 write!(f, "{}", v.trim_end_matches("!important").trim())?;
353 }
354 if let Some(v) = &self.border_style {
355 write!(f, " {}", v.trim_end_matches("!important").trim())?;
356 }
357 if let Some(v) = &self.border_color {
358 write!(f, " {}", v.trim_end_matches("!important").trim())?;
359 }
360 if self.all_elements_has_important() {
361 write!(f, "!important")?;
362 }
363
364 Ok(())
365 }
366}
367
368#[derive(Debug, Default)]
369struct OutlineShortHand {
370 outline_width: Option<Value>,
371 outline_style: Option<Value>,
372 outline_color: Option<Value>,
373}
374
375impl OutlineShortHand {
376 fn is_maybe_shorted(&self) -> bool {
377 (self.outline_width.is_some()
378 || self.outline_style.is_some()
379 || self.outline_color.is_some())
380 && (self.all_elements_has_important() || self.no_one_element_has_no_important())
381 }
382
383 fn all_elements_has_important(&self) -> bool {
384 if_some_has_important(self.outline_width.as_ref())
385 && if_some_has_important(self.outline_style.as_ref())
386 && if_some_has_important(self.outline_color.as_ref())
387 }
388
389 fn no_one_element_has_no_important(&self) -> bool {
390 !(none_or_has_important(self.outline_width.as_ref())
391 || none_or_has_important(self.outline_style.as_ref())
392 || none_or_has_important(self.outline_color.as_ref()))
393 }
394
395 fn add(&mut self, name: &Name, value: Value) {
396 match name.as_str() {
397 "outline-width" => self.outline_width = Some(value),
398 "outline-style" => self.outline_style = Some(value),
399 "outline-color" => self.outline_color = Some(value),
400 _ => {}
401 }
402 }
403}
404
405impl Display for OutlineShortHand {
406 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
407 if let Some(v) = &self.outline_width {
408 write!(f, "{}", v.trim_end_matches("!important").trim())?;
409 }
410 if let Some(v) = &self.outline_style {
411 write!(f, " {}", v.trim_end_matches("!important").trim())?;
412 }
413 if let Some(v) = &self.outline_color {
414 write!(f, " {}", v.trim_end_matches("!important").trim())?;
415 }
416 if self.all_elements_has_important() {
417 write!(f, "!important")?;
418 }
419
420 Ok(())
421 }
422}
423
424#[derive(Debug, Default)]
425struct TransitionShortHand {
426 transition_property: Option<Value>,
427 transition_duration: Option<Value>,
428 transition_delay: Option<Value>,
429 transition_timing_function: Option<Value>,
430}
431
432impl TransitionShortHand {
433 fn is_maybe_shorted(&self) -> bool {
434 (self.transition_property.is_some()
435 || self.transition_duration.is_some()
436 || self.transition_delay.is_some()
437 || self.transition_timing_function.is_some())
438 && (self.all_elements_has_important() || self.no_one_element_has_no_important())
439 }
440
441 fn all_elements_has_important(&self) -> bool {
442 if_some_has_important(self.transition_property.as_ref())
443 && if_some_has_important(self.transition_duration.as_ref())
444 && if_some_has_important(self.transition_delay.as_ref())
445 && if_some_has_important(self.transition_timing_function.as_ref())
446 }
447
448 fn no_one_element_has_no_important(&self) -> bool {
449 !(none_or_has_important(self.transition_property.as_ref())
450 || none_or_has_important(self.transition_duration.as_ref())
451 || none_or_has_important(self.transition_delay.as_ref())
452 || none_or_has_important(self.transition_timing_function.as_ref()))
453 }
454
455 fn add(&mut self, name: &Name, value: Value) {
456 match name.as_str() {
457 "transition-property" => self.transition_property = Some(value),
458 "transition-duration" => self.transition_duration = Some(value),
459 "transition-delay" => self.transition_delay = Some(value),
460 "transition-timing-function" => self.transition_timing_function = Some(value),
461 _ => {}
462 }
463 }
464}
465
466impl Display for TransitionShortHand {
467 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
468 if let Some(v) = &self.transition_property {
469 write!(f, "{}", v.trim_end_matches("!important").trim())?;
470 }
471 if let Some(v) = &self.transition_duration {
472 write!(f, " {}", v.trim_end_matches("!important").trim())?;
473 }
474 if let Some(v) = &self.transition_delay {
475 write!(f, " {}", v.trim_end_matches("!important").trim())?;
476 }
477 if let Some(v) = &self.transition_timing_function {
478 write!(f, " {}", v.trim_end_matches("!important").trim())?;
479 }
480 if self.all_elements_has_important() {
481 write!(f, "!important")?;
482 }
483
484 Ok(())
485 }
486}
487
488#[cfg(test)]
489mod test {
490 use crate::optimizations::merge_shorthand::MergeShortHand;
491 use crate::optimizations::transformer::Transform;
492 use crate::structure::{Block, Parameters, Selectors};
493 use indexmap::map::IndexMap;
494
495 #[test]
496 fn test_compress_font() {
497 assert_eq!(
498 MergeShortHand::default().transform(
499 Block {
500 selectors: Selectors::default(),
501 parameters: {
502 let mut map = IndexMap::new();
503 map.insert("font-style".into(), "italic".into());
504 map.insert("font-weight".into(), "bold".into());
505 map.insert("font-size".into(), ".8em".into());
506 map.insert("line-height".into(), "1.2".into());
507 map.insert("font-family".into(), "Arial, sans-serif".into());
508 Parameters(map)
509 },
510 }
511 .into()
512 ),
513 Block {
514 selectors: Selectors::default(),
515 parameters: {
516 let mut map = IndexMap::new();
517 map.insert(
518 "font".into(),
519 "italic bold .8em/1.2 Arial, sans-serif".into(),
520 );
521 Parameters(map)
522 },
523 }
524 .into()
525 )
526 }
527
528 #[test]
529 fn test_compress_background() {
530 assert_eq!(
531 MergeShortHand::default().transform(
532 Block {
533 selectors: Selectors::default(),
534 parameters: {
535 let mut map = IndexMap::new();
536 map.insert("background-color".into(), "#000".into());
537 map.insert("background-image".into(), "url(images/bg.gif)".into());
538 map.insert("background-repeat".into(), "no-repeat".into());
539 map.insert("background-position".into(), "left top".into());
540 Parameters(map)
541 },
542 }
543 .into()
544 ),
545 Block {
546 selectors: Selectors::default(),
547 parameters: {
548 let mut map = IndexMap::new();
549 map.insert(
550 "background".into(),
551 "#000 url(images/bg.gif) no-repeat left top".into(),
552 );
553 Parameters(map)
554 },
555 }
556 .into()
557 )
558 }
559
560 #[test]
561 fn test_background_important() {
562 assert_eq!(
563 MergeShortHand::default().transform(
564 Block {
565 selectors: Selectors::default(),
566 parameters: {
567 let mut map = IndexMap::new();
568 map.insert("background-color".into(), "#000 !important".into());
569 Parameters(map)
570 },
571 }
572 .into()
573 ),
574 Block {
575 selectors: Selectors::default(),
576 parameters: {
577 let mut map = IndexMap::new();
578 map.insert("background".into(), "#000!important".into());
579 Parameters(map)
580 },
581 }
582 .into()
583 )
584 }
585
586 #[test]
587 fn test_compress_border() {
588 assert_eq!(
589 MergeShortHand::default().transform(
590 Block {
591 selectors: Selectors::default(),
592 parameters: {
593 let mut map = IndexMap::new();
594 map.insert("border-width".into(), "1px".into());
595 map.insert("border-style".into(), "solid".into());
596 map.insert("border-color".into(), "#000".into());
597 Parameters(map)
598 },
599 }
600 .into()
601 ),
602 Block {
603 selectors: Selectors::default(),
604 parameters: {
605 let mut map = IndexMap::new();
606 map.insert("border".into(), "1px solid #000".into());
607 Parameters(map)
608 },
609 }
610 .into()
611 )
612 }
613
614 #[test]
615 fn test_compress_outline() {
616 assert_eq!(
617 MergeShortHand::default().transform(
618 Block {
619 selectors: Selectors::default(),
620 parameters: {
621 let mut map = IndexMap::new();
622 map.insert("outline-width".into(), "1px".into());
623 map.insert("outline-style".into(), "solid".into());
624 map.insert("outline-color".into(), "#000".into());
625 Parameters(map)
626 },
627 }
628 .into()
629 ),
630 Block {
631 selectors: Selectors::default(),
632 parameters: {
633 let mut map = IndexMap::new();
634 map.insert("outline".into(), "1px solid #000".into());
635 Parameters(map)
636 },
637 }
638 .into()
639 )
640 }
641}