The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/usr/bin/perl
# 4.9.2009, Sampo Kellomaki (sampo@iki.fi)
# $Id: xml-pretty.pl,v 1.1 2009-09-05 02:23:41 sampo Exp $
#
# Pretty Print XML  --  Make XML almost human readable
#
# Usage: cat foo.saml | ./zxdecode | ./xml-pretty.pl
# tailf /var/zxid/log/xml.dbg | ./xml-pretty.pl
use Data::Dumper;

$ascii = 2;

# See https://wiki.archlinux.org/index.php/Color_Bash_Prompt
sub red   { $ascii > 1 ? "\e[1;31m$_[0]\e[0m" : $_[0]; }  # red text
#sub green { $ascii > 1 ? "\e[1;32m$_[0]\e[0m" : $_[0]; }
#sub red    { $ascii > 1 ? "\e[1;41m$_[0]\e[0m" : $_[0]; }  # red background, black bold text
sub green  { $ascii > 1 ? "\e[1;42m$_[0]\e[0m" : $_[0]; }
sub redy   { $ascii > 1 ? "\e[41m$_[0]\e[0m" : $_[0]; }    # red background, black text (no bold)
sub greeny { $ascii > 1 ? "\e[42m$_[0]\e[0m" : $_[0]; }
sub yely { $ascii > 1 ? "\e[43m$_[0]\e[0m" : $_[0]; }
sub bluy { $ascii > 1 ? "\e[46m$_[0]\e[0m" : $_[0]; }

$indent = '';

sub xml_pretty {
    my $res = '';
    my ($x,$at,$noindent);
    #warn "start res($res) indent($indent) data($_[0])";  # tail dup seems perl error
    for $x (split /(<\/?\w.*?>)/, $_[0]) {
	next if !length $x;
	#print "*";
	if ($x !~ /^</) {
	    $last_tag = undef;
	    if (length $x < 40) {
		if ($x =~ /^\s*$/s) {
		    #$res .= "\n";
		    #warn "HERE1($x)";
		} else {
		    #warn "HERE3($x)";
		    chomp $res;
		    $res .= green($x);
		}
		$noindent = 1;
	    } else {
		my $xx = $x;
		chomp $xx;
		if ($xx =~ /^\s*$/s) {
		    $res .= "\n";
		    #warn "HERE($xx)";
		} else {
		    #warn "HERE2($xx)";
		    $res .= $indent.greeny($xx)."\n";
		}
	    }
	    next;
	}
	if ($x =~ /^<!--/) {
	    $last_tag = undef;
	    $x =~ s/\e\[\d+m//g;
	    $res .= bluy($x)."\n";
	    $indent = '';
	    next;
	}
	if ($x =~ /^<\?/) {
	    $last_tag = undef;
	    $res .= "$indent$x\n";
	    next;
	}
	if ($x =~ /^<\//) {           # close tag
	    substr($indent,-2) = '';
	    $rx = red($x);
	    if ($noindent) {
		$res .= "</>";
	    } else {
		$xx = substr($x,2,-1);
		#warn "       x($xx)      indent($indent)\nlast_tag($last_tag) last_indent($last_indent)";
		if ($xx eq $last_tag && $indent eq $last_indent) {
		    die "Res does not end in > res($res)" if substr($res,-2) ne ">\n";
		    chop $res;
		    chop $res;
		    $res .= "/>\n";
		} else {
		    $res .= "$indent$rx\n";
		}
	    }
	    $last_tag = undef;
	    next;
	}
	if ($noindent) {
	    $noindent = 0;
	    $res .= "\n";
	}
	#            1               12   3   32 4  4
	if ($x =~ /^<([A-Za-z0-9_:-]+)(\s+(.*?))?(\/)?>$/) {
	    $last_tag = $1;
	    $last_indent = $indent;
	    $res .= "$indent<".red($1);
	    my @ats = split / /, $3;
	    if ($#ats == 0) {
		my ($name,$val) = split /=/, $ats[0], 2;
		$res .= " $name=".yely($val);
	    } else {
		for $at (@ats) {
		    my ($name,$val) = split /=/, $at, 2;
		    $res .= "\n$indent    $name=".yely($val);
		}
	    }
	    if ($4) {
		$res .= "/>\n";   
		$last_tag = undef;
	    } else {
		$res .= ">\n";   
		$indent .= '  ';
	    }
	} else {
	    die "unprocessable start tag($x)";
	}
    }
    return $res;
}

while (defined($line = <STDIN>)) {
    #$line =~ tr[\r][];
    print xml_pretty($line);
}

__END__