File: tplxml

package info (click to toggle)
libtpl 1.6.1-1.2
  • links: PTS
  • area: main
  • in suites: forky, sid
  • size: 2,028 kB
  • sloc: ansic: 5,669; perl: 1,062; makefile: 101; cpp: 32; sh: 18
file content (299 lines) | stat: -rwxr-xr-x 10,965 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
#!/usr/bin/perl

# tplxml 
# by Troy Hanson   27 Feb 2006
# convert between tpl and XML

# Copyright (c) 2005-2013, Troy Hanson     http://troydhanson.github.com/tpl/
# 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.
#
# 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/</&lt;/g;
    $$str =~ s/>/&gt;/g;
}
sub unquote_chars {
    my $str = shift;
    $$str =~ s/&lt;/</g;
    $$str =~ s/&gt;/>/g;
    $$str =~ s/&amp;/&/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";