use oxvg_ast::{
element::Element,
visitor::{Context, Visitor},
};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "wasm")]
use tsify::Tsify;
use crate::error::JobsError;
#[cfg_attr(feature = "wasm", derive(Tsify))]
#[cfg_attr(feature = "napi", napi)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Default, PartialEq)]
#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
pub enum XMLNSOrder {
Alphabetical,
#[default]
Front,
#[doc(hidden)]
#[cfg(feature = "napi")]
Napi(),
}
#[cfg_attr(feature = "wasm", derive(Tsify))]
#[cfg_attr(feature = "napi", napi(object))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
pub struct SortAttrs {
#[cfg_attr(feature = "wasm", tsify(optional))]
pub order: Option<Vec<String>>,
#[cfg_attr(feature = "wasm", tsify(optional))]
pub xmlns_order: Option<XMLNSOrder>,
}
impl<'input, 'arena> Visitor<'input, 'arena> for SortAttrs {
type Error = JobsError<'input>;
fn element(
&self,
element: &Element<'input, 'arena>,
_context: &mut Context<'input, 'arena, '_>,
) -> Result<(), Self::Error> {
let xmlns_order = self.xmlns_order.is_none() || self.xmlns_order == Some(XMLNSOrder::Front);
match self.order.as_ref() {
Some(order) => element.attributes().sort(order, xmlns_order),
None => element.attributes().sort(DEFAULT_ORDER, xmlns_order),
}
Ok(())
}
}
const DEFAULT_ORDER: &[&str] = &[
"id", "width", "height", "x", "x1", "x2", "y", "y1", "y2", "cx", "cy", "r", "fill", "stroke",
"marker", "d", "points",
];
#[test]
fn sort_attrs() -> anyhow::Result<()> {
use crate::test_config;
insta::assert_snapshot!(test_config(
r#"{ "sortAttrs": {} }"#,
Some(
r#"<svg r="" b="" x2="" cx="" y1="" a="" y="" y2="" x1="" cy="" x="">
<!-- sort according to default list alphabetically -->
</svg>"#
),
)?);
insta::assert_snapshot!(test_config(
r#"{ "sortAttrs": {} }"#,
Some(
r#"<svg a="" fill-opacity="" stroke="" fill="" stroke-opacity="">
<!-- sort derived attributes like fill and fill-opacity -->
</svg>"#
),
)?);
insta::assert_snapshot!(test_config(
r#"{ "sortAttrs": {} }"#,
Some(
r#"<svg xmlns:editor2="link2" fill="" b="" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:editor1="link1" xmlns="" d="">
<!-- put xmlns and namespace attributes before others by default -->
<rect editor2:b="" editor1:b="" editor2:a="" editor1:a="" />
</svg>"#
),
)?);
insta::assert_snapshot!(test_config(
r#"{ "sortAttrs": { "xmlnsOrder": "alphabetical" } }"#,
Some(
r#"<svg foo="bar" xmlns="http://www.w3.org/2000/svg" height="10" baz="quux" width="10" hello="world">
<!-- optionally sort xmlns attributes alphabetically -->
<rect x="0" y="0" width="100" height="100" stroke-width="1" stroke-linejoin="round" fill="red" stroke="orange" xmlns="http://www.w3.org/2000/svg"/>
test
</svg>"#
),
)?);
Ok(())
}