/// XML pretty printer and highlighter. library; import 'dart:io'; import 'package:args/args.dart' as args; import 'package:xml/xml.dart'; const entityMapping = XmlDefaultEntityMapping.xml(); const String ansiReset = '\u001b[0m'; const String ansiRed = '\u001b[31m'; const String ansiGreen = '\u001b[32m'; const String ansiYellow = '\u001b[33m'; const String ansiBlue = '\u001b[34m'; const String ansiMagenta = '\u001b[35m'; const String ansiCyan = '\u001b[36m'; const String attributeStyle = ansiBlue; const String cdataStyle = ansiYellow; const String commentStyle = ansiGreen; const String declarationStyle = ansiCyan; const String doctypeStyle = ansiCyan; const String documentStyle = ansiReset; const String documentFragmentStyle = ansiCyan; const String elementStyle = ansiMagenta; const String nameStyle = ansiRed; const String processingStyle = ansiCyan; const String textStyle = ansiReset; final args.ArgParser argumentParser = args.ArgParser() ..addFlag( 'color', abbr: 'c', help: 'Colorize the output.', defaultsTo: stdout.supportsAnsiEscapes, ) ..addOption( 'indent', abbr: 'i', help: 'Customize the indention when pretty printing.', defaultsTo: ' ', ) ..addOption( 'newline', abbr: 'n', help: 'Change the newline character when pretty printing.', defaultsTo: '\n', ) ..addFlag( 'pretty', abbr: 'p', help: 'Reformat the output to be pretty.', defaultsTo: true, ); void printUsage() { stdout.writeln('Usage: xml_pp [options] {files}'); stdout.writeln(); stdout.writeln(argumentParser.usage); exit(1); } void main(List arguments) { final files = []; final results = argumentParser.parse(arguments); final color = results['color'] as bool; final indent = results['indent'] as String; final newLine = results['newline'] as String; final pretty = results['pretty'] as bool; for (final argument in results.rest) { final file = File(argument); if (file.existsSync()) { files.add(file); } else { stderr.writeln('File not found: $file'); exit(2); } } if (files.isEmpty) { printUsage(); } // Select the appropriate printing visitor. For simpler use-cases one would // just call `document.toXmlString(pretty: true, indent: ' ')`. final visitor = pretty ? (color ? XmlColoredPrettyWriter( stdout, entityMapping: entityMapping, indent: indent, newLine: newLine, ) : XmlPrettyWriter( stdout, entityMapping: entityMapping, indent: indent, newLine: newLine, )) : (color ? XmlColoredWriter(stdout, entityMapping: entityMapping) : XmlWriter(stdout, entityMapping: entityMapping)); for (final file in files) { visitor.visit(XmlDocument.parse(file.readAsStringSync())); } } mixin ColoredWriter { StringSink get buffer; List get styles; void style(String style, void Function() callback) { styles.add(style); buffer.write(style); callback(); styles.removeLast(); buffer.write(styles.isEmpty ? ansiReset : styles.last); } } class XmlColoredWriter extends XmlWriter with ColoredWriter { XmlColoredWriter(super.buffer, {super.entityMapping}); @override final List styles = []; @override void visitAttribute(XmlAttribute node) => style(attributeStyle, () => super.visitAttribute(node)); @override void visitCDATA(XmlCDATA node) => style(cdataStyle, () => super.visitCDATA(node)); @override void visitComment(XmlComment node) => style(commentStyle, () => super.visitComment(node)); @override void visitDeclaration(XmlDeclaration node) => style(declarationStyle, () => super.visitDeclaration(node)); @override void visitDocument(XmlDocument node) => style(documentStyle, () => super.visitDocument(node)); @override void visitDocumentFragment(XmlDocumentFragment node) => style(documentFragmentStyle, () => super.visitDocumentFragment(node)); @override void visitDoctype(XmlDoctype node) => style(doctypeStyle, () => super.visitDoctype(node)); @override void visitElement(XmlElement node) => style(elementStyle, () => super.visitElement(node)); @override void visitName(XmlName name) => style(nameStyle, () => super.visitName(name)); @override void visitProcessing(XmlProcessing node) => style(processingStyle, () => super.visitProcessing(node)); @override void visitText(XmlText node) => style(textStyle, () => super.visitText(node)); } class XmlColoredPrettyWriter extends XmlPrettyWriter with ColoredWriter { XmlColoredPrettyWriter( super.buffer, { super.entityMapping, super.indent, super.newLine, }); @override final List styles = []; @override void visitAttribute(XmlAttribute node) => style(attributeStyle, () => super.visitAttribute(node)); @override void visitCDATA(XmlCDATA node) => style(cdataStyle, () => super.visitCDATA(node)); @override void visitComment(XmlComment node) => style(commentStyle, () => super.visitComment(node)); @override void visitDeclaration(XmlDeclaration node) => style(declarationStyle, () => super.visitDeclaration(node)); @override void visitDocument(XmlDocument node) => style(documentStyle, () => super.visitDocument(node)); @override void visitDocumentFragment(XmlDocumentFragment node) => style(documentFragmentStyle, () => super.visitDocumentFragment(node)); @override void visitDoctype(XmlDoctype node) => style(doctypeStyle, () => super.visitDoctype(node)); @override void visitElement(XmlElement node) => style(elementStyle, () => super.visitElement(node)); @override void visitName(XmlName name) => style(nameStyle, () => super.visitName(name)); @override void visitProcessing(XmlProcessing node) => style(processingStyle, () => super.visitProcessing(node)); @override void visitText(XmlText node) => style(textStyle, () => super.visitText(node)); }