1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13:
14: class Horde_Xml_Wbxml_Decoder extends Horde_Xml_Wbxml_ContentHandler
15: {
16: 17: 18: 19: 20: 21: 22: 23: 24:
25: protected $_dpi;
26:
27: 28: 29:
30: protected $_stringTable = array();
31:
32: 33: 34: 35:
36: protected $_ch;
37:
38: protected $_tagDTD;
39:
40: protected $_prevAttributeDTD;
41:
42: protected $_attributeDTD;
43:
44: 45: 46:
47: protected $_tagStack = array();
48: protected $_isAttribute;
49: protected $_isData = false;
50:
51: protected $_error = false;
52:
53: 54: 55: 56: 57:
58: protected $_dtdManager;
59:
60: 61: 62: 63: 64:
65: protected $_strpos;
66:
67: 68: 69:
70: public function __construct()
71: {
72: $this->_dtdManager = new Horde_Xml_Wbxml_DtdManager();
73: }
74:
75: 76: 77: 78: 79: 80:
81: public function setContentHandler($ch)
82: {
83: $this->_ch = $ch;
84: }
85: 86: 87: 88: 89:
90: public function getByte($input)
91: {
92: return ord($input{$this->_strpos++});
93: }
94:
95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105:
106: public function decodeToString($wbxml)
107: {
108: $this->_ch = new Horde_Xml_Wbxml_ContentHandler();
109: $this->decode($wbxml);
110: return $this->_ch->getOutput();
111: }
112:
113: 114: 115: 116: 117: 118: 119: 120: 121: 122:
123: public function decode($wbxml)
124: {
125: $this->_error = false;
126:
127: $this->_strpos = 0;
128:
129: if (empty($this->_ch)) {
130: throw new Horde_Xml_Wbxml_Exception('No Contenthandler defined.');
131: }
132:
133:
134:
135:
136: $this->_wbxmlVersion = $this->getVersionNumber($wbxml);
137:
138:
139:
140:
141:
142:
143: $dpiStruct = $this->getDocumentPublicIdentifier($wbxml);
144:
145:
146:
147: $this->_charset = $this->getCharset($wbxml);
148:
149:
150:
151: $this->retrieveStringTable($wbxml);
152:
153:
154: $this->_dpi = $this->getDocumentPublicIdentifierImpl($dpiStruct['dpiType'],
155: $dpiStruct['dpiNumber'],
156: $this->_stringTable);
157:
158:
159:
160:
161:
162:
163: $this->_dtdManager = new Horde_Xml_Wbxml_DtdManager();
164:
165:
166: $this->_tagDTD = $this->_dtdManager->getInstance($this->_dpi);
167:
168: if (!$this->_tagDTD) {
169: throw new Horde_Xml_Wbxml_Exception(
170: 'No DTD found for ' . $this->_dpi . '/' . $dpiStruct['dpiNumber']);
171: }
172:
173: $this->_attributeDTD = $this->_tagDTD;
174:
175: while (empty($this->_error) && $this->_strpos < strlen($wbxml)) {
176: $this->_decode($wbxml);
177: }
178: if (!empty($this->_error)) {
179: throw $this->_error;
180: }
181: }
182:
183: public function getVersionNumber($input)
184: {
185: return $this->getByte($input);
186: }
187:
188: public function getDocumentPublicIdentifier($input)
189: {
190: $i = Horde_Xml_Wbxml::MBUInt32ToInt($input, $this->_strpos);
191: if ($i == 0) {
192: return array('dpiType' => 2,
193: 'dpiNumber' => $this->getByte($input));
194: } else {
195: return array('dpiType' => 1,
196: 'dpiNumber' => $i);
197: }
198: }
199:
200: public function getDocumentPublicIdentifierImpl($dpiType, $dpiNumber)
201: {
202: if ($dpiType == 1) {
203: return Horde_Xml_Wbxml::getDPIString($dpiNumber);
204: } else {
205: return $this->getStringTableEntry($dpiNumber);
206: }
207: }
208:
209: 210: 211: 212: 213: 214:
215: public function getCharset($input)
216: {
217: $cs = Horde_Xml_Wbxml::MBUInt32ToInt($input, $this->_strpos);
218: return Horde_Xml_Wbxml::getCharsetString($cs);
219: }
220:
221: 222: 223: 224: 225: 226: 227: 228:
229: public function retrieveStringTable($input)
230: {
231: $size = Horde_Xml_Wbxml::MBUInt32ToInt($input, $this->_strpos);
232: $this->_stringTable = substr($input, $this->_strpos, $size);
233: $this->_strpos += $size;
234:
235: }
236:
237: public function getStringTableEntry($index)
238: {
239: if ($index >= strlen($this->_stringTable)) {
240: $this->_error = new Horde_Xml_Wbxml_Exception(
241: 'Invalid offset ' . $index
242: . ' value encountered around position ' . $this->_strpos
243: . '. Broken wbxml?');
244: return '';
245: }
246:
247:
248:
249: $str = '#';
250:
251: $i = 0;
252: $ch = $this->_stringTable[$index++];
253: if (ord($ch) == 0) {
254: return '';
255: }
256:
257: while (ord($ch) != 0) {
258: $str[$i++] = $ch;
259: if ($index >= strlen($this->_stringTable)) {
260: break;
261: }
262: $ch = $this->_stringTable[$index++];
263: }
264:
265: return $str;
266:
267: }
268:
269: protected function _decode($input)
270: {
271: $token = $this->getByte($input);
272: $str = '';
273:
274:
275:
276: switch ($token) {
277: case Horde_Xml_Wbxml::GLOBAL_TOKEN_STR_I:
278:
279: $str = $this->termstr($input);
280: $this->_ch->characters($str);
281:
282: break;
283:
284: case Horde_Xml_Wbxml::GLOBAL_TOKEN_STR_T:
285:
286: $x = Horde_Xml_Wbxml::MBUInt32ToInt($input, $this->_strpos);
287: $str = $this->getStringTableEntry($x);
288: $this->_ch->characters($str);
289: break;
290:
291: case Horde_Xml_Wbxml::GLOBAL_TOKEN_EXT_I_0:
292: case Horde_Xml_Wbxml::GLOBAL_TOKEN_EXT_I_1:
293: case Horde_Xml_Wbxml::GLOBAL_TOKEN_EXT_I_2:
294:
295: $str = $this->termstr($input);
296: $this->_ch->characters($str);
297: break;
298:
299: case Horde_Xml_Wbxml::GLOBAL_TOKEN_EXT_T_0:
300: case Horde_Xml_Wbxml::GLOBAL_TOKEN_EXT_T_1:
301: case Horde_Xml_Wbxml::GLOBAL_TOKEN_EXT_T_2:
302:
303: $str = $this->getStringTableEnty(Horde_Xml_Wbxml::MBUInt32ToInt($input, $this->_strpos));
304: $this->_ch->characters($str);
305: break;
306:
307: case Horde_Xml_Wbxml::GLOBAL_TOKEN_EXT_0:
308: case Horde_Xml_Wbxml::GLOBAL_TOKEN_EXT_1:
309: case Horde_Xml_Wbxml::GLOBAL_TOKEN_EXT_2:
310:
311: $extension = $this->getByte($input);
312: $this->_ch->characters($extension);
313: break;
314:
315: case Horde_Xml_Wbxml::GLOBAL_TOKEN_ENTITY:
316:
317:
318: $entity = $this->entity(Horde_Xml_Wbxml::MBUInt32ToInt($input, $this->_strpos));
319:
320: $this->_ch->characters('&#' . $entity . ';');
321: break;
322:
323: case Horde_Xml_Wbxml::GLOBAL_TOKEN_PI:
324:
325: throw new Horde_Xml_Wbxml_Exception('WBXML global token processing instruction is unsupported');
326:
327: case Horde_Xml_Wbxml::GLOBAL_TOKEN_LITERAL:
328:
329: $str = $this->getStringTableEntry(Horde_Xml_Wbxml::MBUInt32ToInt($input, $this->_strpos));
330: $this->parseTag($input, $str, false, false);
331: break;
332:
333: case Horde_Xml_Wbxml::GLOBAL_TOKEN_LITERAL_A:
334:
335: $str = $this->getStringTableEntry(Horde_Xml_Wbxml::MBUInt32ToInt($input, $this->_strpos));
336: $this->parseTag($input, $str, true, false);
337: break;
338:
339: case Horde_Xml_Wbxml::GLOBAL_TOKEN_LITERAL_AC:
340:
341: $str = $this->getStringTableEntry(Horde_Xml_Wbxml::MBUInt32ToInt($input, $this->_strpos));
342: $this->parseTag($input, $string, true, true);
343: break;
344:
345: case Horde_Xml_Wbxml::GLOBAL_TOKEN_LITERAL_C:
346:
347: $str = $this->getStringTableEntry(Horde_Xml_Wbxml::MBUInt32ToInt($input, $this->_strpos));
348: $this->parseTag($input, $str, false, true);
349: break;
350:
351: case Horde_Xml_Wbxml::GLOBAL_TOKEN_OPAQUE:
352:
353: $size = Horde_Xml_Wbxml::MBUInt32ToInt($input, $this->_strpos);
354: if ($size>0) {
355: $b = substr($input, $this->_strpos, $size);
356:
357: $this->_strpos += $size;
358:
359:
360:
361:
362:
363:
364: if ($size > 0 && $this->_isData && ord($b) <= 10) {
365: $decoder = new Horde_Xml_Wbxml_Decoder();
366: $decoder->setContentHandler($this->_ch);
367: $decoder->decode($b);
368:
369:
370:
371:
372: } else {
373:
374:
375: $this->_ch->characters($b);
376: }
377: }
378:
379:
380:
381:
382:
383:
384:
385:
386:
387:
388:
389: break;
390:
391: case Horde_Xml_Wbxml::GLOBAL_TOKEN_END:
392:
393: $str = $this->endTag();
394: break;
395:
396: case Horde_Xml_Wbxml::GLOBAL_TOKEN_SWITCH_PAGE:
397:
398: $codePage = $this->getByte($input);
399:
400: $this->switchElementCodePage($codePage);
401: break;
402:
403: default:
404:
405:
406: $hasAttributes = (($token & 0x80) != 0);
407: $hasContent = (($token & 0x40) != 0);
408: $realToken = $token & 0x3F;
409: $str = $this->getTag($realToken);
410:
411:
412: $this->parseTag($input, $str, $hasAttributes, $hasContent);
413:
414: if ($realToken == 0x0f) {
415:
416:
417:
418: $this->_isData = true;
419: } else {
420: $this->_isData = false;
421: }
422: break;
423: }
424: }
425:
426: public function parseTag($input, $tag, $hasAttributes, $hasContent)
427: {
428: $attrs = array();
429: if ($hasAttributes) {
430: $attrs = $this->getAttributes($input);
431: }
432:
433: $this->_ch->startElement($this->getCurrentURI(), $tag, $attrs);
434:
435: if ($hasContent) {
436:
437:
438: $this->_tagStack[] = $tag;
439: } else {
440: $this->_ch->endElement($this->getCurrentURI(), $tag);
441: }
442: }
443:
444: public function endTag()
445: {
446: if (count($this->_tagStack)) {
447: $tag = array_pop($this->_tagStack);
448: } else {
449: $tag = 'Unknown';
450: }
451:
452: $this->_ch->endElement($this->getCurrentURI(), $tag);
453:
454: return $tag;
455: }
456:
457: public function getAttributes($input)
458: {
459: $this->startGetAttributes();
460: $hasMoreAttributes = true;
461:
462: $attrs = array();
463: $attr = null;
464: $value = null;
465: $token = null;
466:
467: while ($hasMoreAttributes) {
468: $token = $this->getByte($input);
469:
470: switch ($token) {
471:
472: case Horde_Xml_Wbxml::GLOBAL_TOKEN_LITERAL:
473:
474: if (isset($attr)) {
475: $attrs[] = array('attribute' => $attr,
476: 'value' => $value);
477: }
478:
479: $attr = $this->getStringTableEntry(Horde_Xml_Wbxml::MBUInt32ToInt($input, $this->_strpos));
480: break;
481:
482:
483: case Horde_Xml_Wbxml::GLOBAL_TOKEN_EXT_I_0:
484: case Horde_Xml_Wbxml::GLOBAL_TOKEN_EXT_I_1:
485: case Horde_Xml_Wbxml::GLOBAL_TOKEN_EXT_I_2:
486:
487: $value .= $this->termstr($input);
488: break;
489:
490: case Horde_Xml_Wbxml::GLOBAL_TOKEN_EXT_T_0:
491: case Horde_Xml_Wbxml::GLOBAL_TOKEN_EXT_T_1:
492: case Horde_Xml_Wbxml::GLOBAL_TOKEN_EXT_T_2:
493:
494: $value .= $this->getStringTableEntry(Horde_Xml_Wbxml::MBUInt32ToInt($input, $this->_strpos));
495: break;
496:
497: case Horde_Xml_Wbxml::GLOBAL_TOKEN_EXT_0:
498: case Horde_Xml_Wbxml::GLOBAL_TOKEN_EXT_1:
499: case Horde_Xml_Wbxml::GLOBAL_TOKEN_EXT_2:
500:
501: $value .= $input[$this->_strpos++];
502: break;
503:
504: case Horde_Xml_Wbxml::GLOBAL_TOKEN_ENTITY:
505:
506: $value .= $this->entity(Horde_Xml_Wbxml::MBUInt32ToInt($input, $this->_strpos));
507: break;
508:
509: case Horde_Xml_Wbxml::GLOBAL_TOKEN_STR_I:
510:
511: $value .= $this->termstr($input);
512: break;
513:
514: case Horde_Xml_Wbxml::GLOBAL_TOKEN_STR_T:
515:
516: $value .= $this->getStringTableEntry(Horde_Xml_Wbxml::MBUInt32ToInt($input, $this->_strpos));
517: break;
518:
519: case Horde_Xml_Wbxml::GLOBAL_TOKEN_OPAQUE:
520:
521: $size = Horde_Xml_Wbxml::MBUInt32ToInt($input, $this->_strpos);
522: $b = substr($input, $this->_strpos, $this->_strpos + $size);
523: $this->_strpos += $size;
524:
525: $value .= $b;
526: break;
527:
528: case Horde_Xml_Wbxml::GLOBAL_TOKEN_END:
529:
530: $hasMoreAttributes = false;
531: if (isset($attr)) {
532: $attrs[] = array('attribute' => $attr,
533: 'value' => $value);
534: }
535: break;
536:
537: case Horde_Xml_Wbxml::GLOBAL_TOKEN_SWITCH_PAGE:
538:
539: $codePage = $this->getByte($input);
540: if (!$this->_prevAttributeDTD) {
541: $this->_prevAttributeDTD = $this->_attributeDTD;
542: }
543:
544: $this->switchAttributeCodePage($codePage);
545: break;
546:
547: default:
548: if ($token > 128) {
549: if (isset($attr)) {
550: $attrs[] = array('attribute' => $attr,
551: 'value' => $value);
552: }
553: $attr = $this->_attributeDTD->toAttribute($token);
554: } else {
555:
556: $value .= $this->_attributeDTD->toAttribute($token);
557: }
558: break;
559: }
560: }
561:
562: if (!$this->_prevAttributeDTD) {
563: $this->_attributeDTD = $this->_prevAttributeDTD;
564: $this->_prevAttributeDTD = false;
565: }
566:
567: $this->stopGetAttributes();
568: }
569:
570: public function startGetAttributes()
571: {
572: $this->_isAttribute = true;
573: }
574:
575: public function stopGetAttributes()
576: {
577: $this->_isAttribute = false;
578: }
579:
580: public function getCurrentURI()
581: {
582: $uri = $this->_isAttribute
583: ? $this->_tagDTD->getURI()
584: : $this->_attributeDTD->getURI();
585: return str_replace(
586: array('syncml:metinf1.0',
587: 'syncml:metinf1.1',
588: 'syncml:metinf1.2',
589: 'syncml:devinf1.0',
590: 'syncml:devinf1.1',
591: 'syncml:devinf1.2'),
592: array('syncml:metinf',
593: 'syncml:metinf',
594: 'syncml:metinf',
595: 'syncml:devinf',
596: 'syncml:devinf',
597: 'syncml:devinf'),
598: $uri);
599: }
600:
601: public function writeString($str)
602: {
603: $this->_ch->characters($str);
604: }
605:
606: public function getTag($tag)
607: {
608:
609: return $this->_tagDTD->toTagStr($tag);
610: }
611:
612: public function getAttribute($attribute)
613: {
614:
615: $this->_attributeDTD->toAttributeInt($attribute);
616: }
617:
618: public function switchElementCodePage($codePage)
619: {
620: $this->_tagDTD = $this->_dtdManager->getInstance($this->_tagDTD->toCodePageStr($codePage));
621: $this->switchAttributeCodePage($codePage);
622: }
623:
624: public function switchAttributeCodePage($codePage)
625: {
626: $this->_attributeDTD = $this->_dtdManager->getInstance($this->_attributeDTD->toCodePageStr($codePage));
627: }
628:
629: 630: 631:
632: public function entity($entity)
633: {
634: return dechex($entity);
635: }
636:
637: 638: 639:
640: public function termstr($input)
641: {
642: $str = '#';
643: $i = 0;
644: $ch = $input[$this->_strpos++];
645: if (ord($ch) == 0) {
646: return '';
647: }
648: while (ord($ch) != 0) {
649: $str[$i++] = $ch;
650: $ch = $input[$this->_strpos++];
651: }
652:
653: return $str;
654: }
655: }
656: