File Coverage

blib/lib/WAP/wbxml.pm
Criterion Covered Total %
statement 16 18 88.8
branch n/a
condition n/a
subroutine 6 6 100.0
pod n/a
total 22 24 91.6


line stmt bran cond sub pod time code
1             require 5.005;
2            
3 1     1   464 use strict;
  1         2  
  1         29  
4 1     1   484 use UNIVERSAL;
  1         14  
  1         5  
5            
6             package WbXml;
7 1     1   548 use integer;
  1         10  
  1         4  
8 1     1   503 use bytes;
  1         8  
  1         3  
9            
10 1     1   26 use vars qw($VERSION);
  1         1  
  1         55  
11             $VERSION = '1.10';
12            
13             =head1 NAME
14            
15             WAP::wbxml - Binarization of XML file
16            
17             =head1 SYNOPSIS
18            
19             use XML::DOM;
20             use WAP::WbXml;
21            
22             $parser = new XML::DOM::Parser;
23             $doc_xml = $parser->parsefile($infile);
24            
25             $rules = WbRules::Load();
26             $wbxml = new WbXml($rules,$publicid);
27             $output = $wbxml->compile($doc_xml,$encoding);
28            
29             =head1 DESCRIPTION
30            
31             This module implements binarisation of XML file according the specification :
32            
33             WAP - Wireless Application Protocol /
34             Binary XML Content Format Specification /
35             Version 1.3 WBXML (15th May 2000 Approved)
36            
37             The XML input file must refere to a DTD with a public identifier.
38            
39             The file WAP/wap.wbrules.xml configures this tool for all known DTD.
40            
41             This module needs I18N::Charset and XML::DOM modules.
42            
43             WAP Specifications, including Binary XML Content Format (WBXML)
44             are available on Ehttp://www.wapforum.org/E.
45            
46             =over 4
47            
48             =cut
49            
50 1     1   779 use XML::DOM;
  0            
  0            
51             use I18N::Charset;
52            
53             # Global tokens
54             use constant SWITCH_PAGE => 0x00;
55             use constant _END => 0x01;
56             use constant ENTITY => 0x02;
57             use constant STR_I => 0x03;
58             use constant LITERAL => 0x04;
59             use constant EXT_I_0 => 0x40;
60             use constant EXT_I_1 => 0x41;
61             use constant EXT_I_2 => 0x42;
62             use constant PI => 0x43;
63             use constant LITERAL_C => 0x44;
64             use constant EXT_T_0 => 0x80;
65             use constant EXT_T_1 => 0x81;
66             use constant EXT_T_2 => 0x82;
67             use constant STR_T => 0x83;
68             use constant LITERAL_A => 0x84;
69             use constant EXT_0 => 0xC0;
70             use constant EXT_1 => 0xC1;
71             use constant EXT_2 => 0xC2;
72             use constant OPAQUE => 0xC3;
73             use constant LITERAL_AC => 0xC4;
74             # Global token masks
75             use constant NULL => 0x00;
76             use constant HAS_CHILD => 0x40;
77             use constant HAS_ATTR => 0x80;
78            
79             =item new
80            
81             $wbxml = new WbXml($rules,$publicid);
82            
83             Create a instance of WBinarizer for a specified kind of DTD.
84            
85             If the DTD is not known in the rules, default rules are used.
86            
87             =cut
88            
89             sub new {
90             my $proto = shift;
91             my $class = ref($proto) || $proto;
92             my $self = {};
93             bless($self, $class);
94             my ($rules,$publicid) = @_;
95             $self->{publicid} = $publicid;
96             $self->{rules} = $rules;
97             $self->{rulesApp} = $rules->{App}->{$publicid};
98             unless ($self->{rulesApp}) {
99             $self->{rulesApp} = $rules->{DefaultApp};
100             warn "Using default rules.\n";
101             }
102             $self->{skipDefault} = $self->{rulesApp}->{skipDefault};
103             $self->{variableSubs} = $self->{rulesApp}->{variableSubs};
104             $self->{tagCodepage} = 0;
105             $self->{attrCodepage} = 0;
106             return $self;
107             }
108            
109             sub compileDatetime {
110             # WAP / WML
111             my $self = shift;
112             my ($content) = @_;
113             my $str;
114             if ($content =~ /(\d+)-(\d+)-(\d+)T(\d+)\.(\d+)\.(\d+)Z/) {
115             my $year = chr (16 * ($1 / 1000) + (($1 / 100) % 10))
116             . chr (16 * (($1 / 10) % 10) + ($1 % 10));
117             my $month = chr (16 * ($2 / 10) + ($2 % 10));
118             my $day = chr (16 * ($3 / 10) + ($3 % 10));
119             my $hour = chr (16 * ($4 / 10) + ($4 % 10));
120             my $min = chr (16 * ($5 / 10) + ($5 % 10));
121             my $sec = chr (16 * ($6 / 10) + ($6 % 10));
122             $str = $year . $month . $day;
123             $str .= $hour if (ord $hour or ord $min or ord $sec);
124             $str .= $min if (ord $min or ord $sec);
125             $str .= $sec if (ord $sec);
126             } else {
127             warn "Validate 'Datetime' error : $content.\n";
128             $str = "\x19\x70\x01\x01";
129             }
130             $self->putb('body',OPAQUE);
131             $self->putmb('body',length $str);
132             $self->putstr('body',$str);
133             }
134            
135             sub compileBinaryWV {
136             # WV
137             use MIME::Base64;
138             my $self = shift;
139             my ($value) = @_;
140             $value =~ s/\s+//g;
141             my $data = decode_base64($value);
142             if (length $data) {
143             $self->putb('body',OPAQUE);
144             $self->putmb('body',length $data);
145             $self->putstr('body',$data);
146             }
147             }
148            
149             sub compileIntegerWV {
150             # WV
151             my $self = shift;
152             my ($value) = @_;
153             $value =~ s/\s+/ /g;
154             unless ($value =~ /^\s*$/) {
155             if ($value < 0 and $value > 4294967295) {
156             warn "'Integer' error : $value.\n";
157             $self->compilePreserveStringI($value);
158             } else {
159             $self->putb('body',OPAQUE);
160             if ($value < 256) {
161             $self->putmb('body',1);
162             $self->putb('body',$value);
163             } elsif ($value < 65536) {
164             $self->putmb('body',2);
165             $self->putstr('body',pack("n",$value));
166             } else {
167             $self->putmb('body',4);
168             $self->putstr('body',pack("N",$value));
169             }
170             }
171             }
172             }
173            
174             sub compileDatetimeWV {
175             # WV
176             my $self = shift;
177             my ($content) = @_;
178             my $str;
179             if ($content =~ /^\s*(\d\d\d\d)(\d\d)(\d\d)T(\d\d)(\d\d)(\d\d)(Z)?\s*$/) {
180             my $year = $1;
181             my $month = $2;
182             my $day = $3;
183             my $hour = $4;
184             my $min = $5;
185             my $sec = $6;
186             my $tz = $7 || "\0";
187             $self->putb('body',OPAQUE);
188             $self->putmb('body',6);
189             $self->putb('body',$year >> 6);
190             $self->putb('body',(($year & 0x03F) << 2) | ($month >> 2));
191             $self->putb('body',(($month & 0x3) << 6) | ($day << 1) | ($hour >> 4));
192             $self->putb('body',(($hour & 0xF) << 4) | ($min >> 2));
193             $self->putb('body',(($min & 0x3) << 6) | $sec);
194             $self->putb('body',ord $tz);
195             } else {
196             warn "'Datetime' error : $content.\n";
197             $self->compilePreserveStringI($content);
198             }
199             }
200            
201             sub compilePreserveStringT {
202             my $self = shift;
203             my ($str) = @_;
204             if (exists $self->{h_str}->{$str}) {
205             $self->putmb('body',$self->{h_str}->{$str});
206             } else {
207             my $pos = length $self->{strtbl};
208             $self->{h_str}->{$str} = $pos;
209             # print $pos," ",$str,"\n";
210             $self->putmb('body',$pos);
211             $self->putstr('strtbl',$str);
212             $self->putb('strtbl',NULL);
213             }
214             }
215            
216             sub compilePreserveStringI {
217             my $self = shift;
218             my ($str) = @_;
219             my $idx = $self->{rulesApp}->getExtValue($str, "Ext0Values");
220             if (defined $idx) {
221             $self->putb('body',EXT_T_0);
222             $self->putmb('body',$idx);
223             return;
224             }
225             $idx = $self->{rulesApp}->getExtValue($str, "Ext1Values");
226             if (defined $idx) {
227             $self->putb('body',EXT_T_1);
228             $self->putmb('body',$idx);
229             return;
230             }
231             $idx = $self->{rulesApp}->getExtValue($str, "Ext2Values");
232             if (defined $idx) {
233             $self->putb('body',EXT_T_2);
234             $self->putmb('body',$idx);
235             return;
236             }
237             $self->putb('body',STR_I);
238             $self->putstr('body',$str);
239             $self->putb('body',NULL);
240             }
241            
242             sub compileStringI {
243             my $self = shift;
244             my ($str) = @_;
245             $str =~ s/\s+/ /g;
246             $self->compilePreserveStringI($str) unless ($str =~ /^\s*$/);
247             }
248            
249             sub compileStringIwithVariables {
250             # WAP / WML
251             my $self = shift;
252             my ($str) = @_;
253             my $text = '';
254             while ($str) {
255             for ($str) {
256             s/^([^\$]+)//
257             and $text .= $1,
258             last;
259            
260             s/^\$\$//
261             and $text .= '$',
262             last;
263            
264             s/^\$([A-Z_a-z][0-9A-Z_a-z]*)//
265             and $self->compileStringI($text),
266             $text = '',
267             $self->putb('body',EXT_T_2),
268             $self->compilePreserveStringT($1),
269             last;
270            
271             s/^\$\(\s*([A-Z_a-z][0-9A-Z_a-z]*)\s*\)//
272             and $self->compileStringI($text),
273             $text = '',
274             $self->putb('body',EXT_T_2),
275             $self->compilePreserveStringT($1),
276             last;
277            
278             s/^\$\(\s*([A-Z_a-z][0-9A-Z_a-z]*)\s*:\s*escape\s*\)//
279             and $self->compileStringI($text),
280             $text = '',
281             $self->putb('body',EXT_T_0),
282             $self->compilePreserveStringT($1),
283             last;
284            
285             s/^\$\(\s*([A-Z_a-z][0-9A-Z_a-z]*)\s*:\s*unesc\s*\)//
286             and $self->compileStringI($text),
287             $text = '',
288             $self->putb('body',EXT_T_1),
289             $self->compilePreserveStringT($1),
290             last;
291            
292             s/^\$\(\s*([A-Z_a-z][0-9A-Z_a-z]*)\s*:\s*noesc\s*\)//
293             and $self->compileStringI($text),
294             $text = '',
295             $self->putb('body',EXT_T_2),
296             $self->compilePreserveStringT($1),
297             last;
298            
299             s/^\$\(\s*([A-Z_a-z][0-9A-Z_a-z]*)\s*:\s*([Ee]([Ss][Cc][Aa][Pp][Ee])?)\s*\)//
300             and $self->compileStringI($text),
301             $text = '',
302             $self->putb('body',EXT_T_0),
303             $self->compilePreserveStringT($1),
304             warn "deprecated-var : $1:$2\n",
305             last;
306            
307             s/^\$\(\s*([A-Z_a-z][0-9A-Z_a-z]*)\s*:\s*([Uu][Nn]([Ee][Ss][Cc])?)\s*\)//
308             and $self->compileStringI($text),
309             $text = '',
310             $self->putb('body',EXT_T_1),
311             $self->compilePreserveStringT($1),
312             warn "deprecated-var : $1:$2\n",
313             last;
314            
315             s/^\$\(\s*([A-Z_a-z][0-9A-Z_a-z]*)\s*:\s*([Nn][Oo]([Ee][Ss][Cc])?)\s*\)//
316             and $self->compileStringI($text),
317             $text = '',
318             $self->putb('body',EXT_T_2),
319             $self->compilePreserveStringT($1),
320             warn "deprecated-var : $1:$2\n",
321             last;
322            
323             warn "Pb with: $str \n";
324             return;
325             }
326             }
327             $self->compileStringI($text);
328             }
329            
330             sub compileEntity {
331             my $self = shift;
332             my ($entity) = @_;
333             if (exists $self->{rulesApp}->{CharacterEntity}{$entity}) {
334             my $code = $self->{rulesApp}->{CharacterEntity}{$entity};
335             $self->putb('body',ENTITY);
336             $self->putmb('body',$code);
337             } else {
338             warn "entity reference : $entity";
339             }
340             }
341            
342             sub compileAttributeExtToken {
343             my $self = shift;
344             my ($ext_token) = @_;
345             my $codepage = $ext_token / 256;
346             my $token = $ext_token % 256;
347             if ($codepage != $self->{attrCodepage}) {
348             $self->putb('body',SWITCH_PAGE);
349             $self->putb('body',$codepage);
350             $self->{attrCodepage} = $codepage;
351             }
352             $self->putb('body',$token);
353             }
354            
355             sub compileTagExtToken {
356             my $self = shift;
357             my ($ext_token) = @_;
358             my $codepage = $ext_token / 256;
359             my $token = $ext_token % 256;
360             if ($codepage != $self->{tagCodepage}) {
361             $self->putb('body',SWITCH_PAGE);
362             $self->putb('body',$codepage);
363             $self->{tagCodepage} = $codepage;
364             }
365             $self->putb('body',$token);
366             }
367            
368             sub compileAttributeValues {
369             my $self = shift;
370             my ($value) = @_;
371             my $attr;
372             my $start;
373             my $end;
374             while (1) {
375             ($attr,$start,$end) = $self->{rulesApp}->getAttrValue($value,$self->{attrCodepage});
376             last unless ($attr);
377             $self->compilePreserveStringI($start) if ($start);
378             $self->compileAttributeExtToken($attr->{ext_token});
379             $value = $end;
380             }
381             $self->compilePreserveStringI($start) if ($start);
382             }
383            
384             sub compileProcessingInstruction {
385             my $self = shift;
386             my ($target,$data) = @_;
387             $self->putb('body',PI);
388             my ($attr_start,$dummy) = $self->{rulesApp}->getAttrStart($target,"",$self->{attrCodepage});
389             if ($attr_start) {
390             # well-known attribute name
391             $self->compileAttributeExtToken($attr_start->{ext_token});
392             } else {
393             # unknown attribute name
394             $self->putb('body',LITERAL);
395             $self->compilePreserveStringT($target);
396             }
397             if ($data) {
398             $self->compileAttributeValues($data);
399             }
400             $self->putb('body',_END);
401             }
402            
403             sub prepareAttribute {
404             my $self = shift;
405             my ($tagname,$attr) = @_;
406             my $attr_name = $attr->getName();
407             my $attr_value = $attr->getValue();
408             my ($attr_start,$remain) = $self->{rulesApp}->getAttrStart($attr_name,$attr_value,$self->{attrCodepage});
409             if ($attr_start) {
410             # well-known attribute name
411             my $default_list = $attr_start->{default} || "";
412             my $fixed_list = $attr_start->{fixed} || "";
413             if (! $remain) {
414             return 0 if (index($fixed_list,$tagname) >= 0);
415             return 0 if ($self->{skipDefault} and index($default_list,$tagname) >= 0);
416             }
417             }
418             return 1;
419             }
420            
421             sub compileAttribute {
422             my $self = shift;
423             my ($tagname,$attr) = @_;
424             my $attr_name = $attr->getName();
425             my $attr_value = $attr->getValue();
426             my ($attr_start,$remain) = $self->{rulesApp}->getAttrStart($attr_name,$attr_value,$self->{attrCodepage});
427             if ($attr_start) {
428             # well-known attribute name
429             my $default_list = $attr_start->{default} || "";
430             my $fixed_list = $attr_start->{fixed} || "";
431             my $validate = $attr_start->{validate} || "";
432             my $encoding = $attr_start->{encoding} || "";
433             unless ($remain) {
434             return if (index($fixed_list,$tagname) >= 0);
435             return if ($self->{skipDefault} and index($default_list,$tagname) >= 0);
436             }
437             $self->compileAttributeExtToken($attr_start->{ext_token});
438            
439             if ($encoding eq "iso-8601") {
440             $self->compileDatetime($attr_value);
441             } else {
442             if ($remain ne "") {
443             if ($validate eq "length") {
444             warn "Validate 'length' error : $remain.\n"
445             unless ($remain =~ /^[0-9]+%?$/);
446             $self->compilePreserveStringI($remain);
447             } else {
448             if ($self->{variableSubs} and $validate eq "vdata") {
449             if (index($remain,"\$") >= 0) {
450             $self->compileStringIwithVariables($remain);
451             } else {
452             $self->compileAttributeValues($remain);
453             }
454             } else {
455             $self->compileAttributeValues($remain);
456             }
457             }
458             }
459             }
460             } else {
461             # unknown attribute name
462             $self->putb('body',LITERAL);
463             $self->compilePreserveStringT($attr_name);
464             $self->putb('body',STR_T);
465             $self->compilePreserveStringT($attr_value);
466             }
467             }
468            
469             sub compileElement {
470             my $self = shift;
471             my ($elt,$xml_lang,$xml_space) = @_;
472             my $cpl_token = NULL;
473             my $tagname = $elt->getNodeName();
474             my $attrs = $elt->getAttributes();
475             if ($attrs->getLength()) {
476             my $attr;
477             $attr = $elt->getAttribute("xml:lang");
478             $xml_lang = $attr if ($attr);
479             $attr = $elt->getAttribute("xml:space");
480             $xml_space = $attr if ($attr);
481             my $nb = 0;
482             for (my $i = 0; $i < $attrs->getLength(); $i ++) {
483             my $attr = $attrs->item($i);
484             if ($attr->getNodeType() == ATTRIBUTE_NODE) {
485             $nb += $self->prepareAttribute($tagname,$attr);
486             }
487             }
488             $cpl_token |= HAS_ATTR if ($nb);
489             }
490             if ($elt->hasChildNodes()) {
491             $cpl_token |= HAS_CHILD;
492             }
493             my $tag_token = $self->{rulesApp}->getTag($tagname, $self->{tagCodepage});
494             if ($tag_token) {
495             # well-known tag name
496             $self->compileTagExtToken($cpl_token | $tag_token->{ext_token});
497             } else {
498             # unknown tag name
499             $self->putb('body',$cpl_token | LITERAL);
500             $self->compilePreserveStringT($tagname);
501             }
502             if ($cpl_token & HAS_ATTR) {
503             for (my $i = 0; $i < $attrs->getLength(); $i ++) {
504             my $attr = $attrs->item($i);
505             if ($attr->getNodeType() == ATTRIBUTE_NODE) {
506             $self->compileAttribute($tagname,$attr);
507             }
508             }
509             $self->putb('body',_END);
510             }
511             if ($cpl_token & HAS_CHILD) {
512             $self->compileContent($elt->getFirstChild(),$tag_token,$xml_lang,$xml_space);
513             $self->putb('body',_END);
514             }
515             }
516            
517             sub compileContent {
518             my $self = shift;
519             my ($tag,$parent,$xml_lang,$xml_space) = @_;
520             for (my $node = $tag;
521             $node;
522             $node = $node->getNextSibling() ) {
523             my $type = $node->getNodeType();
524             if ($type == ELEMENT_NODE) {
525             $self->compileElement($node,$xml_lang,$xml_space);
526             } elsif ($type == TEXT_NODE) {
527             my $value = $node->getNodeValue();
528             if ($self->{variableSubs}) {
529             $self->compileStringIwithVariables($value);
530             } else {
531             if ($xml_space eq "preserve") {
532             $self->compilePreserveStringI($value) unless ($value =~ /^\s*$/);
533             } else {
534             my $encoding = ($parent and exists $parent->{encoding}) ? $parent->{encoding} : "";
535             if ($encoding eq "base64") {
536             $self->compileBinaryWV($value);
537             } elsif ($encoding eq "datetime") {
538             $self->compileDatetimeWV($value);
539             } elsif ($encoding eq "integer") {
540             $self->compileIntegerWV($value);
541             } else {
542             $self->compileStringI($value);
543             }
544             }
545             }
546             } elsif ($type == CDATA_SECTION_NODE) {
547             my $value = $node->getNodeValue();
548             $self->compilePreserveStringI($value);
549             } elsif ($type == COMMENT_NODE) {
550             # do nothing
551             } elsif ($type == ENTITY_REFERENCE_NODE) {
552             $self->compileEntity($node->getNodeName());
553             } elsif ($type == PROCESSING_INSTRUCTION_NODE) {
554             my $target = $node->getTarget();
555             my $data = $node->getData();
556             $self->compileProcessingInstruction($target,$data);
557             } else {
558             die "unexcepted ElementType in compileContent : $type\n";
559             }
560             }
561             }
562            
563             sub compileBody {
564             my $self = shift;
565             my ($doc) = @_;
566             my $xml_lang = "";
567             my $xml_space = $self->{rulesApp}->{xmlSpace};
568             for (my $node = $doc->getFirstChild();
569             $node;
570             $node = $node->getNextSibling() ) {
571             my $type = $node->getNodeType();
572             if ($type == ELEMENT_NODE) {
573             $self->compileElement($node,$xml_lang,$xml_space);
574             } elsif ($type == PROCESSING_INSTRUCTION_NODE) {
575             my $target = $node->getTarget();
576             my $data = $node->getData();
577             $self->compileProcessingInstruction($target,$data);
578             }
579             }
580             }
581            
582             sub compileCharSet {
583             my $self = shift;
584             my ($encoding) = @_;
585             if ($encoding) {
586             my $mib = charset_name_to_mib($encoding);
587             if (defined $mib) {
588             $self->putmb('header',$mib);
589             } else {
590             warn "unknown encoding.\n";
591             $self->putmb('header',0); # unknown encoding
592             }
593             } else {
594             $self->putmb('header',106); # UTF-8 : default XML encoding
595             }
596             }
597            
598             sub compilePublicId {
599             my $self = shift;
600             if (exists $self->{rules}->{PublicIdentifiers}->{$self->{publicid}}) {
601             my $publicid = $self->{rules}->{PublicIdentifiers}->{$self->{publicid}};
602             $self->putmb('header',$publicid);
603             } else {
604             $self->putb('header',NULL);
605             my $pos = length $self->{strtbl}; # 0
606             $self->{h_str}->{$self->{publicid}} = $pos;
607             $self->putmb('header',$pos);
608             $self->putstr('strtbl',$self->{publicid});
609             $self->putb('strtbl',NULL);
610             }
611             }
612            
613             sub compileVersion {
614             my $self = shift;
615             $self->putb('header',$self->{rules}->{version});
616             }
617            
618             =item compile
619            
620             $output = $wbxml->compile($doc_xml,$encoding);
621            
622             Compiles a XML document.
623            
624             =cut
625            
626             sub compile {
627             my $self = shift;
628             my ($doc,$encoding) = @_;
629             $self->{header} = "";
630             $self->{body} = "";
631             $self->{strtbl} = "";
632             $self->{h_str} = {};
633             $self->{tagCodepage} = 0;
634             $self->{attrCodepage} = 0;
635             $self->compileVersion();
636             $self->compilePublicId();
637             $self->compileCharSet($encoding);
638             $self->compileBody($doc);
639             $self->putmb('header',length $self->{strtbl});
640             my $out = $self->{header} . $self->{strtbl} . $self->{body};
641             return $out;
642             }
643            
644             =item outfile
645            
646             $filename = $wbxml->outfile($infile);
647            
648             Builds output filename with the good extension.
649            
650             =cut
651            
652             sub outfile {
653             my $self = shift;
654             my ($infile) = @_;
655             my $filename = $infile;
656             if ($filename =~ /\.[^\.]+$/) {
657             $filename =~ s/\.[^\.]+$/\./;
658             } else {
659             $filename .= '.';
660             }
661             $filename .= $self->{rulesApp}->{tokenisedExt};
662             return $filename;
663             }
664            
665             sub putb {
666             my $self = shift;
667             my ($str,$val) = @_;
668             $self->{$str} = $self->{$str} . chr $val;
669             }
670            
671             sub putmb {
672             my $self = shift;
673             my ($str,$val) = @_;
674             my $tmp = chr ($val & 0x7f);
675             for ($val >>= 7; $val != 0; $val >>= 7) {
676             $tmp = chr (0x80 | ($val & 0x7f)) . $tmp;
677             }
678             $self->{$str} = $self->{$str} . $tmp;
679             }
680            
681             sub putstr {
682             my $self = shift;
683             my ($str,$val) = @_;
684             $self->{$str} = $self->{$str} . $val;
685             }
686            
687             package Token;
688            
689             sub new {
690             my $proto = shift;
691             my $class = ref($proto) || $proto;
692             my $self = {};
693             bless($self, $class);
694             my ($token,$codepage) = @_;
695             $self->{ext_token} = 256 * hex($codepage) + hex($token);
696             return $self;
697             }
698            
699             package TagToken;
700            
701             use base qw(Token);
702            
703             sub new {
704             my $proto = shift;
705             my $class = ref($proto) || $proto;
706             my ($token,$name,$codepage,$encoding) = @_;
707             my $self = new Token($token,$codepage);
708             bless($self, $class);
709             $self->{name} = $name;
710             $self->{encoding} = $encoding if ($encoding ne "");
711             return $self;
712             }
713            
714             package AttrStartToken;
715            
716             use base qw(Token);
717            
718             sub new {
719             my $proto = shift;
720             my $class = ref($proto) || $proto;
721             my ($token,$name,$value,$codepage,$default,$fixed,$validate,$encoding) = @_;
722             my $self = new Token($token,$codepage);
723             bless($self, $class);
724             $self->{name} = $name;
725             $self->{value} = $value if ($value ne "");
726             $self->{default} = $default if ($default ne "");
727             $self->{fixed} = $fixed if ($fixed ne "");
728             $self->{validate} = $validate if ($validate ne "");
729             $self->{encoding} = $encoding if ($encoding ne "");
730             return $self;
731             }
732            
733             package AttrValueToken;
734            
735             use base qw(Token);
736            
737             sub new {
738             my $proto = shift;
739             my $class = ref($proto) || $proto;
740             my ($token,$value,$codepage) = @_;
741             my $self = new Token($token,$codepage);
742             bless($self, $class);
743             $self->{value} = $value;
744             return $self;
745             }
746            
747             package ExtValue;
748            
749             sub new {
750             my $proto = shift;
751             my $class = ref($proto) || $proto;
752             my $self = {};
753             bless($self, $class);
754             my ($index,$value) = @_;
755             $self->{index} = hex($index);
756             $self->{value} = $value;
757             return $self;
758             }
759            
760             package WbRulesApp;
761            
762             sub new {
763             my $proto = shift;
764             my $class = ref($proto) || $proto;
765             my $self = {};
766             bless($self, $class);
767             my ($publicid,$use_default,$variable_subs,$textual_ext,$tokenised_ext,$xml_space) = @_;
768             $self->{publicid} = $publicid;
769             $self->{skipDefault} = $use_default eq "yes";
770             $self->{variableSubs} = $variable_subs eq "yes";
771             $self->{textualExt} = $textual_ext || "xml";
772             $self->{tokenisedExt} = $tokenised_ext || "wbxml";
773             $self->{xmlSpace} = $xml_space || "preserve";
774             $self->{TagTokens} = [];
775             $self->{AttrStartTokens} = [];
776             $self->{AttrValueTokens} = [];
777             return $self;
778             }
779            
780             sub getTag {
781             my $self = shift;
782             my ($tagname, $curr_page) = @_;
783             if ($tagname) {
784             my @found = ();
785             foreach (@{$self->{TagTokens}}) {
786             if ($tagname eq $_->{name}) {
787             # print "Tag $_->{name}.\n";
788             if ($_->{ext_token} / 256 == $curr_page) {
789             return $_;
790             } else {
791             push @found, $_;
792             }
793             }
794             }
795             if (scalar @found) {
796             return shift @found;
797             }
798             }
799             return undef;
800             }
801            
802             sub getAttrStart {
803             my $self = shift;
804             my ($name, $value, $curr_page) = @_;
805             my $best = undef;
806             my $remain = $value;
807             if ($name) {
808             my $max_len = -1;
809             foreach (@{$self->{AttrStartTokens}}) {
810             if ($name eq $_->{name}) {
811             if (exists $_->{value}) {
812             my $attr_value = $_->{value};
813             my $len = length $attr_value;
814             if ( ($attr_value eq $value) or
815             ($len < length $value and $attr_value eq substr($value,0,$len)) ) {
816             if ($len > $max_len) {
817             $max_len = $len;
818             $best = $_;
819             } elsif ($len == $max_len) {
820             if ($_->{ext_token} / 256 == $curr_page) {
821             $best = $_;
822             }
823             }
824             }
825             } else {
826             if ($max_len == -1) {
827             $max_len = 0;
828             $best = $_;
829             } elsif ($max_len == 0) {
830             if ($_->{ext_token} / 256 == $curr_page) {
831             $best = $_;
832             }
833             }
834             }
835             }
836             }
837             if ($best and $max_len != -1) {
838             $remain = substr $remain,$max_len;
839             # if (exists $best->{value}) {
840             # print "AttrStart : $best->{name} $best->{value}.\n";
841             # } else {
842             # print "AttrStart : $best->{name}.\n";
843             # }
844             }
845             }
846             return ($best,$remain);
847             }
848            
849             sub getAttrValue {
850             my $self = shift;
851             my ($start, $curr_page) = @_;
852             my $best = undef;
853             my $end = "";
854             if ($start ne "") {
855             my $max_len = 0;
856             my $best_found = length $start;
857             foreach (@{$self->{AttrValueTokens}}) {
858             my $value = $_->{value};
859             if ($value ne "") {
860             my $len = length $value;
861             my $found = index $start,$value;
862             if ($found >= 0) {
863             if ($found == $best_found) {
864             if ($len > $max_len) {
865             $max_len = $len;
866             $best = $_;
867             } elsif ($len == $max_len) {
868             if ($_->{ext_token} / 256 == $curr_page) {
869             $best = $_;
870             }
871             }
872             } elsif ($found < $best_found) {
873             $best = $_;
874             $best_found = $found;
875             $max_len = $len;
876             }
877             }
878             }
879             }
880             if ($best) {
881             $end = substr $start,$best_found+$max_len;
882             $start = substr $start,0,$best_found;
883             # print "AttrValue : $best->{value} ($start,$end).\n";
884             }
885             }
886             return ($best,$start,$end);
887             }
888            
889             sub getExtValue {
890             my $self = shift;
891             my ($value, $ext) = @_;
892             if ($value and exists $self->{$ext} and scalar $self->{$ext}) {
893             foreach (@{$self->{$ext}}) {
894             if ($value eq $_->{value}) {
895             # print "ExtValue : $value\n";
896             return $_->{index} ;
897             }
898             }
899             }
900             return undef;
901             }
902            
903             package WbRules;
904            
905             sub new {
906             my $proto = shift;
907             my $class = ref($proto) || $proto;
908             my $self = {};
909             bless($self, $class);
910             my ($version) = @_;
911             if ($version =~ /(\d+)\.(\d+)/) {
912             $self->{version} = 16 * ($1 - 1) + $2;
913             } else {
914             $self->{version} = 0x03; # WBXML 1.3 : latest known version
915             }
916             $self->{PublicIdentifiers} = {};
917             $self->{App} = {};
918             $self->{DefaultApp} = new WbRulesApp("DEFAULT","","","","","");
919             return $self;
920             }
921            
922             package constructVisitor;
923             use XML::DOM;
924            
925             sub new {
926             my $proto = shift;
927             my $class = ref($proto) || $proto;
928             my $self = {};
929             bless($self, $class);
930             my($doc) = @_;
931             $self->{doc} = $doc;
932             return $self;
933             }
934            
935             sub visitwbxml {
936             my $self = shift;
937             my($parent) = @_;
938             my $version = $parent->getAttribute("version");
939             $self->{wbrules} = new WbRules($version);
940             for (my $node = $parent->getFirstChild();
941             $node;
942             $node = $node->getNextSibling() ) {
943             if ($node->getNodeType() == ELEMENT_NODE) {
944             $self->{doc}->visitElement($node,$self);
945             }
946             }
947             }
948            
949             sub visitCharacterSets {
950             # empty
951             }
952            
953             sub visitPublicIdentifiers {
954             my $self = shift;
955             my($parent) = @_;
956             for (my $node = $parent->getFirstChild();
957             $node;
958             $node = $node->getNextSibling() ) {
959             if ($node->getNodeType() == ELEMENT_NODE) {
960             $self->{doc}->visitElement($node,$self);
961             }
962             }
963             }
964            
965             sub visitPublicIdentifier {
966             my $self = shift;
967             my($node) = @_;
968             my $name = $node->getAttribute("name");
969             my $value = $node->getAttribute("value"); # hexadecimal
970             $self->{wbrules}->{PublicIdentifiers}->{$name} = hex $value;
971             }
972            
973             sub visitApp {
974             my $self = shift;
975             my($parent) = @_;
976             my $publicid = $parent->getAttribute("publicid");
977             my $use_default = $parent->getAttribute("use-default");
978             my $variable_subs = $parent->getAttribute("variable-subs");
979             my $textual_ext = $parent->getAttribute("textual-ext");
980             my $tokenised_ext = $parent->getAttribute("tokenised-ext");
981             my $xml_space = $parent->getAttribute("xml-space");
982             my $app = new WbRulesApp($publicid,$use_default,$variable_subs,$textual_ext,$tokenised_ext,$xml_space);
983             $self->{wbrules}->{App}->{$publicid} = $app;
984             $self->{wbrulesapp} = $app;
985             for (my $node = $parent->getFirstChild();
986             $node;
987             $node = $node->getNextSibling() ) {
988             if ($node->getNodeType() == ELEMENT_NODE) {
989             $self->{doc}->visitElement($node,$self);
990             }
991             }
992             }
993            
994             sub visitTagTokens {
995             my $self = shift;
996             my($parent) = @_;
997             for (my $node = $parent->getFirstChild();
998             $node;
999             $node = $node->getNextSibling() ) {
1000             if ($node->getNodeType() == ELEMENT_NODE) {
1001             $self->{doc}->visitElement($node,$self);
1002             }
1003             }
1004             }
1005            
1006             sub visitTAG {
1007             my $self = shift;
1008             my($node) = @_;
1009             my $token = $node->getAttribute("token");
1010             my $name = $node->getAttribute("name");
1011             my $codepage = $node->getAttribute("codepage");
1012             my $encoding = $node->getAttribute("encoding");
1013             my $tag = new TagToken($token,$name,$codepage,$encoding);
1014             push @{$self->{wbrulesapp}->{TagTokens}}, $tag;
1015             }
1016            
1017             sub visitAttrStartTokens {
1018             my $self = shift;
1019             my($parent) = @_;
1020             for (my $node = $parent->getFirstChild();
1021             $node;
1022             $node = $node->getNextSibling() ) {
1023             if ($node->getNodeType() == ELEMENT_NODE) {
1024             $self->{doc}->visitElement($node,$self);
1025             }
1026             }
1027             }
1028            
1029             sub visitATTRSTART {
1030             my $self = shift;
1031             my($node) = @_;
1032             my $token = $node->getAttribute("token");
1033             my $name = $node->getAttribute("name");
1034             my $value = $node->getAttribute("value");
1035             my $codepage = $node->getAttribute("codepage");
1036             my $default = $node->getAttribute("default");
1037             my $fixed = $node->getAttribute("fixed");
1038             my $validate = $node->getAttribute("validate");
1039             my $encoding = $node->getAttribute("encoding");
1040             my $tag = new AttrStartToken($token,$name,$value,$codepage,$default,$fixed,$validate,$encoding);
1041             push @{$self->{wbrulesapp}->{AttrStartTokens}}, $tag;
1042             }
1043            
1044             sub visitAttrValueTokens {
1045             my $self = shift;
1046             my($parent) = @_;
1047             for (my $node = $parent->getFirstChild();
1048             $node;
1049             $node = $node->getNextSibling() ) {
1050             if ($node->getNodeType() == ELEMENT_NODE) {
1051             $self->{doc}->visitElement($node,$self);
1052             }
1053             }
1054             }
1055            
1056             sub visitATTRVALUE {
1057             my $self = shift;
1058             my($node) = @_;
1059             my $token = $node->getAttribute("token");
1060             my $value = $node->getAttribute("value");
1061             my $codepage = $node->getAttribute("codepage");
1062             my $tag = new AttrValueToken($token,$value,$codepage);
1063             push @{$self->{wbrulesapp}->{AttrValueTokens}}, $tag;
1064             }
1065            
1066             sub visitExt0Values {
1067             my $self = shift;
1068             my($parent) = @_;
1069             for (my $node = $parent->getFirstChild();
1070             $node;
1071             $node = $node->getNextSibling() ) {
1072             if ($node->getNodeType() == ELEMENT_NODE) {
1073             $self->{doc}->visitElement($node,$self,"Ext0Values");
1074             }
1075             }
1076             }
1077            
1078             sub visitExt1Values {
1079             my $self = shift;
1080             my($parent) = @_;
1081             for (my $node = $parent->getFirstChild();
1082             $node;
1083             $node = $node->getNextSibling() ) {
1084             if ($node->getNodeType() == ELEMENT_NODE) {
1085             $self->{doc}->visitElement($node,$self,"Ext1Values");
1086             }
1087             }
1088             }
1089            
1090             sub visitExt2Values {
1091             my $self = shift;
1092             my($parent) = @_;
1093             for (my $node = $parent->getFirstChild();
1094             $node;
1095             $node = $node->getNextSibling() ) {
1096             if ($node->getNodeType() == ELEMENT_NODE) {
1097             $self->{doc}->visitElement($node,$self,"Ext0Values");
1098             }
1099             }
1100             }
1101            
1102             sub visitEXTVALUE {
1103             my $self = shift;
1104             my($node,$ext) = @_;
1105             my $index = $node->getAttribute("index");
1106             my $value = $node->getAttribute("value");
1107             my $tag = new ExtValue($index,$value);
1108             push @{$self->{wbrulesapp}->{$ext}}, $tag;
1109             }
1110            
1111             sub visitCharacterEntities {
1112             my $self = shift;
1113             my($parent) = @_;
1114             for (my $node = $parent->getFirstChild();
1115             $node;
1116             $node = $node->getNextSibling() ) {
1117             if ($node->getNodeType() == ELEMENT_NODE) {
1118             $self->{doc}->visitElement($node,$self);
1119             }
1120             }
1121             }
1122            
1123             sub visitCharacterEntity {
1124             my $self = shift;
1125             my($node) = @_;
1126             my $code = $node->getAttribute("code");
1127             my $name = $node->getAttribute("name");
1128             $self->{wbrulesapp}->{CharacterEntity}{$name} = $code;
1129             }
1130            
1131             package doc;
1132             use XML::DOM;
1133            
1134             sub new {
1135             my $proto = shift;
1136             my $class = ref($proto) || $proto;
1137             my $self = {};
1138             bless($self, $class);
1139             my ($file) = @_;
1140             my $parser = new XML::DOM::Parser;
1141             eval { $self->{doc} = $parser->parsefile($file); };
1142             die $@ if ($@);
1143             return undef unless ($self->{doc});
1144             $self->{root} = $self->{doc}->getDocumentElement();
1145             return $self;
1146             }
1147            
1148             sub visitElement {
1149             my $self = shift;
1150             my $node = shift;
1151             my $visitor = shift;
1152             my $name = $node->getNodeName();
1153             $name =~ s/^wbxml://; # backward compat
1154             my $func = 'visit' . $name;
1155             if($visitor->can($func)) {
1156             $visitor->$func($node,@_);
1157             } else {
1158             warn "unknown element '$name'\n";
1159             }
1160             }
1161            
1162             package WbRules;
1163            
1164             =item Load
1165            
1166             $rules = WbRules::Load( [PATH] );
1167            
1168             Loads rules from PATH.
1169            
1170             WAP/wap.wbrules.pl is a serialized version (Data::Dumper).
1171            
1172             WAP/wap.wbrules.xml supplies rules for WAP files, but it could extended to over XML applications.
1173            
1174             =cut
1175            
1176             sub Load {
1177             my($path) = @_;
1178             my $config;
1179             my $persistance;
1180            
1181             if ($path) {
1182             $config = $path;
1183             $persistance = $path;
1184             $persistance =~ s/\.\w+$//;
1185             $persistance .= '.pl';
1186             } else {
1187             $path = $INC{'WAP/wbxml.pm'};
1188             $path =~ s/wbxml\.pm$//i;
1189             $persistance = $path . 'wap.wbrules.pl';
1190             $config = $path . 'wap.wbrules.xml';
1191             }
1192            
1193             my @st_config = stat($config);
1194             die "can't found original rules ($config).\n" unless (@st_config);
1195             my @st_persistance = stat($persistance);
1196             if (@st_persistance) {
1197             if ($st_config[9] > $st_persistance[9]) { # mtime
1198             print "$persistance needs update\n";
1199             die "can't unlink serialized rules ($persistance).\n"
1200             unless (unlink $persistance);
1201             }
1202             }
1203             use vars qw($rules);
1204             do $persistance;
1205             unless (ref $rules eq 'WbRules') {
1206             use Data::Dumper;
1207             print "parse rules\n";
1208             my $doc = new doc($config);
1209             if ($doc) {
1210             use POSIX qw(ctime);
1211             my $visitor = new constructVisitor($doc);
1212             $doc->visitElement($doc->{root},$visitor);
1213             $rules = $visitor->{wbrules};
1214             $doc = undef;
1215             my $d = Data::Dumper->new([$rules], [qw($rules)]);
1216             # $d->Indent(1);
1217             $d->Indent(0);
1218             open PERSISTANCE,"> $persistance";
1219             print PERSISTANCE "# This file is generated. DO NOT modify it.\n";
1220             print PERSISTANCE "# From file : ",$config,"\n";
1221             print PERSISTANCE "# Generation date : ",POSIX::ctime(time());
1222             print PERSISTANCE $d->Dump();
1223             close PERSISTANCE;
1224             } else {
1225             $WbRules::rules = new WbRules("");
1226             }
1227             }
1228             return $WbRules::rules;
1229             }
1230            
1231             =back
1232            
1233             =head1 SEE ALSO
1234            
1235             wbxmlc, WAP::SAXDriver::wbxml
1236            
1237             =head1 COPYRIGHT
1238            
1239             (c) 2000-2005 Francois PERRAD, France. All rights reserved.
1240            
1241             This program (WAP::wbxml.pm and the internal DTD of wbrules.xml) is distributed
1242             under the terms of the Artistic Licence.
1243            
1244             The WAP Specification are copyrighted by the Wireless Application Protocol Forum Ltd.
1245             See Ehttp://www.wapforum.org/what/copyright.htmE.
1246            
1247             =head1 AUTHOR
1248            
1249             Francois PERRAD, francois.perrad@gadz.org
1250            
1251             =cut
1252            
1253             1;
1254