PPNode_Hash_Tree.php 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  1. <?php
  2. /**
  3. * This program is free software; you can redistribute it and/or modify
  4. * it under the terms of the GNU General Public License as published by
  5. * the Free Software Foundation; either version 2 of the License, or
  6. * (at your option) any later version.
  7. *
  8. * This program is distributed in the hope that it will be useful,
  9. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. * GNU General Public License for more details.
  12. *
  13. * You should have received a copy of the GNU General Public License along
  14. * with this program; if not, write to the Free Software Foundation, Inc.,
  15. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  16. * http://www.gnu.org/copyleft/gpl.html
  17. *
  18. * @file
  19. * @ingroup Parser
  20. */
  21. /**
  22. * @ingroup Parser
  23. */
  24. // phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
  25. class PPNode_Hash_Tree implements PPNode {
  26. public $name;
  27. /**
  28. * The store array for children of this node. It is "raw" in the sense that
  29. * nodes are two-element arrays ("descriptors") rather than PPNode_Hash_*
  30. * objects.
  31. */
  32. private $rawChildren;
  33. /**
  34. * The store array for the siblings of this node, including this node itself.
  35. */
  36. private $store;
  37. /**
  38. * The index into $this->store which contains the descriptor of this node.
  39. */
  40. private $index;
  41. /**
  42. * The offset of the name within descriptors, used in some places for
  43. * readability.
  44. */
  45. const NAME = 0;
  46. /**
  47. * The offset of the child list within descriptors, used in some places for
  48. * readability.
  49. */
  50. const CHILDREN = 1;
  51. /**
  52. * Construct an object using the data from $store[$index]. The rest of the
  53. * store array can be accessed via getNextSibling().
  54. *
  55. * @param array $store
  56. * @param int $index
  57. */
  58. public function __construct( array $store, $index ) {
  59. $this->store = $store;
  60. $this->index = $index;
  61. list( $this->name, $this->rawChildren ) = $this->store[$index];
  62. }
  63. /**
  64. * Construct an appropriate PPNode_Hash_* object with a class that depends
  65. * on what is at the relevant store index.
  66. *
  67. * @param array $store
  68. * @param int $index
  69. * @return PPNode_Hash_Tree|PPNode_Hash_Attr|PPNode_Hash_Text|false
  70. * @throws MWException
  71. */
  72. public static function factory( array $store, $index ) {
  73. if ( !isset( $store[$index] ) ) {
  74. return false;
  75. }
  76. $descriptor = $store[$index];
  77. if ( is_string( $descriptor ) ) {
  78. $class = PPNode_Hash_Text::class;
  79. } elseif ( is_array( $descriptor ) ) {
  80. if ( $descriptor[self::NAME][0] === '@' ) {
  81. $class = PPNode_Hash_Attr::class;
  82. } else {
  83. $class = self::class;
  84. }
  85. } else {
  86. throw new MWException( __METHOD__ . ': invalid node descriptor' );
  87. }
  88. return new $class( $store, $index );
  89. }
  90. /**
  91. * Convert a node to XML, for debugging
  92. * @return string
  93. */
  94. public function __toString() {
  95. $inner = '';
  96. $attribs = '';
  97. for ( $node = $this->getFirstChild(); $node; $node = $node->getNextSibling() ) {
  98. if ( $node instanceof PPNode_Hash_Attr ) {
  99. $attribs .= ' ' . $node->name . '="' . htmlspecialchars( $node->value ) . '"';
  100. } else {
  101. $inner .= $node->__toString();
  102. }
  103. }
  104. if ( $inner === '' ) {
  105. return "<{$this->name}$attribs/>";
  106. } else {
  107. return "<{$this->name}$attribs>$inner</{$this->name}>";
  108. }
  109. }
  110. /**
  111. * @return PPNode_Hash_Array
  112. */
  113. public function getChildren() {
  114. $children = [];
  115. foreach ( $this->rawChildren as $i => $child ) {
  116. $children[] = self::factory( $this->rawChildren, $i );
  117. }
  118. return new PPNode_Hash_Array( $children );
  119. }
  120. /**
  121. * Get the first child, or false if there is none. Note that this will
  122. * return a temporary proxy object: different instances will be returned
  123. * if this is called more than once on the same node.
  124. *
  125. * @return PPNode_Hash_Tree|PPNode_Hash_Attr|PPNode_Hash_Text|false
  126. */
  127. public function getFirstChild() {
  128. if ( !isset( $this->rawChildren[0] ) ) {
  129. return false;
  130. } else {
  131. return self::factory( $this->rawChildren, 0 );
  132. }
  133. }
  134. /**
  135. * Get the next sibling, or false if there is none. Note that this will
  136. * return a temporary proxy object: different instances will be returned
  137. * if this is called more than once on the same node.
  138. *
  139. * @return PPNode_Hash_Tree|PPNode_Hash_Attr|PPNode_Hash_Text|false
  140. */
  141. public function getNextSibling() {
  142. return self::factory( $this->store, $this->index + 1 );
  143. }
  144. /**
  145. * Get an array of the children with a given node name
  146. *
  147. * @param string $name
  148. * @return PPNode_Hash_Array
  149. */
  150. public function getChildrenOfType( $name ) {
  151. $children = [];
  152. foreach ( $this->rawChildren as $i => $child ) {
  153. if ( is_array( $child ) && $child[self::NAME] === $name ) {
  154. $children[] = self::factory( $this->rawChildren, $i );
  155. }
  156. }
  157. return new PPNode_Hash_Array( $children );
  158. }
  159. /**
  160. * Get the raw child array. For internal use.
  161. * @return array
  162. */
  163. public function getRawChildren() {
  164. return $this->rawChildren;
  165. }
  166. /**
  167. * @return bool
  168. */
  169. public function getLength() {
  170. return false;
  171. }
  172. /**
  173. * @param int $i
  174. * @return bool
  175. */
  176. public function item( $i ) {
  177. return false;
  178. }
  179. /**
  180. * @return string
  181. */
  182. public function getName() {
  183. return $this->name;
  184. }
  185. /**
  186. * Split a "<part>" node into an associative array containing:
  187. * - name PPNode name
  188. * - index String index
  189. * - value PPNode value
  190. *
  191. * @throws MWException
  192. * @return array
  193. */
  194. public function splitArg() {
  195. return self::splitRawArg( $this->rawChildren );
  196. }
  197. /**
  198. * Like splitArg() but for a raw child array. For internal use only.
  199. * @param array $children
  200. * @return array
  201. */
  202. public static function splitRawArg( array $children ) {
  203. $bits = [];
  204. foreach ( $children as $i => $child ) {
  205. if ( !is_array( $child ) ) {
  206. continue;
  207. }
  208. if ( $child[self::NAME] === 'name' ) {
  209. $bits['name'] = new self( $children, $i );
  210. if ( isset( $child[self::CHILDREN][0][self::NAME] )
  211. && $child[self::CHILDREN][0][self::NAME] === '@index'
  212. ) {
  213. $bits['index'] = $child[self::CHILDREN][0][self::CHILDREN][0];
  214. }
  215. } elseif ( $child[self::NAME] === 'value' ) {
  216. $bits['value'] = new self( $children, $i );
  217. }
  218. }
  219. if ( !isset( $bits['name'] ) ) {
  220. throw new MWException( 'Invalid brace node passed to ' . __METHOD__ );
  221. }
  222. if ( !isset( $bits['index'] ) ) {
  223. $bits['index'] = '';
  224. }
  225. return $bits;
  226. }
  227. /**
  228. * Split an "<ext>" node into an associative array containing name, attr, inner and close
  229. * All values in the resulting array are PPNodes. Inner and close are optional.
  230. *
  231. * @throws MWException
  232. * @return array
  233. */
  234. public function splitExt() {
  235. return self::splitRawExt( $this->rawChildren );
  236. }
  237. /**
  238. * Like splitExt() but for a raw child array. For internal use only.
  239. * @param array $children
  240. * @return array
  241. */
  242. public static function splitRawExt( array $children ) {
  243. $bits = [];
  244. foreach ( $children as $i => $child ) {
  245. if ( !is_array( $child ) ) {
  246. continue;
  247. }
  248. switch ( $child[self::NAME] ) {
  249. case 'name':
  250. $bits['name'] = new self( $children, $i );
  251. break;
  252. case 'attr':
  253. $bits['attr'] = new self( $children, $i );
  254. break;
  255. case 'inner':
  256. $bits['inner'] = new self( $children, $i );
  257. break;
  258. case 'close':
  259. $bits['close'] = new self( $children, $i );
  260. break;
  261. }
  262. }
  263. if ( !isset( $bits['name'] ) ) {
  264. throw new MWException( 'Invalid ext node passed to ' . __METHOD__ );
  265. }
  266. return $bits;
  267. }
  268. /**
  269. * Split an "<h>" node
  270. *
  271. * @throws MWException
  272. * @return array
  273. */
  274. public function splitHeading() {
  275. if ( $this->name !== 'h' ) {
  276. throw new MWException( 'Invalid h node passed to ' . __METHOD__ );
  277. }
  278. return self::splitRawHeading( $this->rawChildren );
  279. }
  280. /**
  281. * Like splitHeading() but for a raw child array. For internal use only.
  282. * @param array $children
  283. * @return array
  284. */
  285. public static function splitRawHeading( array $children ) {
  286. $bits = [];
  287. foreach ( $children as $i => $child ) {
  288. if ( !is_array( $child ) ) {
  289. continue;
  290. }
  291. if ( $child[self::NAME] === '@i' ) {
  292. $bits['i'] = $child[self::CHILDREN][0];
  293. } elseif ( $child[self::NAME] === '@level' ) {
  294. $bits['level'] = $child[self::CHILDREN][0];
  295. }
  296. }
  297. if ( !isset( $bits['i'] ) ) {
  298. throw new MWException( 'Invalid h node passed to ' . __METHOD__ );
  299. }
  300. return $bits;
  301. }
  302. /**
  303. * Split a "<template>" or "<tplarg>" node
  304. *
  305. * @throws MWException
  306. * @return array
  307. */
  308. public function splitTemplate() {
  309. return self::splitRawTemplate( $this->rawChildren );
  310. }
  311. /**
  312. * Like splitTemplate() but for a raw child array. For internal use only.
  313. * @param array $children
  314. * @return array
  315. */
  316. public static function splitRawTemplate( array $children ) {
  317. $parts = [];
  318. $bits = [ 'lineStart' => '' ];
  319. foreach ( $children as $i => $child ) {
  320. if ( !is_array( $child ) ) {
  321. continue;
  322. }
  323. switch ( $child[self::NAME] ) {
  324. case 'title':
  325. $bits['title'] = new self( $children, $i );
  326. break;
  327. case 'part':
  328. $parts[] = new self( $children, $i );
  329. break;
  330. case '@lineStart':
  331. $bits['lineStart'] = '1';
  332. break;
  333. }
  334. }
  335. if ( !isset( $bits['title'] ) ) {
  336. throw new MWException( 'Invalid node passed to ' . __METHOD__ );
  337. }
  338. $bits['parts'] = new PPNode_Hash_Array( $parts );
  339. return $bits;
  340. }
  341. }