// Copyright 2014-2017 The html5ever Project Developers. See the // COPYRIGHT file at the top-level directory of this distribution. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. use log::warn; pub use markup5ever::serialize::{AttrRef, Serialize, Serializer, TraversalScope}; use markup5ever::{local_name, namespace_url, ns}; use std::default::Default; use std::io::{self, Write}; use crate::{LocalName, QualName}; pub fn serialize(writer: Wr, node: &T, opts: SerializeOpts) -> io::Result<()> where Wr: Write, T: Serialize, { let mut ser = HtmlSerializer::new(writer, opts.clone()); node.serialize(&mut ser, opts.traversal_scope) } #[derive(Clone)] pub struct SerializeOpts { /// Is scripting enabled? pub scripting_enabled: bool, /// Serialize the root node? Default: ChildrenOnly pub traversal_scope: TraversalScope, /// If the serializer is asked to serialize an invalid tree, the default /// behavior is to panic in the event that an `end_elem` is created without a /// matching `start_elem`. Setting this to true will prevent those panics by /// creating a default parent on the element stack. No extra start elem will /// actually be written. Default: false pub create_missing_parent: bool, } impl Default for SerializeOpts { fn default() -> SerializeOpts { SerializeOpts { scripting_enabled: true, traversal_scope: TraversalScope::ChildrenOnly(None), create_missing_parent: false, } } } #[derive(Default)] struct ElemInfo { html_name: Option, ignore_children: bool } pub struct HtmlSerializer { pub writer: Wr, opts: SerializeOpts, stack: Vec, } fn tagname(name: &QualName) -> LocalName { match name.ns { ns!(html) | ns!(mathml) | ns!(svg) => (), ref ns => { // FIXME(#122) warn!("node with weird namespace {:?}", ns); }, } name.local.clone() } impl HtmlSerializer { pub fn new(writer: Wr, opts: SerializeOpts) -> Self { let html_name = match opts.traversal_scope { TraversalScope::IncludeNode | TraversalScope::ChildrenOnly(None) => None, TraversalScope::ChildrenOnly(Some(ref n)) => Some(tagname(n)), }; HtmlSerializer { writer, opts, stack: vec![ElemInfo { html_name, ignore_children: false, }], } } fn parent(&mut self) -> &mut ElemInfo { if self.stack.is_empty() { if self.opts.create_missing_parent { warn!("ElemInfo stack empty, creating new parent"); self.stack.push(Default::default()); } else { panic!("no parent ElemInfo") } } self.stack.last_mut().unwrap() } fn write_escaped(&mut self, text: &str, attr_mode: bool) -> io::Result<()> { for c in text.chars() { match c { '&' => self.writer.write_all(b"&"), '\u{00A0}' => self.writer.write_all(b" "), '"' if attr_mode => self.writer.write_all(b"""), '<' if !attr_mode => self.writer.write_all(b"<"), '>' if !attr_mode => self.writer.write_all(b">"), c => self.writer.write_fmt(format_args!("{}", c)), }?; } Ok(()) } } impl Serializer for HtmlSerializer { fn start_elem<'a, AttrIter>(&mut self, name: QualName, attrs: AttrIter) -> io::Result<()> where AttrIter: Iterator>, { let html_name = match name.ns { ns!(html) => Some(name.local.clone()), _ => None, }; if self.parent().ignore_children { self.stack.push(ElemInfo { html_name, ignore_children: true, }); return Ok(()); } self.writer.write_all(b"<")?; self.writer.write_all(tagname(&name).as_bytes())?; for (name, value) in attrs { self.writer.write_all(b" ")?; match name.ns { ns!() => (), ns!(xml) => self.writer.write_all(b"xml:")?, ns!(xmlns) => { if name.local != local_name!("xmlns") { self.writer.write_all(b"xmlns:")?; } }, ns!(xlink) => self.writer.write_all(b"xlink:")?, ref ns => { // FIXME(#122) warn!("attr with weird namespace {:?}", ns); self.writer.write_all(b"unknown_namespace:")?; }, } self.writer.write_all(name.local.as_bytes())?; self.writer.write_all(b"=\"")?; self.write_escaped(value, true)?; self.writer.write_all(b"\"")?; } self.writer.write_all(b">")?; let ignore_children = name.ns == ns!(html) && match name.local { local_name!("area") | local_name!("base") | local_name!("basefont") | local_name!("bgsound") | local_name!("br") | local_name!("col") | local_name!("embed") | local_name!("frame") | local_name!("hr") | local_name!("img") | local_name!("input") | local_name!("keygen") | local_name!("link") | local_name!("meta") | local_name!("param") | local_name!("source") | local_name!("track") | local_name!("wbr") => true, _ => false, }; self.stack.push(ElemInfo { html_name, ignore_children, }); Ok(()) } fn end_elem(&mut self, name: QualName) -> io::Result<()> { let info = match self.stack.pop() { Some(info) => info, None if self.opts.create_missing_parent => { warn!("missing ElemInfo, creating default."); Default::default() }, _ => panic!("no ElemInfo"), }; if info.ignore_children { return Ok(()); } self.writer.write_all(b"") } fn write_text(&mut self, text: &str) -> io::Result<()> { let escape = match self.parent().html_name { Some(local_name!("style")) | Some(local_name!("script")) | Some(local_name!("xmp")) | Some(local_name!("iframe")) | Some(local_name!("noembed")) | Some(local_name!("noframes")) | Some(local_name!("plaintext")) => false, Some(local_name!("noscript")) => !self.opts.scripting_enabled, _ => true, }; if escape { self.write_escaped(text, false) } else { self.writer.write_all(text.as_bytes()) } } fn write_comment(&mut self, text: &str) -> io::Result<()> { self.writer.write_all(b"") } fn write_doctype(&mut self, name: &str) -> io::Result<()> { self.writer.write_all(b"") } fn write_processing_instruction(&mut self, target: &str, data: &str) -> io::Result<()> { self.writer.write_all(b"") } }