PageRenderTime 37ms CodeModel.GetById 8ms RepoModel.GetById 0ms app.codeStats 0ms

/extensions/WikiSync/WikiSyncQXML.php

https://github.com/ChuguluGames/mediawiki-svn
PHP | 352 lines | 225 code | 17 blank | 110 comment | 53 complexity | d6158fbceb193bf81d7e272513ead2ac MD5 | raw file
  1. <?php
  2. /**
  3. * ***** BEGIN LICENSE BLOCK *****
  4. * This file is part of WikiSync.
  5. *
  6. * WikiSync is free software; you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation; either version 2 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * WikiSync is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with WikiSync; if not, write to the Free Software
  18. * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  19. *
  20. * ***** END LICENSE BLOCK *****
  21. *
  22. * WikiSync allows an AJAX-based synchronization of revisions and files between
  23. * global wiki site and it's local mirror.
  24. *
  25. * To activate this extension :
  26. * * Create a new directory named WikiSync into the directory "extensions" of MediaWiki.
  27. * * Place the files from the extension archive there.
  28. * * Add this line at the end of your LocalSettings.php file :
  29. * require_once "$IP/extensions/WikiSync/WikiSync.php";
  30. *
  31. * @version 0.3.2
  32. * @link http://www.mediawiki.org/wiki/Extension:WikiSync
  33. * @author Dmitriy Sintsov <questpc@rambler.ru>
  34. * @addtogroup Extensions
  35. */
  36. if ( !defined( 'MEDIAWIKI' ) ) {
  37. die( "This file is a part of MediaWiki extension.\n" );
  38. }
  39. /**
  40. * render output data v0.2
  41. */
  42. class _QXML {
  43. /**
  44. * The sample stucture of $tag array is like this:
  45. * array( '__tag'=>'td', 'class'=>'myclass', 0=>'text before li', 1=>array( '__tag'=>'li', 0=>'text inside li' ), 2=>'text after li' )
  46. *
  47. * '__tag' key specifies node name
  48. * associative keys specify node attributes
  49. * numeric keys specify inner nodes of node
  50. *
  51. * both tagged (with '__tag' attribute) and tagless lists are supported
  52. *
  53. * tagless lists cannot have associative keys (node attributes)
  54. *
  55. */
  56. # next tags ignore text padding on opening (safe indent)
  57. static $inner_indent_tags = array( 'table', 'tbody', 'tr' );
  58. # next tags ignore text padding on closing (safe indent)
  59. static $outer_indent_tags = array( 'table', 'tbody', 'tr', 'th', 'td', 'p' );
  60. # next tags can be self-closed, according to
  61. # http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd
  62. # otherwise, simple switching of Content-Type / DOCTYPE may make generated tree invalid
  63. # see also
  64. # http://stackoverflow.com/questions/97522/what-are-all-the-valid-self-closing-tags-in-xhtml-as-implemented-by-the-major-br
  65. static $self_closed_tags = array( 'base', 'meta', 'link', 'hr', 'br', 'basefont', 'param', 'img', 'area', 'input', 'isindex', 'col' );
  66. # indent types
  67. # initial caller
  68. const TOP_NODE = -1;
  69. # text node
  70. const NODE_TEXT = 0;
  71. # tag without indentation
  72. const NO_INDENT = 1;
  73. # tag with outer indent
  74. const OUTER_INDENT = 2;
  75. # tag with inner indent
  76. const INNER_INDENT = 3;
  77. # used for detection of indent in non-tagged list of nodes
  78. static $prev_indent_type;
  79. /**
  80. * used for erroneous $tag content reporting
  81. */
  82. static function getTagDump( &$tag ) {
  83. ob_start();
  84. var_dump( $tag );
  85. $tagdump = ob_get_contents();
  86. ob_end_clean();
  87. return $tagdump;
  88. }
  89. /**
  90. * recursive tags generator
  91. * @param $tag nested associative array of tag nodes (see an example above)
  92. * @param $indent level of indentation (negative to completely suppress indent)
  93. */
  94. static function toText( &$tag, $indent = -1 ) {
  95. self::$prev_indent_type = self::TOP_NODE;
  96. return self::_toText( $tag, $indent, self::TOP_NODE );
  97. }
  98. /**
  99. * recursive tags generator
  100. * @param $tag nested associative array of tag nodes (see an example above)
  101. * @param $indent level of indentation (negative to completely suppress indent)
  102. * @param $caller_indent_type indent type of lower level (caller)
  103. */
  104. static private function _toText( &$tag, $indent = -1, $caller_indent_type ) {
  105. $tag_open =
  106. $tag_close = '';
  107. # $tag_val is a recusively concatenated inner content of tag
  108. # by default, null value indicates a self-closing tag
  109. $tag_val = null;
  110. # current and nested indent levels
  111. $nested_indent = $indent;
  112. if ( is_array( $tag ) ) {
  113. ksort( $tag );
  114. $current_indent_type = self::NODE_TEXT;
  115. if ( isset( $tag['__tag'] ) ) {
  116. $tag_name = strtolower( $tag['__tag'] );
  117. $current_indent_type = self::NO_INDENT;
  118. if ( $indent >= 0 ) {
  119. # inner has predecense (outer contains inner inside)
  120. if ( in_array( $tag_name, self::$inner_indent_tags ) ) {
  121. $current_indent_type = self::INNER_INDENT;
  122. # also indent every indented tag that is inside
  123. $nested_indent++;
  124. } elseif ( in_array( $tag_name, self::$outer_indent_tags ) ) {
  125. $current_indent_type = self::OUTER_INDENT;
  126. # also indent every indented tag that is inside
  127. $nested_indent++;
  128. }
  129. }
  130. # list inside tag
  131. $tag_open .= '<' . $tag['__tag'];
  132. foreach ( $tag as $attr_key => &$attr_val ) {
  133. if ( is_int( $attr_key ) ) {
  134. # numeric node values are going into $tag_val
  135. if ( $tag_val === null ) {
  136. $tag_val = '';
  137. }
  138. if ( is_array( $attr_val ) ) {
  139. # recursive list
  140. $tag_val .= self::_toText( $attr_val, $nested_indent, $current_indent_type );
  141. } else {
  142. # text node inside tag
  143. self::$prev_indent_type = self::NODE_TEXT;
  144. # use the following format for the debug printouts: "!$attr_val!"
  145. $tag_val .= $attr_val;
  146. }
  147. } else {
  148. # string keys are for tag attributes
  149. if ( substr( $attr_key, 0, 2 ) !== '__' ) {
  150. # include only non-reserved attributes
  151. $tag_open .= " $attr_key=\"$attr_val\"";
  152. }
  153. }
  154. }
  155. if ( $tag_val === null && !in_array( $tag_name, self::$self_closed_tags ) ) {
  156. $tag_val = '';
  157. }
  158. if ( $tag_val === null ) {
  159. $tag_open .= " />";
  160. } else {
  161. $tag_open .= '>';
  162. $tag_close .= '</' . $tag['__tag'] . '>';
  163. }
  164. } else {
  165. # tagless list
  166. $tag_val = '';
  167. foreach ( $tag as $attr_key => &$attr_val ) {
  168. if ( is_int( $attr_key ) ) {
  169. if ( is_array( $attr_val ) ) {
  170. # recursive tags
  171. $tag_val .= self::_toText( $attr_val, $indent, $caller_indent_type );
  172. } else {
  173. # text
  174. if ( self::$prev_indent_type === self::INNER_INDENT ) {
  175. $attr_val = "\n$attr_val";
  176. }
  177. $caller_indent_type = self::NODE_TEXT;
  178. # use for debug printout
  179. # $tag_val .= '~' . $attr_val . self::$prev_indent_type . '~';
  180. $tag_val .= $attr_val;
  181. }
  182. } else {
  183. $tag_val = "Invalid argument: tagless list cannot have tag attribute values in key=$attr_key, " . self::getTagDump( $tag );
  184. }
  185. }
  186. return $tag_val;
  187. }
  188. } else {
  189. # just a text; use "?$tag?" for debug printout
  190. return $tag;
  191. }
  192. # uncomment for the debug printout
  193. # $tag_close .= "($current_indent_type,$caller_indent_type," . self::$prev_indent_type . ")";
  194. if ( $current_indent_type === self::INNER_INDENT ) {
  195. $tag_open = str_repeat( "\t", $indent ) . "$tag_open\n";
  196. $tag_close = str_repeat( "\t", $indent ) . $tag_close;
  197. if ( in_array( $caller_indent_type, array( self::TOP_NODE, self::INNER_INDENT ) ) ) {
  198. $tag_close = "$tag_close\n";
  199. if ( self::$prev_indent_type === self::NODE_TEXT ) {
  200. $tag_open = "\n$tag_open";
  201. }
  202. } else {
  203. $tag_open = "\n$tag_open";
  204. }
  205. } elseif ( $current_indent_type === self::OUTER_INDENT ) {
  206. if ( $caller_indent_type === self::INNER_INDENT ) {
  207. $tag_open = str_repeat( "\t", $indent ) . $tag_open;
  208. $tag_close = "$tag_close\n";
  209. }
  210. if ( self::$prev_indent_type === self::INNER_INDENT ) {
  211. $tag_close = "\n" . str_repeat( "\t", $indent ) . $tag_close;
  212. }
  213. } elseif ( $current_indent_type === self::NO_INDENT ) {
  214. if ( $indent >= 0 && $caller_indent_type === self::INNER_INDENT ) {
  215. $tag_close .= "\n";
  216. if ( self::$prev_indent_type === self::INNER_INDENT ) {
  217. $tag_close = "\n$tag_close";
  218. }
  219. }
  220. } elseif ( $current_indent_type === self::NODE_TEXT ) {
  221. if ( self::$prev_indent_type === self::INNER_INDENT ) {
  222. $tag_close = "\n$tag_close";
  223. }
  224. }
  225. # we support __end only for compatibility to older versions
  226. # it's deprecated and the usage is discouraged
  227. if ( isset( $tag['__end'] ) ) {
  228. $end = $tag['__end'];
  229. if ( $end === "\n" ) {
  230. if ( substr( $tag_close, -1 ) === "\n" ) {
  231. $end = '';
  232. }
  233. }
  234. $tag_close .= $end;
  235. }
  236. self::$prev_indent_type = $current_indent_type;
  237. return $tag_open . $tag_val . $tag_close;
  238. }
  239. # creates one "htmlobject" row of the table
  240. # elements of $row can be either a string/number value of cell or an array( "count"=>colspannum, "attribute"=>value, 0=>html_inside_tag )
  241. # attribute maps can be like this: ("name"=>0, "count"=>colspan" )
  242. static function newRow( $row, $rowattrs = "", $celltag = "td", $attribute_maps = null ) {
  243. $result = "";
  244. if ( count( $row ) > 0 ) {
  245. foreach ( $row as &$cell ) {
  246. if ( !is_array( $cell ) ) {
  247. $cell = array( 0 => $cell );
  248. }
  249. $cell[ '__tag' ] = $celltag;
  250. if ( is_array( $attribute_maps ) ) {
  251. # converts ("count"=>3) to ("colspan"=>3) in table headers - don't use frequently
  252. foreach ( $attribute_maps as $key => $val ) {
  253. if ( isset( $cell[$key] ) ) {
  254. $cell[ $val ] = $cell[ $key ];
  255. unset( $cell[ $key ] );
  256. }
  257. }
  258. }
  259. }
  260. $result = array( '__tag' => 'tr', 0 => $row );
  261. if ( is_array( $rowattrs ) ) {
  262. $result = array_merge( $rowattrs, $result );
  263. } elseif ( $rowattrs !== "" ) {
  264. $result[0][] = __METHOD__ . ':invalid rowattrs supplied';
  265. }
  266. }
  267. return $result;
  268. }
  269. # add row to the table
  270. static function addRow( &$table, $row, $rowattrs = "", $celltag = "td", $attribute_maps = null ) {
  271. $table[] = self::newRow( $row, $rowattrs, $celltag, $attribute_maps );
  272. }
  273. # add column to the table
  274. static function addColumn( &$table, $column, $rowattrs = "", $celltag = "td", $attribute_maps = null ) {
  275. if ( count( $column ) > 0 ) {
  276. $row = 0;
  277. foreach ( $column as &$cell ) {
  278. if ( !is_array( $cell ) ) {
  279. $cell = array( 0 => $cell );
  280. }
  281. $cell[ '__tag' ] = $celltag;
  282. if ( is_array( $attribute_maps ) ) {
  283. # converts ("count"=>3) to ("rowspan"=>3) in table headers - don't use frequently
  284. foreach ( $attribute_maps as $key => $val ) {
  285. if ( isset( $cell[$key] ) ) {
  286. $cell[ $val ] = $cell[ $key ];
  287. unset( $cell[ $key ] );
  288. }
  289. }
  290. }
  291. if ( is_array( $rowattrs ) ) {
  292. $cell = array_merge( $rowattrs, $cell );
  293. } elseif ( $rowattrs !== "" ) {
  294. $cell[ 0 ] = __METHOD__ . ':invalid rowattrs supplied';
  295. }
  296. if ( !isset( $table[$row] ) ) {
  297. $table[ $row ] = array( '__tag' => 'tr' );
  298. }
  299. $table[ $row ][] = $cell;
  300. if ( isset( $cell['rowspan'] ) ) {
  301. $row += intval( $cell[ 'rowspan' ] );
  302. } else {
  303. $row++;
  304. }
  305. }
  306. $result = array( '__tag' => 'tr', 0 => $column );
  307. }
  308. }
  309. static function displayRow( $row, $rowattrs = "", $celltag = "td", $attribute_maps = null ) {
  310. return self::toText( self::newRow( $row, $rowattrs, $celltag, $attribute_maps ) );
  311. }
  312. // use newRow() or addColumn() to add resulting row/column to the table
  313. // if you want to use the resulting row with toText(), don't forget to apply attrs=array('__tag'=>'td')
  314. static function applyAttrsToRow( &$row, $attrs ) {
  315. if ( is_array( $attrs ) && count( $attrs > 0 ) ) {
  316. foreach ( $row as &$cell ) {
  317. if ( !is_array( $cell ) ) {
  318. $cell = array_merge( $attrs, array( $cell ) );
  319. } else {
  320. foreach ( $attrs as $attr_key => $attr_val ) {
  321. if ( !isset( $cell[$attr_key] ) ) {
  322. $cell[ $attr_key ] = $attr_val;
  323. }
  324. }
  325. }
  326. }
  327. }
  328. }
  329. static function entities( $s ) {
  330. return htmlentities( $s, ENT_COMPAT, 'UTF-8' );
  331. }
  332. static function specialchars( $s ) {
  333. return htmlspecialchars( $s, ENT_COMPAT, 'UTF-8' );
  334. }
  335. } /* end of _QXML class */