wacore 0.7.0

Core WhatsApp protocol implementation without runtime dependencies
Documentation
use std::fmt::{self, Write as _};
use wacore_binary::{Node, NodeContent, NodeContentRef, NodeRef};

pub struct DisplayableNode<'a>(pub &'a Node);

pub struct DisplayableNodeRef<'a, 'b>(pub &'a NodeRef<'b>);

fn get_printable_str(data: &[u8]) -> Option<&str> {
    let s = std::str::from_utf8(data).ok()?;
    if s.chars().all(|c| !c.is_control()) {
        Some(s)
    } else {
        None
    }
}

/// Trait for formatting XML nodes generically.
trait XmlFormattable {
    fn tag(&self) -> &str;
    fn format_attributes(&self, out: &mut String);
    fn format_content_lines(&self, indent: bool) -> Vec<String>;
}

impl XmlFormattable for Node {
    fn tag(&self) -> &str {
        &self.tag
    }

    fn format_attributes(&self, out: &mut String) {
        if self.attrs.is_empty() {
            return;
        }
        let mut keys: Vec<_> = self.attrs.keys().collect();
        keys.sort_unstable();

        for key in keys {
            if let Some(value) = self.attrs.get(key) {
                let _ = write!(out, " {}=\"{}\"", key, value);
            }
        }
    }

    fn format_content_lines(&self, indent: bool) -> Vec<String> {
        match &self.content {
            Some(NodeContent::Nodes(nodes)) => nodes
                .iter()
                .flat_map(|n| {
                    DisplayableNode(n)
                        .to_string()
                        .lines()
                        .map(String::from)
                        .collect::<Vec<_>>()
                })
                .collect(),
            Some(NodeContent::Bytes(bytes)) => format_bytes_content(bytes, indent),
            Some(NodeContent::String(s)) => format_string_content(s, indent),
            None => vec![],
        }
    }
}

impl<'a> XmlFormattable for NodeRef<'a> {
    fn tag(&self) -> &str {
        &self.tag
    }

    fn format_attributes(&self, out: &mut String) {
        if self.attrs.is_empty() {
            return;
        }
        for (key, value) in self.attrs.iter() {
            let _ = write!(out, " {}=\"{}\"", key, value);
        }
    }

    fn format_content_lines(&self, indent: bool) -> Vec<String> {
        match self.content.as_ref() {
            Some(NodeContentRef::Nodes(nodes)) => nodes
                .iter()
                .flat_map(|n| {
                    DisplayableNodeRef(n)
                        .to_string()
                        .lines()
                        .map(String::from)
                        .collect::<Vec<_>>()
                })
                .collect(),
            Some(NodeContentRef::Bytes(bytes)) => format_bytes_content(bytes.as_ref(), indent),
            Some(NodeContentRef::String(s)) => format_string_content(s, indent),
            None => vec![],
        }
    }
}

fn format_bytes_content(bytes: &[u8], indent: bool) -> Vec<String> {
    if let Some(s) = get_printable_str(bytes) {
        format_string_content(s, indent)
    } else {
        vec![format!("<!-- {} bytes -->", bytes.len())]
    }
}

fn format_string_content(s: &str, indent: bool) -> Vec<String> {
    if indent {
        s.lines().map(String::from).collect()
    } else {
        vec![s.replace('\n', "\\n")]
    }
}

fn format_node<T: XmlFormattable>(node: &T, f: &mut fmt::Formatter<'_>) -> fmt::Result {
    let indent_xml = false;
    let mut attrs = String::new();
    node.format_attributes(&mut attrs);
    let mut content_lines = node.format_content_lines(indent_xml);

    if content_lines.is_empty() {
        write!(f, "<{}{}/>", node.tag(), attrs)
    } else {
        let newline = "";
        let indent = if indent_xml { "  " } else { "" };

        for line in content_lines.iter_mut() {
            *line = format!("{}{}", indent, line);
        }
        let final_content = content_lines.join(newline);

        write!(
            f,
            "<{}{}>{}{}{}</{}>",
            node.tag(),
            attrs,
            newline,
            final_content,
            newline,
            node.tag()
        )
    }
}

impl fmt::Display for DisplayableNode<'_> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        format_node(self.0, f)
    }
}

impl fmt::Display for DisplayableNodeRef<'_, '_> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        format_node(self.0, f)
    }
}