"Fossies" - the Fresh Open Source Software Archive 
As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) PHP source code syntax highlighting (style:
standard) with prefixed line numbers and
code folding option.
Alternatively you can here
view or
download the uninterpreted source code file.
For more information about "wbxml.php" see the
Fossies "Dox" file reference documentation.
1 <?php
2 /***********************************************
3 * File : wbxml.php
4 * Project : Z-Push
5 * Descr : WBXML mapping file
6 *
7 * Created : 01.10.2007
8 *
9 * � Zarafa Deutschland GmbH, www.zarafaserver.de
10 * This file is distributed under GPL v2.
11 * Consult LICENSE file for details
12 ************************************************/
13 include_once('debug.php');
14
15 define('WBXML_DEBUG', false);
16
17 define('WBXML_SWITCH_PAGE', 0x00);
18 define('WBXML_END', 0x01);
19 define('WBXML_ENTITY', 0x02);
20 define('WBXML_STR_I', 0x03);
21 define('WBXML_LITERAL', 0x04);
22 define('WBXML_EXT_I_0', 0x40);
23 define('WBXML_EXT_I_1', 0x41);
24 define('WBXML_EXT_I_2', 0x42);
25 define('WBXML_PI', 0x43);
26 define('WBXML_LITERAL_C', 0x44);
27 define('WBXML_EXT_T_0', 0x80);
28 define('WBXML_EXT_T_1', 0x81);
29 define('WBXML_EXT_T_2', 0x82);
30 define('WBXML_STR_T', 0x83);
31 define('WBXML_LITERAL_A', 0x84);
32 define('WBXML_EXT_0', 0xC0);
33 define('WBXML_EXT_1', 0xC1);
34 define('WBXML_EXT_2', 0xC2);
35 define('WBXML_OPAQUE', 0xC3);
36 define('WBXML_LITERAL_AC', 0xC4);
37
38 define('EN_TYPE', 1);
39 define('EN_TAG', 2);
40 define('EN_CONTENT', 3);
41 define('EN_FLAGS', 4);
42 define('EN_ATTRIBUTES', 5);
43
44 define('EN_TYPE_STARTTAG', 1);
45 define('EN_TYPE_ENDTAG', 2);
46 define('EN_TYPE_CONTENT', 3);
47
48 define('EN_FLAGS_CONTENT', 1);
49 define('EN_FLAGS_ATTRIBUTES', 2);
50
51 class WBXMLDecoder {
52 var $dtd;
53 var $in;
54
55 var $version;
56 var $publicid;
57 var $publicstringid;
58 var $charsetid;
59 var $stringtable;
60
61 var $tagcp = 0;
62 var $attrcp = 0;
63
64 var $ungetbuffer;
65
66 var $logStack = array();
67 var $_inputRaw = '';
68
69 function WBXMLDecoder($input, $dtd) {
70 $this->in = $input;
71 $this->dtd = $dtd;
72
73 $this->version = $this->getByte();
74 $this->publicid = $this->getMBUInt();
75 if($this->publicid == 0) {
76 $this->publicstringid = $this->getMBUInt();
77 }
78
79 $this->charsetid = $this->getMBUInt();
80 $this->stringtable = $this->getStringTable();
81
82 }
83
84 // Returns either start, content or end, and auto-concatenates successive content
85 function getElement()
86 {
87 $element = $this->getToken();
88
89 switch($element[EN_TYPE]) {
90 case EN_TYPE_STARTTAG:
91 return $element;
92 case EN_TYPE_ENDTAG:
93 return $element;
94 case EN_TYPE_CONTENT:
95 while(1) {
96 $next = $this->getToken();
97 if($next == false)
98 return false;
99 else if($next[EN_TYPE] == EN_CONTENT) {
100 $element[EN_CONTENT] .= $next[EN_CONTENT];
101 } else {
102 $this->ungetElement($next);
103 break;
104 }
105 }
106 return $element;
107 }
108
109 return false;
110 }
111
112 function peek()
113 {
114 $element = $this->getElement();
115
116 $this->ungetElement($element);
117
118 return $element;
119 }
120
121 function getElementStartTag($tag)
122 {
123 $element = $this->getToken();
124
125 // debugLog("WBXML Element getElementStartTag ".print_r($element,true));
126 if($element[EN_TYPE] == EN_TYPE_STARTTAG && $element[EN_TAG] == $tag)
127 return $element;
128 else {
129 debug("Unmatched tag $tag:");
130 debug(print_r($element,true));
131 $this->ungetElement($element);
132 }
133
134 return false;
135 }
136
137 function getElementEndTag()
138 {
139 $element = $this->getToken();
140
141 // debugLog("WBXML Element getElementEndTag ".print_r($element,true));
142 if($element[EN_TYPE] == EN_TYPE_ENDTAG)
143 return $element;
144 else {
145 debug("Unmatched end tag:");
146 if (defined('WBXML_DEBUG') &&
147 WBXML_DEBUG == true) {
148 debugLog("WBXML RAW Input (bin2hex): ".bin2hex($this->_inputRaw));
149 }
150 debug(print_r($element,true));
151 $bt = debug_backtrace();
152 $c = count($bt);
153 debugLog(print_r($bt,true));
154 debug("From " . $bt[$c-2]["file"] . ":" . $bt[$c-2]["line"]);
155 $this->ungetElement($element);
156 }
157
158 return false;
159 }
160
161 function getElementContent()
162 {
163 $element = $this->getToken();
164
165 // debugLog("WBXML Element getElementContent ".print_r($element,true));
166 if($element[EN_TYPE] == EN_TYPE_CONTENT) {
167 return $element[EN_CONTENT];
168 }
169 // also allow empty tags
170 // else if($element[EN_TYPE] == EN_TYPE_ENDTAG) {
171 // debugLog("WBXML Element EN_TYPE_ENDTAG ".print_r($element,true));
172 // $this->ungetElement($element);
173 // return "";
174 // }
175 else {
176 debug("Unmatched content:");
177 debug(print_r($element, true));
178 $this->ungetElement($element);
179 }
180
181 return false;
182 }
183
184 // ---------------------- Private functions ------------------------
185
186 function getToken() {
187 // See if there's something in the ungetBuffer
188 if($this->ungetbuffer) {
189 $element = $this->ungetbuffer;
190 $this->ungetbuffer = false;
191 return $element;
192 }
193
194 $el = $this->_getToken();
195
196 $this->logToken($el);
197
198 return $el;
199 }
200
201 function logToken($el) {
202 if(!WBXML_DEBUG)
203 return;
204 $spaces = str_repeat(" ", count($this->logStack));
205
206 switch($el[EN_TYPE]) {
207 case EN_TYPE_STARTTAG:
208 if($el[EN_FLAGS] & EN_FLAGS_CONTENT) {
209 debugLog("I " . $spaces . " <". $el[EN_TAG] . ">");
210 array_push($this->logStack, $el[EN_TAG]);
211 } else
212 debugLog("I " . $spaces . " <" . $el[EN_TAG] . "/>");
213
214 break;
215 case EN_TYPE_ENDTAG:
216 $tag = array_pop($this->logStack);
217 debugLog("I " . $spaces . "</" . $tag . ">");
218 break;
219 case EN_TYPE_CONTENT:
220 debugLog("I " . $spaces . " " . $el[EN_CONTENT]);
221 break;
222 default:
223 debugLog("I " . $spaces . " " . $el[EN_TYPE]);
224 }
225 }
226
227 // Returns either a start tag, content or end tag
228 function _getToken() {
229
230 // Get the data from the input stream
231 $element = array();
232
233 while(1) {
234 $byte = $this->getByte();
235 if(!isset($byte))
236 break;
237
238 // debugLog("Byte: ".ord($byte));
239
240 switch($byte) {
241 case WBXML_SWITCH_PAGE:
242 $this->tagcp = $this->getByte();
243 continue;
244
245 case WBXML_END:
246 $element[EN_TYPE] = EN_TYPE_ENDTAG;
247 return $element;
248
249 case WBXML_ENTITY:
250 $entity = $this->getMBUInt();
251 $element[EN_TYPE] = EN_TYPE_CONTENT;
252 $element[EN_CONTENT] = $this->entityToCharset($entity);
253 return $element;
254
255 case WBXML_STR_I:
256 $element[EN_TYPE] = EN_TYPE_CONTENT;
257 $element[EN_CONTENT] = $this->getTermStr();
258 return $element;
259
260 case WBXML_LITERAL:
261 $element[EN_TYPE] = EN_TYPE_STARTTAG;
262 $element[EN_TAG] = $this->getStringTableEntry($this->getMBUInt());
263 $element[EN_FLAGS] = 0;
264 return $element;
265
266 case WBXML_EXT_I_0:
267 case WBXML_EXT_I_1:
268 case WBXML_EXT_I_2:
269 $this->getTermStr();
270 // Ignore extensions
271 continue;
272
273 case WBXML_PI:
274 // Ignore PI
275 $this->getAttributes();
276 continue;
277
278 case WBXML_LITERAL_C:
279 $element[EN_TYPE] = EN_TYPE_STARTTAG;
280 $element[EN_TAG] = $this->getStringTableEntry($this->getMBUInt());
281 $element[EN_FLAGS] = EN_FLAGS_CONTENT;
282 return $element;
283
284 case WBXML_EXT_T_0:
285 case WBXML_EXT_T_1:
286 case WBXML_EXT_T_2:
287 $this->getMBUInt();
288 // Ingore extensions;
289 continue;
290
291 case WBXML_STR_T:
292 $element[EN_TYPE] = EN_TYPE_CONTENT;
293 $element[EN_CONTENT] = $this->getStringTableEntry($this->getMBUInt());
294 return $element;
295
296 case WBXML_LITERAL_A:
297 $element[EN_TYPE] = EN_TYPE_STARTTAG;
298 $element[EN_TAG] = $this->getStringTableEntry($this->getMBUInt());
299 $element[EN_ATTRIBUTES] = $this->getAttributes();
300 $element[EN_FLAGS] = EN_FLAGS_ATTRIBUTES;
301 return $element;
302 case WBXML_EXT_0:
303 case WBXML_EXT_1:
304 case WBXML_EXT_2:
305 continue;
306
307 case WBXML_OPAQUE:
308 $length = $this->getMBUInt();
309 $element[EN_TYPE] = EN_TYPE_CONTENT;
310 $element[EN_CONTENT] = $this->getOpaque($length);
311 return $element;
312
313 case WBXML_LITERAL_AC:
314 $element[EN_TYPE] = EN_TYPE_STARTTAG;
315 $element[EN_TAG] = $this->getStringTableEntry($this->getMBUInt());
316 $element[EN_ATTRIBUTES] = $this->getAttributes();
317 $element[EN_FLAGS] = EN_FLAGS_ATTRIBUTES | EN_FLAGS_CONTENT;
318 return $element;
319
320 default:
321 $element[EN_TYPE] = EN_TYPE_STARTTAG;
322 $element[EN_TAG] = $this->getMapping($this->tagcp, $byte & 0x3f);
323 $element[EN_FLAGS] = ($byte & 0x80 ? EN_FLAGS_ATTRIBUTES : 0) | ($byte & 0x40 ? EN_FLAGS_CONTENT : 0);
324 if($byte & 0x80)
325 $element[EN_ATTRIBUTES] = $this->getAttributes();
326 return $element;
327 }
328 }
329 }
330
331 function ungetElement($element) {
332 if($this->ungetbuffer)
333 debugLog("Double unget!");
334
335 $this->ungetbuffer = $element;
336 }
337
338 function getAttributes() {
339 $attributes = array();
340 $attr = "";
341
342 while(1) {
343 $byte = $this->getByte();
344
345 if(count($byte) == 0)
346 break;
347
348 switch($byte) {
349 case WBXML_SWITCH_PAGE:
350 $this->attrcp = $this->getByte();
351 break;
352
353 case WBXML_END:
354 if($attr != "")
355 $attributes += $this->splitAttribute($attr);
356
357 return $attributes;
358
359 case WBXML_ENTITY:
360 $entity = $this->getMBUInt();
361 $attr .= $this->entityToCharset($entity);
362 return $element;
363
364 case WBXML_STR_I:
365 $attr .= $this->getTermStr();
366 return $element;
367
368 case WBXML_LITERAL:
369 if($attr != "")
370 $attributes += $this->splitAttribute($attr);
371
372 $attr = $this->getStringTableEntry($this->getMBUInt());
373 return $element;
374
375 case WBXML_EXT_I_0:
376 case WBXML_EXT_I_1:
377 case WBXML_EXT_I_2:
378 $this->getTermStr();
379 continue;
380
381 case WBXML_PI:
382 case WBXML_LITERAL_C:
383 // Invalid
384 return false;
385
386 case WBXML_EXT_T_0:
387 case WBXML_EXT_T_1:
388 case WBXML_EXT_T_2:
389 $this->getMBUInt();
390 continue;
391
392 case WBXML_STR_T:
393 $attr .= $this->getStringTableEntry($this->getMBUInt());
394 return $element;
395
396 case WBXML_LITERAL_A:
397 return false;
398
399 case WBXML_EXT_0:
400 case WBXML_EXT_1:
401 case WBXML_EXT_2:
402 continue;
403
404 case WBXML_OPAQUE:
405 $length = $this->getMBUInt();
406 $attr .= $this->getOpaque($length);
407 return $element;
408
409 case WBXML_LITERAL_AC:
410 return false;
411
412 default:
413 if($byte < 128) {
414 if($attr != "") {
415 $attributes += $this->splitAttribute($attr);
416 $attr = "";
417 }
418 }
419
420 $attr .= $this->getMapping($this->attrcp, $byte);
421 break;
422 }
423 }
424
425 }
426
427 function splitAttribute($attr) {
428 $attributes = array();
429
430 $pos = strpos($attr,chr(61)); // equals sign
431
432 if($pos)
433 $attributes[byte_substr($attr, 0, $pos)] = byte_substr($attr, $pos+1);
434 else
435 $attributes[$attr] = null;
436
437 return $attributes;
438 }
439
440 function getTermStr() {
441 $str = "";
442 while(1) {
443 $in = $this->getByte();
444
445 if($in == 0)
446 break;
447 else
448 $str .= chr($in);
449 }
450
451 return $str;
452 }
453
454 function getOpaque($len) {
455 $result = "";
456 while (byte_strlen($result) < $len) {
457 if (($ch = fread($this->in, $len-byte_strlen($result))) == false) return false;
458 $result .= $ch;
459 };
460 $this->_inputRaw .= $result;
461 return $result;
462 }
463
464 function getByte() {
465
466 // $ch = fread($this->in, 1);
467 // Start to cover timeout scenarios in input stream where stream is not eof but no char is read
468 // Could solve t-sync problem. if not above line was replaced until END
469 $i = 0;
470 do {
471 if ($i > 0) usleep(500);
472 $ch = fread($this->in, 1);
473 $i++;
474 } while (!feof($this->in) &&
475 (byte_strlen($ch) == 0 && $i < 10));
476 if ($i == 10 &&
477 byte_strlen($ch) == 0 &&
478 !feof($this->in)) {
479 debugLog("wbxml: input stream is not feof and limit 10 reached during read without getting any byte from input!");
480 }
481 // END
482 $this->_inputRaw .= $ch;
483 if(byte_strlen($ch) > 0)
484 return ord($ch);
485 else
486 return;
487 }
488
489 function getMBUInt() {
490 $uint = 0;
491
492 while(1) {
493 $byte = $this->getByte();
494
495 $uint |= $byte & 0x7f;
496
497 if($byte & 0x80)
498 $uint = $uint << 7;
499 else
500 break;
501 }
502
503 return $uint;
504 }
505
506 function getStringTable() {
507 $stringtable = "";
508
509 $length = $this->getMBUInt();
510 if($length > 0)
511 $stringtable = fread($this->in, $length);
512
513 $this->_inputRaw .= $stringtable;
514 return $stringtable;
515 }
516
517 function getMapping($cp, $id) {
518 if(!isset($this->dtd["codes"][$cp]) || !isset($this->dtd["codes"][$cp][$id]))
519 return false;
520 else {
521 if(isset($this->dtd["namespaces"][$cp])) {
522 return $this->dtd["namespaces"][$cp] . ":" . $this->dtd["codes"][$cp][$id];
523 } else
524 return $this->dtd["codes"][$cp][$id];
525 }
526 }
527 }
528
529 class WBXMLEncoder {
530 var $_dtd;
531 var $_out;
532
533 var $_tagcp;
534 var $_attrcp;
535
536 // ADDED dw2412 to support multipart output
537 var $_multipart; // in case we need to export as multipart i.e. ITEM_OPERATIONS
538
539 var $logStack = array();
540
541 // We use a delayed output mechanism in which we only output a tag when it actually has something
542 // in it. This can cause entire XML trees to disappear if they don't have output data in them; Ie
543 // calling 'startTag' 10 times, and then 'endTag' will cause 0 bytes of output apart from the header.
544
545 // Only when content() is called do we output the current stack of tags
546
547 var $_stack;
548
549 function WBXMLEncoder($output, $dtd) {
550 $this->_out = $output;
551
552 $this->_tagcp = 0;
553 $this->_attrcp = 0;
554
555 // reverse-map the DTD
556 foreach($dtd["namespaces"] as $nsid => $nsname) {
557 $this->_dtd["namespaces"][$nsname] = $nsid;
558 }
559
560 foreach($dtd["codes"] as $cp => $value) {
561 $this->_dtd["codes"][$cp] = array();
562 foreach($dtd["codes"][$cp] as $tagid => $tagname) {
563 $this->_dtd["codes"][$cp][$tagname] = $tagid;
564 }
565 }
566 $this->_stack = array();
567 }
568
569 function startWBXML($multipart=false) {
570 // START CHANGED dw2412 to support multipart output
571 $this->_multipart=$multipart;
572 if ($this->_multipart==true) {
573 header("Content-Type: application/vnd.ms-sync.multipart");
574 } else {
575 header("Content-Type: application/vnd.ms-sync.wbxml");
576 }
577 // END CHANGED dw2412 to support multipart output
578
579 $this->outByte(0x03); // WBXML 1.3
580 $this->outMBUInt(0x01); // Public ID 1
581 $this->outMBUInt(106); // UTF-8
582 $this->outMBUInt(0x00); // string table length (0)
583 }
584
585 function startTag($tag, $attributes = false, $nocontent = false) {
586 $stackelem = array();
587
588 if(!$nocontent) {
589 $stackelem['tag'] = $tag;
590 $stackelem['attributes'] = $attributes;
591 $stackelem['nocontent'] = $nocontent;
592 $stackelem['sent'] = false;
593
594 array_push($this->_stack, $stackelem);
595
596 // If 'nocontent' is specified, then apparently the user wants to force
597 // output of an empty tag, and we therefore output the stack here
598 } else {
599 $this->_outputStack();
600 $this->_startTag($tag, $attributes, $nocontent);
601 }
602 }
603
604 function endTag() {
605 $stackelem = array_pop($this->_stack);
606
607 // Only output end tags for items that have had a start tag sent
608 if($stackelem['sent']) {
609 $this->_endTag();
610 // START ADDED dw2412 multipart output handling
611 if(sizeof($this->_stack)==0 && $this->_multipart==true) {
612 // NOT THE NICE WAY IMHO but this keeps existing logic of data output in index.php file...
613 // first we grab the existing wbxml output, manipulate it and write it buffered way back.
614 $len = ob_get_length();
615 $data = ob_get_contents();
616 ob_end_clean();
617 ob_start();
618 $blockstart = ((sizeof($this->_bodyparts)+1)*2)*4+4;
619 $sizeinfo = pack("iii",sizeof($this->_bodyparts)+1,$blockstart,$len);
620 debugLog("GZip compressed Multipart Debug Output Total parts " . (sizeof($this->_bodyparts)+1));
621 debugLog(sprintf("Datapart BlockStart: %d Len: %d Content: %s",$blockstart,$len,bin2hex($data)));
622 foreach($this->_bodyparts as $bp) {
623 $blockstart = $blockstart + $len;
624 $len = byte_strlen(bin2hex($bp))/ 2;
625 $sizeinfo .= pack("ii",$blockstart,$len);
626 debugLog(sprintf("Bodypart BlockStart: %d Len: %d Content: %s",$blockstart,$len,bin2hex($bp)));
627 }
628 fwrite($this->_out,$sizeinfo);
629 fwrite($this->_out,$data);
630 foreach($this->_bodyparts as $bp) {
631 fwrite($this->_out,$bp);
632 }
633 }
634 // END ADDED dw2412 multipart output handling
635 }
636 }
637
638 function content($content) {
639 // We need to filter out any \0 chars because it's the string terminator in WBXML. We currently
640 // cannot send \0 characters within the XML content anywhere.
641 $content = str_replace("\0","",$content);
642
643 if("x" . $content == "x")
644 return;
645 $this->_outputStack();
646 $this->_content($content);
647 }
648
649 function contentopaque($content) {
650 if("x" . $content == "x")
651 return;
652 $this->_outputStack();
653 $this->_contentopaque($content);
654 }
655
656 // Output any tags on the stack that haven't been output yet
657 function _outputStack() {
658 for($i=0;$i<count($this->_stack);$i++) {
659 if(!$this->_stack[$i]['sent']) {
660 $this->_startTag($this->_stack[$i]['tag'], $this->_stack[$i]['attributes'], $this->_stack[$i]['nocontent']);
661 $this->_stack[$i]['sent'] = true;
662 }
663 }
664 }
665
666 // Outputs an actual start tag
667 function _startTag($tag, $attributes = false, $nocontent = false) {
668 $this->logStartTag($tag, $attributes, $nocontent);
669
670 $mapping = $this->getMapping($tag);
671
672 if(!$mapping)
673 return false;
674
675 if($this->_tagcp != $mapping["cp"]) {
676 $this->outSwitchPage($mapping["cp"]);
677 $this->_tagcp = $mapping["cp"];
678 }
679
680 $code = $mapping["code"];
681 if(isset($attributes) && is_array($attributes) && count($attributes) > 0) {
682 $code |= 0x80;
683 }
684
685 if(!isset($nocontent) || !$nocontent)
686 $code |= 0x40;
687
688 $this->outByte($code);
689
690 if($code & 0x80)
691 $this->outAttributes($attributes);
692 }
693
694 // Outputs actual data
695 function _content($content) {
696 $this->logContent($content);
697 $this->outByte(WBXML_STR_I);
698 $this->outTermStr($content);
699 }
700
701 // Outputs actual data
702 function _contentopaque($content) {
703 $this->logContent("OPAQUE: ".bin2hex($content));
704 $this->outByte(WBXML_OPAQUE);
705 $this->outByte(byte_strlen($content));
706 $this->outOpaque($content);
707 }
708
709 // Outputs an actual end tag
710 function _endTag() {
711 $this->logEndTag();
712 $this->outByte(WBXML_END);
713 }
714
715 // --------------------------- Private
716
717 function outByte($byte) {
718 fwrite($this->_out, chr($byte));
719 }
720
721 function outMBUInt($uint) {
722 while(1) {
723 $byte = $uint & 0x7f;
724 $uint = $uint >> 7;
725 if($uint == 0) {
726 $this->outByte($byte);
727 break;
728 } else {
729 $this->outByte($byte | 0x80);
730 }
731 }
732 }
733
734 function outTermStr($content) {
735 fwrite($this->_out, $content);
736 fwrite($this->_out, chr(0));
737 }
738
739 function outOpaque($content) {
740 fwrite($this->_out, $content);
741 }
742
743 function outAttributes() {
744 // We don't actually support this, because to do so, we would have
745 // to build a string table before sending the data (but we can't
746 // because we're streaming), so we'll just send an END, which just
747 // terminates the attribute list with 0 attributes.
748 $this->outByte(WBXML_END);
749 }
750
751 function outSwitchPage($page) {
752 $this->outByte(WBXML_SWITCH_PAGE);
753 $this->outByte($page);
754 }
755
756 function getMapping($tag) {
757 $mapping = array();
758
759 $split = $this->splitTag($tag);
760
761 if(isset($split["ns"])) {
762 $cp = $this->_dtd["namespaces"][$split["ns"]];
763 } else {
764 $cp = 0;
765 }
766
767 $code = $this->_dtd["codes"][$cp][$split["tag"]];
768
769 $mapping["cp"] = $cp;
770 $mapping["code"] = $code;
771
772 return $mapping;
773 }
774
775 function splitTag($fulltag) {
776 $ns = false;
777 $pos = strpos($fulltag, chr(58)); // chr(58) == ':'
778
779 if($pos) {
780 $ns = byte_substr($fulltag, 0, $pos);
781 $tag = byte_substr($fulltag, $pos+1);
782 } else {
783 $tag = $fulltag;
784 }
785
786 $ret = array();
787 if($ns)
788 $ret["ns"] = $ns;
789 $ret["tag"] = $tag;
790
791 return $ret;
792 }
793
794 function logStartTag($tag, $attr, $nocontent) {
795 if(!WBXML_DEBUG)
796 return;
797
798 $spaces = str_repeat(" ", count($this->logStack));
799 if($nocontent)
800 debugLog("O " . $spaces . " <$tag/>");
801 else {
802 array_push($this->logStack, $tag);
803 debugLog("O " . $spaces . " <$tag>");
804 }
805 }
806
807 function logEndTag() {
808 if(!WBXML_DEBUG)
809 return;
810
811 $spaces = str_repeat(" ", count($this->logStack));
812 $tag = array_pop($this->logStack);
813 debugLog("O " . $spaces . "</$tag>");
814 }
815
816 function logContent($content) {
817 if(!WBXML_DEBUG)
818 return;
819
820 $spaces = str_repeat(" ", count($this->logStack));
821 debugLog("O " . $spaces . $content);
822 }
823 }