Extension:StyleBySection
From MediaWiki.org
|
StyleBySection Release status: stable |
|
|---|---|
| Implementation | Page action |
| Description | Wraps sections according to header hierarchy. Gives class and id to the wrapping divs according to section title. |
| Author(s) | François Moreau |
| Version | 1.0 |
| MediaWiki | 1.8+ |
| Download | no link |
| Hooks used | |
[edit] Introduction
StyleBySection does two things to the rendered xhtml pages:
- it wraps each section according to the nesting hierarchy of their respective headers;
- it may give a class and/or an id to each wrapping div, depending on the header's content and the set of rules defined for the plugin.
The rules (and thus the class and id given to specific sections) will be applied to:
- headers which content is an exact match of the 'title=' of the rule, if defined;
- headers which content is a match of the regular expression 'title-match' of the rule, if defined.
This is useful for anything classes and ids usually are: styling and javascripts. We have used this primarily has a mean to bring stronger semantics to wiki pages, which result was then used to produce XML content with further extensions.
The 'attr' of each rule is an array containing each attribute to be given to the concerned sections. Two placeholders are recognized in their definitions:
-
-
- LEVEL##, which will be replaced by the header level (h3 -> "3") ;
- COUNTER##, which will be replaced by an auto-incrementing integer.
-
[edit] Example
$rules = array( 'rule-one' => array('title=' => 'Example', 'attr' => array('class' => 'section-h##LEVEL##')), 'rule-two' =>array('title-match' => '/^Second/', 'attr' => array('id' => 'second-##COUNTER##', 'class' => 'second'))); $wgHooks['OutputPageBeforeHTML'][] = array( new StyleBySection(), 'apply', $rules);
[edit] Code
<?php class StyleBySection { protected $opening_tag_level = '<div%s>'; protected $closing_tag_level = '</div>'; protected $_attributeRules = array(); protected $_titleExtractPattern = null; protected $_rulesCounter = array(); public function apply($rules = null, $parser, &$text) { if(is_array($rules)) { $this->_attributeRules = $rules; if(array_key_exists('_EXTRACT_TITLE_PATTERN', $this->_attributeRules) && is_string($this->_attributeRules['_EXTRACT_TITLE_PATTERN'])) { $this->_titleExtractPattern = $this->_attributeRules['_EXTRACT_TITLE_PATTERN']; unset($this->_attributeRules['_EXTRACT_TITLE_PATTERN']); } elseif(array_key_exists('wgVersion', $GLOBALS) && -1 < version_compare($GLOBALS['wgVersion'], '1.10')) { $this->_titleExtractPattern = '/<span class="mw-headline">(.*)<\/span>/'; } } $text = $this->_traverseText($text); return true; } private function _traverseText($text) { $text = "<_STYLEBYSECTION>$text</_STYLEBYSECTION>"; $pointer = 0; $prev_pointer = 0; $result = ''; $pile = array(); while( (false !== $pointer)) { $result .= substr($text, $prev_pointer, $pointer-$prev_pointer); switch($this->_tagType($pointer, $text)) { case 'opening_tag': $header_level = $text[$pointer+2]; $current = end($pile); if($current && $header_level <= $current['level'] && 0 == $current['num_nesting'] ) { while($current && $header_level <= $current['level']) { $result .= $this->closing_tag_level; array_pop($pile); $current = end($pile); } } $result .= $this->_openingTagWithAttr($text, $pointer, $header_level); $this->_incrNesting($pile); $pile[] = array('level' => $header_level, 'num_nesting' => 1); break; case 'opening': $this->_incrNesting($pile); break; case 'closing': $this->_incrNesting($pile, -1); reset($pile); $closed_piles = false; while(list($key, $val) = each($pile)) { if(0 > $val['num_nesting']) { $result .= $this->closing_tag_level; unset($pile[$key]); $closed_piles = true; } } if($closed_piles) $pile = array_values($pile); break; } $prev_pointer = $pointer; $pointer = strpos($text, '<', 1+ $pointer); } $result .= substr($text, $prev_pointer); $result .= str_repeat($this->closing_tag_level, count($pile)); return str_replace ( array('<_STYLEBYSECTION>', '</_STYLEBYSECTION>'), array('', ''), $result); } private function _tagType($pointer, $text) { if('h' == $text[$pointer+1] && chr($text[$pointer+2]) <= chr('6') && chr($text[$pointer+2]) >= chr('1')) { return 'opening_tag'; } if('/' == $text[$pointer+1]) { return 'closing'; } $tag_end_pos = strpos($text, '>', $pointer); if('/' == $text[$tag_end_pos-1]) { return 'autoclosing'; } return 'opening'; } private function _openingTagWithAttr($text, $pointer, $header_level) { $str_attr = ''; $header_content_start_pointer = 1 + strpos ( $text, '>', $pointer ); $header_content_end_pointer = strpos ( $text, "</h{$header_level}>", $pointer ); $header_content = substr ( $text, $header_content_start_pointer, $header_content_end_pointer - $header_content_start_pointer); if(is_string($this->_titleExtractPattern)) { $is_found = preg_match($this->_titleExtractPattern, $header_content, $match); if($is_found && 2 == count($match)) { $header_content = trim($match[1]); } } $rules_by_priority = array_reverse ( $this->_attributeRules, $preserve_keys=true ); foreach($rules_by_priority as $rule_index => $rule) { $to_apply = false; if(array_key_exists('title=', $rule)) { $to_apply = $header_content == $rule['title=']; } elseif(array_key_exists('title-match', $rule)) { $to_apply = (bool) preg_match ( $rule['title-match'], $header_content ); } if($to_apply) { foreach($rule['attr'] as $name => $val) { if(!array_key_exists($rule_index, $this->_rulesCounter)) { $this->_rulesCounter[$rule_index] = 0; } if(strpos($val, '##COUNTER##')) ++$this->_rulesCounter[$rule_index]; $val = str_replace ( array('"', '##COUNTER##', '##LEVEL##'), array('"', $this->_rulesCounter[$rule_index], $header_level), $val) ; $str_attr .= ' ' . $name . '="' . $val . '"' ; } break; } } return sprintf($this->opening_tag_level, $str_attr); } private function _incrNesting(&$pile, $increment=1) { reset($pile); while(list($key, $val) = each($pile)) { $pile[$key]['num_nesting'] += $increment; } } }

