Show "block" link in Special:Recentchanges for logged in users, too, if
[lhc/web/wiklou.git] / includes / ParserXML.php
1 <?php
2 require_once ( "Parser.php" ) ;
3
4 /**
5 * This should one day become the XML->(X)HTML parser
6 * Based on work by Jan Hidders and Magnus Manske
7 * To use, set
8 * $wgUseXMLparser = true ;
9 * $wgEnableParserCache = false ;
10 * $wgWiki2xml to the path and executable of the command line version (cli)
11 * in LocalSettings.php
12 * @package MediaWiki
13 * @subpackage Experimental
14 */
15
16 /**
17 * the base class for an element
18 */
19 class element {
20 var $name = '';
21 var $attrs = array();
22 var $children = array();
23
24 /**
25 * This finds the ATTRS element and returns the ATTR sub-children as a single string
26 */
27 function getSourceAttrs ()
28 {
29 $ret = "" ;
30 foreach ($this->children as $child)
31 {
32 if ( !is_string($child) AND $child->name == "ATTRS" )
33 {
34 $ret = $child->makeXHTML ( $parser );
35 }
36 }
37 return $ret ;
38 }
39
40 /**
41 * This collects the ATTR thingies for getSourceAttrs()
42 */
43 function getTheseAttrs ()
44 {
45 $ret = array() ;
46 foreach ($this->children as $child)
47 {
48 if ( !is_string($child) AND $child->name == "ATTR" )
49 {
50 $ret[] = $child->attrs["NAME"] . "='" . $child->children[0] . "'" ;
51 }
52 }
53 return implode ( " " , $ret ) ;
54 }
55
56 function fixLinkTails ( &$parser , $key )
57 {
58 $k2 = $key + 1 ;
59 if ( !isset ( $this->children[$k2] ) ) return ;
60 if ( !is_string ( $this->children[$k2]) ) return ;
61 if ( is_string ( $this->children[$key]) ) return ;
62 if ( $this->children[$key]->name != "LINK" ) return ;
63
64 $n = $this->children[$k2] ;
65 $s = "" ;
66 while ( $n != "" AND
67 ( ( $n[0] >= 'a' AND $n[0] <= 'z' ) OR
68 $n[0] == 'ä' OR $n[0] == 'ö' OR
69 $n[0] == 'ü' OR $n[0] == 'ß' ) )
70 {
71 $s .= $n[0] ;
72 $n = substr ( $n , 1 ) ;
73 }
74 $this->children[$k2] = $n ;
75
76 if ( count ( $this->children[$key]->children ) > 1 )
77 {
78 $kl = array_keys ( $this->children[$key]->children ) ;
79 $kl = array_pop ( $kl ) ;
80 $this->children[$key]->children[$kl]->children[] = $s ;
81 }
82 else
83 {
84 $e = new element ;
85 $e->name = "LINKOPTION" ;
86 $t = $this->children[$key]->sub_makeXHTML ( $parser ) ;
87 $e->children[] = trim ( $t ) . $s ;
88 $this->children[$key]->children[] = $e ;
89 }
90 }
91
92 /**
93 * This function generates the XHTML for the entire subtree
94 */
95 function sub_makeXHTML ( &$parser , $tag = "" , $attr = "" )
96 {
97 $ret = "" ;
98
99 $attr2 = $this->getSourceAttrs () ;
100 if ( $attr != "" AND $attr2 != "" ) $attr .= " " ;
101 $attr .= $attr2 ;
102
103 if ( $tag != "" )
104 {
105 $ret .= "<" . $tag ;
106 if ( $attr != "" ) $ret .= " " . $attr ;
107 $ret .= ">" ;
108 }
109
110 # THIS SHOULD BE DONE IN THE WIKI2XML-PARSER INSTEAD
111 # foreach ( array_keys ( $this->children ) AS $x )
112 # $this->fixLinkTails ( $parser , $x ) ;
113
114 foreach ($this->children as $key => $child) {
115 if ( is_string($child) ) {
116 $ret .= $child ;
117 } else if ( $child->name != "ATTRS" ) {
118 $ret .= $child->makeXHTML ( $parser );
119 }
120 }
121 if ( $tag != "" )
122 $ret .= "</" . $tag . ">\n" ;
123 return $ret ;
124 }
125
126 /**
127 * Link functions
128 */
129 function createInternalLink ( &$parser , $target , $display_title , $options )
130 {
131 global $wgUser ;
132 $skin = $wgUser->getSkin() ;
133 $tp = explode ( ":" , $target ) ; # tp = target parts
134 $title = "" ; # The plain title
135 $language = "" ; # The language/meta/etc. part
136 $namespace = "" ; # The namespace, if any
137 $subtarget = "" ; # The '#' thingy
138
139
140 $nt = Title::newFromText ( $target ) ;
141 $fl = strtoupper ( $this->attrs["FORCEDLINK"] ) == "YES" ;
142
143 if ( $fl || count ( $tp ) == 1 ) $title = $target ; # Plain and simple case
144 else # There's stuff missing here...
145 {
146 if ( $nt->getNamespace() == NS_IMAGE )
147 {
148 $options[] = $display_title ;
149 return $skin->makeImageLinkObj ( $nt , implode ( "|" , $options ) ) ;
150 }
151 else $title = $target ; # Default
152 }
153
154 if ( $language != "" ) # External link within the WikiMedia project
155 {
156 return "{language link}" ;
157 }
158 else if ( $namespace != "" ) # Link to another namespace, check for image/media stuff
159 {
160 return "{namespace link}" ;
161 }
162 else
163 {
164 return $skin->makeLink ( $target , $display_title ) ;
165 }
166 }
167
168 function makeInternalLink ( &$parser )
169 {
170 $target = "" ;
171 $option = array () ;
172 foreach ($this->children as $child) {
173 if ( is_string($child) ) {
174 # This shouldn't be the case!
175 } else {
176 if ( $child->name == "LINKTARGET" )
177 $target = trim ( $child->makeXHTML ( $parser ) ) ;
178 else
179 $option[] = trim ( $child->makeXHTML ( $parser ) ) ;
180 }
181 }
182
183 if ( count ( $option ) == 0 ) $option[] = $target ; # Create dummy display title
184 $display_title = array_pop ( $option ) ;
185 return $this->createInternalLink ( $parser , $target , $display_title , $option ) ;
186 }
187
188 function getTemplateXHTML ( $title , $parts , &$parser ) {
189 global $wgLang , $wgUser ;
190 $skin = $wgUser->getSkin() ;
191 $ot = $title ; # Original title
192 if ( count ( explode ( ":" , $title ) ) == 1 )
193 $title = $wgLang->getNsText ( NS_TEMPLATE ) . ":" . $title ;
194 $nt = Title::newFromText ( $title ) ;
195 $id = $nt->getArticleID() ;
196 if ( $id == 0 ) { # No/non-existing page
197 return $skin->makeBrokenLink ( $title , $ot ) ;
198 }
199
200 $a = 0 ;
201 $tv = array () ; # Template variables
202 foreach ( $parts AS $part ) {
203 $a++ ;
204 $x = explode ( "=" , $part , 2 ) ;
205 if ( count ( $x ) == 1 ) $key = "{$a}" ;
206 else $key = $x[0] ;
207 $value = array_pop ( $x ) ;
208 $tv[$key] = $value ;
209 }
210 $art = new Article ( $nt ) ;
211 $text = $art->getContent ( false ) ;
212 $parser->plain_parse ( $text , true , $tv ) ;
213
214 return $text ;
215 }
216
217 /**
218 * This function actually converts wikiXML into XHTML tags
219 */
220 function makeXHTML ( &$parser )
221 {
222 $ret = "" ;
223 $n = $this->name ; # Shortcut
224
225 if ( $n == "EXTENSION" ) # Fix allowed HTML
226 {
227 $old_n = $n ;
228 $ext = strtoupper ( $this->attrs["NAME"] ) ;
229 if ( $ext == "B" || $ext == "STRONG" ) $n = "BOLD" ;
230 else if ( $ext == "I" || $ext == "EM" ) $n = "ITALICS" ;
231 else if ( $ext == "U" ) $n = "UNDERLINED" ; # Hey, virtual wiki tag! ;-)
232 else if ( $ext == "S" ) $n = "STRIKE" ;
233 else if ( $ext == "P" ) $n = "PARAGRAPH" ;
234 else if ( $ext == "TABLE" ) $n = "TABLE" ;
235 else if ( $ext == "TR" ) $n = "TABLEROW" ;
236 else if ( $ext == "TD" ) $n = "TABLECELL" ;
237 else if ( $ext == "TH" ) $n = "TABLEHEAD" ;
238 else if ( $ext == "CAPTION" ) $n = "CAPTION" ;
239 else if ( $ext == "NOWIKI" ) $n = "NOWIKI" ;
240 if ( $n != $old_n ) unset ( $this->attrs["NAME"] ) ; # Cleanup
241 else if ( $parser->nowiki > 0 ) $n = "" ; # No "real" wiki tags allowed in nowiki section
242 }
243
244 if ( $n == "ARTICLE" )
245 $ret .= $this->sub_makeXHTML ( $parser ) ;
246 else if ( $n == "HEADING" )
247 $ret .= $this->sub_makeXHTML ( $parser , "h" . $this->attrs["LEVEL"] ) ;
248 else if ( $n == "PARAGRAPH" )
249 $ret .= $this->sub_makeXHTML ( $parser , "p" ) ;
250 else if ( $n == "BOLD" )
251 $ret .= $this->sub_makeXHTML ( $parser , "strong" ) ;
252 else if ( $n == "ITALICS" )
253 $ret .= $this->sub_makeXHTML ( $parser , "em" ) ;
254
255 # These don't exist as wiki markup
256 else if ( $n == "UNDERLINED" )
257 $ret .= $this->sub_makeXHTML ( $parser , "u" ) ;
258 else if ( $n == "STRIKE" )
259 $ret .= $this->sub_makeXHTML ( $parser , "strike" ) ;
260
261 # HTML comment
262 else if ( $n == "COMMENT" )
263 $ret .= "" ; # Comments are parsed out
264
265 # Links
266 else if ( $n == "LINK" )
267 $ret .= $this->makeInternalLink ( $parser ) ;
268 else if ( $n == "LINKTARGET" )
269 $ret .= $this->sub_makeXHTML ( $parser ) ;
270 else if ( $n == "LINKOPTION" )
271 $ret .= $this->sub_makeXHTML ( $parser ) ;
272
273 else if ( $n == "TEMPLATE" )
274 {
275 $parts = $this->sub_makeXHTML ( $parser ) ;
276 $parts = explode ( "|" , $parts ) ;
277 $title = array_shift ( $parts ) ;
278 $ret .= $this->getTemplateXHTML ( $title , $parts , &$parser ) ;
279 }
280 else if ( $n == "TEMPLATEVAR" )
281 {
282 $x = $this->sub_makeXHTML ( $parser ) ;
283 if ( isset ( $parser->mCurrentTemplateOptions["{$x}"] ) )
284 $ret .= $parser->mCurrentTemplateOptions["{$x}"] ;
285 }
286
287 else if ( $n == "IGNORE" ) # Internal use, not generated by wiki2xml parser
288 $ret .= $this->sub_makeXHTML ( $parser ) ;
289
290 else if ( $n == "NOWIKI" )
291 {
292 $parser->nowiki++ ;
293 $ret .= $this->sub_makeXHTML ( $parser , "" ) ;
294 $parser->nowiki-- ;
295 }
296
297 # Unknown HTML extension
298 else if ( $n == "EXTENSION" ) # This is currently a dummy!!!
299 {
300 $ext = $this->attrs["NAME"] ;
301
302 $ret .= "&lt;" . $ext . "&gt;" ;
303 $ret .= $this->sub_makeXHTML ( $parser ) ;
304 $ret .= "&lt;/" . $ext . "&gt; " ;
305 }
306
307 # Table stuff
308 else if ( $n == "TABLE" )
309 {
310 $ret .= $this->sub_makeXHTML ( $parser , "table" ) ;
311 }
312 else if ( $n == "TABLEROW" )
313 {
314 $ret .= $this->sub_makeXHTML ( $parser , "tr" ) ;
315 }
316 else if ( $n == "TABLECELL" )
317 {
318 $ret .= $this->sub_makeXHTML ( $parser , "td" ) ;
319 }
320 else if ( $n == "TABLEHEAD" )
321 {
322 $ret .= $this->sub_makeXHTML ( $parser , "th" ) ;
323 }
324 else if ( $n == "CAPTION" )
325 {
326 $ret .= $this->sub_makeXHTML ( $parser , "caption" ) ;
327 }
328
329 else if ( $n == "ATTRS" ) # SPECIAL CASE : returning attributes
330 {
331 return $this->getTheseAttrs () ;
332 }
333
334 # Lists
335 else if ( $n == "LISTITEM" )
336 {
337 if ( $parser->mListType == "dl" ) $ret .= $this->sub_makeXHTML ( $parser , "dd" ) ;
338 else $ret .= $this->sub_makeXHTML ( $parser , "li" ) ;
339 }
340 else if ( $n == "LIST" )
341 {
342 $type = "ol" ; # Default
343 if ( $this->attrs["TYPE"] == "bullet" ) $type = "ul" ;
344 else if ( $this->attrs["TYPE"] == "indent" ) $type = "dl" ;
345 $oldtype = $parser->mListType ;
346 $parser->mListType = $type ;
347 $ret .= $this->sub_makeXHTML ( $parser , $type ) ;
348 $parser->mListType = $oldtype ;
349 }
350
351 # Something else entirely
352 else
353 {
354 $ret .= "&lt;" . $n . "&gt;" ;
355 $ret .= $this->sub_makeXHTML ( $parser ) ;
356 $ret .= "&lt;/" . $n . "&gt; " ;
357 }
358
359 $ret = "\n{$ret}\n" ;
360 $ret = str_replace ( "\n\n" , "\n" , $ret ) ;
361 return $ret ;
362 }
363
364 /**
365 * A function for additional debugging output
366 */
367 function myPrint() {
368 $ret = "<ul>\n";
369 $ret .= "<li> <b> Name: </b> $this->name </li>\n";
370 // print attributes
371 $ret .= '<li> <b> Attributes: </b>';
372 foreach ($this->attrs as $name => $value) {
373 $ret .= "$name => $value; " ;
374 }
375 $ret .= " </li>\n";
376 // print children
377 foreach ($this->children as $child) {
378 if ( is_string($child) ) {
379 $ret .= "<li> $child </li>\n";
380 } else {
381 $ret .= $child->myPrint();
382 }
383 }
384 $ret .= "</ul>\n";
385 return $ret;
386 }
387 }
388
389 $ancStack = array(); // the stack with ancestral elements
390
391 // Three global functions needed for parsing, sorry guys
392 function wgXMLstartElement($parser, $name, $attrs) {
393 global $ancStack;
394
395 $newElem = new element;
396 $newElem->name = $name;
397 $newElem->attrs = $attrs;
398
399 array_push($ancStack, $newElem);
400 }
401
402 function wgXMLendElement($parser, $name) {
403 global $ancStack, $rootElem;
404 // pop element off stack
405 $elem = array_pop ($ancStack);
406 if (count ($ancStack) == 0)
407 $rootElem = $elem;
408 else
409 // add it to its parent
410 array_push ($ancStack[count($ancStack)-1]->children, $elem);
411 }
412
413 function wgXMLcharacterData($parser, $data) {
414 global $ancStack;
415 $data = trim ($data); // Don't add blank lines, they're no use...
416 // add to parent if parent exists
417 if ( $ancStack && $data != "" ) {
418 array_push ($ancStack[count($ancStack)-1]->children, $data);
419 }
420 }
421
422
423 /**
424 * Here's the class that generates a nice tree
425 */
426 class xml2php {
427
428 function &scanFile( $filename ) {
429 global $ancStack, $rootElem;
430 $ancStack = array();
431
432 $xml_parser = xml_parser_create();
433 xml_set_element_handler ($xml_parser, 'wgXMLstartElement', 'wgXMLendElement');
434 xml_set_character_data_handler ($xml_parser, 'wgXMLcharacterData');
435 if (!($fp = fopen($filename, 'r'))) {
436 die('could not open XML input');
437 }
438 while ($data = fread($fp, 4096)) {
439 if (!xml_parse($xml_parser, $data, feof($fp))) {
440 die(sprintf("XML error: %s at line %d",
441 xml_error_string(xml_get_error_code($xml_parser)),
442 xml_get_current_line_number($xml_parser)));
443 }
444 }
445 xml_parser_free($xml_parser);
446
447 // return the remaining root element we copied in the beginning
448 return $rootElem;
449 }
450
451 function scanString ( $input ) {
452 global $ancStack, $rootElem;
453 $ancStack = array();
454
455 $xml_parser = xml_parser_create();
456 xml_set_element_handler ($xml_parser, 'wgXMLstartElement', 'wgXMLendElement');
457 xml_set_character_data_handler ($xml_parser, 'wgXMLcharacterData');
458
459 if (!xml_parse ($xml_parser, $input, true)) {
460 die (sprintf ("XML error: %s at line %d",
461 xml_error_string(xml_get_error_code($xml_parser)),
462 xml_get_current_line_number($xml_parser)));
463 }
464 xml_parser_free ($xml_parser);
465
466 // return the remaining root element we copied in the beginning
467 return $rootElem;
468 }
469
470 }
471
472 class ParserXML EXTENDS Parser
473 {
474 /**#@+
475 * @access private
476 */
477 # Persistent:
478 var $mTagHooks, $mListType;
479
480 # Cleared with clearState():
481 var $mOutput, $mAutonumber, $mDTopen, $mStripState = array();
482 var $mVariables, $mIncludeCount, $mArgStack, $mLastSection, $mInPre;
483
484 # Temporary:
485 var $mOptions, $mTitle, $mOutputType,
486 $mTemplates, // cache of already loaded templates, avoids
487 // multiple SQL queries for the same string
488 $mTemplatePath; // stores an unsorted hash of all the templates already loaded
489 // in this path. Used for loop detection.
490
491 var $nowikicount , $mCurrentTemplateOptions ;
492
493 /**#@-*/
494
495 /**
496 * Constructor
497 *
498 * @access public
499 */
500 function ParserXML() {
501 $this->mTemplates = array();
502 $this->mTemplatePath = array();
503 $this->mTagHooks = array();
504 $this->clearState();
505 }
506
507 /**
508 * Clear Parser state
509 *
510 * @access private
511 */
512 function clearState() {
513 $this->mOutput = new ParserOutput;
514 $this->mAutonumber = 0;
515 $this->mLastSection = "";
516 $this->mDTopen = false;
517 $this->mVariables = false;
518 $this->mIncludeCount = array();
519 $this->mStripState = array();
520 $this->mArgStack = array();
521 $this->mInPre = false;
522 }
523
524 /**
525 * Turns the wikitext into XML by calling the external parser
526 *
527 */
528 function html2xml ( &$text ) {
529 global $wgWiki2xml ;
530
531 # generating html2xml command path
532 $a = $wgWiki2xml ;
533 $a = explode ( "/" , $a ) ;
534 array_pop ( $a ) ;
535 $a[] = "html2xml" ;
536 $html2xml = implode ( "/" , $a ) ;
537 $a = array () ;
538
539 $tmpfname = tempnam("/tmp", "FOO");
540 $handle = fopen($tmpfname, "w");
541 fwrite($handle, utf8_encode ( $text ) );
542 fclose($handle);
543 exec ( $html2xml . " < " . $tmpfname , $a ) ;
544 $text = utf8_decode ( implode ( "\n" , $a ) ) ;
545 unlink($tmpfname);
546 }
547
548 function runXMLparser ( &$text ) {
549 global $wgWiki2xml ;
550
551 $this->html2xml ( $text ) ;
552
553 $tmpfname = tempnam("/tmp", "FOO");
554 $handle = fopen($tmpfname, "w");
555 fwrite($handle, $text ) ;
556 fclose($handle);
557 exec ( $wgWiki2xml . " < " . $tmpfname , $a ) ;
558 $text = utf8_decode ( implode ( "\n" , $a ) ) ;
559 unlink($tmpfname);
560 }
561
562 function plain_parse ( &$text , $inline = false , $templateOptions = array () ) {
563 $this->runXMLparser ( $text ) ;
564 $nowikicount = 0 ;
565 $w = new xml2php;
566 $result = $w->scanString( $text );
567
568 $oldTemplateOptions = $this->mCurrentTemplateOptions ;
569 $this->mCurrentTemplateOptions = $templateOptions ;
570
571 if ( $inline ) { # Inline rendering off for templates
572 if ( count ( $result->children ) == 1 )
573 $result->children[0]->name = "IGNORE" ;
574 }
575
576 if ( 1 ) $text = $result->makeXHTML ( $this ) ; # No debugging info
577 else $text = $result->makeXHTML ( $this ) . "<hr>" . $text . "<hr>" . $result->myPrint();
578 $this->mCurrentTemplateOptions = $oldTemplateOptions ;
579 }
580
581 function parse( $text, &$title, $options, $linestart = true, $clearState = true ) {
582 $this->plain_parse ( $text ) ;
583 $this->mOutput->setText ( $text ) ;
584 return $this->mOutput;
585 }
586
587 }
588
589 ?>