I needed a function to rotate the results of a preg_match_all query, and made this. Not sure if it exists.
<?php
function turn_array($m)
{
for ($z = 0;$z < count($m);$z++)
{
for ($x = 0;$x < count($m[$z]);$x++)
{
$rt[$x][$z] = $m[$z][$x];
}
}
return $rt;
}
?>
Example - Take results of some preg_match_all query:
Array
(
[0] => Array
(
[1] => Banff
[2] => Canmore
[3] => Invermere
)
[1] => Array
(
[1] => AB
[2] => AB
[3] => BC
)
[2] => Array
(
[1] => 51.1746254
[2] => 51.0938416
[3] => 50.5065193
)
[3] => Array
(
[1] => -115.5719757
[2] => -115.3517761
[3] => -116.0321884
)
[4] => Array
(
[1] => T1L 1B3
[2] => T1W 1N2
[3] => V0B 2G0
)
)
Rotate it 90 degrees to group results as records:
Array
(
[0] => Array
(
[1] => Banff
[2] => AB
[3] => 51.1746254
[4] => -115.5719757
[5] => T1L 1B3
)
[1] => Array
(
[1] => Canmore
[2] => AB
[3] => 51.0938416
[4] => -115.3517761
[5] => T1W 1N2
)
[2] => Array
(
[1] => Invermere
[2] => BC
[3] => 50.5065193
[4] => -116.0321884
[5] => V0B 2G0
)
)
preg_match_all
(PHP 4, PHP 5)
preg_match_all — Realiza una comparación global de una expresión regular
Descripción
$pattern
, string $subject
[, array &$matches
[, int $flags = PREG_PATTERN_ORDER
[, int $offset = 0
]]] )
Busca en subject todas las coincidencias de la expresión
regular dada en pattern y las introduce en
matches en el orden especificado por
flags.
Después haber encontrado la primera coincidencia, las búsquedas subsiguientes continuarán desde el final de la dicha coincidencia.
Parámetros
-
pattern -
El patrón de búsqueda, dado como string.
-
subject -
La cadena de entrada.
-
matches -
Un array con todas las coincidencias en un array multi-dimensional ordenadas según
flags. -
flags -
Puede ser una combinación de las siguientes banderas (observe que no tiene sentido usar
PREG_PATTERN_ORDERjunto conPREG_SET_ORDER):-
PREG_PATTERN_ORDER -
Ordena los resultados de forma que $matches[0] es un array de coincidencias del patrón completo, $matches[1] es un array de cadenas coincidentes por el primer subpatrón entre paréntesis, y así sucesivamente.
<?php
preg_match_all("|<[^>]+>(.*)</[^>]+>|U",
"<b>ejemplo: </b><div align=left>esto es una prueba</div>",
$salida, PREG_PATTERN_ORDER);
echo $salida[0][0] . ", " . $salida[0][1] . "\n";
echo $salida[1][0] . ", " . $salida[1][1] . "\n";
?>El resultado del ejemplo sería:
<b>ejemplo: </b>, <div align=left>esto es una prueba</div> ejemplo: , esto es una prueba
Así, $salida[0] contiene un array de cadenas que coinciden con el patrón completo, y $salida[1] contiene un array de cadenas encerradas entre etiquetas.
-
PREG_SET_ORDER -
Ordena los resultados de forma que $matches[0] es un array del primer conjunto de coincidencias, $matches[1] es un array del segundo conjunto de coincidencias, y así sucesivamente.
<?php
preg_match_all("|<[^>]+>(.*)</[^>]+>|U",
"<b>ejemplo: </b><div align=\"left\">esto es una prueba</div>",
$salida, PREG_SET_ORDER);
echo $salida[0][0] . ", " . $salida[0][1] . "\n";
echo $salida[1][0] . ", " . $salida[1][1] . "\n";
?>El resultado del ejemplo sería:
<b>ejemplo: </b>, ejemplo: <div align="left">esto es una prueba</div>, esto es una prueba
-
PREG_OFFSET_CAPTURE -
Si se pasa esta bandera, por cada coincidencia producida, el índice de la cadena añadida también será devulto. Observe que esto cambia el valor de
matchesdentro de un array donde cada elemento es un array consistente en la cadena coincidente en el índice 0 y su índice de cadena dentro desubjecten el índice 1.
Si no se da un orden de bandera, se asume
PREG_PATTERN_ORDER. -
-
offset -
Normalmente, la búsqueda comienza por el principio de la cadena objetivo. El parámetro opcional
offsetse puede usar para especificar el lugar alternativo desde el cual comenzar la búsqueda (en bytes).Nota:
Usar
offsetno es equivalente a pasar substr($sujeto, $índice) a preg_match() en lugar de la cadena objetivo, ya quepatternpuede contener declaraciones como ^, $ o (?<=x). Véase preg_match() para los ejemplos.
Valores devueltos
Devuelve el número de coincidencias del patrón completo (el cual puede ser cero),
o FALSE si se produjo un error.
Historial de cambios
| Versión | Descripción |
|---|---|
| 5.4.0 |
El parámetro matches ahora es opcional.
|
| 5.3.6 |
Devuelve FALSE si offset
es mayor que la longitud de
subject.
|
| 5.2.2 | Los sub-patrones nominados ahora aceptan la sintaxis (?<nombre>) y (?'nombre') así como (?P<nombre>). Versiones anteriores sólo aceptaban (?P<nombre>). |
| 4.3.3 |
Se añadió el parámetro offset
|
| 4.3.0 |
Se añadió la bandera PREG_OFFSET_CAPTURE
|
Ejemplos
Ejemplo #1 Obtener todos los números de teléfono de algún texto.
<?php
preg_match_all("/\(? (\d{3})? \)? (?(1) [\-\s] ) \d{3}-\d{4}/x",
"Llame al 555-1212 o al 1-800-555-1212", $teléfonos);
?>
Ejemplo #2 Buscar coincidencias de etiquetas HTML (codicioso)
<?php
// El \\2 es un ejemplo de retro-referencia. Esto le dice a pcre que
// debe coincidir el segundo conjunto de paréntesis en la expresión regular
// misma, el cual sería el ([\w]+) en este caso. La barra invertida extra es
// necesaria ya que la cadena está entre comilla dobles.
$html = "<b>texto en negrita</b><a href=hola.html>Haz clic sobre mí</a>";
preg_match_all("/(<([\w]+)[^>]*>)(.*?)(<\/\\2>)/", $html, $coincidencias, PREG_SET_ORDER);
foreach ($coincidencias as $valor) {
echo "coindicido: " . $valor[0] . "\n";
echo "parte 1: " . $valor[1] . "\n";
echo "parte 2: " . $valor[2] . "\n";
echo "parte 3: " . $valor[3] . "\n";
echo "parte 4: " . $valor[4] . "\n\n";
}
?>
El resultado del ejemplo sería:
coindicido: <b>texto en negrita</b> parte 1: <b> parte 2: b parte 3: texto en negrita parte 4: </b> coindicido: <a href=hola.html>Haz clic sobre mí</a> parte 1: <a href=hola.html> parte 2: a parte 3: Haz clic sobre mí parte 4: </a>
Ejemplo #3 Usar sub-patrones nominados
<?php
$cadena = <<<FOO
a: 1
b: 2
c: 3
FOO;
preg_match_all('/(?P<nombre>\w+): (?P<dígito>\d+)/', $cadena, $coincidencias);
/* Esto también funciona en PHP 5.2.2 (PCRE 7.0) y posteriores, sin embargo
* la forma de arriba es la recomendada por compatibilidad con versiones anteriores */
// preg_match_all('/(?<nombre>\w+): (?<dígito>\d+)/', $cadena, $coincidencias);
print_r($coincidencias);
?>
El resultado del ejemplo sería:
Array
(
[0] => Array
(
[0] => a: 1
[1] => b: 2
[2] => c: 3
)
[nombre] => Array
(
[0] => a
[1] => b
[2] => c
)
[1] => Array
(
[0] => a
[1] => b
[2] => c
)
[dígito] => Array
(
[0] => 1
[1] => 2
[2] => 3
)
[2] => Array
(
[0] => 1
[1] => 2
[2] => 3
)
)
Ver también
- Patrones de PCRE
- preg_match() - Realiza una comparación con una expresión regular
- preg_replace() - Realiza una búsqueda y sustitución de una expresión regular
- preg_split() - Divide una cadena mediante una expresión regular
- preg_last_error() - Devuelve el código de error de la última ejecución de expresión regular PCRE
Here is a function that replaces all occurrences of a number in a string by the number--
<?php
function decremente_chaine($chaine)
{
//récupérer toutes les occurrences de nombres et leurs indices
preg_match_all("/[0-9]+/",$chaine,$out,PREG_OFFSET_CAPTURE);
//parcourir les occurrences
for($i=0;$i<sizeof($out[0]);$i++)
{
$longueurnombre = strlen((string)$out[0][$i][0]);
$taillechaine = strlen($chaine);
// découper la chaine en 3 morceaux
$debut = substr($chaine,0,$out[0][$i][1]);
$milieu = ($out[0][$i][0])-1;
$fin = substr($chaine,$out[0][$i][1]+$longueurnombre,$taillechaine);
// si c'est 10,100,1000 etc. on décale tout de 1 car le résultat comporte un chiffre de moins
if(preg_match('#[1][0]+$#', $out[0][$i][0]))
{
for($j = $i+1;$j<sizeof($out[0]);$j++)
{
$out[0][$j][1] = $out[0][$j][1] -1;
}
}
$chaine = $debut.$milieu.$fin;
}
return $chaine;
}
?>
Extract fields out of csv string : ( since before php5.3 you can't use str_getcsv function )
Here is the regex :
<?php
$csvData = <<<EOF
10,'20',"30","'40","'50'","\"60","70,80","09\\/18,/\"2011",'a,sdfcd'
EOF
$reg = <<<EOF
/
(
(
([\'\"])
(
(
[^\'\"]
|
(\\\\.)
)*
)
(\\3)
|
(
[^,]
|
(\\\\.)
)*
),)
/x
EOF;
preg_match_all($reg,$csvData,$matches);
// to extract csv fields
print_r($matches[2]);
?>
i have made up a simple function to extract a number from a string..
I am not sure how good it is, but it works.
It gets only the numbers 0-9, the "-", " ", "(", ")", "."
characters.. This is as far as I know the most widely used characters for a Phone number.
<?php
function clean_phone_number($phone) {
if (!empty($phone)) {
//var_dump($phone);
preg_match_all('/[0-9\(\)+.\- ]/s', $phone, $cleaned);
foreach($cleaned[0] as $k=>$v) {
$ready .= $v;
}
var_dump($ready);
die;
if (mb_strlen($cleaned) > 4 && mb_strlen($cleaned) <=25) {
return $cleaned;
}
else {
return false;
}
}
return false;
}
?>
Perhaps you want to find the positions of all anchor tags. This will return a two dimensional array of which the starting and ending positions will be returned.
<?php
function getTagPositions($strBody)
{
define(DEBUG, false);
define(DEBUG_FILE_PREFIX, "/tmp/findlinks_");
preg_match_all("/<[^>]+>(.*)<\/[^>]+>/U", $strBody, $strTag, PREG_PATTERN_ORDER);
$intOffset = 0;
$intIndex = 0;
$intTagPositions = array();
foreach($strTag[0] as $strFullTag) {
if(DEBUG == true) {
$fhDebug = fopen(DEBUG_FILE_PREFIX.time(), "a");
fwrite($fhDebug, $fulltag."\n");
fwrite($fhDebug, "Starting position: ".strpos($strBody, $strFullTag, $intOffset)."\n");
fwrite($fhDebug, "Ending position: ".(strpos($strBody, $strFullTag, $intOffset) + strlen($strFullTag))."\n");
fwrite($fhDebug, "Length: ".strlen($strFullTag)."\n\n");
fclose($fhDebug);
}
$intTagPositions[$intIndex] = array('start' => (strpos($strBody, $strFullTag, $intOffset)), 'end' => (strpos($strBody, $strFullTag, $intOffset) + strlen($strFullTag)));
$intOffset += strlen($strFullTag);
$intIndex++;
}
return $intTagPositions;
}
$strBody = 'I have lots of <a href="http://my.site.com">links</a> on this <a href="http://my.site.com">page</a> that I want to <a href="http://my.site.com">find</a> the positions.';
$strBody = strip_tags(html_entity_decode($strBody), '<a>');
$intTagPositions = getTagPositions($strBody);
print_r($intTagPositions);
/*****
Output:
Array (
[0] => Array (
[start] => 15
[end] => 53 )
[1] => Array (
[start] => 62
[end] => 99 )
[2] => Array (
[start] => 115
[end] => 152 )
)
*****/
?>
PREG_OFFSET_CAPTURE always seems to provide byte offsets, rather than character position offsets, even when you are using the unicode /u modifier.
if you want to extract all {token}s from a string:
<?php
$pattern = "/{[^}]*}/";
$subject = "{token1} foo {token2} bar";
preg_match_all($pattern, $subject, $matches);
print_r($matches);
?>
output:
Array
(
[0] => Array
(
[0] => {token1}
[1] => {token2}
)
)
As I intended to create for my own purpose a clean PHP class to act on XML files, combining the use of DOM and simplexml functions, I had that small problem, but very annoying, that the offsets in a path is not numbered the same in both.
That is to say, for example, if i get a DOM xpath object it appears like:
/ANODE/ANOTHERNODE/SOMENODE[9]/NODE[2]
and as a simplexml object would be equivalent to:
ANODE->ANOTHERNODE->SOMENODE[8]->NODE[1]
So u see what I mean? I used preg_match_all to solve that problem, and finally I got this after some hours of headlock (as I'm french the names of variables are in French sorry), hoping it could be useful to some of you:
<?php
function decrease_string($string)
{
/* retrieve all occurrences AND offsets of numbers in the original string: */
preg_match_all("/[0-9]+/",$chaine,$out,PREG_OFFSET_CAPTURE);
for($i=0;$i<sizeof($out[0]);$i++)
{
$longueurnombre = strlen((string)$out[0][$i][0]);
$taillechaine = strlen($chaine);
// cut the string in 3 pieces
$debut = substr($chaine,0,$out[0][$i][1]);
$milieu = ($out[0][$i][0])-1;
$fin = substr($chaine,$out[0][$i][1]+$longueurnombre,$taillechaine);
/* if it's 10,100,1000, the problem is that the string gets shorter and it shifts all the offsets, so we have to decrease them of 1 */
if(preg_match('#[1][0]+$#', $out[0][$i][0]))
{
for($j = $i+1;$j<sizeof($out[0]);$j++)
{
$out[0][$j][1] = $out[0][$j][1] -1;
}
}
$chaine = $debut.$milieu.$fin;
}
return $chaine;
}
?>
Using preg_match_all I made a pretty handy function.
<?php
function reg_smart_replace($pattern, $replacement, $subject, $replacementChar = "$$$", $limit = -1)
{
if (! $pattern || ! $subject || ! $replacement ) { return false; }
$replacementChar = preg_quote($replacementChar);
preg_match_all ( $pattern, $subject, $matches);
if ($limit > -1) {
foreach ($matches as $count => $value )
{
if ($count + 1 > $limit ) { unset($matches[$count]); }
}
}
foreach ($matches[0] as $match) {
$rep = ereg_replace($replacementChar, $match, $replacement);
$subject = ereg_replace($match, $rep, $subject);
}
return $subject;
}
?>
This function can turn blocks of text into clickable links or whatever. Example:
<?php
reg_smart_replace(EMAIL_REGEX, '<a href="mailto:$$$">$$$</a>', $description)
?>
will turn all email addresses into actual links.
Just substitute $$$ with the text that will be found by the regex. If you can't use $$$ then use the 4th parameter $replacementChar
If you'd like to include DOUBLE QUOTES on a regular expression for use with preg_match_all, try ESCAPING THRICE, as in: \\\"
For example, the pattern:
'/<table>[\s\w\/<>=\\\"]*<\/table>/'
Should be able to match:
<table>
<row>
<col align="left" valign="top">a</col>
<col align="right" valign="bottom">b</col>
</row>
</table>
.. with all there is under those table tags.
I'm not really sure why this is so, but I tried just the double quote and one or even two escape characters and it won't work. In my frustration I added another one and then it's cool.
Better use preg_replace to convert text in a clickable link with tag <a>
$html = preg_replace('"\b(http://\S+)"', '<a href="$1">$1</a>', $text);
For parsing queries with entities use:
<?php
preg_match_all("/(?:^|(?<=\&(?![a-z]+\;)))([^\=]+)=(.*?)(?:$|\&(?![a-z]+\;))/i",
$s, $m, PREG_SET_ORDER );
?>
The next function works with almost any complex xml/xhtml string
<?php
/**
* Find and close unclosed xml tags
**/
function close_tags($text) {
$patt_open = "%((?<!</)(?<=<)[\s]*[^/!>\s]+(?=>|[\s]+[^>]*[^/]>)(?!/>))%";
$patt_close = "%((?<=</)([^>]+)(?=>))%";
if (preg_match_all($patt_open,$text,$matches))
{
$m_open = $matches[1];
if(!empty($m_open))
{
preg_match_all($patt_close,$text,$matches2);
$m_close = $matches2[1];
if (count($m_open) > count($m_close))
{
$m_open = array_reverse($m_open);
foreach ($m_close as $tag) $c_tags[$tag]++;
foreach ($m_open as $k => $tag) if ($c_tags[$tag]--<=0) $text.='</'.$tag.'>';
}
}
}
return $text;
}
?>
I have received complains, that my html2a() code (see below) doesn't work in some cases.
It is however not the problem with algorithm or procedure, but with PCRE recursive stack limits.
If you use recursive PCRE (?R) you should remember to increase those two ini settings:
ini_set('pcre.backtrack_limit', 10000000);
ini_set('pcre.recursion_limit', 10000000);
But be warned: (from php.ini)
;Please note that if you set this value to a high number you may consume all
;the available process stack and eventually crash PHP (due to reaching the
;stack size limit imposed by the Operating System).
I have written this example mainly to demonstrate the power of PCRE LANGUAGE, not the power of it's implementation :)
But if you like it, use it, of course on your own risk.
Recently I had to write search engine in hebrew and ran into huge amount of problems. My data was stored in MySQL table with utf8_bin encoding.
So, to be able to write hebrew in utf8 table you need to do
<?php
$prepared_text = addslashes(urf8_encode($text));
?>
But then I had to find if some word exists in stored text. This is the place I got stuck. Simple preg_match would not find text since hebrew doesnt work that easy. I've tried with /u and who kows what else.
Solution was somewhat logical and simple...
<?php
$db_text = bin2hex(stripslashes(utf8_decode($db_text)));
$word = bin2hex($word);
$found = preg_match_all("/($word)+/i", $db_text, $matches);
?>
I've used preg_match_all since it returns number of occurences. So I could sort search results acording to that.
Hope someone finds this useful!
Here is a way to match everything on the page, performing an action for each match as you go. I had used this idiom in other languages, where its use is customary, but in PHP it seems to be not quite as common.
<?php
function custom_preg_match_all($pattern, $subject)
{
$offset = 0;
$match_count = 0;
while(preg_match($pattern, $subject, $matches, PREG_OFFSET_CAPTURE, $offset))
{
// Increment counter
$match_count++;
// Get byte offset and byte length (assuming single byte encoded)
$match_start = $matches[0][1];
$match_length = strlen(matches[0][0]);
// (Optional) Transform $matches to the format it is usually set as (without PREG_OFFSET_CAPTURE set)
foreach($matches as $k => $match) $newmatches[$k] = $match[0];
$matches = $new_matches;
// Your code here
echo "Match number $match_count, at byte offset $match_start, $match_length bytes long: ".$matches[0]."\r\n";
// Update offset to the end of the match
$offset = $match_start + $match_length;
}
return $match_count;
}
?>
Note that the offsets returned are byte values (not necessarily number of characters) so you'll have to make sure the data is single-byte encoded. (Or have a look at paolo mosna's strByte function on the strlen manual page).
I'd be interested to know how this method performs speedwise against using preg_match_all and then recursing through the results.
I found simpleXML to be useful only in cases where the XML was extremely small, otherwise the server would run out of memory (I suspect there is a memory leak or something?). So while searching for alternative parsers, I decided to try a simpler approach. I don't know how this compares with cpu usage, but I know it works with large XML structures. This is more a manual method, but it works for me since I always know what structure of data I will be receiving.
Essentially I just preg_match() unique nodes to find the values I am looking for, or I preg_match_all to find multiple nodes. This puts the results in an array and I can then process this data as I please.
I was unhappy though, that preg_match_all() stores the data twice (requiring twice the memory), one array for all the full pattern matches, and one array for all the sub pattern matches. You could probably write your own function that overcame this. But for now this works for me, and I hope it saves someone else some time as well.
// SAMPLE XML
<RETS ReplyCode="0" ReplyText="Operation Successful">
<COUNT Records="14" />
<DELIMITER value="09" />
<COLUMNS>PropertyID</COLUMNS>
<DATA>521897</DATA>
<DATA>677208</DATA>
<DATA>686037</DATA>
</RETS>
<?PHP
// SAMPLE FUNCTION
function parse_xml($xml) {
// GET DELIMITER (single instance)
$match_res = preg_match('/<DELIMITER value ?= ?"(.*)" ?\/>/', $xml, $matches);
if(!empty($matches[1])) {
$results["delimiter"] = chr($matches[1]);
} else {
// DEFAULT DELIMITER
$results["delimiter"] = "\t";
}
unset($match_res, $matches);
// GET MULTIPLE DATA NODES (multiple instances)
$results["data_count"] = preg_match_all("/<DATA>(.*)<\/DATA>/", $xml, $matches);
// GET MATCHES OF SUB PATTERN, DISCARD THE REST
$results["data"]=$matches[1];
unset($match_res, $matches);
// UNSET XML TO SAVE MEMORY (should unset outside the function as well)
unset($xml);
// RETURN RESULTS ARRAY
return $results;
}
?>
To count str_length in UTF-8 string i use
$count = preg_match_all("/[[:print:]\pL]/u", $str, $pockets);
where
[:print:] - printing characters, including space
\pL - UTF-8 Letter
/u - UTF-8 string
other unicode character properties on http://www.pcre.org/pcre.txt
please note, that the function of "mail at SPAMBUSTER at milianw dot de" can result in invalid xhtml in some cases. think i used it in the right way but my result is sth like this:
<img src="./img.jpg" alt="nice picture" />foo foo foo foo </img>
correct me if i'm wrong.
i'll see when there's time to fix that. -.-
<?php
// Returns an array of strings where the start and end are found
function findinside($start, $end, $string) {
preg_match_all('/' . preg_quote($start, '/') . '([^\.)]+)'. preg_quote($end, '/').'/i', $string, $m);
return $m[1];
}
$start = "mary has";
$end = "lambs.";
$string = "mary has 6 lambs. phil has 13 lambs. mary stole phil's lambs. now mary has all the lambs.";
$out = findinside($start, $end, $string);
print_r ($out);
/* Results in
(
[0] => 6
[1] => all the
)
*/
?>
This is a function to convert byte offsets into (UTF-8) character offsets (this is reagardless of whether you use /u modifier:
<?php
function mb_preg_match_all($ps_pattern, $ps_subject, &$pa_matches, $pn_flags = PREG_PATTERN_ORDER, $pn_offset = 0, $ps_encoding = NULL) {
// WARNING! - All this function does is to correct offsets, nothing else:
//
if (is_null($ps_encoding))
$ps_encoding = mb_internal_encoding();
$pn_offset = strlen(mb_substr($ps_subject, 0, $pn_offset, $ps_encoding));
$ret = preg_match_all($ps_pattern, $ps_subject, $pa_matches, $pn_flags, $pn_offset);
if ($ret && ($pn_flags & PREG_OFFSET_CAPTURE))
foreach($pa_matches as &$ha_match)
foreach($ha_match as &$ha_match)
$ha_match[1] = mb_strlen(substr($ps_subject, 0, $ha_match[1]), $ps_encoding);
//
// (code is independent of PREG_PATTER_ORDER / PREG_SET_ORDER)
return $ret;
}
?>
Here's some fleecy code to 1. validate RCF2822 conformity of address lists and 2. to extract the address specification (the part commonly known as 'email'). I wouldn't suggest using it for input form email checking, but it might be just what you want for other email applications. I know it can be optimized further, but that part I'll leave up to you nutcrackers. The total length of the resulting Regex is about 30000 bytes. That because it accepts comments. You can remove that by setting $cfws to $fws and it shrinks to about 6000 bytes. Conformity checking is absolutely and strictly referring to RFC2822. Have fun and email me if you have any enhancements!
<?php
function mime_extract_rfc2822_address($string)
{
//rfc2822 token setup
$crlf = "(?:\r\n)";
$wsp = "[\t ]";
$text = "[\\x01-\\x09\\x0B\\x0C\\x0E-\\x7F]";
$quoted_pair = "(?:\\\\$text)";
$fws = "(?:(?:$wsp*$crlf)?$wsp+)";
$ctext = "[\\x01-\\x08\\x0B\\x0C\\x0E-\\x1F" .
"!-'*-[\\]-\\x7F]";
$comment = "(\\((?:$fws?(?:$ctext|$quoted_pair|(?1)))*" .
"$fws?\\))";
$cfws = "(?:(?:$fws?$comment)*(?:(?:$fws?$comment)|$fws))";
//$cfws = $fws; //an alternative to comments
$atext = "[!#-'*+\\-\\/0-9=?A-Z\\^-~]";
$atom = "(?:$cfws?$atext+$cfws?)";
$dot_atom_text = "(?:$atext+(?:\\.$atext+)*)";
$dot_atom = "(?:$cfws?$dot_atom_text$cfws?)";
$qtext = "[\\x01-\\x08\\x0B\\x0C\\x0E-\\x1F!#-[\\]-\\x7F]";
$qcontent = "(?:$qtext|$quoted_pair)";
$quoted_string = "(?:$cfws?\"(?:$fws?$qcontent)*$fws?\"$cfws?)";
$dtext = "[\\x01-\\x08\\x0B\\x0C\\x0E-\\x1F!-Z\\^-\\x7F]";
$dcontent = "(?:$dtext|$quoted_pair)";
$domain_literal = "(?:$cfws?\\[(?:$fws?$dcontent)*$fws?]$cfws?)";
$domain = "(?:$dot_atom|$domain_literal)";
$local_part = "(?:$dot_atom|$quoted_string)";
$addr_spec = "($local_part@$domain)";
$display_name = "(?:(?:$atom|$quoted_string)+)";
$angle_addr = "(?:$cfws?<$addr_spec>$cfws?)";
$name_addr = "(?:$display_name?$angle_addr)";
$mailbox = "(?:$name_addr|$addr_spec)";
$mailbox_list = "(?:(?:(?:(?<=:)|,)$mailbox)+)";
$group = "(?:$display_name:(?:$mailbox_list|$cfws)?;$cfws?)";
$address = "(?:$mailbox|$group)";
$address_list = "(?:(?:^|,)$address)+";
//output length of string (just so you see how f**king long it is)
echo(strlen($address_list) . " ");
//apply expression
preg_match_all("/^$address_list$/", $string, $array, PREG_SET_ORDER);
return $array;
};
?>
The power of pregs is limited only by your *imagination* :)
I wrote this html2a() function using preg recursive match (?R) which provides quite safe and bulletproof html/xml extraction:
<?php
function html2a ( $html ) {
if ( !preg_match_all( '
@
\<\s*?(\w+)((?:\b(?:\'[^\']*\'|"[^"]*"|[^\>])*)?)\>
((?:(?>[^\<]*)|(?R))*)
\<\/\s*?\\1(?:\b[^\>]*)?\>
|\<\s*(\w+)(\b(?:\'[^\']*\'|"[^"]*"|[^\>])*)?\/?\>
@uxis', $html = trim($html), $m, PREG_OFFSET_CAPTURE | PREG_SET_ORDER) )
return $html;
$i = 0;
$ret = array();
foreach ($m as $set) {
if ( strlen( $val = trim( substr($html, $i, $set[0][1] - $i) ) ) )
$ret[] = $val;
$val = $set[1][1] < 0
? array( 'tag' => strtolower($set[4][0]) )
: array( 'tag' => strtolower($set[1][0]), 'val' => html2a($set[3][0]) );
if ( preg_match_all( '
/(\w+)\s*(?:=\s*(?:"([^"]*)"|\'([^\']*)\'|(\w+)))?/usix
', isset($set[5]) && $set[2][1] < 0
? $set[5][0]
: $set[2][0]
,$attrs, PREG_SET_ORDER ) ) {
foreach ($attrs as $a) {
$val['attr'][$a[1]]=$a[count($a)-1];
}
}
$ret[] = $val;
$i = $set[0][1]+strlen( $set[0][0] );
}
$l = strlen($html);
if ( $i < $l )
if ( strlen( $val = trim( substr( $html, $i, $l - $i ) ) ) )
$ret[] = $val;
return $ret;
}
?>
Now let's try it with this example: (there are some really nasty xhtml compliant bugs, but ... we shouldn't worry)
<?php
$html = <<<EOT
some leftover text...
< DIV class=noCompliant style = "text-align:left;" >
... and some other ...
< dIv > < empty> </ empty>
<p> This is yet another text <br >
that wasn't <b>compliant</b> too... <br />
</p>
<div class="noClass" > this one is better but we don't care anyway </div ><P>
<input type= "text" name ='my "name' value = "nothin really." readonly>
end of paragraph </p> </Div> </div> some trailing text
EOT;
$a = html2a($html);
//now we will make some neat html out of it
echo a2html($a);
function a2html ( $a, $in = "" ) {
if ( is_array($a) ) {
$s = "";
foreach ($a as $t)
if ( is_array($t) ) {
$attrs="";
if ( isset($t['attr']) )
foreach( $t['attr'] as $k => $v )
$attrs.=" ${k}=".( strpos( $v, '"' )!==false ? "'$v'" : "\"$v\"" );
$s.= $in."<".$t['tag'].$attrs.( isset( $t['val'] ) ? ">\n".a2html( $t['val'], $in." " ).$in."</".$t['tag'] : "/" ).">\n";
} else
$s.= $in.$t."\n";
} else {
$s = empty($a) ? "" : $in.$a."\n";
}
return $s;
}
?>
This produces:
some leftover text...
<div class="noCompliant" style="text-align:left;">
... and some other ...
<div>
<empty>
</empty>
<p>
This is yet another text
<br/>
that wasn't
<b>
compliant
</b>
too...
<br/>
</p>
<div class="noClass">
this one is better but we don't care anyway
</div>
<p>
<input type="text" name='my "name' value="nothin really." readonly="readonly"/>
end of paragraph
</p>
</div>
</div>
some trailing text
Just few lines of code to convert in a clickable link with tag <a>
the pattern "http://......." in a string:
<?php
$stringa = " bla bla bla http://www.example.com bla bla http://www.example.net bla bla bla";
$m = preg_match_all('/http:\/\/[a-z0-9A-Z.]+(?(?=[\/])(.*))/', $stringa, $match);
if ($m) {
$links=$match[0];
for ($j=0;$j<$m;$j++) {
$stringa=str_replace($links[$j],'<a href="'.$links[$j].'">'.$links[$j].'</a>',$stringa);
}
}
echo $stringa;
?>
