123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306 |
- #!/usr/bin/perl
- # tplxml
- # by Troy Hanson 27 Feb 2006
- # convert between tpl and XML
- # Copyright (c) 2005-2006, Troy Hanson http://tpl.sourceforge.net
- # All rights reserved.
- #
- # Redistribution and use in source and binary forms, with or without
- # modification, are permitted provided that the following conditions are met:
- #
- # * Redistributions of source code must retain the above copyright
- # notice, this list of conditions and the following disclaimer.
- # * Redistributions in binary form must reproduce the above copyright
- # notice, this list of conditions and the following disclaimer in
- # the documentation and/or other materials provided with the
- # distribution.
- # * Neither the name of the copyright holder nor the names of its
- # contributors may be used to endorse or promote products derived
- # from this software without specific prior written permission.
- #
- # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
- # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
- # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
- # PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
- # OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
- # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
- # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
- # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- use strict;
- use warnings;
- use XML::Parser;
- use FindBin;
- use lib "$FindBin::Bin"; #locate Tpl.pm in same directory as tplxml
- use Tpl;
- use bytes;
- sub quote_chars {
- my $str = shift;
- $$str =~ s/&/&/g; #order matters
- $$str =~ s/</</g;
- $$str =~ s/>/>/g;
- }
- sub unquote_chars {
- my $str = shift;
- $$str =~ s/</</g;
- $$str =~ s/>/>/g;
- $$str =~ s/&/&/g;
- }
- sub hex_chars {
- my $str = shift;
- my $hex;
- for(my $i=0; $i < length $$str; $i++) {
- my $byte = unpack("C",substr($$str,$i,1));
- $hex .= sprintf("%02x", $byte);
- }
- $$str = $hex;
- }
- sub unhex_chars {
- my $str = shift;
- my $bytes;
- for(my $i=0; $i < length $$str; $i+=2) {
- my $hexbyte = substr($$str,$i,2);
- $bytes .= pack("C", hex($hexbyte));
- }
- $$str= $bytes;
- }
- sub tpl2xml {
- my $src = shift;
- my (@out,@args);
- # build list of references to hold output of unpacking
- my ($fmt,@fxlens) = peek_fmt($src);
- for(my ($i,$j,$k)=(0,0,0);$i<length($fmt);$i++) {
- push @args, [] if substr($fmt,$i,2) =~ /^[iucfIU]\#$/; # octothorpic
- push @args, \$out[$j++] if substr($fmt,$i,2) =~ /^[iuBscfIU][^\#]*$/;
- push @args, $fxlens[$k++] if substr($fmt,$i,1) eq "#";
- }
- my $tpl = Tpl->tpl_map($fmt,@args);
- $tpl->tpl_load($src);
- $tpl->tpl_unpack(0);
- # construct xml preamble
- my $pre = qq{<?xml version="1.0" encoding="utf-8" ?>
- <!DOCTYPE tplxml [
- <!ELEMENT tplxml (A|i|u|I|U|B|s|c|f|fx)*>
- <!ATTLIST tplxml
- format CDATA #REQUIRED
- fxlens CDATA #REQUIRED
- >
- <!ELEMENT i (#PCDATA)>
- <!ELEMENT u (#PCDATA)>
- <!ELEMENT I (#PCDATA)>
- <!ELEMENT U (#PCDATA)>
- <!ELEMENT B (#PCDATA)>
- <!ELEMENT s (#PCDATA)>
- <!ELEMENT c (#PCDATA)>
- <!ELEMENT f (#PCDATA)>
- <!ELEMENT A (el)*>
- <!ELEMENT el (A|i|u|I|U|B|s|c|f|fx)+>
- <!ELEMENT fx (i|u|I|U|c|f)*>
- ]>\n};
- print $pre;
- my $fxattr = join ",", @fxlens;
- print qq{<tplxml format="$fmt" fxlens="$fxattr">\n};
- tpl2xml_node($tpl,"A0",1);
- print qq{</tplxml>\n};
- }
- sub tpl2xml_node {
- my $tpl = shift;
- my $node = shift;
- my $indent = shift;
- my $i = " " x $indent;
- for my $c (@{ $tpl->{$node} }) {
- if (ref($c)) {
- my ($type,$addr,$fxlen) = @$c;
- quote_chars $addr if $type eq 's';
- hex_chars $addr if $type eq 'B';
- if (not defined $fxlen) {
- print qq{$i<$type>$$addr</$type>\n}; # singleton
- } else {
- # all elements of octothorpic fixed-len array
- print qq{$i<fx>\n};
- print qq{$i <$type>$addr->[$_]</$type>\n} for (0..$fxlen-1);
- print qq{$i</fx>\n};
- }
- } else {
- # A node
- print qq{$i<A>\n};
- my $idx = $1 if $c =~ /^A(\d+)$/;
- while($tpl->tpl_unpack($idx) > 0) {
- print qq{$i<el>\n};
- tpl2xml_node($tpl,$c,$indent+1);
- print qq{$i</el>\n};
- }
- print qq{$i</A>\n};
- }
- }
- }
- sub xml2tpl {
- my $src = shift;
- my $p = new XML::Parser( Style => 'Tree' );
- my $tree = $p->parse($$src);
- die "not a tpl xml document" unless $tree->[0] eq 'tplxml';
- die "no format attribute" unless defined $tree->[1][0]->{format};
- my $fmt = $tree->[1][0]->{format};
- die "no fxlens attribute" unless defined $tree->[1][0]->{fxlens};
- my @fxlens = split /,/, $tree->[1][0]->{fxlens};
- # build list of references to variables for use in packing
- my (@args,@out);
- for(my ($i,$j,$k)=(0,0,0);$i<length($fmt);$i++) {
- push @args, [] if substr($fmt,$i,2) =~ /^[iucfIU]\#$/; # octothorpic
- push @args, \$out[$j++] if substr($fmt,$i,2) =~ /^[iuBscfIU][^\#]*$/;
- push @args, $fxlens[$k++] if substr($fmt,$i,1) eq "#";
- }
- my $tpl = Tpl->tpl_map($fmt,@args);
- xml2tpl_dfs($tpl,$tree->[1]);
- $tpl->tpl_pack(0);
- print $tpl->tpl_dump;
- }
- sub xml2tpl_dfs {
- my $tpl = shift;
- my $xml = shift;
- my @next = @$xml; # ($attr,@tagvals) = $$xml;
- shift @next; # discard <tplxml> attributes
- my @tpltoks = @{ $tpl->{"A0"} }; #expected tokens when parsing
-
- TAG: while (@next) {
- my $xmltag = shift @next;
- my $xmlval = shift @next;
- # skip whitespace/newlines embedded between tags
- next TAG if ($xmltag eq "0" and $xmlval =~ /^\s+$/);
- # pack if necessary. consume tokens by look-ahead until non-pack token.
- while (@tpltoks > 0 and $tpltoks[0] =~ /^P(\d+)$/) {
- shift @tpltoks;
- $tpl->tpl_pack($1);
- }
- # If tpl format specifies a non-array type should appear at this point
- # in the XML tree, then validate the type matches the format and assign
- # the value from the XML to the variable from which it'll be packed
- my $tpltoken = shift @tpltoks;
- my $octothorpic=0;
- if (ref $tpltoken) {
- my ($tpltype,$tpladdr,$fxlen) = @$tpltoken;
- # This block is how we handle octothorpic (fixed length) arrays.
- # If $fxlen is defined then an octothorpic <fx> node is expected.
- # After finding the <fx> node we put its subnodes (the array elements)
- # onto the @next array for immediate parsing and we use $fxlen:$remaining
- # as a signet version of the $fxlen to induce the element-processing loop.
- if (defined $fxlen) {
- if ($fxlen =~ /^(\d+):(\d+)$/) { # $1==orig $fxlen, $2==remain $fxlen
- $octothorpic=1;
- unshift @tpltoks, [$tpltype, $tpladdr, $1.":".($2-1)] if $2 > 1;
- } else { # octothorpic array expected; look for <fx> parent node
- die "expected '<fx>' but got '<$xmltag>'" unless $xmltag eq 'fx';
- @{ $tpladdr } = (); # Empty accumulator array for octothorpic values
- unshift @tpltoks, [$tpltype, $tpladdr, "$fxlen:$fxlen"]; # x:x signet
- shift @$xmlval; # discard 'A' attributes
- unshift @next, @$xmlval; #parse xml subtree now (dfs)
- next TAG; # proceed to children of <fx> node
- }
- }
- if ($tpltype ne $xmltag) {
- die "mismatch: xml has '$xmltag' where format specifies '$tpltype'";
- }
- # expect @$xmlval to be ({},0,'value') i.e. a single, terminal text node
- if (@$xmlval > 3 || $xmlval->[1] ne '0') {
- die "error: xml tag '$xmltag' cannot enclose sub-tags";
- }
- if ($octothorpic) {
- push @{ $tpladdr }, $xmlval->[2];
- } else {
- $$tpladdr = $xmlval->[2];
- }
- unquote_chars $tpladdr if $tpltype eq 's';
- unhex_chars $tpladdr if $tpltype eq 'B';
- } elsif ($tpltoken =~ /^A(\d+)$/) {
- # tpl format specifies an array should appear at this point in the XML
- if ($xmltag ne 'A') {
- die "mismatch: xml has '$xmltag' where format specifies 'A'";
- }
- shift @$xmlval; # discard 'A' attributes
- # form token that means "replace me with tokens from A(n), x times"
- # (where x is the number of elements contained by this array).
- my $array_count=0;
- for(my $i=0; $i < @$xmlval; $i+=2) {
- $array_count++ if $xmlval->[$i] eq 'el';
- }
- unshift @tpltoks, "N$1:$array_count" if $array_count > 0;
- unshift @next, @$xmlval; #parse xml subtree now (dfs)
- } elsif ($tpltoken =~ /^N(\d+):(\d+)$/) {
- if ($xmltag ne "el") {
- die "mismatch: xml has '$xmltag' where array 'el' is expected";
- }
- # prepend A$1's tokens (and decremented N:count) to expected tokens
- my ($n,$elsleft) = ($1, ($2 - 1));
- unshift @tpltoks, "N$n:$elsleft" if $elsleft > 0;
- unshift @tpltoks, "P$n"; # "pack me now" token
- unshift @tpltoks, @{ $tpl->{"A$1"} };
-
- shift @$xmlval; # discard 'el' attributes
- unshift @next, @$xmlval; # proceed to parse el subtree (dfs)
- } else {
- die "internal error, unexpected token $tpltoken";
- }
- }
- # pack if necessary. consume tokens by look-ahead until non-pack token.
- while (@tpltoks > 0 and $tpltoks[0] =~ /^P(\d+)$/) {
- shift @tpltoks;
- $tpl->tpl_pack($1);
- }
- if (@tpltoks > 0) {
- die "error: end of xml document reached but format requires more data";
- }
- }
- sub peek_fmt {
- my $buf = shift;
- die "invalid tpl file" unless ($$buf =~ /^tpl/);
- my $flags = CORE::unpack("C", substr($$buf,3,1));
- my $UF = ($flags & 1) ? "N" : "V"; # big or little endian fxlens
- my $fmt = (CORE::unpack("Z*", substr($$buf,8)));
- my $num_octothorpes = scalar (my @o = ($fmt =~ /#/g));
- my @fxlens;
- my $fx = 8 + length($fmt) + 1;
- for(my $i=0; $i < $num_octothorpes; $i++) {
- my $fxlen_bytes = substr($$buf,$fx,4);
- my $fxlen = unpack($UF, $fxlen_bytes);
- push @fxlens, $fxlen;
- $fx += 4;
- }
- return ($fmt,@fxlens);
- }
- ##########################################################################
- # Slurp input file, auto-detect if conversion is to tpl or XML, and run.
- ##########################################################################
- undef $/;
- my $src = <>;
- our $to = (substr($src,0,3) eq "tpl") ? "xml" : "tpl";
- xml2tpl(\$src) if $to eq "tpl";
- tpl2xml(\$src) if $to eq "xml";
|