#xml #html #svg #template #format

no-std format_xml

Fast, minimal, feature-rich, xml-like formatting syntax for Rust!

9 releases

0.3.0 Jul 28, 2022
0.2.0 Nov 21, 2020
0.1.6 Oct 17, 2020
0.1.5 Jun 21, 2020
0.1.2 Aug 26, 2019

#33 in Template engine

Download history 74/week @ 2022-10-04 45/week @ 2022-10-11 31/week @ 2022-10-18 61/week @ 2022-10-25 94/week @ 2022-11-01 47/week @ 2022-11-08 69/week @ 2022-11-15 42/week @ 2022-11-22 35/week @ 2022-11-29 48/week @ 2022-12-06 42/week @ 2022-12-13 51/week @ 2022-12-20 24/week @ 2022-12-27 35/week @ 2023-01-03 72/week @ 2023-01-10 28/week @ 2023-01-17

164 downloads per month
Used in pelite

MIT license

40KB
706 lines

Xml-like formatting

MIT License crates.io docs.rs Build status

Fast, minimal, feature-rich, xml-like formatting syntax for Rust!

We say xml-like because due to limitations and flexibility some concessions had to be made; see the examples below.

Features include:

  • Arbitrary expressions inside the formatting braces
  • Generates optimized Rust code at compiletime
  • Supports rust-analyzer auto complete, refactoring and more!
  • Supports Rust's standard formatting specifiers
  • Control flow allows conditional and repeated formatting
  • Capture variables by value or by reference
  • Escape hatch to inject custom formatting code

In your Cargo.toml, add:

[dependencies]
format_xml = "0.2"

Examples

Basic usage

let point = (20, 30);
let name = "World";

let string = format_xml::format! {
	<svg width="200" height="200">
		<line x1="0" y1="0" x2={point.0} y2={point.1} stroke="black" stroke-width="2" />
		<text x={point.1} y={point.0}>"Hello '"{name}"'!"</text>
	</svg>
};

assert_eq!(string, r#"<svg width="200" height="200"><line x1="0" y1="0" x2="20" y2="30" stroke="black" stroke-width="2" /><text x="30" y="20">Hello 'World'!</text></svg>"#);

The value arguments can be arbitrary expressions. They are inlined in the formatting braces and are outside the string literals.

The values inside formatting braces are escaped by default, the text literals are not. Use the escape hatch to bypass the automatic escaping.

Formatting specifiers

let value = 42;

let string = format_xml::format! {
	<span data-value={value}>{value:#x?}</span>
};

assert_eq!(string, r#"<span data-value="42">0x2a</span>"#);

The rules for the specifiers are exactly the same as the standard library of Rust.

Escaping

let value = "\"quote\"";
let text = "<script>&</script>";

let string = format_xml::format! {
	<p data-value={value}>{text}</p>
};

assert_eq!(string, r#"<p data-value="&quot;quote&quot;">&lt;script&gt;&amp;&lt;/script&gt;</p>"#);

The values inside formatting braces are escaped by default, the text literals are not.

  • Text elements escape <, &, >.
  • Attribute values escape <, &, >, ', ".
  • Comment nodes escape -- by removing it altogether.
  • CDATA sections escape ]]>.

Escaping is not implemented in some HTML contexts: inside <script>, <style> tags or their respective attribute equivalents (event lers and inline styles), do not format user controlled values in these locations!

Supported syntax

let string = format_xml::format! {
	<!doctype html>
	<?xml version="1.0" encoding="UTF-8"?>
	<tag-name></tag-name>
	<self-closing-tag />
	<!-- "comment" -->
	<![CDATA["cdata"]]>
};

assert_eq!(string, r#"<!doctype html><?xml version="1.0" encoding="UTF-8"?><tag-name></tag-name><self-closing-tag /><!-- comment --><![CDATA[cdata]]>"#);

Examples of element naming and namespace syntax support:

let string = format_xml::format! {
	<tag>
	<tag-foo>
	<tag.foo>
	<ns:tag>
	<"_t-0.z">
};

assert_eq!(string, r#"<tag><tag-foo><tag.foo><ns:tag><_t-0.z>"#);

There are no restrictions on matching open/close tags or reject tags which cannot be self-closing.

Unfinished implementation:

  • Document type definitions (DTD) are not correctly implemented. The <!doctype> tag is barely functional.
  • Processing instructions are not correctly implemented. The <?xml?> tag is barely functional.

Control flow

let switch = true;
let opt = Some("World");

let string = format_xml::format! {
	if let Some(name) = (opt) {
		<h1>"Hello "{name}</h1>
	}
	else if (switch) {
		<h1>"Hello User"</h1>
	}
};

assert_eq!(string, "<h1>Hello World</h1>");
let string: Result<f32, i32> = Err(13);

let string = format_xml::format! {
	match string {
		Ok(f) => <i>{f}</i>,
		Err(i) => <b>{i}</b>,
	}
};

assert_eq!(string, "<b>13</b>");
let string = format_xml::format! {
	<ul>
	for i in (1..=5) {
		let times_five = i * 5;
		<li>{i}"*5="{times_five}</li>
	}
	</ul>
};

assert_eq!(string, "<ul><li>1*5=5</li><li>2*5=10</li><li>3*5=15</li><li>4*5=20</li><li>5*5=25</li></ul>");

Control flow is only supported outside tags, not in attributes.

Escape hatch

fn compose(f: &mut std::fmt::Formatter, a: i32) -> std::fmt::Result {
	format_xml::write!(f, <span>{a}</span>)
}

let string = format_xml::format! {
	<p>|f| compose(f, 42)?;</p>
};

assert_eq!(string, r#"<p><span>42</span></p>"#);

Closure syntax provides an escape hatch to inject code if needed. The argument's type is &mut Formatter.

Important! Anything written to the formatter f is not escaped. This makes it useful to compose different components wich is not possible with {}.

License

Licensed under MIT License, see license.txt.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, shall be licensed as above, without any additional terms or conditions.

Dependencies

~31KB