Overview

Packages

  • Xml
    • Wbxml

Classes

  • Horde_Xml_Wbxml
  • Horde_Xml_Wbxml_ContentHandler
  • Horde_Xml_Wbxml_Decoder
  • Horde_Xml_Wbxml_Dtd
  • Horde_Xml_Wbxml_Dtd_SyncMl
  • Horde_Xml_Wbxml_Dtd_SyncMlDevInf
  • Horde_Xml_Wbxml_Dtd_SyncMlMetInf
  • Horde_Xml_Wbxml_DtdManager
  • Horde_Xml_Wbxml_Encoder
  • Horde_Xml_Wbxml_Exception
  • Horde_Xml_Wbxml_HashTable
  • Horde_Xml_Wbxml_LifoQueue
  • Overview
  • Package
  • Class
  • Tree
  1: <?php
  2: /**
  3:  * From Binary XML Content Format Specification Version 1.3, 25 July 2001
  4:  * found at http://www.wapforum.org
  5:  *
  6:  * Copyright 2003-2012 Horde LLC (http://www.horde.org/)
  7:  *
  8:  * See the enclosed file COPYING for license information (LGPL). If you
  9:  * did not receive this file, see http://www.horde.org/licenses/lgpl21.
 10:  *
 11:  * @author  Anthony Mills <amills@pyramid6.com>
 12:  * @package Xml_Wbxml
 13:  */
 14: class Horde_Xml_Wbxml_Decoder extends Horde_Xml_Wbxml_ContentHandler
 15: {
 16:     /**
 17:      * Document Public Identifier type
 18:      * 1 mb_u_int32 well known type
 19:      * 2 string table
 20:      * from spec but converted into a string.
 21:      *
 22:      * Document Public Identifier
 23:      * Used with dpiType.
 24:      */
 25:     protected $_dpi;
 26: 
 27:     /**
 28:      * String table as defined in 5.7
 29:      */
 30:     protected $_stringTable = array();
 31: 
 32:     /**
 33:      * Content handler.
 34:      * Currently just outputs raw XML.
 35:      */
 36:     protected $_ch;
 37: 
 38:     protected $_tagDTD;
 39: 
 40:     protected $_prevAttributeDTD;
 41: 
 42:     protected $_attributeDTD;
 43: 
 44:     /**
 45:      * State variables.
 46:      */
 47:     protected $_tagStack = array();
 48:     protected $_isAttribute;
 49:     protected $_isData = false;
 50: 
 51:     protected $_error = false;
 52: 
 53:     /**
 54:      * The DTD Manager.
 55:      *
 56:      * @var Horde_Xml_Wbxml_DtdManager
 57:      */
 58:     protected $_dtdManager;
 59: 
 60:     /**
 61:      * The string position.
 62:      *
 63:      * @var integer
 64:      */
 65:     protected $_strpos;
 66: 
 67:     /**
 68:      * Constructor.
 69:      */
 70:     public function __construct()
 71:     {
 72:         $this->_dtdManager = new Horde_Xml_Wbxml_DtdManager();
 73:     }
 74: 
 75:     /**
 76:      * Sets the contentHandler that will receive the output of the
 77:      * decoding.
 78:      *
 79:      * @param Horde_Xml_Wbxml_ContentHandler $ch The contentHandler
 80:      */
 81:     public function setContentHandler($ch)
 82:     {
 83:         $this->_ch = $ch;
 84:     }
 85:     /**
 86:      * Return one byte from the input stream.
 87:      *
 88:      * @param string $input  The WBXML input string.
 89:      */
 90:     public function getByte($input)
 91:     {
 92:         return ord($input{$this->_strpos++});
 93:     }
 94: 
 95:     /**
 96:      * Takes a WBXML input document and returns decoded XML.
 97:      * However the preferred and more effecient method is to
 98:      * use decode() rather than decodeToString() and have an
 99:      * appropriate contentHandler deal with the decoded data.
100:      *
101:      * @param string $wbxml  The WBXML document to decode.
102:      *
103:      * @return string  The decoded XML document.
104:      * @throws Horde_Xml_Wbxml_Exception
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:      * Takes a WBXML input document and decodes it.
115:      * Decoding result is directly passed to the contentHandler.
116:      * A contenthandler must be set using setContentHandler
117:      * prior to invocation of this method
118:      *
119:      * @param string $wbxml  The WBXML document to decode.
120:      *
121:      * @throws Horde_Xml_Wbxml_Exception
122:      */
123:     public function decode($wbxml)
124:     {
125:         $this->_error = false; // reset state
126: 
127:         $this->_strpos = 0;
128: 
129:         if (empty($this->_ch)) {
130:             throw new Horde_Xml_Wbxml_Exception('No Contenthandler defined.');
131:         }
132: 
133:         // Get Version Number from Section 5.4
134:         // version = u_int8
135:         // currently 1, 2 or 3
136:         $this->_wbxmlVersion = $this->getVersionNumber($wbxml);
137: 
138:         // Get Document Public Idetifier from Section 5.5
139:         // publicid = mb_u_int32 | (zero index)
140:         // zero = u_int8
141:         // Containing the value zero (0)
142:         // The actual DPI is determined after the String Table is read.
143:         $dpiStruct = $this->getDocumentPublicIdentifier($wbxml);
144: 
145:         // Get Charset from 5.6
146:         // charset = mb_u_int32
147:         $this->_charset = $this->getCharset($wbxml);
148: 
149:         // Get String Table from 5.7
150:         // strb1 = length *byte
151:         $this->retrieveStringTable($wbxml);
152: 
153:         // Get Document Public Idetifier from Section 5.5.
154:         $this->_dpi = $this->getDocumentPublicIdentifierImpl($dpiStruct['dpiType'],
155:                                                              $dpiStruct['dpiNumber'],
156:                                                              $this->_stringTable);
157: 
158:         // Now the real fun begins.
159:         // From Sections 5.2 and 5.8
160: 
161: 
162:         // Default content handler.
163:         $this->_dtdManager = new Horde_Xml_Wbxml_DtdManager();
164: 
165:         // Get the starting DTD.
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:      * Returns the character encoding. Only default character
211:      * encodings from J2SE are supported.  From
212:      * http://www.iana.org/assignments/character-sets and
213:      * http://java.sun.com/j2se/1.4.2/docs/api/java/nio/charset/Charset.html
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:      * Retrieves the string table.
223:      * The string table consists of an mb_u_int32 length
224:      * and then length bytes forming the table.
225:      * References to the string table refer to the
226:      * starting position of the (null terminated)
227:      * string in this table.
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:         // print "stringtable($size):" . $this->_stringTable ."\n";
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:         // copy of method termstr but without modification of this->_strpos
248: 
249:         $str = '#'; // must start with nonempty string to allow array access
250: 
251:         $i = 0;
252:         $ch = $this->_stringTable[$index++];
253:         if (ord($ch) == 0) {
254:             return ''; // don't 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:         // print "string table entry: $str\n";
265:         return $str;
266: 
267:     }
268: 
269:     protected function _decode($input)
270:     {
271:         $token = $this->getByte($input);
272:         $str = '';
273: 
274:         // print "position: " . $this->_strpos . " token: " . $token . " str10: " . substr($input, $this->_strpos, 10) . "\n"; // @todo: remove debug output
275: 
276:         switch ($token) {
277:         case Horde_Xml_Wbxml::GLOBAL_TOKEN_STR_I:
278:             // Section 5.8.4.1
279:             $str = $this->termstr($input);
280:             $this->_ch->characters($str);
281:             // print "str:$str\n"; // @TODO Remove debug code
282:             break;
283: 
284:         case Horde_Xml_Wbxml::GLOBAL_TOKEN_STR_T:
285:             // Section 5.8.4.1
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:             // Section 5.8.4.2
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:             // Section 5.8.4.2
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:             // Section 5.8.4.2
311:             $extension = $this->getByte($input);
312:             $this->_ch->characters($extension);
313:             break;
314: 
315:         case Horde_Xml_Wbxml::GLOBAL_TOKEN_ENTITY:
316:             // Section 5.8.4.3
317:             // UCS-4 chracter encoding?
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:             // Section 5.8.4.4
325:             throw new Horde_Xml_Wbxml_Exception('WBXML global token processing instruction is unsupported');
326: 
327:         case Horde_Xml_Wbxml::GLOBAL_TOKEN_LITERAL:
328:             // Section 5.8.4.5
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:             // Section 5.8.4.5
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:             // Section 5.8.4.5
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:             // Section 5.8.4.5
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:             // Section 5.8.4.6
353:             $size = Horde_Xml_Wbxml::MBUInt32ToInt($input, $this->_strpos);
354:             if ($size>0) {
355:                 $b = substr($input, $this->_strpos, $size);
356:                 // print "opaque of size $size: ($b)\n"; // @todo remove debug
357:                 $this->_strpos += $size;
358:                 // opaque data inside a <data> element may or may not be
359:                 // a nested wbxml document (for example devinf data).
360:                 // We find out by checking the first byte of the data: if it's
361:                 // 1, 2 or 3 we expect it to be the version number of a wbxml
362:                 // document and thus start a new wbxml decoder instance on it.
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:             //                /* // @todo: FIXME currently we can't decode Nokia
369:                     // DevInf data. So ignore error for the time beeing.
370:                     // */
371:                     // $this->_ch->characters($s);
372:                 } else {
373:                     /* normal opaque behaviour: just copy the raw data: */
374:                     // print "opaque handled as string=$b\n"; // @todo remove debug
375:                     $this->_ch->characters($b);
376:                 }
377:             }
378:             // old approach to deal with opaque data inside ContentHandler:
379:             // FIXME Opaque is used by SYNCML.  Opaque data that depends on the context
380:             // if (contentHandler instanceof OpaqueContentHandler) {
381:             //     ((OpaqueContentHandler)contentHandler).opaque(b);
382:             // } else {
383:             //     String str = new String(b, 0, size, charset);
384:             //     char[] chars = str.toCharArray();
385: 
386:             //     contentHandler.characters(chars, 0, chars.length);
387:             // }
388: 
389:             break;
390: 
391:         case Horde_Xml_Wbxml::GLOBAL_TOKEN_END:
392:             // Section 5.8.4.7.1
393:             $str = $this->endTag();
394:             break;
395: 
396:         case Horde_Xml_Wbxml::GLOBAL_TOKEN_SWITCH_PAGE:
397:             // Section 5.8.4.7.2
398:             $codePage = $this->getByte($input);
399:             // print "switch to codepage $codePage\n"; // @todo: remove debug code
400:             $this->switchElementCodePage($codePage);
401:             break;
402: 
403:         default:
404:             // Section 5.8.2
405:             // Section 5.8.3
406:             $hasAttributes = (($token & 0x80) != 0);
407:             $hasContent = (($token & 0x40) != 0);
408:             $realToken = $token & 0x3F;
409:             $str = $this->getTag($realToken);
410: 
411:             // print "element:$str\n"; // @TODO Remove debug code
412:             $this->parseTag($input, $str, $hasAttributes, $hasContent);
413: 
414:             if ($realToken == 0x0f) {
415:                 // store if we're inside a Data tag. This may contain
416:                 // an additional enclosed wbxml document on which we have
417:                 // to run a seperate encoder
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:             // FIXME I forgot what does this does. Not sure if this is
437:             // right?
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:             // Attribute specified.
472:             case Horde_Xml_Wbxml::GLOBAL_TOKEN_LITERAL:
473:                 // Section 5.8.4.5
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:             // Value specified.
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:                 // Section 5.8.4.2
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:                 // Section 5.8.4.2
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:                 // Section 5.8.4.2
501:                 $value .= $input[$this->_strpos++];
502:                 break;
503: 
504:             case Horde_Xml_Wbxml::GLOBAL_TOKEN_ENTITY:
505:                 // Section 5.8.4.3
506:                 $value .= $this->entity(Horde_Xml_Wbxml::MBUInt32ToInt($input, $this->_strpos));
507:                 break;
508: 
509:             case Horde_Xml_Wbxml::GLOBAL_TOKEN_STR_I:
510:                 // Section 5.8.4.1
511:                 $value .= $this->termstr($input);
512:                 break;
513: 
514:             case Horde_Xml_Wbxml::GLOBAL_TOKEN_STR_T:
515:                 // Section 5.8.4.1
516:                 $value .= $this->getStringTableEntry(Horde_Xml_Wbxml::MBUInt32ToInt($input, $this->_strpos));
517:                 break;
518: 
519:             case Horde_Xml_Wbxml::GLOBAL_TOKEN_OPAQUE:
520:                 // Section 5.8.4.6
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:                 // Section 5.8.4.7.1
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:                 // Section 5.8.4.7.2
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:                     // Value.
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:         // Should know which state it is in.
609:         return $this->_tagDTD->toTagStr($tag);
610:     }
611: 
612:     public function getAttribute($attribute)
613:     {
614:         // Should know which state it is in.
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:      * Return the hex version of the base 10 $entity.
631:      */
632:     public function entity($entity)
633:     {
634:         return dechex($entity);
635:     }
636: 
637:     /**
638:      * Reads a null terminated string.
639:      */
640:     public function termstr($input)
641:     {
642:         $str = '#'; // must start with nonempty string to allow array access
643:         $i = 0;
644:         $ch = $input[$this->_strpos++];
645:         if (ord($ch) == 0) {
646:             return ''; // don't return '#'
647:         }
648:         while (ord($ch) != 0) {
649:             $str[$i++] = $ch;
650:             $ch = $input[$this->_strpos++];
651:         }
652: 
653:         return $str;
654:     }
655: }
656: 
API documentation generated by ApiGen