Location: PHPKode > projects > BACKDRAFT Workgroup Management > backoffice/wikirenderer/WikiRenderer.lib.php
<?php
/**
 * Bibliotheque d'objets permettant de tranformer un texte, contenant des signes de formatages
 * simples de type wiki, en un autre format tel que XHTML 1.0/strict
 * @author Laurent Jouanneau <hide@address.com>
 * @copyright 2003-2004 Laurent Jouanneau
 * @module Wiki Renderer
 * @version 2.0.6
 * @since 26/09/2004
 * http://ljouanneau.com/softs/wikirenderer/
 * Thanks to all users who found bugs : Loic, Edouard Guerin, Sylvain, Ludovic L.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */
define('WIKIRENDERER_PATH', dirname(__FILE__).'/');

/**
 * Implémente les propriétés d'un tag inline wiki et le fonctionnement pour la génération
 * du code html correspondant
 */
class WikiTag {
   var $name;
   var $beginTag;
   var $endTag;
   var $useSeparator=true;
   var $attribute=array();
   var $builderFunction=null;

   var $contents=array();
   var $separatorCount=0;
   var $isDummy=false;

   function WikiTag($name, $properties){
      $this->name=$name;
      $this->beginTag=$properties[0];
      $this->endTag=$properties[1];
      if($this->name == 'dummie')
         $this->isDummy=true;

      if(is_null($properties[2])){
         $this->attribute=array();
         $this->useSeparator=false;
      }else{
         $this->attribute=$properties[2];
         $this->useSeparator=(count($this->attribute)>0);
      }

      $this->builderFunction=$properties[3];
   }

   function addContent($string, $escape=true){
      if(!isset($this->contents[$this->separatorCount]))
         $this->contents[$this->separatorCount]='';

      if($escape)
         $this->contents[$this->separatorCount] .= htmlspecialchars($string);
      else
         $this->contents[$this->separatorCount] .= $string;
   }

   function addseparator(){
      $this->separatorCount++;
   }

   function getHtmlContent(){
      if(is_null($this->builderFunction)){
         $attr='';
         if($this->useSeparator){
            $cntattr=count($this->attribute);
            $count=($this->separatorCount > $cntattr?$cntattr:$this->separatorCount);
            for($i=1;$i<=$count;$i++){
               $attr.=' '.$this->attribute[$i-1].'="'.$this->contents[$i].'"';
            }
         }
         if(isset($this->contents[0]))
            return '<'.$this->name.$attr.'>'.$this->contents[0].'</'.$this->name.'>';
         else
            return '<'.$this->name.$attr.' />';
      }else{
         $fct=$this->builderFunction;
         return $fct($this->contents, $this->attribute);
      }
   }
}

/**
 * Moteur permettant de transformer les tags wiki inline d'une chaine en équivalent HTML
 */
class WikiInlineParser {

   var $resultline='';
   var $error=false;
   var $listTag=array();
   var $str=array();
   var $splitPattern='';
   var $checkWikiWord=false;
   var $checkWikiWordFunction=null;
   var $_separator;
   var $escapeHtml=true;
   /**
    * constructeur
    * @param   array    $inlinetags liste des tags permis
    *   @param   string   caractère séparateur des différents composants d'un tag wiki
    */
   function WikiInlineParser($inlinetags, $simpletags, $separator='|', $checkWikiWord=false,
      $funcCheckWikiWord=null, $escapeHtml=true  ){

      foreach($inlinetags as $name=>$prop){
         $this->listTag[$prop[0]]=new WikiTag($name,$prop);

         $this->splitPattern.=preg_replace ( '/([^\w\s\d])/', '\\\\\\1',$prop[0]).')|(';
         if($prop[1] != $prop[0])
            $this->splitPattern.=preg_replace ( '/([^\w\s\d])/', '\\\\\\1',$prop[1]).')|(';
      }
      foreach($simpletags as $tag=>$html){
         $this->splitPattern.=preg_replace ( '/([^\w\s\d])/', '\\\\\\1',$tag).')|(';
      }

      $this->simpletags= $simpletags;
      $this->_separator=$separator;
      $this->checkWikiWord=$checkWikiWord;
      $this->checkWikiWordFunction=$funcCheckWikiWord;
      $this->escapeHtml=$escapeHtml;
   }

   /**
    * fonction principale du parser.
    * @param   string   $line avec des eventuels tag wiki
    * @return  string   chaine $line avec les tags wiki transformé en HTML
    */
   function parse($line){
      $this->error=false;

      $this->str=preg_split('/('.$this->splitPattern.'\\'.$this->_separator.')|(\\\\)/',$line, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
      $this->end=count($this->str);
      if($this->end > 1){
         $firsttag=new WikiTag('dummie',array('','', null,'wikibuilddummie'));
         $pos=-1;
         return $this->_parse($firsttag, $pos);
      }else{
         if($this->escapeHtml){
            if($this->checkWikiWord && $this->checkWikiWordFunction !== null)
               return  $this->_doCheckWikiWord(htmlspecialchars($line));
            else
               return htmlspecialchars($line);
         }else{
            if($this->checkWikiWord && $this->checkWikiWordFunction !== null)
               return  $this->_doCheckWikiWord($line);
            else
               return $line;
         }

      }
   }

   /**
    * coeur du parseur. Appelé récursivement
    */
   function _parse($tag, &$posstart){

      $checkNextTag=true;
      $checkBeginTag=true;

      // on parcours la chaine,  morceau aprés morceau
      for($i=$posstart+1; $i < $this->end; $i++){
            $t=&$this->str[$i];
            // a t-on un antislash ?
            if($t=='\\'){
               if($checkNextTag){
                  $t=''; // oui -> on l'efface et on ignore le tag (on continue)
                  $checkNextTag=false;
               }else{
                  // si on est là, c'est que précédement c'etait un \
                  $tag->addContent('\\',false);
                  $checkNextTag=true;
               }

            // est-ce un séparateur ?
            }elseif($t == $this->_separator){
               if($tag->isDummy || !$checkNextTag)
                  $tag->addContent($this->_separator,false);
               elseif($tag->useSeparator){
                  $checkBeginTag=false;
                  $tag->addSeparator();
               }else{
                  $tag->addContent($this->_separator,false);
               }
            // a-t-on une balise de fin du tag ?
            }elseif($checkNextTag && $tag->endTag == $t && !$tag->isDummy){
               $posstart=$i;
               return $tag->getHtmlContent();

            // a-t-on une balise de debut de tag quelconque ?
            }elseif($checkBeginTag && $checkNextTag && isset($this->listTag[$t]) ){

               $content = $this->_parse($this->listTag[$t],$i);
               if($content)
                  $tag->addContent($content,false);
               else{
                  if($tag->separatorCount == 0 && $this->checkWikiWord && $this->checkWikiWordFunction !== null){
                     if($this->escapeHtml)
                        $tag->addContent($this->_doCheckWikiWord(htmlspecialchars($t)),false);
                     else
                        $tag->addContent($this->_doCheckWikiWord($t),false);
                  }else
                       $tag->addContent($t,$this->escapeHtml);
               }

            // a-t-on un saut de ligne forcé ?
            }elseif($checkNextTag && $checkBeginTag && isset($this->simpletags[$t])){
               $tag->addContent($this->simpletags[$t],false);
            }else{
               if($tag->separatorCount == 0 && $this->checkWikiWord && $this->checkWikiWordFunction !== null){
                  if($this->escapeHtml)
                     $tag->addContent($this->_doCheckWikiWord(htmlspecialchars($t)),false);
                  else
                     $tag->addContent($this->_doCheckWikiWord($t),false);
               }else
                  $tag->addContent($t,$this->escapeHtml);
               $checkNextTag=true;
            }
      }
      if(!$tag->isDummy ){
         //--- on n'a pas trouvé le tag de fin
         // on met en erreur
         $this->error=true;
         return false;
      }else
         return $tag->getHtmlContent();
   }

   function _doCheckWikiWord($string){
      if(preg_match_all("/(?<=\b)[A-Z][a-z]+[A-Z0-9]\w*/", $string, $matches)){
         $fct=$this->checkWikiWordFunction;
         $match = array_unique($matches[0]); // il faut avoir une liste sans doublon, à cause du str_replace plus loin...
         $string=str_replace($match, $fct($match), $string);
      }
      return $string;
   }

}



/**
 * classe de base pour la transformation des élements de type bloc
 * @abstract
 */
class WikiRendererBloc {

    /**
    * @var string  code identifiant le type de bloc
    */
   var $type='';

   /**
    * @var string  chaine contenant le tag XHTML d'ouverture du bloc
    * @access private
    */
   var $_openTag='';

   /**
    * @var string  chaine contenant le tag XHTML de fermeture du bloc
    * @access private
    */
   var $_closeTag='';
   /**
    * @var boolean    indique si le bloc doit être immediatement fermé aprés détection
    * @access private
    */
   var $_closeNow=false;

   /**
    * @var WikiRenderer      référence à la classe principale
    */
   var $engine=null;

   /**
    * @var   array      liste des élements trouvés par l'expression régulière regexp
    */
   var $_detectMatch=null;

   /**
    * @var string      expression régulière permettant de reconnaitre le bloc
    */
   var $regexp='';

   /**
    * constructeur à surcharger pour définir les valeurs des différentes proprietés
    * @param WikiRender    $wr   l'objet moteur wiki
    * @abstract
    */
   function WikiRendererBloc(&$wr){
      $this->engine = &$wr;
   }

   /**
    * renvoi une chaine correspondant à l'ouverture du bloc
    * @return string
    */
   function open(){
      return $this->_openTag;
   }

   /**
    * renvoi une chaine correspondant à la fermeture du bloc
    * @return string
    */
   function close(){
      return $this->_closeTag;
   }

   /**
    * indique si le bloc doit etre immédiatement fermé
    * @return string
    */
   function closeNow(){
      return $this->_closeNow;
   }

   /**
    * test si la chaine correspond au debut ou au contenu d'un bloc
    * @param string   $string
    * @return boolean   true: appartient au bloc
    */
   function detect($string){
      return preg_match($this->regexp, $string, $this->_detectMatch);
   }

   /**
    * renvoi la ligne, traitée pour le bloc. A surcharger éventuellement.
    * @return string
    * @abstract
    */
   function getRenderedLine(){
      return $this->_renderInlineTag($this->_detectMatch[1]);
   }

   /**
    * traite le rendu des signes de type inline (qui se trouvent necessairement dans des blocs
    * @param   string  $string une chaine contenant une ou plusieurs balises wiki
    * @return  string  la chaine transformée en XHTML
    * @see WikiRendererInline
    */
   function _renderInlineTag($string){
      return $this->engine->inlineParser->parse($string);
   }

   /**
    * détection d'attributs de bloc (ex:  >°°attr1|attr2|attr3°° la citation )
    * @todo à terminer pour une version ulterieure
    */
   function _checkAttributes(&$string){
      $bat=$this->engine->config->blocAttributeTag;
      if(preg_match("/^$bat(.*)$bat(.*)$/",$string,$result)){
         $string=$result[2];
         return explode($this->engine->config->inlineTagSeparator,$result[1]);
      }else
         return false;
   }

}

require(WIKIRENDERER_PATH . 'WikiRenderer.conf.php');



/**
 * Moteur de rendu. Classe principale à instancier pour transformer un texte wiki en texte XHTML.
 * utilisation :
 *      $ctr = new WikiRenderer();
 *      $monTexteXHTML = $ctr->render($montexte);
 */
class WikiRenderer {

   /**
    * @var   string   contient la version HTML du texte analysé
    * @access private
    */
   var $_newtext;

   /**
    * @var   boolean
    * @access private
    */
   var $_isBlocOpen=false;

   /**
    * @var WikiRendererBloc element bloc ouvert en cours
    * @access private
    */
   var $_currentBloc;

   /**
    * @var array       liste des differents types de blocs disponibles
    * @access private
    */
   var $_blocList= array();

   /**
    * @var   array      liste de paramètres pour le moteur
    */
   var $params=array();

   /**
    * @var WikiInlineParser   analyseur pour les tags wiki inline
    */
   var $inlineParser=null;

   /**
    * liste des lignes où il y a une erreur wiki
    */
   var $errors;


   var $config=null;
   /**
    * instancie les différents objets pour le rendu des elements inline et bloc.
    */
   function WikiRenderer( $config=null){
      if(is_null($config))
         $this->config= & new WikiRendererConfig;
      else
         $this->config=$config;

      $this->_currentBloc = new WikiRendererBloc($this); // bloc 'fantome'
      $this->inlineParser =& new WikiInlineParser($this->config->inlinetags,
         $this->config->simpletags, $this->config->inlineTagSeparator,
         $this->config->checkWikiWord, $this->config->checkWikiWordFunction, $this->config->escapeSpecialChars);

      foreach($this->config->bloctags as $name=>$ok){
         $name='WRB_'.$name;
         if($ok) $this->_blocList[]=& new $name($this);
      }


   }

   /**
    * Methode principale qui transforme les tags wiki en tag XHTML
    * @param   string  $texte le texte à convertir
     * @return  string  le texte converti en XHTML
    */
   function render($texte){
      $lignes=preg_split("/\015\012|\015|\012/",$texte); // on remplace les \r (mac), les \n (unix) et les \r\n (windows) par un autre caractère pour découper proprement

      $this->_newtext=array();
      $this->_isBlocOpen=false;
      $this->errors=false;
      $this->_currentBloc = new WikiRendererBloc($this);

      // parcours de l'ensemble des lignes du texte
      foreach($lignes as $num=>$ligne){

         if($ligne == '') { // pas de trim à cause des pre
            // ligne vide
            $this->_closeBloc();
         }else{

            // detection de debut de bloc (liste, tableau, hr, titre)
            foreach($this->_blocList as $bloc){
               if($bloc->detect($ligne))
                  break;
            }

            // c'est le debut d'un bloc (ou ligne d'un bloc en cours)
            if($bloc->type != $this->_currentBloc->type){
               $this->_closeBloc(); // on ferme le precedent si c'etait un different
               $this->_currentBloc= $bloc;
               if($this->_openBloc()){
                  $this->_newtext[]=$this->_currentBloc->getRenderedLine();
               }else{
                  $this->_newtext[]=$this->_currentBloc->getRenderedLine();
                  $this->_newtext[]=$this->_currentBloc->close();
                  $this->_isBlocOpen = false;
                  $this->_currentBloc = new WikiRendererBloc($this);
               }

            }else{
               $this->_currentBloc->_detectMatch=$bloc->_detectMatch;
               $this->_newtext[]=$this->_currentBloc->getRenderedLine();
            }
            if($this->inlineParser->error){
               $this->errors[$num+1]=$ligne;
            }

         }


      }
      $this->_closeBloc();
      
      

      
      
      $this->_newtext = implode("\n",$this->_newtext);
      
   $translation_array = array(
'AOL' => '<acronym title="America Online">AOL</acronym>',
'API' => '<acronym title="Application Programming Interface">API</acronym>',
'BTW' => '<acronym title="By The Way">BTW</acronym>',
'CD' => '<acronym title="Compact Disc">CD</acronym>',
'CGI' => '<acronym title="Common Gateway Interface">CGI</acronym>',
'CLI' => '<acronym title="Common Language Interpreter">CLI</acronym>',
'CMS' => '<acronym title="Content Management System">CMS</acronym>',
'CSS' => '<acronym title="Cascading Style Sheets">CSS</acronym>',
'CVS' => '<acronym title="Concurrent Versions System">CVS</acronym>',
'DNS' => '<acronym title="Domain Name System">DNS</acronym>',
'DOM' => '<acronym title="Document Object Model">DOM</acronym>',
'DTD' => '<acronym title="Document Type Definition">DTD</acronym>',
'DVD' => '<acronym title="Digital Video Disc">DVD</acronym>',
'EE' => '<acronym title="ExpressionEngine">EE</acronym>',
'ELF' => '<acronym title="Executable and Linking Format">ELF</acronym>',
'FAQ' => '<acronym title="Frequently Asked Questions">FAQ</acronym>',
'FOAF' => '<acronym title="Friend Of A Friend is a RDF dialect for describing relationships">FOAF</acronym>',
'FSF' => '<acronym title="Free Software Foundation">FSF</acronym>',
'GB' => '<acronym title="Gigabyte">GB</acronym>',
'GFDL' => '<acronym title="GNU Free Documentation License">GFDL</acronym>',
'GPL' => '<acronym title="GNU General Public License">GPL</acronym>',
'GTK' => '<acronym title="GUI ToolKit - The GIMP Tool Kit for creating user-interfaces">GTK</acronym>',
'GUI' => '<acronym title="Graphical User Interface">GUI</acronym>',
'GNOME' => '<acronym title="Gnu Network Object Model Environment">GNOME</acronym>',
'HDTV' => '<acronym title="High Definition TeleVision">HDTV</acronym>',
'HTML' => '<acronym title="HyperText Markup Language">HTML</acronym>',
'HTTP' => '<acronym title="HyperText Transfer Protocol">HTTP</acronym>',
'IE' => '<acronym title="Internet Explorer">IE</acronym>',
'ICANN' => '<acronym title="Internet Corporation for Assigned Names and Numbers">ICANN</acronym>',
'IHOP' => '<acronym title="International House of Pancakes">IHOP</acronym>',
'IIRC' => '<acronym title="If I Remember Correctly">IIRC</acronym>',
'IIS' => '<acronym title="Internet Infomation Server">IIS</acronym>',
'IM' => '<acronym title="Instant Message">IM</acronym>',
'IMAP' => '<acronym title="Internet Message Access Protocol">IMAP</acronym>',
'IP' => '<acronym title="Internet Protocol">IP</acronym>',
'IRC' => '<acronym title="Internet Relay Chat - like Instant Messaging for groups">IRC</acronym>',
'JSP' => '<acronym title="Java Server Pages">JSP</acronym>',
'KB' => '<acronym title="Kilobyte">KB</acronym>',
'KDE' => '<acronym title="K Desktop Environment">KDE</acronym>',
'KVM' => '<acronym title="Keyboard, Video, Mouse switch for controlling multiple computers">KVM</acronym>',
'LDAP' => '<acronym title="Lightweight Directory Access Protocol">LDAP</acronym>',
'LGPL' => '<acronym title="GNU Lesser General Public License">LGPL</acronym>',
'MAPI' => '<acronym title="Mail Application Programming Interface">MAPI</acronym>',
'MB' => '<acronym title="Megabyte">MB</acronym>',
'MP3' => '<acronym title="MPEG Layer 3 - a common audio codec for music files">MP3</acronym>',
'MS' => '<acronym title="Microsoft">MS</acronym>',
'MSDN' => '<acronym title="Microsoft Developer Network">MSDN</acronym>',
'MSIE' => '<acronym title="Microsoft Internet Explorer">MSIE</acronym>',
'MSN' => '<acronym title="Microsoft Network">MSN</acronym>',
'MT' => '<acronym title="Movable Type">MT</acronym>',
'NNTP' => '<acronym title="Network News Transfer Protocol - the protocol used for NewsGroups">NNTP</acronym>',
'NPR' => '<acronym title="National Public Radio">NPR</acronym>',
'OPML' => '<acronym title="Outline Processor Markup Language">OPML</acronym>',
'OS' => '<acronym title="Operating System">OS</acronym>',
'P2P' => '<acronym title="Peer To Peer">P2P</acronym>',
'PBS' => '<acronym title="Public Broadcasting System">PBS</acronym>',
'PDF' => '<acronym title="Portable Document Format">PDF</acronym>',
'PHP' => '<acronym title="Hypertext PreProcessing">PHP</acronym>',
'PNG' => '<acronym title="Portable Network Graphics">PNG</acronym>',
'RAID' => '<acronym title="Redundant Array of Independent Disks">RAID</acronym>',
'RC' => '<acronym title="Release Candidate">RC</acronym>',
'RDF' => '<acronym title="Resource Description Framework">RDF</acronym>',
'RPC' => '<acronym title="Remote Procedure Call">RPC</acronym>',
'RSS' => '<acronym title="Really Simple Syndication">RSS</acronym>',
'SIG' => '<acronym title="Special Interest Group">SIG</acronym>',
'SOAP' => '<acronym title="Simple Object Access Protocol">SOAP</acronym>',
'SSN' => '<acronym title="Social Security Number">SSN</acronym>',
'SVG' => '<acronym title="Scalable Vector Graphics">SVG</acronym>',
'TCP' => '<acronym title="Transmission Control Protocol">TCP</acronym>',
'UDP' => '<acronym title="User Datagram Protocol">UDP</acronym>',
'UI' => '<acronym title="User Interface">UI</acronym>',
'URI' => '<acronym title="Uniform Resource Identifier">URI</acronym>',
'URL' => '<acronym title="Uniform Resource Locator">URL</acronym>',
'USB' => '<acronym title="Universal Serial Bus">USB</acronym>',
'VB' => '<acronym title="Visual Basic">VB</acronym>',
'VBS' => '<acronym title="Visual Basic Script">VBS</acronym>',
'VM' => '<acronym title="Virtual Machine">VM</acronym>',
'VNC' => '<acronym title="Virtual Network Computing">VNC</acronym>',
'W3C' => '<acronym title="World Wide Web Consortium">W3C</acronym>',
'WCAG' => '<acronym title="Web Content Accessibility Guidelines">WCAG</acronym>',
'WP' => '<acronym title="Wordpress">WP</acronym>',
'WYSIWYG' => '<acronym title="What You See Is What You Get">WYSIWYG</acronym>',
'XHTML' => '<acronym title="eXtensible HyperText Markup Language - HTML reformulated as XML">XHTML</acronym>',
'XML' => '<acronym title="eXtensible Markup Language">XML</acronym>',
'XSL' => '<acronym title="eXtensible Stylesheet Language">XSL</acronym>',
'XSLT' => '<acronym title="eXtensible Stylesheet Language Transformation">XSLT</acronym>',
'YMMV' => '<acronym title="Your Mileage May Vary">YMMV</acronym>'


);

return substr(strtr($this->_newtext, $translation_array),4);

   
   }


   /**
    * ferme un bloc
    * @access private
    */
   function _closeBloc(){
      if($this->_isBlocOpen){
         $this->_isBlocOpen=false;
         $this->_newtext[]=$this->_currentBloc->close();
         $this->_currentBloc = new WikiRendererBloc($this);
      }
   }

   /**
    * ouvre un bloc et le referme eventuellement suivant sa nature
    * @return boolean  indique si le bloc reste ouvert ou pas
    * @access private
    */
   function _openBloc(){
      if(!$this->_isBlocOpen){
         $this->_newtext[]=$this->_currentBloc->open();
         $this->_isBlocOpen=true;
         return !$this->_currentBloc->closeNow();
      }else
         return true;
   }

}


?>
Return current item: BACKDRAFT Workgroup Management