PageRenderTime 49ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/framework/Xml_Wbxml/lib/Horde/Xml/Wbxml/Encoder.php

https://github.com/sgtcarneiro/horde
PHP | 431 lines | 296 code | 61 blank | 74 comment | 82 complexity | 9d835c78f5363450a08255afb25bb1c2 MD5 | raw file
  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-2011 The Horde Project (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.fsf.org/copyleft/lgpl.html.
  10. *
  11. * @author Anthony Mills <amills@pyramid6.com>
  12. * @package Xml_Wbxml
  13. */
  14. class Horde_Xml_Wbxml_Encoder extends Horde_Xml_Wbxml_ContentHandler
  15. {
  16. protected $_strings = array();
  17. protected $_stringTable;
  18. protected $_hasWrittenHeader = false;
  19. protected $_dtd;
  20. protected $_output = '';
  21. protected $_uris = array();
  22. protected $_uriNums = array();
  23. protected $_currentURI;
  24. protected $_subParser = null;
  25. protected $_subParserStack = 0;
  26. /**
  27. * The XML parser.
  28. *
  29. * @var resource
  30. */
  31. protected $_parser;
  32. /**
  33. * The DTD Manager.
  34. *
  35. * @var Horde_Xml_Wbxml_DtdManager
  36. */
  37. protected $_dtdManager;
  38. /**
  39. * Constructor.
  40. */
  41. public function Horde_Xml_Wbxml_Encoder()
  42. {
  43. $this->_dtdManager = new Horde_Xml_Wbxml_DtdManager();
  44. $this->_stringTable = new Horde_Xml_Wbxml_HashTable();
  45. }
  46. /**
  47. * Take the input $xml and turn it into WBXML. This is _not_ the
  48. * intended way of using this class. It is derived from
  49. * Contenthandler and one should use it as a ContentHandler and
  50. * produce the XML-structure with startElement(), endElement(),
  51. * and characters().
  52. *
  53. * @throws Horde_Xml_Wbxml_Exception
  54. */
  55. public function encode($xml)
  56. {
  57. // Create the XML parser and set method references.
  58. $this->_parser = xml_parser_create_ns($this->_charset);
  59. xml_set_object($this->_parser, $this);
  60. xml_parser_set_option($this->_parser, XML_OPTION_CASE_FOLDING, false);
  61. xml_set_element_handler($this->_parser, '_startElement', '_endElement');
  62. xml_set_character_data_handler($this->_parser, '_characters');
  63. xml_set_processing_instruction_handler($this->_parser, '');
  64. xml_set_external_entity_ref_handler($this->_parser, '');
  65. if (!xml_parse($this->_parser, $xml)) {
  66. throw new Horde_Xml_Wbxml_Exception(
  67. sprintf('XML error: %s at line %d',
  68. xml_error_string(xml_get_error_code($this->_parser)),
  69. xml_get_current_line_number($this->_parser)));
  70. }
  71. xml_parser_free($this->_parser);
  72. return $this->_output;
  73. }
  74. /**
  75. * This will write the correct headers.
  76. *
  77. * @throws Horde_Xml_Wbxml_Exception
  78. */
  79. public function writeHeader($uri)
  80. {
  81. $this->_dtd = $this->_dtdManager->getInstanceURI($uri);
  82. if (!$this->_dtd) {
  83. // TODO: proper error handling
  84. die('Unable to find dtd for ' . $uri);
  85. }
  86. $dpiString = $this->_dtd->getDPI();
  87. // Set Version Number from Section 5.4
  88. // version = u_int8
  89. // currently 1, 2 or 3
  90. $this->writeVersionNumber($this->_wbxmlVersion);
  91. // Set Document Public Idetifier from Section 5.5
  92. // publicid = mb_u_int32 | ( zero index )
  93. // zero = u_int8
  94. // containing the value zero (0)
  95. // The actual DPI is determined after the String Table is read.
  96. $this->writeDocumentPublicIdentifier($dpiString, $this->_strings);
  97. // Set Charset from 5.6
  98. // charset = mb_u_int32
  99. $this->writeCharset($this->_charset);
  100. // Set String Table from 5.7
  101. // strb1 = length *byte
  102. $this->writeStringTable($this->_strings, $this->_charset, $this->_stringTable);
  103. $this->_currentURI = $uri;
  104. $this->_hasWrittenHeader = true;
  105. }
  106. public function writeVersionNumber($version)
  107. {
  108. $this->_output .= chr($version);
  109. }
  110. public function writeDocumentPublicIdentifier($dpiString, &$strings)
  111. {
  112. $i = 0;
  113. // The OMA test suite doesn't like DPI as integer code.
  114. // So don't try lookup and always send full DPI string.
  115. // $i = Horde_Xml_Wbxml::getDPIInt($dpiString);
  116. if ($i == 0) {
  117. $strings[0] = $dpiString;
  118. $this->_output .= chr(0);
  119. $this->_output .= chr(0);
  120. } else {
  121. Horde_Xml_Wbxml::intToMBUInt32($this->_output, $i);
  122. }
  123. }
  124. /**
  125. * @throws Horde_Xml_Wbxml_Exception
  126. */
  127. public function writeCharset($charset)
  128. {
  129. $cs = Horde_Xml_Wbxml::getCharsetInt($charset);
  130. if ($cs == 0) {
  131. throw new Horde_Xml_Wbxml_Exception('Unsupported Charset: ' . $charset);
  132. } else {
  133. Horde_Xml_Wbxml::intToMBUInt32($this->_output, $cs);
  134. }
  135. }
  136. public function writeStringTable($strings, $charset, $stringTable)
  137. {
  138. $stringBytes = array();
  139. $count = 0;
  140. foreach ($strings as $str) {
  141. $bytes = $this->_getBytes($str, $charset);
  142. $stringBytes = array_merge($stringBytes, $bytes);
  143. $nullLength = $this->_addNullByte($bytes);
  144. $this->_stringTable->set($str, $count);
  145. $count += count($bytes) + $nullLength;
  146. }
  147. Horde_Xml_Wbxml::intToMBUInt32($this->_output, count($stringBytes));
  148. $this->_output .= implode('', $stringBytes);
  149. }
  150. public function writeString($str, $cs)
  151. {
  152. $bytes = $this->_getBytes($str, $cs);
  153. $this->_output .= implode('', $bytes);
  154. $this->writeNull($cs);
  155. }
  156. public function writeNull($charset)
  157. {
  158. $this->_output .= chr(0);
  159. return 1;
  160. }
  161. protected function _addNullByte(&$bytes)
  162. {
  163. $bytes[] = chr(0);
  164. return 1;
  165. }
  166. protected function _getBytes($string, $cs)
  167. {
  168. $nbytes = strlen($string);
  169. $bytes = array();
  170. for ($i = 0; $i < $nbytes; $i++) {
  171. $bytes[] = $string{$i};
  172. }
  173. return $bytes;
  174. }
  175. protected function _splitURI($tag)
  176. {
  177. $parts = explode(':', $tag);
  178. $name = array_pop($parts);
  179. $uri = implode(':', $parts);
  180. return array($uri, $name);
  181. }
  182. /**
  183. * @throws Horde_Xml_Wbxml_Exception
  184. */
  185. public function startElement($uri, $name, $attributes = array())
  186. {
  187. if ($this->_subParser == null) {
  188. if (!$this->_hasWrittenHeader) {
  189. $this->writeHeader($uri);
  190. }
  191. if ($this->_currentURI != $uri) {
  192. $this->changecodepage($uri);
  193. $this->_currentURI = $uri;
  194. }
  195. if ($this->_subParser == null) {
  196. $this->writeTag($name, $attributes, true, $this->_charset);
  197. } else {
  198. $this->_subParser->startElement($uri, $name, $attributes);
  199. }
  200. } else {
  201. $this->_subParserStack++;
  202. $this->_subParser->startElement($uri, $name, $attributes);
  203. }
  204. }
  205. protected function _startElement($parser, $tag, $attributes)
  206. {
  207. list($uri, $name) = $this->_splitURI($tag);
  208. $this->startElement($uri, $name, $attributes);
  209. }
  210. public function opaque($o)
  211. {
  212. $this->_output .= chr(Horde_Xml_Wbxml::GLOBAL_TOKEN_OPAQUE);
  213. Horde_Xml_Wbxml::intToMBUInt32($this->_output, strlen($o));
  214. $this->_output .= $o;
  215. }
  216. public function characters($chars)
  217. {
  218. $chars = trim($chars);
  219. if (strlen($chars)) {
  220. /* We definitely don't want any whitespace. */
  221. if ($this->_subParser == null) {
  222. $i = $this->_stringTable->get($chars);
  223. if ($i != null) {
  224. $this->_output .= chr(Horde_Xml_Wbxml::GLOBAL_TOKEN_STR_T);
  225. Horde_Xml_Wbxml::intToMBUInt32($this->_output, $i);
  226. } else {
  227. $this->_output .= chr(Horde_Xml_Wbxml::GLOBAL_TOKEN_STR_I);
  228. $this->writeString($chars, $this->_charset);
  229. }
  230. } else {
  231. $this->_subParser->characters($chars);
  232. }
  233. }
  234. }
  235. protected function _characters($parser, $chars)
  236. {
  237. $this->characters($chars);
  238. }
  239. /**
  240. * @throws Horde_Xml_Wbxml_Exception
  241. */
  242. public function writeTag($name, $attrs, $hasContent, $cs)
  243. {
  244. if ($attrs != null && !count($attrs)) {
  245. $attrs = null;
  246. }
  247. $t = $this->_dtd->toTagInt($name);
  248. if ($t == -1) {
  249. $i = $this->_stringTable->get($name);
  250. if ($i == null) {
  251. throw new Horde_Xml_Wbxml_Exception($name . ' is not found in String Table or DTD');
  252. } else {
  253. if ($attrs == null && !$hasContent) {
  254. $this->_output .= chr(Horde_Xml_Wbxml::GLOBAL_TOKEN_LITERAL);
  255. } elseif ($attrs == null && $hasContent) {
  256. $this->_output .= chr(Horde_Xml_Wbxml::GLOBAL_TOKEN_LITERAL_A);
  257. } elseif ($attrs != null && $hasContent) {
  258. $this->_output .= chr(Horde_Xml_Wbxml::GLOBAL_TOKEN_LITERAL_C);
  259. } elseif ($attrs != null && !$hasContent) {
  260. $this->_output .= chr(Horde_Xml_Wbxml::GLOBAL_TOKEN_LITERAL_AC);
  261. }
  262. Horde_Xml_Wbxml::intToMBUInt32($this->_output, $i);
  263. }
  264. } else {
  265. if ($attrs == null && !$hasContent) {
  266. $this->_output .= chr($t);
  267. } elseif ($attrs == null && $hasContent) {
  268. $this->_output .= chr($t | 64);
  269. } elseif ($attrs != null && $hasContent) {
  270. $this->_output .= chr($t | 128);
  271. } elseif ($attrs != null && !$hasContent) {
  272. $this->_output .= chr($t | 192);
  273. }
  274. }
  275. if ($attrs != null && is_array($attrs) && count($attrs) > 0) {
  276. $this->writeAttributes($attrs, $cs);
  277. }
  278. }
  279. public function writeAttributes($attrs, $cs)
  280. {
  281. foreach ($attrs as $name => $value) {
  282. $this->writeAttribute($name, $value, $cs);
  283. }
  284. $this->_output .= chr(Horde_Xml_Wbxml::GLOBAL_TOKEN_END);
  285. }
  286. /**
  287. * @throws Horde_Xml_Wbxml_Exception
  288. */
  289. public function writeAttribute($name, $value, $cs)
  290. {
  291. $a = $this->_dtd->toAttribute($name);
  292. if ($a == -1) {
  293. $i = $this->_stringTable->get($name);
  294. if ($i == null) {
  295. throw new Horde_Xml_Wbxml_Exception($name . ' is not found in String Table or DTD');
  296. } else {
  297. $this->_output .= chr(Horde_Xml_Wbxml::GLOBAL_TOKEN_LITERAL);
  298. Horde_Xml_Wbxml::intToMBUInt32($this->_output, $i);
  299. }
  300. } else {
  301. $this->_output .= $a;
  302. }
  303. $i = $this->_stringTable->get($name);
  304. if ($i != null) {
  305. $this->_output .= chr(Horde_Xml_Wbxml::GLOBAL_TOKEN_STR_T);
  306. Horde_Xml_Wbxml::intToMBUInt32($this->_output, $i);
  307. } else {
  308. $this->_output .= chr(Horde_Xml_Wbxml::GLOBAL_TOKEN_STR_I);
  309. $this->writeString($value, $cs);
  310. }
  311. }
  312. public function endElement($uri, $name)
  313. {
  314. if ($this->_subParser == null) {
  315. $this->_output .= chr(Horde_Xml_Wbxml::GLOBAL_TOKEN_END);
  316. } else {
  317. $this->_subParser->endElement($uri, $name);
  318. $this->_subParserStack--;
  319. if ($this->_subParserStack == 0) {
  320. $this->_output .= chr(Horde_Xml_Wbxml::GLOBAL_TOKEN_OPAQUE);
  321. Horde_Xml_Wbxml::intToMBUInt32($this->_output,
  322. strlen($this->_subParser->getOutput()));
  323. $this->_output .= $this->_subParser->getOutput();
  324. $this->_subParser = null;
  325. }
  326. }
  327. }
  328. protected function _endElement($parser, $tag)
  329. {
  330. list($uri, $name) = $this->_splitURI($tag);
  331. $this->endElement($uri, $name);
  332. }
  333. public function changecodepage($uri)
  334. {
  335. // @todo: this is a hack!
  336. if ($this->_dtd->getVersion() == 2 && !preg_match('/1\.2$/', $uri)) {
  337. $uri .= '1.2';
  338. }
  339. if ($this->_dtd->getVersion() == 1 && !preg_match('/1\.1$/', $uri)) {
  340. $uri .= '1.1';
  341. }
  342. if ($this->_dtd->getVersion() == 0 && !preg_match('/1\.0$/', $uri)) {
  343. $uri .= '1.0';
  344. }
  345. $cp = $this->_dtd->toCodePageURI($uri);
  346. if (strlen($cp)) {
  347. $this->_dtd = $this->_dtdManager->getInstanceURI($uri);
  348. if (!$this->_dtd) {
  349. // TODO: proper error handling
  350. die('Unable to find dtd for ' . $uri);
  351. }
  352. $this->_output .= chr(Horde_Xml_Wbxml::GLOBAL_TOKEN_SWITCH_PAGE);
  353. $this->_output .= chr($cp);
  354. } else {
  355. $this->_subParser = new Horde_Xml_Wbxml_Encoder(true);
  356. $this->_subParserStack = 1;
  357. }
  358. }
  359. /**
  360. * Getter for property output.
  361. */
  362. public function getOutput()
  363. {
  364. return $this->_output;
  365. }
  366. public function getOutputSize()
  367. {
  368. return strlen($this->_output);
  369. }
  370. }