<?php

$wgExtensionCredits['other'][] = array(
	'name' => 'StyleBySection',
	'url' => 'http://www.mediawiki.org/wiki/Extension:StyleBySection',
	'author' => 'Francois Moreau',
	'description' => 'Wraps sections according to header hierarchy',
);

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;
        }
    }
 
 
}
?>