Back to index

d-push  2.0
wbxmlencoder.php
Go to the documentation of this file.
00001 <?php
00002 /***********************************************
00003 * File      :   wbxmlencoder.php
00004 * Project   :   Z-Push
00005 * Descr     :   WBXMLEncoder encodes to Wap Binary XML
00006 *
00007 * Created   :   01.10.2007
00008 *
00009 * Copyright 2007 - 2011 Zarafa Deutschland GmbH
00010 *
00011 * This program is free software: you can redistribute it and/or modify
00012 * it under the terms of the GNU Affero General Public License, version 3,
00013 * as published by the Free Software Foundation with the following additional
00014 * term according to sec. 7:
00015 *
00016 * According to sec. 7 of the GNU Affero General Public License, version 3,
00017 * the terms of the AGPL are supplemented with the following terms:
00018 *
00019 * "Zarafa" is a registered trademark of Zarafa B.V.
00020 * "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
00021 * The licensing of the Program under the AGPL does not imply a trademark license.
00022 * Therefore any rights, title and interest in our trademarks remain entirely with us.
00023 *
00024 * However, if you propagate an unmodified version of the Program you are
00025 * allowed to use the term "Z-Push" to indicate that you distribute the Program.
00026 * Furthermore you may use our trademarks where it is necessary to indicate
00027 * the intended purpose of a product or service provided you use it in accordance
00028 * with honest practices in industrial or commercial matters.
00029 * If you want to propagate modified versions of the Program under the name "Z-Push",
00030 * you may only do so if you have a written permission by Zarafa Deutschland GmbH
00031 * (to acquire a permission please contact Zarafa at trademark@zarafa.com).
00032 *
00033 * This program is distributed in the hope that it will be useful,
00034 * but WITHOUT ANY WARRANTY; without even the implied warranty of
00035 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
00036 * GNU Affero General Public License for more details.
00037 *
00038 * You should have received a copy of the GNU Affero General Public License
00039 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
00040 *
00041 * Consult LICENSE file for details
00042 ************************************************/
00043 
00044 
00045 class WBXMLEncoder extends WBXMLDefs {
00046     private $_dtd;
00047     private $_out;
00048 
00049     private $_tagcp;
00050     private $_attrcp;
00051 
00052     private $logStack = array();
00053 
00054     // We use a delayed output mechanism in which we only output a tag when it actually has something
00055     // in it. This can cause entire XML trees to disappear if they don't have output data in them; Ie
00056     // calling 'startTag' 10 times, and then 'endTag' will cause 0 bytes of output apart from the header.
00057 
00058     // Only when content() is called do we output the current stack of tags
00059 
00060     private $_stack;
00061 
00062     public function WBXMLEncoder($output) {
00063         // make sure WBXML_DEBUG is defined. It should be at this point
00064         if (!defined('WBXML_DEBUG')) define('WBXML_DEBUG', false);
00065 
00066         $this->_out = $output;
00067 
00068         $this->_tagcp = 0;
00069         $this->_attrcp = 0;
00070 
00071         // reverse-map the DTD
00072         foreach($this->dtd["namespaces"] as $nsid => $nsname) {
00073             $this->_dtd["namespaces"][$nsname] = $nsid;
00074         }
00075 
00076         foreach($this->dtd["codes"] as $cp => $value) {
00077             $this->_dtd["codes"][$cp] = array();
00078             foreach($this->dtd["codes"][$cp] as $tagid => $tagname) {
00079                 $this->_dtd["codes"][$cp][$tagname] = $tagid;
00080             }
00081         }
00082         $this->_stack = array();
00083     }
00084 
00091     public function startWBXML() {
00092         header("Content-Type: application/vnd.ms-sync.wbxml");
00093 
00094         $this->outByte(0x03); // WBXML 1.3
00095         $this->outMBUInt(0x01); // Public ID 1
00096         $this->outMBUInt(106); // UTF-8
00097         $this->outMBUInt(0x00); // string table length (0)
00098     }
00099 
00110     public function startTag($tag, $attributes = false, $nocontent = false) {
00111         $stackelem = array();
00112 
00113         if(!$nocontent) {
00114             $stackelem['tag'] = $tag;
00115             $stackelem['attributes'] = $attributes;
00116             $stackelem['nocontent'] = $nocontent;
00117             $stackelem['sent'] = false;
00118 
00119             array_push($this->_stack, $stackelem);
00120 
00121             // If 'nocontent' is specified, then apparently the user wants to force
00122             // output of an empty tag, and we therefore output the stack here
00123         } else {
00124             $this->_outputStack();
00125             $this->_startTag($tag, $attributes, $nocontent);
00126         }
00127     }
00128 
00135     public function endTag() {
00136         $stackelem = array_pop($this->_stack);
00137 
00138         // Only output end tags for items that have had a start tag sent
00139         if($stackelem['sent']) {
00140             $this->_endTag();
00141         }
00142     }
00143 
00152     public function content($content) {
00153         // We need to filter out any \0 chars because it's the string terminator in WBXML. We currently
00154         // cannot send \0 characters within the XML content anywhere.
00155         $content = str_replace("\0","",$content);
00156 
00157         if("x" . $content == "x")
00158             return;
00159         $this->_outputStack();
00160         $this->_content($content);
00161     }
00162 
00173     private function _outputStack() {
00174         for($i=0;$i<count($this->_stack);$i++) {
00175             if(!$this->_stack[$i]['sent']) {
00176                 $this->_startTag($this->_stack[$i]['tag'], $this->_stack[$i]['attributes'], $this->_stack[$i]['nocontent']);
00177                 $this->_stack[$i]['sent'] = true;
00178             }
00179         }
00180     }
00181 
00188     private function _startTag($tag, $attributes = false, $nocontent = false) {
00189         $this->logStartTag($tag, $attributes, $nocontent);
00190 
00191         $mapping = $this->getMapping($tag);
00192 
00193         if(!$mapping)
00194             return false;
00195 
00196         if($this->_tagcp != $mapping["cp"]) {
00197             $this->outSwitchPage($mapping["cp"]);
00198             $this->_tagcp = $mapping["cp"];
00199         }
00200 
00201         $code = $mapping["code"];
00202         if(isset($attributes) && is_array($attributes) && count($attributes) > 0) {
00203             $code |= 0x80;
00204         }
00205 
00206         if(!isset($nocontent) || !$nocontent)
00207             $code |= 0x40;
00208 
00209         $this->outByte($code);
00210 
00211         if($code & 0x80)
00212             $this->outAttributes($attributes);
00213     }
00214 
00221     private function _content($content) {
00222         $this->logContent($content);
00223         $this->outByte(WBXML_STR_I);
00224         $this->outTermStr($content);
00225     }
00226 
00233     private function _endTag() {
00234         $this->logEndTag();
00235         $this->outByte(WBXML_END);
00236     }
00237 
00246     private function outByte($byte) {
00247         fwrite($this->_out, chr($byte));
00248     }
00249 
00258     private function outMBUInt($uint) {
00259         while(1) {
00260             $byte = $uint & 0x7f;
00261             $uint = $uint >> 7;
00262             if($uint == 0) {
00263                 $this->outByte($byte);
00264                 break;
00265             } else {
00266                 $this->outByte($byte | 0x80);
00267             }
00268         }
00269     }
00270 
00279     private function outTermStr($content) {
00280         fwrite($this->_out, $content);
00281         fwrite($this->_out, chr(0));
00282     }
00283 
00294     private function outAttributes() {
00295         $this->outByte(WBXML_END);
00296     }
00297 
00306     private function outSwitchPage($page) {
00307         $this->outByte(WBXML_SWITCH_PAGE);
00308         $this->outByte($page);
00309     }
00310 
00319     private function getMapping($tag) {
00320         $mapping = array();
00321 
00322         $split = $this->splitTag($tag);
00323 
00324         if(isset($split["ns"])) {
00325             $cp = $this->_dtd["namespaces"][$split["ns"]];
00326         }
00327         else {
00328             $cp = 0;
00329         }
00330 
00331         $code = $this->_dtd["codes"][$cp][$split["tag"]];
00332 
00333         $mapping["cp"] = $cp;
00334         $mapping["code"] = $code;
00335 
00336         return $mapping;
00337     }
00338 
00347     private function splitTag($fulltag) {
00348         $ns = false;
00349         $pos = strpos($fulltag, chr(58)); // chr(58) == ':'
00350 
00351         if($pos) {
00352             $ns = substr($fulltag, 0, $pos);
00353             $tag = substr($fulltag, $pos+1);
00354         }
00355         else {
00356             $tag = $fulltag;
00357         }
00358 
00359         $ret = array();
00360         if($ns)
00361             $ret["ns"] = $ns;
00362         $ret["tag"] = $tag;
00363 
00364         return $ret;
00365     }
00366 
00377     private function logStartTag($tag, $attr, $nocontent) {
00378         if(!WBXML_DEBUG)
00379             return;
00380 
00381         $spaces = str_repeat(" ", count($this->logStack));
00382         if($nocontent)
00383             ZLog::Write(LOGLEVEL_WBXML,"O " . $spaces . " <$tag/>");
00384         else {
00385             array_push($this->logStack, $tag);
00386             ZLog::Write(LOGLEVEL_WBXML,"O " . $spaces . " <$tag>");
00387         }
00388     }
00389 
00396     private function logEndTag() {
00397         if(!WBXML_DEBUG)
00398             return;
00399 
00400         $spaces = str_repeat(" ", count($this->logStack));
00401         $tag = array_pop($this->logStack);
00402         ZLog::Write(LOGLEVEL_WBXML,"O " . $spaces . "</$tag>");
00403     }
00404 
00413     private function logContent($content) {
00414         if(!WBXML_DEBUG)
00415             return;
00416 
00417         $spaces = str_repeat(" ", count($this->logStack));
00418         ZLog::Write(LOGLEVEL_WBXML,"O " . $spaces . $content);
00419     }
00420 }
00421 
00422 ?>