Introduction
Rdxl is a set of macros for generating xhtml from Rust. By using rdxl it is possible to intermix xhtml and Rust code to generate interactive documents.
In this book we will document the exact grammar of each rdxl macro and provide many motivating examples and several cheat sheets.
extern crate rdxl; fn main() { println!("{}",rdxl::xhtml!( <p>This copy, version {{rdxl::version}}, is the latest copy of this document</p> <p>We hope that this will satisfy all your web templating needs</p> )); }
HTML Markup
When generating markup, the most common macro to be used is xhtml!. This macro takes mixed markup and rust code to output markup formatted as a String.
Inside xhtml! there are several possible types of markup that can be sent to the formatter:
- XHTML Tags
- Unquoted Text Data
- Quoted Text Data
To generate XHTML Tags, place the tags in angle brackets like normal markup. The tags can be self-closing or contain inner HTML.
extern crate rdxl; fn main() { println!("{}",rdxl::xhtml!( <br/> )); }
To generate Unquoted Text Data it is usually sufficient to place the text directly in the markup. Note that macros are lexed as Rust code before being sent to the macro procedure. This means that not all XHTML can be placed directly inline.
extern crate rdxl; fn main() { println!("{}",rdxl::xhtml!( <p>This paragraph is formatted normally. The breaking spaces are recognized as part of the macro rules.</p> )); }
For Text Data that does not work well with the Rust lexer, the text may be placed inside a rust quoted string literal. This string will be placed directly into the formatted output. It should be noted that raw strings are very a good way to fit even more text data into one quote without the need for escape characters.
extern crate rdxl; fn main() { println!("{}",rdxl::xhtml!( r#"((((( the lexer expects closing braces, brackets, and parentheses [[[[ but what do we care. {{"# )); }
Rust Code
Rust code greatly enhances the generative capabilities of the Rdxl macro rules. Most rust expressions and statements may be placed inside of Rdxl markup and generate code nearly identical to what is stated. This is very helpful not only for creating new powerful abstractions, but also for fixing bugs: error messages for syntax or type errors are correctly tracked and blamed with the same helpful error formatting that Rust is so well known for.
In Rdxl, Rust code is divided into two kinds of syntax expressions: Rust statements and Rust expressions. The difference between a statement or expression is always determined by syntax rather than inference. Expressions always emit a value that implements the Display trait as the return value of the expression. Statements may emit data directly to the string buffer but never as the return value of the statement.
Both expressions and statements are surrounded by double braces to signify that they should be interpreted as Rust code rather than xhtml.
A simple example of a rust expression is a variable interpolated into the markup:
extern crate rdxl; fn main() { let x = 5; println!("{}", rdxl::xhtml!( These strings are literals, but {{x}} is a variable. )); }
For a complete reference of all expressions, see Chapter 5: Expression Reference.
A simple statement would be a for loop:
#![allow(unused_variables)] fn main() { println!("{}", rdxl::xhtml!( {{ for x in 0..10 {{ These strings are literals, but {{x}} is a variable. }} }} )); }
For a complete reference of all statements, see Chapter 6: Statement Reference.
Markup Reference
All html tags may be used directly in the body of the macro invocation. Most tag attributes may be used normally as well. Some tag attribute names, for example those with dashes in them, confuse the Rust lexer and therefore should be quoted. All strings are valid attribute names as long as they are quoted as a string literal.
extern crate rdxl; fn main() { println!("{}", rdxl::xhtml!( <a href="/this_is_ok" "this-must-be-quoted"="abcd">body of link</a> )); }
Rust expressions may be interpolated as attribute values. To insert a Rust expression in attribute position, use the double braces format. When rust expressions are used as attributes, the string value is quoted and escape characters are inserted in place of double quotes etc.
extern crate rdxl; fn main() { let a_class = "my_class"; let a_style = "position:absolute; top:0; left:0;"; println!("{}", rdxl::xhtml!( <div class={{a_class}} style={{a_style}}>inner html</div> )); }
Expression Reference
All interpolated Rust code is interpreted as an expression provided that
- The snippet does not start with a reserved statement keyword
- The snippet does not end with a semicolon
The statement keywords are: if, let, for, while, and loop.
extern crate rdxl; fn main() { let a_flag = true; println!("{}", rdxl::xhtml!( {{ if a_flag {{ this is a statement }} }} {{ (if a_flag { "this is" } else { "an expression" }) }} )); }
Aside from these reservations, all Rust code may be used as expressions as long as they return a value that implements the Display trait.
extern crate rdxl; fn main() { println!("{}", rdxl::xhtml!( {{ "ab" }} {{ 'c' }} {{ format!("{} {}", 2, 3) }} {{ true }} )); }
Statement Reference
The statement forms are: if, let, for, while, and loop. Also, if an expression ends with a semicolon its value will be discarded.
Semicolon
extern crate rdxl; fn main() { let mux x = 3; println!("{}", rdxl::xhtml!( {{ x += 2; }} )); }
If
extern crate rdxl; fn main() { let x = 3; println!("{}", rdxl::xhtml!( {{ if x<2 {{ Case 1 }} else if x<5 {{ Case 2 }} else {{ Case 3 }} )); }
Let
extern crate rdxl; fn main() { println!("{}", rdxl::xhtml!( {{ let mut x = 5; }} {{ x }} {{ x += 2; }} {{ x }} )); }
For
extern crate rdxl; fn main() { println!("{}", rdxl::xhtml!( {{ for x in 0..10 {{ {{x}} }} }} )); }
While
extern crate rdxl; fn main() { let mut x = 3; println!("{}", rdxl::xhtml!( {{ while x>0 {{ {{ x }} {{ x -= 1; }} }} }} )); }
Loop
extern crate rdxl; fn main() { println!("{}", rdxl::xhtml!( {{ loop {{ <p>inside loop</p> {{ break; }} }} }} )); }
Breaking Spaces
Rdxl macros try to guess when inserting breaking spaces would be appropriate. This is quite difficult and absurd when it comes to statements. For that reason, most statements just emit breaking spaces regardless of whether there are any spaces in the input.
The implementation of breaking space detection is reliant on the span location information provided to procedural macros. This feature has been under ongoing development, so there may be quirks from Rust version to version.
xrender
The xrender! macro defines a Display trait for a type. The type is supplied as the first argument, and the rest of the macro defines the XHTML that is to be formatted as output.
All fields of the type can be accessed through the self value which is defined over the body of the macro invocation.
#![allow(unused_variables)] fn main() { extern crate rdxl; xrender!(MyList, <ul> <li>{{ self.my_string }}</li> <li>{{ self.my_int }}</li> {{ for i in self.children.iter() {{ {{ if let MyListChildren::MyItem(my_item) = i {{ <li>MyItem: {{ my_item.my_bool }}</li> }} else if let MyListChildren::MyOtherItem(my_other_item) = i {{ <li>MyOtherItem: {{ my_other_item.my_char }}</li> }} }} }} }} </ul>); }
xtype
The xtype! macro defines an XML-like struct. An XML-like struct has zero or more attributes, possibly having default values, and zero or more possible children types.
Attributes that do not supply a default value must have a type which implements the std::default::Default trait. This is an implementation quirk, so it is notable, although undesirable.
Children can have any type, which may be defined inline as another tag, or reference an existing type. A boxed Display type is available by using the ? tag, which is a handy way to include miscellaneous content which does not require special rendering logic.
#![allow(unused_variables)] fn main() { extern crate rdxl; rdxl::xtype!(<!MyTag a:u64={{32}} b:String> <?> </MyTag>); }
the above code generates the following items
#![allow(unused_variables)] fn main() { struct MyTag { a: u64, b: String, children: Vec<MyTagChildren> } enum MyTagChildren { Display(Box<dyn std::fmt::Display>) } impl MyTag { pub fn new() -> MyTag { MyTag { a: 32, b: std::default::Default::default(), children: Vec::new(), } } pub fn set_a(mut self, v: u64) -> MyTag { self.a = v; self } pub fn set_b(mut self, v: String) -> MyTag { self.b = v; self } pub fn set_children(mut self, v: Vec<MyTagChildren>) -> MyTag { self.children = v; self } } }