<?php

/** @class o7aCode
*
* @author Bertrand Brasselet <cntc.1@o7acode.net>
* @version 1.1.3
* @since 26/02/2007
* http://o7acode.net/
*
*/

class o7aCode {

/**
*
* @var array	param					contain all parameters of the class ; detailed below.
* @access	public
*
* ----
*
* @var boolean	param['active_*something*']		do the syntax for *something* must be translated ?
*
* = Some clarifications =
*
** 'active_link_wikipedia' &  'active_link_lastfm'  correspond  to extensions of the links syntax
** and allow to more quickly make links to wikipedia/last.fm acticles.
**
** 'active_uppercase' : a letter preceded  by  a  caret ^ is automatically put in uppercase. This
** syntax is useful with accented letters.
**
** 'active_backslash' : a character preceded by a blackslash isn't interpreted.
**
** 'active_blockcode' : display of code in block, within pre markups.
*
* ----
*
* @var boolean	param['blockcode_with_color']		do the code in block is colored up (need for GeSHi) ? (1)
* @var array	param['blockcode_languages_with_css']	list of languages using css class for color up the code (2)
* @var boolean	param['o7aCode_by_default']		do the document is written by default in o7aCode ? (3)
* @var boolean	param['insert_xhtml']			do xhtml parts can be incorporated within o7aCode ? (4)
* @var boolean	param['convert_by_htmlentities']	do the content must be convert by htmlentities ? (5)
* @var string	param['str_encoding']			fill in the characters encoding (used by htmlentities)
* @var integer	param['first_title_level']		xhtml level of the o7aCode first level titles
*
* (1) & (2) For more info about GeSHi and the CSS associated, look at the end of the file.
* (2) Other languages are colored up puting  css  within  the style attribute of the span markups.
* (3) If false, o7aCode parts must be between <## and ##>.
* (4) Xhtml parts must be between #> and <#.
* (5) The activation of this option is highly recommended !
*
* = Note =
*
** If param['o7aCode_by_default']  is  set  to  false, param['insert_xhtml']  is  in  either  case
** considered as true.
*
* ----
*
* @var string	param['link_wikipedia_lang']		fill in the language of the wikipedia links (en,fr...)
* @var string	param['link_wikipedia_title']		give the title attribute of the wikipedia links (*)
* @var string	param['link_lastfm_lang']		fill in the language of the lastfm links (en,fr...)
* @var string	param['link_lastfm_title']		give the title attribute of the lastfm links (*)
*
* (*) These values are followed by the article name.
*
*/

	var $param;

/**
*
* @var array[string]	backslash			each characters deactived by backslashes (*)
* @access		private
* @var array[string]	xhtml_blended			each xhtml parts inserted within o7aCode (*)
* @access		private
* @var array[string]	block_code			each code in block to display, within pre markups (*)
* @access		private
*
* (*) all kept aside during the interpreting then put back on its initial positions.
*
*/

	var $backslash;
	var $xhtml_blended;
	var $block_code;

	/**
	* @method	o7aCode				initialize all parameters
	* @since	1.0.0
	* @access	public
	*/
	function o7aCode() {
		$this->setParam('active_comment',true);
		$this->setParam('active_title',true);
		$this->setParam('active_paragraph',true);
		$this->setParam('active_list',true);
		$this->setParam('active_link',true);
		$this->setParam('active_link_wikipedia',true);
		$this->setParam('active_link_lastfm',true);
		$this->setParam('active_img',true);
		$this->setParam('active_strongEmphasis',true);
		$this->setParam('active_emphasis',true);
		$this->setParam('active_del',true);
		$this->setParam('active_ins',true);
		$this->setParam('active_quote',true);
		$this->setParam('active_cite',true);
		$this->setParam('active_blockquote',true);
		$this->setParam('active_code',true);
		$this->setParam('active_abbr',true);
		$this->setParam('active_hr',true);
		$this->setParam('active_br',true);

		$this->setParam('active_EnDash',true);
		$this->setParam('active_clearer',true);

		$this->setParam('active_uppercase',true);
		$this->setParam('active_backslash',true);
		$this->setParam('active_blockcode',true);

		$this->setParam('blockcode_with_color',true);
		$this->setParam('blockcode_languages_with_css',array('xml','php','css'));

		$this->setParam('insert_xhtml',true);

		$this->setParam('convert_by_htmlentities',true);

		$this->setParam('str_encoding','ISO-8859-1');
		$this->setParam('first_title_level',3);

		$this->setParam('o7aCode_by_default',true);

		$this->setParam('link_wikipedia_lang','fr');
		$this->setParam('link_wikipedia_title','Article de Wikipedia sur ');
		$this->setParam('link_lastfm_lang','fr');
		$this->setParam('link_lastfm_title','Fiche de Last.fm sur le groupe ');
	}

	/**
	* @metod	setParam			set a parameter value
	* @param	string		parameter	parameter entitled
	* @param	string		value		parameter value
	* @since	1.0.0
	* @access	public
	*/
	function setParam($parameter, $value) {
		$this->param[$parameter] = $value;
	}

	/**
	* @metod	getParam			gives the value of a specified parameter
	* @param	string		parameter	parameter entitled
	* @return	string				parameter value
	* @since	1.0.0
	* @access	protected
	*/
	function getParam($parameter) {
		return (!empty($this->param[$parameter])) ? $this->param[$parameter] : false;
	}

	/**
	* @metod	transform			Final method, give parse() only the o7aCode parts
	* @param	string		content		xhtml / o7aCode document
	* @return	string				final xhtml document
	* @since	1.0.0
	* @access	public
	*
	*/
	function transform($content) {

		/// Interpretation of o7aCode parts which are between <## and ##>
		if (!$this->getParam('o7aCode_by_default')) {
			$regex = '/<##(.*)##>/Umse';
			# don't quiver ! All these backslashes allow to not modify the quote characters of our '\1'
			$php_action = get_class($this)."::parse(preg_replace('#\\\\\\\\\\\\\"#','\"','\\1'))";
			$final_xhtml = preg_replace($regex,$php_action,$content);
		}
		/// The document must only be in o7aCode.
		else {
			# All the document is interpreted
			$final_xhtml = $this->parse($content);
		}
		return $final_xhtml;
	}

	/**
	* @metod	parse				Convert o7aCode into xhtml
	* @param	string		o7aCode		an o7aCode part
	* @return	string				the converted (xhtml) version of the part
	* @since	1.0.0
	* @access	private
	*
	* = Legend =
	*
	** '## ITEM' precede this item interpretation.
	**
	** In this method, some  item  interpretations  are  splitted into two parts, the PING one
	** where some parts of the document are  kept aside and temporary replaced by recognizable
	** strings which, contrary  to  the  parts  kept  aside,  don't bother the following items
	** interpretation  and  aren't  modified  by  them.  Secondly, the PONG, these strings are
	** replaced by its respective parts.
	**
	** '==>' corresponds to 'must precede',  '::'  corresponds to 'because', '(!)' corresponds
	** to 'in the contrary case'.
	**
	** (:ITEM) is an anchor on clarifications of the item interpretation.
	*
	*/
	function parse($o7aCode) {

		# degree symbol
		$deg = '(?:'.chr(176).'|'.chr(194).')';

		## BACKSLASH PING
		if ($this->getParam('active_backslash')) {
			$o7aCode = preg_replace_callback('#\\\\(.)#m',array(&$this,'charBackslashed2strNum'),$o7aCode);
		}

		/// BACKSLASH PING ==> *ALL BELOW* :: backslashes must be able to deactive all the
		/// o7aCode syntax.

		## BLOCKCODE PING
		if ($this->getParam('active_blockcode')) {
			$regex = '/^				# on a new line l.1
				(\s*)'.$deg.$deg.$deg.'		# open l.1
				(.*)				# specify the language l.1
				\r?\n
				(.*)				# code in block l.2..n
				\r?\n
				(\s*)'.$deg.$deg.$deg.'		# close l.n+1
				(.*)				# attributes declaration l.n+1
				(\r|$)
				/Usmx';
			$o7aCode = preg_replace_callback($regex,array(&$this,'blockCode2strNum'),$o7aCode);
		}

		## INSERT XHTML PING
		if ($this->getParam('insert_xhtml') || !$this->getParam('o7aCode_by_default')) {
			$regex = '/(^\s*|)##>(?sU)(.*)(?-sU)<##(\s*$|)/m';
			$o7aCode = preg_replace_callback($regex,array(&$this,'xhtml2strNum'),$o7aCode);
		}

		/// BLOCKCODE PING & INSERT XHTML  PING  ==>  COMMENT  ::  comments  whithin  code
		/// mustn't be deleted by COMMENT.

		/// Syntax for in-line comment is two  slash,  which is routinely met in URLs. The
		/// two  slash  of  URLs  and  the  preceding  colon  are  temporary  replaced  by
		/// "colon_slash_slash_url" in order to not interpret it as comment (:COMMENT).

		## COMMENT
		if ($this->getParam('active_comment')) {
			#  In-line
			$o7aCode = preg_replace('#://#m','colon_slash_slash_url',$o7aCode);
			$o7aCode = preg_replace('# ?//.*$#m','',$o7aCode);
			$o7aCode = preg_replace('#colon_slash_slash_url#m','://',$o7aCode);
			#  Block
			$o7aCode = preg_replace('#/\*.*\*/#Usm','',$o7aCode);
		}

		/// COMMENT ==> *ALL BELOW*

		/// About  the  end  of  the   method,   html  special  characters  are  converted
		/// (:HTMLENTITIES)  ;  Only  strings   between   html   markups   are   given  to
		/// htmlentities(), that's to say  the  strings between > and <.The possible angle
		/// backets into these  strings  would  cause  misfunctioning  of the HTMLENTITIES
		/// regex. That's why before the  insertion  of  every  html markup (which contain
		/// angle backets), all the angle backets  must  be converted by its html entities
		/// (:ANGLE BACKETS).

		/// And-signs are also converted  by  HTMLENTITIES, but and-signs of angle backets
		/// html  entities   mustn't   be   converted   ;   Hence   the  temporary  string
		/// "temporary_and_sign" which take  the  place  of them and will be replace after
		/// HTMLENTITIES (:AND SIGN PONG).

		## ANGLE BACKETS
		$o7aCode = preg_replace('#<#m','temporary_and_sign'.'lt;',$o7aCode);
		$o7aCode = preg_replace('#>#m','temporary_and_sign'.'gt;',$o7aCode);

		/// ANGLE BACKETS ==> *ALL which give html markups* :: see at ANGLE BACKETS anchor.

		## HR
		if ($this->getParam('active_hr')) {
			$regex = '#^(\s*)-{4,}(.*)$#m';
			$o7aCode = preg_replace_callback($regex,array(&$this,'transformHr'),$o7aCode);
		}

		/// HR ==> EN DASH :: (!) hr would be interpreted as two en-dashes.

		#### IN-LINE ####

		## LINK
		if ($this->getParam('active_link')) {
			$regex = '#\[([^\]]*)\](.*)\[/\]#mU';
			$o7aCode = preg_replace_callback($regex,array(&$this,'transformLink'),$o7aCode);
		}

		## IMG
		if ($this->getParam('active_img')) {
			$regex = '#\{([^\}]+)\}#m';
			$o7aCode = preg_replace_callback($regex,array(&$this,'transformImg'),$o7aCode);
		}

		## CITE
		if ($this->getParam('active_cite')) {
			$lt = 'temporary_and_sign'.'lt;';
			$gt = 'temporary_and_sign'.'gt;';
			$regex= '#'.$lt.$lt.'(.*)'.$gt.$gt.'#mU';
			$o7aCode = preg_replace_callback($regex,array(&$this,'transformCite'),$o7aCode);
		}

		## STRONG EMPHASIS
		if ($this->getParam('active_strongEmphasis')) {
			$regex = "#'''(.*)'''#mU";
			$o7aCode = preg_replace_callback($regex,array(&$this,'transformStrongEmphasis'),$o7aCode);
		}

		/// STRONG EMPHASIS ==> EMPHASIS ::  (!)  strong emphasis whould be interpreted as
		/// emphasis with two redundant sigle-quotation marks.

		## EMPHASIS
		if ($this->getParam('active_emphasis')) {
			$regex = "#''(.*)''#mU";
			$o7aCode = preg_replace_callback($regex,array(&$this,'transformEmphasis'),$o7aCode);
		}

		## EN DASH
		if ($this->getParam('active_EnDash')) {
			$o7aCode = preg_replace('#---#mU','temporary_and_sign'.'ndash;',$o7aCode);
		}

		/// EN DASH ==> DEL ::  (!)  del  whould  be  interpreted  as  en-dashes  with two
		/// redundant minus sign.

		## DEL
		if ($this->getParam('active_del')) {
			$o7aCode = preg_replace_callback('#--(.*)--#mU',array(&$this,'transformDel'),$o7aCode);
		}

		## INS
		if ($this->getParam('active_ins')) {
			$regex = '#\+\+(.*)\+\+#mU';
			$o7aCode = preg_replace_callback($regex,array(&$this,'transformIns'),$o7aCode);
		}

		## CODE INLINE
		if ($this->getParam('active_code')) {
			$regex = '#'.$deg.$deg.'(.*)'.$deg.$deg.'#mU';
			$o7aCode = preg_replace_callback($regex,array(&$this,'transformCode'),$o7aCode);
		}

		##  UPPERCASE
		if ($this->getParam('active_uppercase')) {
			$o7aCode = preg_replace('#\^(.)#em','mb_strtoupper("\1")',$o7aCode);
		}

		## ABBR
		if ($this->getParam('active_abbr')) {
			$regex = '#\(([^\)]+)\)(.*)\(/\)#mU';
			$o7aCode = preg_replace_callback($regex,array(&$this,'transformAbbr'),$o7aCode);
		}

		## BR
		if ($this->getParam('active_br')) {
			$o7aCode = preg_replace('#/-/#m','<br />',$o7aCode);
		}

		/// The value of an attribute may be empty ; In this case, the html markup contain
		/// two double quotation marks, which  is  interpreted by QUOTE. In order to avoid
		/// this confusion, (:EMPTY VALUES PING)  replace  these double quotation marks by
		/// "two_double_quotation_marks".  (:EMPTY VALUES PONG),  after  QUOTE,  make  the
		/// upturned replacement.

		## QUOTE
		if ($this->getParam('active_quote')) {
			$o7aCode = preg_replace_callback('#""(.*)""#mU',array(&$this,'transformQuote'),$o7aCode);
		}

		/// QUOTE ==> EMPTY VALUES PONG :: see at the EMPTY VALUES PONG anchor.

		## EMPTY VALUES PONG
		$o7aCode = preg_replace('#two_double_quotation_marks#m','""',$o7aCode);

		#################

		/// IN-LINE ==> IN-BLOCK & PARAGRAPH :: (!) attributes separators  ('::', ';;') of
		/// IN-LINE would be interpreted as attributes separators of IN-BLOCK or PARAGRAPH.

		#### IN-BLOCK ####

		## TITLE
		if ($this->getParam('active_title')) {
			$regex = '#^(\s*)(=+)(.+)\2(\r|$)#m';
			$o7aCode = preg_replace_callback($regex,array(&$this,'transformTitle'),$o7aCode);
		}

		## BLOCKQUOTE
		if ($this->getParam('active_blockquote')) {
			$regex = '#^(\s*)"""\s*\r?\n(.*)\r?\n(\s*)"""(.*)(\r|$)#Usm';
			$o7aCode = preg_replace_callback($regex,array(&$this,'transformBlockquote'),$o7aCode);
		}

		## LIST
		if ($this->getParam('active_list')) {
			$o7aCode = $this->transformList($o7aCode);
		}

		## CLEARER
		if ($this->getParam('active_clearer')) {
			$regex = '#^(\s*)_{4,}\s*(\r|$)#mU';
			$o7aCode = preg_replace($regex,'\1))<div class="clearer"></div>',$o7aCode);
		}

		####################

		/// HR & IN-BLOCK ==> PARAGRAPH :: (!) titles,  lists...  would  be put whithin  p
		/// markups. See also at the ANTI PARAGRAPH PONG anchor.

		## PARAGRAPH
		if ($this->getParam('active_paragraph')) {
			$o7aCode = $this->transformParagraph($o7aCode);
		}

		/// PARAGRAPH ==> ANTI PARAGRAPH PONG

		## ANTI PARAGRAPH PONG
		if (!$this->getParam('active_paragraph')) {
			$regex = '#^(\s*)\)\)(\s*)(?:<(/?)(h[0-9]+|ul|ol|li|div|hr)>|xhtml_blended_num)#m';
			$o7aCode = preg_replace($regex,'\1\2<\3\4>',$o7aCode);
		}

		## HTMLENTITIES
		$regex = '#(^|>)([^<]*)(<|$)#ms';
		$o7aCode = preg_replace_callback($regex,array(&$this,'transformHtmlentities'),$o7aCode);

		/// HTMLENTITIES ==> AND SIGN PONG :: see at the AND SIGN PONG anchor.

		## AND SIGN PONG
		$o7aCode = preg_replace('#temporary_and_sign#m','&',$o7aCode);

		## BLOCKCODE PONG
		if ($this->getParam('active_blockcode')) {
			$regex = '#(?:\)\)|)block_code_([0-9]+)#m';
			$o7aCode = preg_replace_callback($regex,array(&$this,'strNum2block_code'),$o7aCode);
		}

		## XHTML INLINE PONG
		if ($this->getParam('insert_xhtml')) {
			$regex = '#xhtml_blended_num_([0-9]+)#m';
			$o7aCode = preg_replace_callback($regex,array(&$this,'strNum2xhtml'),$o7aCode);
		}

		/// BACKSLASH PONG ==> *ALL ABOVE* :: after all interpretations.

		## BACKSLASH PONG
		if ($this->getParam('active_backslash')) {
			$regex = '#char_bs_num_([0-9]+)#m';
			$o7aCode = preg_replace_callback($regex,array(&$this,'strNum2charBackslashed'),$o7aCode);
		}

		# $xhtml = $o7aCode_interpreted = $o7aCode;
		$xhtml = $o7aCode;

		return $xhtml;
	}

	/// Keeping   aside   some   items   during    the   interpretation   :   Methods   called
	/// *something*2strNum() attribute  a  unique  number  to  each item, which allows methods
	/// called srtNum2*something* to get back the item knowing the number.

	/**
	* Saves the  character  in  $this->backslash  and  return  a  temporary  string  (with the
	* corresponding number) which will take the place of the character during interpretation.
	*
	* @metod	charBackslashed2strNum
	* @param	array[string]	regex_array	array returned by the regex of BACKSLASH PING
	* @return	string				the temporary string
	* @since	1.1.0
	* @access	protected
	*/
	function charBackslashed2strNum($regex_array) {
		$i = sizeof($this->backslash);
		$char = $regex_array[1];
		//echo ord($char).' - ';
		$this->backslash[$i] = $char;
		return 'char_bs_num_'.$i;
	}











	/**
	*
	* Sorry, but methods above aren't  yet  well  documented  ...  I will have again free time
	* about the april 14 2007 to do this.
	*
	*/















	function blockCode2strNum($regex_array) {
		  $attributes = $this->attributesStr2attributesXhtml($regex_array[5],'',0,'class');

		$code = $regex_array[3];

		/// In codes in block to display,  blackslashes  mustn't be interpreted. BACKSLASH
		/// CANCEL cancel BACKSLASH PING actions in $code.

		## BACKSLASH CANCEL
		if ($this->getParam('active_backslash')) {
			$regex = '#char_bs_num_([0-9]+)#m';
			$code = preg_replace_callback($regex,array(&$this,'strNum2charBackslashed2'),$code);
		}

		if ($this->getParam('blockcode_with_color')) {

			$language = strtolower(trim($regex_array[2]));
			if (preg_match('#html#',$language)) $language = 'xml';
			require_once dirname(__FILE__).'/geshi/geshi.php';
			$code2color = new geshi ($code,$language);
			$languages_with_css = $this->getParam('blockcode_languages_with_css');
			if (is_array($languages_with_css)) foreach ($languages_with_css as $language_with_css) {
				if (preg_match('#'.$language_with_css.'#',$language)) $code2color->enable_classes();
			}
			$code = $code2color->parse_code();

			if (preg_match('#<pre([^>]*)class="#',$code)) {
				$code = preg_replace('#<pre([^>]*)class="#','<pre\1class="code ',$code);
			}
			else $code = preg_replace('#<pre#','<pre class="code"',$code);
		}
		else $code = '<pre class="code">'.$this->htmlentities($code).'</pre>';

		$num = sizeof($this->block_code);

		$this->block_code[$num] = $code;

		return '))block_code_'.$num;

	}
	function xhtml2strNum($regex_array) {
		$i = sizeof($this->xhtml_blended);
		$xhtml = $regex_array[2];

		/// In inserted xhtml parts, blackslashes mustn't be interpreted. BACKSLASH CANCEL
		/// cancel BACKSLASH PING actions in $xhtml.

		## BACKSLASH CANCEL
		if ($this->getParam('active_backslash')) {
			$regex = '#char_bs_num_([0-9]+)#m';
			$xhtml = preg_replace_callback($regex,array(&$this,'strNum2charBackslashed2'),$xhtml);
		}

		$this->xhtml_blended[$i] = $xhtml;
		if ($regex_array[1] && $regex_array[3]) $before = $regex_array[1].'))';
		else $before = '';

		return $before.'xhtml_blended_num_'.$i;
	}
	function transformHr($regex_array) {
		  $attributes = $this->attributesStr2attributesXhtml($regex_array[2],'class');
		return $regex_array[1].'))<hr'.$attributes.' />';

	}
	function transformLink($regex_array) {
		  $content = trim($regex_array[2]);
		if (preg_match('#^\s*(::|;;|$)#',$regex_array[1]) && preg_match('#^[a-zA-Z0-9]{2,6}://#',$content)) {
			$attributes_str = $content.$regex_array[1];
		}
		else $attributes_str = $regex_array[1];
		  $attributes = $this->attributesStr2attributesXhtml($attributes_str,'href::title::lang::id');
		//### WIKIPEDIA EXTENSION ###//
		if ($this->GetParam('active_link_wikipedia')) {
			if (preg_match('#href="(?:wp|wikipedia)(?:\s*:\s*([^"]+)|)"#',$attributes,$word)) {
				$wikipedia_url = 'http://'.$this->GetParam('link_wikipedia_lang').'.wikipedia.org/wiki/';
				if ($word[1] != '') $word = $word[1];
				else $word = $content;
				$attributes =
				preg_replace('#href="[^"]+"#U','href="'.$wikipedia_url.$word.'"',$attributes);
				if (!preg_match('#title="#',$attributes)) {
					$attributes.=
					' title="'.$this->GetParam('link_wikipedia_title').'&quot;'.$word.'&quot;'.'"';
				}
			}
		}
		//###########################//
		//### LASTFM EXTENSION ###//
		if ($this->GetParam('active_link_lastfm')) {
			if (preg_match('#href="(?:lf|lastfm)(?:\s*:\s*([^"]+)|)"#',$attributes,$word)) {
				if ($this->GetParam('link_lastfm_lang') == 'en') $lastfm_url = 'http://www.last.fm/music/';
				else $lastfm_url = 'http://www.lastfm.'.$this->GetParam('link_lastfm_lang').'/music/';
				if ($word[1] != '') $word = $word[1];
				else $word = $content;
				$attributes = preg_replace('#href="[^"]+"#U','href="'.$lastfm_url.$word.'"',$attributes);
				if (!preg_match('#title="#',$attributes)) {
					$attributes.= ' title="'.$this->GetParam('link_lastfm_title').$word.'"';
				}
			}
		}
		//###########################//
		return '<a'.$attributes.'>'.$content.'</a>';

	}
	function transformImg($regex_array) {
		  $attributes = $this->attributesStr2attributesXhtml($regex_array[1],'src::alt::class');
		return '<img'.$attributes.' />';

	}
	function transformCite($regex_array) {
		$content_attributes = $this->attributesStr2attributesXhtml($regex_array[1],'class',1);
		  $content = $content_attributes[0];
		  $attributes = $content_attributes[1];
		return '<cite'.$attributes.'>'.$content.'</cite>';

	}
	function transformStrongEmphasis($regex_array) {
		$content_attributes = $this->attributesStr2attributesXhtml($regex_array[1],'class',1);
		  $content = $content_attributes[0];
		  $attributes = $content_attributes[1];
		return '<strong'.$attributes.'>'.$content.'</strong>';

	}
	function transformEmphasis($regex_array) {
		$content_attributes = $this->attributesStr2attributesXhtml($regex_array[1],'class',1);
		  $content = $content_attributes[0];
		  $attributes = $content_attributes[1];
		return '<em'.$attributes.'>'.$content.'</em>';

	}
	function transformDel($regex_array) {
		$content_attributes = $this->attributesStr2attributesXhtml($regex_array[1],'class',1);
		  $content = $content_attributes[0];
		  $attributes = $content_attributes[1];
		return '<del'.$attributes.'>'.$content.'</del>';

	}
	function transformIns($regex_array) {
		$content_attributes = $this->attributesStr2attributesXhtml($regex_array[1],'class',1);
		  $content = $content_attributes[0];
		  $attributes = $content_attributes[1];
		return '<ins'.$attributes.'>'.$content.'</ins>';

	}
	function transformCode($regex_array) {
		$content_attributes = $this->attributesStr2attributesXhtml($regex_array[1],'class',1);
		  $content = $content_attributes[0];
		  $attributes = $content_attributes[1];
		return '<code'.$attributes.'>'.$content.'</code>';

	}
	function transformAbbr($regex_array) {
		  $content = trim($regex_array[2]);
		  $attributes = $this->attributesStr2attributesXhtml($regex_array[1],'title::xml:lang');
		return '<abbr'.$attributes.'>'.$content.'</abbr>';

	}
	function transformQuote($regex_array) {
		$content_attributes = $this->attributesStr2attributesXhtml($regex_array[1],'cite::lang::class',1);
		  $content = $content_attributes[0];
		  $attributes = $content_attributes[1];
		return '<q'.$attributes.'>'.$content.'</q>';

	}
	function transformTitle($regex_array) {
		$level = $this->getParam('first_title_level') + strlen($regex_array[2]) - 1;
		  $entitled = 'h'.$level;
		$content_attributes = $this->attributesStr2attributesXhtml($regex_array[3],'id',1);
		  $content = $content_attributes[0];
		  $attributes = $content_attributes[1];
		return $regex_array[1].'))<'.$entitled.$attributes.'>'.$content.'</'.$entitled.'>';

	}
	function transformBlockquote($regex_array) {
		  $attributes = $this->attributesStr2attributesXhtml($regex_array[4],'cite::lang::class');
		return $regex_array[1].'))'.
		'<blockquote'.$attributes.'>'."\n".$regex_array[2]."\n".$regex_array[3].'))</blockquote>';

	}
	function transformList($content) {
		$content_array = preg_split('#\r?\n#m',$content);
		$content_array[] = false;

		/** @var array every_lists
		[](1)*  [0](2)
		        [1](3)  [](5)  [0](6)
		                       [1](7)*
		                       [2](8)
		        [2](4)
		(1)* : each distinct lists (<ul> or <ol>)
		(2)  : '*' or '#' for respectively ul or ol
		(3)  : content of the list
		(4)  : attributes of the <ul> or <ol>
		(5)  : each item <li>
		(6)  : content of the item : string
		(7)* : content of the item : potential array (an under list) (1)*
		(8)  : attributes of the item
		*/
		/** @var array list_on_levels (temporary array)
		[0..n : levels] (6) in editing : (1)*
		*/

		for ($i=-1;$i<=sizeof($content_array);$i++) {
			//########## ACQUISITION ###########//
			// put in array : $every_lists
			if (preg_match('#^(\s*)([\*\#]*)([\*\#])(.*)$#',$content_array[$i],$preg_li)) {
				if ($preg_li[3] == '*') $this_type = 'ul';
				else $this_type = 'ol';
				$this_level = strlen($preg_li[2]);
				$this_j = $j;

				$diff_level = $this_level - $level;
				if ($diff_level > 0) $this_level = $level+1;
				$same_level = $diff_level == 0;
				$same_type = $this_type == $type;

				if ($diff_level > 0) {
					$list_on_levels[$this_level] = &$item[1];
					$this_j=0;
				}
				elseif ($diff_level < 0) {
					for ($k=0;$k<(-1*$diff_level);$k++) unset($list_on_levels[$level-$k]);
					$this_j = sizeof($list_on_levels[$this_level]) -1;
					if ($list_on_levels[$this_level][$this_j][0] != $this_type) $this_j++;
				}
				elseif (!$same_type) $this_j++;

				if ($this_j != $j || !$same_level) $list_on_levels[$this_level][$this_j][0] = $this_type;

				$item = &$list_on_levels[$this_level][$this_j][1][];

				//// ATTRIBUTES / CONTENT ACQUISITION ////
				$li_content_liul_attributes = preg_split('#\]\]#',$preg_li[4]);
				$content_attributes =
				$this->attributesStr2attributesXhtml($li_content_liul_attributes[0],'class',1);
				$item[0] = $content_attributes[0];
				$item[2] = $content_attributes[1];

				// UL / OL
				$ul_attributes = trim($li_content_liul_attributes[1]);
				if ($ul_attributes != '') {
					if ($list_on_levels[$this_level][$this_j][2] != '') {
						$ul_attributes = '::'.$ul_attributes;
					}
					$list_on_levels[$this_level][$this_j][2].=
					$this->attributesStr2attributesXhtml($ul_attributes);
				}
				//////////////////////////////////////////

				$level = $this_level;
				$type = $this_type;
				$j = $this_j;
			}
			//##################################//

			//########## TRANSFORMATION ###########//
			// array --2--> xhtml
			else {
				if (isset($this_j)) {
					unset($this_j);
					$xhtml_str_lists = $this->liArray2liXhtml($every_lists);
					$xhtml_array_lists = preg_split('#\r?\n#m',$xhtml_str_lists);
					for ($k=0;$k<sizeof($xhtml_array_lists);$k++) {
						$new_content_array[] =
						preg_replace('#^(\s*)<#','\1))<',$xhtml_array_lists[$k]);
					}
				}
				unset($every_lists);
				unset($list_on_levels);
				unset($type);
				$every_lists = &$list_on_levels[0];
				$j = -1;
				$level = 0;
				if (isset($content_array[$i])) $new_content_array[] = $content_array[$i];
			}
			//######################################//
		}
		$content = implode("\n",$new_content_array);
		return $content;
	}
	function transformParagraph($content) {
		$content_array = preg_split('#\r?\n#m',$content);

		$p_line_nb = 0;
		for ($i=-1;$i<=sizeof($content_array);$i++) {
			$is_p = preg_match('#^[^=*+]+#',trim($content_array[$i]));
			$is_desactivated = preg_match('#^(\s*)\)\)(.*)$#',$content_array[$i],$line);
			$is_p = $is_p && !$is_desactivated;
			if ($is_desactivated) $content_array[$i] = $line[1].$line[2];
			if ($is_p) $p_line_nb++;

			if (!$is_p && $p_line_nb > 0) {

				$p_open_position = $i - $p_line_nb;
				$p_close_position = $i - 1;

				$last_p_line_attributes =
				$this->attributesStr2attributesXhtml($content_array[$p_close_position],'class',1);
				$last_p_line = $last_p_line_attributes[0];
				$attributes = $last_p_line_attributes[1];

				if ($p_open_position != $p_close_position) {
					$first_p_line = $content_array[$p_open_position];
				}
				else $first_p_line = $last_p_line;
				preg_match('#^(\s*)(.*)$#',$first_p_line,$first_p_line); // <p> beetween

				if ($p_open_position == $p_close_position) {
					$line = $first_p_line;
					$line_position = $p_open_position;
					$content_array[$line_position] = $line[1].'<p'.$attributes.'>'.$line[2].'</p>';
				}
				else {
					$content_array[$p_open_position] =
					$first_p_line[1].'<p'.$attributes.'>'.$first_p_line[2];
					$content_array[$p_close_position] = $last_p_line.'</p>';
				}
				$p_line_nb = 0;
			}
		}
		$content = implode("\n",$content_array);
		return $content;
	}
	function transformHtmlentities($regex_array) {
		return $regex_array[1].$this->htmlentities($regex_array[2]).$regex_array[3];
	}
	function strNum2block_code($num) {
		$num = $num[1];
		return $this->block_code[$num];
	}
	function strNum2xhtml($num) {
		$num = $num[1];
		return $this->xhtml_blended[$num];

	}
	function strNum2charBackslashed($num) {
		$num = $num[1];
		return $this->htmlentities($this->backslash[$num]);
	}
	function strNum2charBackslashed2($num) {
		$num = $num[1];
		return '\\'.$this->htmlentities($this->backslash[$num]);
	}





	function attributesStr2attributesXhtml($attributes_str,$pattern='class',$with_content=false,$pattern_forbidden='') {

		if ($pattern != '') $attributes_keys = preg_split('#::#',$pattern);
		if ($pattern_forbidden != '') $pattern_forbidden = preg_split('#::#',$pattern_forbidden);

		//// attributesStr to attributesArray ////
		$attributes_content_array = preg_split('#::|;;#',$attributes_str,-1,PREG_SPLIT_OFFSET_CAPTURE);

		for ($i=0;$i<sizeof($attributes_content_array);$i++) {
			$type_position = $attributes_content_array[$i][1] - 1; // : or +
			if ($type_position < 0) $attributes_content_array[$i][1] = ':';
			else $attributes_content_array[$i][1] = $attributes_str[$type_position];
		}

		$first_is_not_an_attribute =
		trim($attributes_content_array[0][0]) == '' && $attributes_content_array[1][1] != ':';
		$first_is_not_an_attribute = $first_is_not_an_attribute || $with_content;

		if ($first_is_not_an_attribute) {
			if ($with_content) $content = trim($attributes_content_array[0][0]);
			for ($i=1;$i<sizeof($attributes_content_array);$i++) {
				$attributes_array[$i-1] = $attributes_content_array[$i];
			}
		}
		else $attributes_array = $attributes_content_array;
		unset($attributes_content_array);

		//////////////////////////////////////////

		//// attributesArray to attributesXhtml ////
		$j=0;
		for ($i=0;$i<sizeof($attributes_array);$i++) {
			if ($attributes_array[$i][1] == ':') {
				if (isset($attributes_keys[$j])) {
					$attributes_final_array[$attributes_keys[$j]] = trim($attributes_array[$i][0]);
					$j++;
				}
			}
			elseif ($attributes_array[$i][1] == ';') {
				$regex = '#^\s*([a-zA-Z0-9]+)\s*[^a-zA-Z0-9](.*)$#';
				if (preg_match($regex,$attributes_array[$i][0],$attribute)) {
					$attributes_final_array[trim($attribute[1])] = trim($attribute[2]);
				}
			}
		}

		$attributes_str = '';
		if (is_array($attributes_final_array))
		foreach ($attributes_final_array as $attribute_key => $attribute_value) {

			$forbidden = false;

			for ($i=0;$i<sizeof($pattern_forbidden);$i++) {
				if (preg_match('#'.preg_quote($attribute_key).'#i',$pattern_forbidden[$i])) {
					$forbidden = true;
				}
			}

			if (!$forbidden) {
				## EMPTY VALUES PING
				if ($this->htmlentities($attribute_value) == '') {
					$attributes_str.= ' '.$this->htmlentities($attribute_key).'=two_double_quotation_marks';
				}
				else {
					$attributes_str.= ' '.$this->htmlentities($attribute_key).'="'.
					  $this->htmlentities($attribute_value).'"';
				}
			}
		}
		////////////////////////////////////////////

		if ($with_content) {
			$returned[0] = $content;
			$returned[1] = $attributes_str;
		}
		else $returned = $attributes_str;

		return $returned;
	}








	function liArray2liXhtml($li_array) {
		$li_xhtml = '';
		for ($i=0;$i<sizeof($li_array);$i++) { // for each list (ul or ol)
			$tag = $li_array[$i][0];
			$items_xhtml = '';

			for ($j=0;$j<sizeof($li_array[$i][1]);$j++) { // for each item (li)
				if ($j>0) $items_xhtml.= "\n";

				$content = $li_array[$i][1][$j][0];
				$attributes = $li_array[$i][1][$j][2];

				$items_xhtml.= '<li'.$attributes.'>';
				$items_xhtml.= $content;
				if (is_array($li_array[$i][1][$j][1])) { // under-list
					$items_xhtml.= "\n";
					$items_xhtml.=
					$this->tabulationner($this->liArray2liXhtml($li_array[$i][1][$j][1]));
					$items_xhtml.= "\n";
				}
				$items_xhtml.=