Extension:StyleBySection

From MediaWiki.org

Jump to: navigation, search
Manual on MediaWiki Extensions
List of MediaWiki Extensions
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

OutputPageBeforeHTML

[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:

      1. LEVEL##, which will be replaced by the header level (h3 -> "3") ;
      2. 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('&quot;', $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;
        }
    }
 
 
}
Personal tools