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