tplxml 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. #!/usr/bin/perl
  2. # tplxml
  3. # by Troy Hanson 27 Feb 2006
  4. # convert between tpl and XML
  5. # Copyright (c) 2005-2006, Troy Hanson http://tpl.sourceforge.net
  6. # All rights reserved.
  7. #
  8. # Redistribution and use in source and binary forms, with or without
  9. # modification, are permitted provided that the following conditions are met:
  10. #
  11. # * Redistributions of source code must retain the above copyright
  12. # notice, this list of conditions and the following disclaimer.
  13. # * Redistributions in binary form must reproduce the above copyright
  14. # notice, this list of conditions and the following disclaimer in
  15. # the documentation and/or other materials provided with the
  16. # distribution.
  17. # * Neither the name of the copyright holder nor the names of its
  18. # contributors may be used to endorse or promote products derived
  19. # from this software without specific prior written permission.
  20. #
  21. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
  22. # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
  23. # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
  24. # PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
  25. # OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  26. # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  27. # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  28. # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  29. # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  30. # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  31. # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  32. use strict;
  33. use warnings;
  34. use XML::Parser;
  35. use FindBin;
  36. use lib "$FindBin::Bin"; #locate Tpl.pm in same directory as tplxml
  37. use Tpl;
  38. use bytes;
  39. sub quote_chars {
  40. my $str = shift;
  41. $$str =~ s/&/&/g; #order matters
  42. $$str =~ s/</&lt;/g;
  43. $$str =~ s/>/&gt;/g;
  44. }
  45. sub unquote_chars {
  46. my $str = shift;
  47. $$str =~ s/&lt;/</g;
  48. $$str =~ s/&gt;/>/g;
  49. $$str =~ s/&amp;/&/g;
  50. }
  51. sub hex_chars {
  52. my $str = shift;
  53. my $hex;
  54. for(my $i=0; $i < length $$str; $i++) {
  55. my $byte = unpack("C",substr($$str,$i,1));
  56. $hex .= sprintf("%02x", $byte);
  57. }
  58. $$str = $hex;
  59. }
  60. sub unhex_chars {
  61. my $str = shift;
  62. my $bytes;
  63. for(my $i=0; $i < length $$str; $i+=2) {
  64. my $hexbyte = substr($$str,$i,2);
  65. $bytes .= pack("C", hex($hexbyte));
  66. }
  67. $$str= $bytes;
  68. }
  69. sub tpl2xml {
  70. my $src = shift;
  71. my (@out,@args);
  72. # build list of references to hold output of unpacking
  73. my ($fmt,@fxlens) = peek_fmt($src);
  74. for(my ($i,$j,$k)=(0,0,0);$i<length($fmt);$i++) {
  75. push @args, [] if substr($fmt,$i,2) =~ /^[iucfIU]\#$/; # octothorpic
  76. push @args, \$out[$j++] if substr($fmt,$i,2) =~ /^[iuBscfIU][^\#]*$/;
  77. push @args, $fxlens[$k++] if substr($fmt,$i,1) eq "#";
  78. }
  79. my $tpl = Tpl->tpl_map($fmt,@args);
  80. $tpl->tpl_load($src);
  81. $tpl->tpl_unpack(0);
  82. # construct xml preamble
  83. my $pre = qq{<?xml version="1.0" encoding="utf-8" ?>
  84. <!DOCTYPE tplxml [
  85. <!ELEMENT tplxml (A|i|u|I|U|B|s|c|f|fx)*>
  86. <!ATTLIST tplxml
  87. format CDATA #REQUIRED
  88. fxlens CDATA #REQUIRED
  89. >
  90. <!ELEMENT i (#PCDATA)>
  91. <!ELEMENT u (#PCDATA)>
  92. <!ELEMENT I (#PCDATA)>
  93. <!ELEMENT U (#PCDATA)>
  94. <!ELEMENT B (#PCDATA)>
  95. <!ELEMENT s (#PCDATA)>
  96. <!ELEMENT c (#PCDATA)>
  97. <!ELEMENT f (#PCDATA)>
  98. <!ELEMENT A (el)*>
  99. <!ELEMENT el (A|i|u|I|U|B|s|c|f|fx)+>
  100. <!ELEMENT fx (i|u|I|U|c|f)*>
  101. ]>\n};
  102. print $pre;
  103. my $fxattr = join ",", @fxlens;
  104. print qq{<tplxml format="$fmt" fxlens="$fxattr">\n};
  105. tpl2xml_node($tpl,"A0",1);
  106. print qq{</tplxml>\n};
  107. }
  108. sub tpl2xml_node {
  109. my $tpl = shift;
  110. my $node = shift;
  111. my $indent = shift;
  112. my $i = " " x $indent;
  113. for my $c (@{ $tpl->{$node} }) {
  114. if (ref($c)) {
  115. my ($type,$addr,$fxlen) = @$c;
  116. quote_chars $addr if $type eq 's';
  117. hex_chars $addr if $type eq 'B';
  118. if (not defined $fxlen) {
  119. print qq{$i<$type>$$addr</$type>\n}; # singleton
  120. } else {
  121. # all elements of octothorpic fixed-len array
  122. print qq{$i<fx>\n};
  123. print qq{$i <$type>$addr->[$_]</$type>\n} for (0..$fxlen-1);
  124. print qq{$i</fx>\n};
  125. }
  126. } else {
  127. # A node
  128. print qq{$i<A>\n};
  129. my $idx = $1 if $c =~ /^A(\d+)$/;
  130. while($tpl->tpl_unpack($idx) > 0) {
  131. print qq{$i<el>\n};
  132. tpl2xml_node($tpl,$c,$indent+1);
  133. print qq{$i</el>\n};
  134. }
  135. print qq{$i</A>\n};
  136. }
  137. }
  138. }
  139. sub xml2tpl {
  140. my $src = shift;
  141. my $p = new XML::Parser( Style => 'Tree' );
  142. my $tree = $p->parse($$src);
  143. die "not a tpl xml document" unless $tree->[0] eq 'tplxml';
  144. die "no format attribute" unless defined $tree->[1][0]->{format};
  145. my $fmt = $tree->[1][0]->{format};
  146. die "no fxlens attribute" unless defined $tree->[1][0]->{fxlens};
  147. my @fxlens = split /,/, $tree->[1][0]->{fxlens};
  148. # build list of references to variables for use in packing
  149. my (@args,@out);
  150. for(my ($i,$j,$k)=(0,0,0);$i<length($fmt);$i++) {
  151. push @args, [] if substr($fmt,$i,2) =~ /^[iucfIU]\#$/; # octothorpic
  152. push @args, \$out[$j++] if substr($fmt,$i,2) =~ /^[iuBscfIU][^\#]*$/;
  153. push @args, $fxlens[$k++] if substr($fmt,$i,1) eq "#";
  154. }
  155. my $tpl = Tpl->tpl_map($fmt,@args);
  156. xml2tpl_dfs($tpl,$tree->[1]);
  157. $tpl->tpl_pack(0);
  158. print $tpl->tpl_dump;
  159. }
  160. sub xml2tpl_dfs {
  161. my $tpl = shift;
  162. my $xml = shift;
  163. my @next = @$xml; # ($attr,@tagvals) = $$xml;
  164. shift @next; # discard <tplxml> attributes
  165. my @tpltoks = @{ $tpl->{"A0"} }; #expected tokens when parsing
  166. TAG: while (@next) {
  167. my $xmltag = shift @next;
  168. my $xmlval = shift @next;
  169. # skip whitespace/newlines embedded between tags
  170. next TAG if ($xmltag eq "0" and $xmlval =~ /^\s+$/);
  171. # pack if necessary. consume tokens by look-ahead until non-pack token.
  172. while (@tpltoks > 0 and $tpltoks[0] =~ /^P(\d+)$/) {
  173. shift @tpltoks;
  174. $tpl->tpl_pack($1);
  175. }
  176. # If tpl format specifies a non-array type should appear at this point
  177. # in the XML tree, then validate the type matches the format and assign
  178. # the value from the XML to the variable from which it'll be packed
  179. my $tpltoken = shift @tpltoks;
  180. my $octothorpic=0;
  181. if (ref $tpltoken) {
  182. my ($tpltype,$tpladdr,$fxlen) = @$tpltoken;
  183. # This block is how we handle octothorpic (fixed length) arrays.
  184. # If $fxlen is defined then an octothorpic <fx> node is expected.
  185. # After finding the <fx> node we put its subnodes (the array elements)
  186. # onto the @next array for immediate parsing and we use $fxlen:$remaining
  187. # as a signet version of the $fxlen to induce the element-processing loop.
  188. if (defined $fxlen) {
  189. if ($fxlen =~ /^(\d+):(\d+)$/) { # $1==orig $fxlen, $2==remain $fxlen
  190. $octothorpic=1;
  191. unshift @tpltoks, [$tpltype, $tpladdr, $1.":".($2-1)] if $2 > 1;
  192. } else { # octothorpic array expected; look for <fx> parent node
  193. die "expected '<fx>' but got '<$xmltag>'" unless $xmltag eq 'fx';
  194. @{ $tpladdr } = (); # Empty accumulator array for octothorpic values
  195. unshift @tpltoks, [$tpltype, $tpladdr, "$fxlen:$fxlen"]; # x:x signet
  196. shift @$xmlval; # discard 'A' attributes
  197. unshift @next, @$xmlval; #parse xml subtree now (dfs)
  198. next TAG; # proceed to children of <fx> node
  199. }
  200. }
  201. if ($tpltype ne $xmltag) {
  202. die "mismatch: xml has '$xmltag' where format specifies '$tpltype'";
  203. }
  204. # expect @$xmlval to be ({},0,'value') i.e. a single, terminal text node
  205. if (@$xmlval > 3 || $xmlval->[1] ne '0') {
  206. die "error: xml tag '$xmltag' cannot enclose sub-tags";
  207. }
  208. if ($octothorpic) {
  209. push @{ $tpladdr }, $xmlval->[2];
  210. } else {
  211. $$tpladdr = $xmlval->[2];
  212. }
  213. unquote_chars $tpladdr if $tpltype eq 's';
  214. unhex_chars $tpladdr if $tpltype eq 'B';
  215. } elsif ($tpltoken =~ /^A(\d+)$/) {
  216. # tpl format specifies an array should appear at this point in the XML
  217. if ($xmltag ne 'A') {
  218. die "mismatch: xml has '$xmltag' where format specifies 'A'";
  219. }
  220. shift @$xmlval; # discard 'A' attributes
  221. # form token that means "replace me with tokens from A(n), x times"
  222. # (where x is the number of elements contained by this array).
  223. my $array_count=0;
  224. for(my $i=0; $i < @$xmlval; $i+=2) {
  225. $array_count++ if $xmlval->[$i] eq 'el';
  226. }
  227. unshift @tpltoks, "N$1:$array_count" if $array_count > 0;
  228. unshift @next, @$xmlval; #parse xml subtree now (dfs)
  229. } elsif ($tpltoken =~ /^N(\d+):(\d+)$/) {
  230. if ($xmltag ne "el") {
  231. die "mismatch: xml has '$xmltag' where array 'el' is expected";
  232. }
  233. # prepend A$1's tokens (and decremented N:count) to expected tokens
  234. my ($n,$elsleft) = ($1, ($2 - 1));
  235. unshift @tpltoks, "N$n:$elsleft" if $elsleft > 0;
  236. unshift @tpltoks, "P$n"; # "pack me now" token
  237. unshift @tpltoks, @{ $tpl->{"A$1"} };
  238. shift @$xmlval; # discard 'el' attributes
  239. unshift @next, @$xmlval; # proceed to parse el subtree (dfs)
  240. } else {
  241. die "internal error, unexpected token $tpltoken";
  242. }
  243. }
  244. # pack if necessary. consume tokens by look-ahead until non-pack token.
  245. while (@tpltoks > 0 and $tpltoks[0] =~ /^P(\d+)$/) {
  246. shift @tpltoks;
  247. $tpl->tpl_pack($1);
  248. }
  249. if (@tpltoks > 0) {
  250. die "error: end of xml document reached but format requires more data";
  251. }
  252. }
  253. sub peek_fmt {
  254. my $buf = shift;
  255. die "invalid tpl file" unless ($$buf =~ /^tpl/);
  256. my $flags = CORE::unpack("C", substr($$buf,3,1));
  257. my $UF = ($flags & 1) ? "N" : "V"; # big or little endian fxlens
  258. my $fmt = (CORE::unpack("Z*", substr($$buf,8)));
  259. my $num_octothorpes = scalar (my @o = ($fmt =~ /#/g));
  260. my @fxlens;
  261. my $fx = 8 + length($fmt) + 1;
  262. for(my $i=0; $i < $num_octothorpes; $i++) {
  263. my $fxlen_bytes = substr($$buf,$fx,4);
  264. my $fxlen = unpack($UF, $fxlen_bytes);
  265. push @fxlens, $fxlen;
  266. $fx += 4;
  267. }
  268. return ($fmt,@fxlens);
  269. }
  270. ##########################################################################
  271. # Slurp input file, auto-detect if conversion is to tpl or XML, and run.
  272. ##########################################################################
  273. undef $/;
  274. my $src = <>;
  275. our $to = (substr($src,0,3) eq "tpl") ? "xml" : "tpl";
  276. xml2tpl(\$src) if $to eq "tpl";
  277. tpl2xml(\$src) if $to eq "xml";