# Examples
The examples have been designed with [`cargo-script`](https://github.com/DanielKeep/cargo-script) in mind.
Here I'll just give broad overview how to install [`cargo script`] for Rust 1.5. For more details, check out [cargo-script repository](https://github.com/DanielKeep/cargo-script).
cargo install cargo-script
# Token printer
The basis of xml5ever is its tokenizer and tree builder. Roughly speaking tokenizer
takes input and returns a set of tokens like comment, processing instruction, start
tag, end tag, etc.
First let's define our dependencies:
```toml
[dependencies]
xml5ever = "0.2.0"
tendril = "0.1.3"
```
With dependencies declared, we can now make a simple tokenizer sink. First step is to
define a [`TokenSink`](https://docs.rs/xml5ever/latest/xml5ever/tokenizer/trait.TokenSink.html). [`TokenSink`](https://docs.rs/xml5ever/latest/xml5ever/tokenizer/trait.TokenSink.html) are traits that received stream of [`Tokens`](https://docs.rs/xml5ever/latest/xml5ever/tokenizer/enum.Token.html).
In our case we'll define a unit struct (i.e. a struct without any fields).
```rust
struct SimpleTokenPrinter;
```
To make `SimpleTokenPrinter` a [`TokenSink`](https://docs.rs/xml5ever/latest/xml5ever/tokenizer/trait.TokenSink.html), we need to implement [process_token](https://docs.rs/xml5ever/latest/xml5ever/tokenizer/trait.TokenSink.html#tymethod.process_token) method.
```rust
impl TokenSink for SimpleTokenPrinter {
fn process_token(&mut self, token: Token) {
match token {
CharacterTokens(b) => {
println!("TEXT: {}", &*b);
},
NullCharacterToken => print!("NULL"),
TagToken(tag) => {
println!("{:?} {} ", tag.kind, &*tag.name.local);
},
ParseError(err) => {
println!("ERROR: {}", err);
},
PIToken(Pi{ref target, ref data}) => {
println!("PI : {} {}?>", &*target, &*data);
},
CommentToken(ref comment) => {
println!("", &*comment);
},
EOFToken => {
println!("EOF");
},
DoctypeToken(Doctype{ref name, ref public_id, ..}) => {
println!("", &*name, &*public_id);
}
}
}
}
```
Now, we need some input to process. For input we'll use `stdin`. However, xml5ever `tokenize_to` method only takes `StrTendril`. So we need to construct a
[`ByteTendril`](https://docs.rs/tendril/latest/tendril/type.ByteTendril.html) using `ByteTendril::new()`, then read the `stdin` using [`read_to_tendril`](https://docs.rs/tendril/latest/tendril/trait.ReadExt.html#tymethod.read_to_tendril) extension.
Once that is set, to make `SimpleTokenPrinter` parse the input, call,
`tokenize_to` with it as the first parameter, input wrapped in Option for second parameter and XmlToke.
```rust
fn main() {
let sink = SimpleTokenPrinter;
// We need a ByteTendril to read a file
let mut input = ByteTendril::new();
// Using SliceExt.read_to_tendril we read stdin
io::stdin().read_to_tendril(&mut input).unwrap();
// For xml5ever we need StrTendril, so we reinterpret it
// into StrTendril.
//
// You might wonder, how does `try_reinterpret` know we
// need StrTendril and the answer is type inference based
// on `tokenize_xml_to` signature.
let input = input.try_reinterpret().unwrap();
// Here we create and run tokenizer
let mut tok = XmlTokenizer::new(sink, Default::default());
// We pass input to parsed.
tok.feed(input);
// tok.end must be invoked for final bytes to be processed.
tok.end();
}
```
NOTE: `unwrap` causes panic, it's only OK to use in simple examples.
For full source code check out: [`examples/simple_xml_tokenizer.rs`](https://github.com/servo/html5ever/blob/main/xml5ever/examples/simple_xml_tokenizer.rs)
Once we have successfully compiled the example we run the example with inline
xml
```bash
cargo script simple_xml_tokenizer.rs <<< "Text with bold words!"
```
or by sending an [`examples/example.xml`](https://github.com/servo/html5ever/blob/main/xml5ever/examples/example.xml) located in same folder as examples.
```bash
cargo script simple_xml_tokenizer.rs < example.xml
```
# Tree printer
To actually get an XML document tree from the xml5ever, you need to use a `TreeSink`.
`TreeSink` is in many way similar to the TokenSink. Basically, TokenSink takes data
and returns list of tokens, while TreeSink takes tokens and returns a tree of parsed
XML document. Do note, that this is a simplified explanation and consult
documentation for more info.
Ok, with that in mind, let's build us a TreePrinter. For example if we get an XML
file like:
```xml
Bobby
Tables
```
We'd want a structure similar to this:
```
#document
student
first-name
#text Bobby
last-name
#text Tables
```
We won't print anything other than element names and text fields. So comments,
doctypes and other such elements are ignored.
First part is similar to making SimpleTokenPrinter:
```rust
// We need to allocate an input tendril for xml5ever
let mut input = ByteTendril::new();
// Using SliceExt.read_to_tendril functions we can read stdin
io::stdin().read_to_tendril(&mut input).unwrap();
let input = input.try_reinterpret().unwrap();
```
This time, we need an implementation of [`TreeSink`](https://docs.rs/xml5ever/latest/xml5ever/tree_builder/trait.TreeSink.html). xml5ever comes with a
built-in `TreeSink` implementation called [`RcDom`](https://docs.rs/markup5ever_rcdom/latest/markup5ever_rcdom/struct.RcDom.html). To process input into
a `TreeSink` we use the following line:
```rust
let dom: RcDom = parse(one_input(input), Default::default());
```
Let's analyze it a bit. First there is `let dom: RcDom`. We need this part,
because the type inferencer can't infer which TreeSink implementation we mean
in this scenario.
Function [`one_input`](https://ygg01.github.io/docs/xml5ever/xml5ever/fn.one_input.html) is a convenience function that turns any value into an iterator. In this case
it converts a StrTendril into an Iterator over itself.
Ok, so now that we parsed our tree what with it? Well, for that we might need some
kind of function that will help us traverse it. We shall call that function `walk`.
```rust
fn walk(prefix: &str, handle: Handle) {
let node = handle.borrow();
// We print out the prefix before we start
print!("{}", prefix);
// We are only interested in following nodes:
// Document, Text and Element, so our match
// reflects that.
match node.node {
Document
=> println!("#document"),
Text(ref text) => {
println!("#text {}", text.escape_default())
},
Element(ref name, _) => {
println!("{}", name.local);
},
_ => {},
}
// We increase indent in child nodes
let new_indent = {
let mut temp = String::new();
temp.push_str(prefix);
temp.push_str(" ");
temp
};
for child in node.children.iter()
// In order to avoid weird indentation, we filter
// only Text/Element nodes.
// We don't need to filter Document since its guaranteed
// child elements don't contain documents
.filter(|child| match child.borrow().node {
Text(_) | Element (_, _) => true,
_ => false,
}
) {
// Recursion - Yay!
walk(&new_indent, child.clone());
}
}
```
For full source code check out: [`examples/xml_tree_printer.rs`](https://github.com/servo/html5ever/blob/main/rcdom/examples/xml_tree_printer.rs)