#svg #html #xml #tag #markup

hypermelon

Write SVG / HTML / XML programmatically

9 releases

0.5.5 Mar 31, 2023
0.5.4 Mar 31, 2023
0.4.0 Oct 15, 2022
0.3.13 Oct 10, 2022
0.1.1 Oct 5, 2022

#82 in Template engine

Download history 28/week @ 2023-01-26 21/week @ 2023-02-02 76/week @ 2023-02-09 104/week @ 2023-02-16 62/week @ 2023-02-23 33/week @ 2023-03-02 34/week @ 2023-03-09 170/week @ 2023-03-16 172/week @ 2023-03-23 235/week @ 2023-03-30 279/week @ 2023-04-06 148/week @ 2023-04-13 165/week @ 2023-04-20 74/week @ 2023-04-27 135/week @ 2023-05-04 65/week @ 2023-05-11

451 downloads per month
Used in 7 crates (5 directly)

MIT license

46KB
1K SLoC

Build xml / html / svg programmatically by chaining structs together or by closures. Instead of using a templating engine, write data/markup that 'looks like' rust. User has control over formatting via the inline() function.

You can find hypermelon on github and crates.io. Documentation at docs.rs

Example

use hypermelon::build;
use hypermelon::prelude::*;

fn main() -> std::fmt::Result {
    let width = 100.0;
    let height = 100.0;

    let rect = build::single("rect").with(attrs!(
        ("x1", 0),
        ("y1", 0),
        ("rx", 20),
        ("ry", 20),
        ("width", width),
        ("height", height),
        ("style", "fill:blue")
    ));

    let style = build::elem("style")
        .inline()
        .append(".test{fill:none;stroke:white;stroke-width:3}");

    let svg = build::elem("svg").with(attrs!(
        ("xmlns", "http://www.w3.org/2000/svg"),
        ("viewBox", format_move!("0 0 {} {}", width, height))
    ));

    let rows = (0..50).step_by(5).map(|r| {
        let o = r % 10 == 0;

        let a =
            o.then(|| build::single("circle").with(attrs!(("cx", 50.0), ("cy", 50.0), ("r", r))));

        let b = (!o).then(|| {
            build::single("rect").with(attrs!(
                ("x", 50 - r),
                ("y", 50 - r),
                ("width", r * 2),
                ("height", r * 2)
            ))
        });

        a.chain(b)
    });

    let table = build::elem("g")
        .with(("class", "test"))
        .append(build::from_iter(rows));

    let all = svg.append(style).append(rect).append(table);

    hypermelon::render(all, hypermelon::stdout_fmt())
}

Output Text:

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
	<style> .test{fill:none;stroke:white;stroke-width:3}</style>
	<rect x1="0" y1="0" rx="20" ry="20" width="100" height="100" style="fill:blue"/>
	<g class="test">
		<circle cx="50" cy="50" r="0"/>
		<rect x="45" y="45" width="10" height="10"/>
		<circle cx="50" cy="50" r="10"/>
		<rect x="35" y="35" width="30" height="30"/>
		<circle cx="50" cy="50" r="20"/>
		<rect x="25" y="25" width="50" height="50"/>
		<circle cx="50" cy="50" r="30"/>
		<rect x="15" y="15" width="70" height="70"/>
		<circle cx="50" cy="50" r="40"/>
		<rect x="5" y="5" width="90" height="90"/>
	</g>
</svg>

Output Image:

demo

See other example outputs at https://github.com/tiby312/hypermelon/tree/main/assets

Which method to use?

You can append elements via building of long adaptor chains, or you can render elements to a writer on the fly. With chaining, you don't have to worry about handling errors because nothing actually gets written out as you're chaining. You can mix and match because you can make elements from closures and then chain those elements together.

Inline function

By default tags insertion newlines and tabs. If you call inline() on an element, all elements within it will be inlined.

Is there escape XML protection?

Attributes are fed through a escape protectors. Tag names are fed through escape protectors. User can bypass this using the raw_escpapable() or from_closure_escapable() functions. This returns the only element type that doesnt implement elem::Locked. render() requires that the chained together element implements Locked. If the user chains in a raw element, the whole chain will not implement Locked. Instead the user would have to use render_escapable(). The element chaining system works by having each element implement a render_head(), and a render_tail() function.

If you want to implement your own custom Elem outside of this crate, you can safefully implement Locked. This crate does not expose an api that allows you to make an Elem that isnt locked.

What happened to the tagger crate?

I left the tagger crate alone and made this into a brand new crate because while it does have all the functionality of tagger, it is more complicated. Some people might just like the simplicity of tagger. However, I recommend people choose hypermelon over tagger, because I think its a lot more flexible. The ability to pass around element chains like structs is really useful in my experience.

Name origin?

So its not easy to find crate names these days. A lot of good ones are taken. This one started out as ht-melon because it has "html" in the name, but it just looked jarring in the code everywhere so I changed it to hypermelon.

No runtime deps