xmlformat 1.2.1

Basic XML raw text or files formatter. Use it as a dependency or install it as a binary.
Documentation
use std::io::{self, Cursor, IsTerminal, Read};
use std::path::PathBuf;

use anyhow::Result;
use quick_xml::events::{BytesText, Event};
use quick_xml::{Reader, Writer};

pub struct Formatter {
    pub compress: bool,
    pub indent: usize,
    pub keep_comments: bool,
    pub eof_newline: bool,
}

impl Formatter {
    pub fn format_file(self, file_path: PathBuf) -> Result<String> {
        let content = std::fs::read_to_string(file_path)?;
        self.format_xml(&content)
    }

    pub fn format_stdin(self) -> Result<String> {
        if io::stdin().is_terminal() {
            anyhow::bail!("No input provided. Use -f <file> or pipe XML data.");
        }

        let mut content = String::new();
        io::stdin().read_to_string(&mut content)?;
        self.format_xml(&content)
    }

    pub fn format_xml(&self, xml_content: &str) -> Result<String> {
        let cursor = Cursor::new(Vec::new());
        let mut reader = Reader::from_str(xml_content);
        reader.config_mut().trim_text(false);

        let mut writer = if self.compress {
            Writer::new(cursor)
        } else {
            Writer::new_with_indent(cursor, b' ', self.indent)
        };

        loop {
            match reader.read_event()? {
                Event::Text(ref e) => {
                    let text_content = reader.decoder().decode(&e)?.to_string();

                    let filtered_lines: Vec<&str> = text_content
                        .lines()
                        .filter(|line| !line.trim().is_empty())
                        .collect();

                    if !filtered_lines.is_empty() {
                        let filtered_text = filtered_lines.join("\n");
                        writer.write_event(Event::Text(BytesText::new(&filtered_text)))?;
                    }
                }
                Event::Comment(e) => {
                    if self.keep_comments {
                        writer.write_event(Event::Comment(e))?;
                    }
                }
                Event::Eof => break,
                event => writer.write_event(event)?,
            }
        }

        let mut result = writer.into_inner().into_inner();
        if self.eof_newline && !result.ends_with(&['\n' as u8]) {
            result.push('\n' as u8);
        }

        Ok(String::from_utf8(result)?)
    }
}
impl Default for Formatter {
    fn default() -> Self {
        Self {
            compress: false,
            indent: 2,
            keep_comments: false,
            eof_newline: false,
        }
    }
}