blob: f2a9d84438f0e78e1b31fd85965c6731c6b427ab [file]
/// XML pretty printer and highlighter.
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<String> arguments) {
final files = <File>[];
final results = argumentParser.parse(arguments);
final color = results['color'];
final indent = results['indent'];
final newLine = results['newline'];
final pretty = results['pretty'];
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<String> 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<String> 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<String> 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));
}