Usamos cookies para medir audiência e melhorar sua experiência. Você pode aceitar ou recusar a qualquer momento. Veja sobre o iMasters.
Bom, aqui vai uma forma de ordenar um XML para quando não se pode utilizar XSL para fazer o trabalho...
Imagine o seguinte XML:
teste1.xml
<?xml version="1.0"?>
<root>
<item data="2008-05-05" />
<item data="2001-05-05" />
<item data="2003-05-05" />
<item data="2005-05-05" />
<item data="2007-05-05" />
<item data="2002-05-05" />
<item data="2006-05-05" />
<item data="2009-05-05" />
<item data="2004-05-05" />
</root>
As datas estão todas desordenadas, com XSL poderíamos usar xsl:sort, porém, como algumas vezes isso não é possível, podemos usar essa extensão de DOMDocument para fazer o trabalho:
function compara( $na , $nb ){
$a = strtotime( $na->getAttribute( "data" ) );
$b = strtotime( $nb->getAttribute( "data" ) );
if ( $a < $b ) return( -1 );
elseif ( $a > $b ) return( 1 );
return( 0 );
}
$dom = new DOM();
$dom->load( "teste1.xml" );
$dom->sort( ".//item" , "compara" );
echo $dom->saveXML();
Para fazer a ordenação, o método sort precisa do XPath (caminho) para o nó que se quer ordenar e de uma função que fará a comparação dos nós; Essa função receberá dois nós e:
Se o nó A for menor que o nó B => retorna -1
Se o nó A e o nó B forem iguais => retorna 0
Se o nó A for maior que o nó B => retorna 1
Depois de processar o arquivo teste1.xml a saída será:
<?xml version="1.0"?>
<root>
<item data="2001-05-05"/>
<item data="2002-05-05"/>
<item data="2003-05-05"/>
<item data="2004-05-05"/>
<item data="2005-05-05"/>
<item data="2006-05-05"/>
<item data="2007-05-05"/>
<item data="2008-05-05"/>
<item data="2009-05-05"/>
</root>
Pode-se também criar várias regras para a ordenação, por exemplo:
teste2.xml
<?xml version="1.0"?>
<root>
<item valor="2" tipo="2" />
<item valor="7" tipo="1"/>
<item valor="3" tipo="1" />
<item valor="8" tipo="2" />
<item valor="5" tipo="1" />
<item valor="10" tipo="2" />
<item valor="9" tipo="1" />
<item valor="6" tipo="2" />
<item valor="1" tipo="1" />
<item valor="4" tipo="2" />
</root>
Primeiro vamos ordenar pelo atributo tipo, depois vamos ordenar só os impares e então só os pares:
$dom = new DOM();
$dom->load( "teste2.xml" );
printf( "Ordenado pelo tipo:\n" );
$dom->sort( ".//item" , create_function( '$na , $nb' , '
$a = (int) $na->getAttribute( "tipo" );
$b = (int) $nb->getAttribute( "tipo" );
if ( $a < $b ) return( -1 );
elseif ( $a > $b ) return( 1 );
return( 0 );
' ) );
printf( "%s\n\n" , $dom->saveXML() );
printf( "Ordenado pelo tipo=1 (impares):\n" );
$dom->sort( ".//item[ @tipo = 1 ]" , create_function( '$na , $nb' , '
$a = (int) $na->getAttribute( "valor" );
$b = (int) $nb->getAttribute( "valor" );
if ( $a < $b ) return( -1 );
elseif ( $a > $b ) return( 1 );
return( 0 );
' ) );
printf( "%s\n\n" , $dom->saveXML() );
printf( "Ordenado pelo tipo=2 (pares):\n" );
$dom->sort( ".//item[ @tipo = 2 ]" , create_function( '$na , $nb' , '
$a = (int) $na->getAttribute( "valor" );
$b = (int) $nb->getAttribute( "valor" );
if ( $a < $b ) return( -1 );
elseif ( $a > $b ) return( 1 );
return( 0 );
' ) );
printf( "%s\n\n" , $dom->saveXML() );
A saída deverá ser:
Ordenado pelo tipo:
<?xml version="1.0"?>
<root>
<item valor="7" tipo="1"/>
<item valor="5" tipo="1"/>
<item valor="1" tipo="1"/>
<item valor="3" tipo="1"/>
<item valor="9" tipo="1"/>
<item valor="6" tipo="2"/>
<item valor="2" tipo="2"/>
<item valor="4" tipo="2"/>
<item valor="10" tipo="2"/>
<item valor="8" tipo="2"/>
</root>
Ordenado pelo tipo=1 (impares):
<?xml version="1.0"?>
<root>
<item valor="1" tipo="1"/>
<item valor="3" tipo="1"/>
<item valor="5" tipo="1"/>
<item valor="7" tipo="1"/>
<item valor="9" tipo="1"/>
<item valor="6" tipo="2"/>
<item valor="2" tipo="2"/>
<item valor="4" tipo="2"/>
<item valor="10" tipo="2"/>
<item valor="8" tipo="2"/>
</root>
Ordenado pelo tipo=2 (pares):
<?xml version="1.0"?>
<root>
<item valor="1" tipo="1"/>
<item valor="3" tipo="1"/>
<item valor="5" tipo="1"/>
<item valor="7" tipo="1"/>
<item valor="9" tipo="1"/>
<item valor="2" tipo="2"/>
<item valor="4" tipo="2"/>
<item valor="6" tipo="2"/>
<item valor="8" tipo="2"/>
<item valor="10" tipo="2"/>
</root>
Veja que a cada ordenação, a ordem antiga foi preservada, criando no final um XML organizado por uma regra especifica para o caso.
Abaixo a classe DOM que estende a classe DOMDocument nativa do PHP:
class DOM extends DOMDocument {
private $temp;
private $compare;
/**
* Ordena um nó do XML especificado pelo XPath
* @param string $xpath O XPath para o nó que será ordenado
* @param string $compare A função que fará a comparação
*/
public function sort( $xpath , $compare ){
if ( !empty( $compare ) && ( is_callable( $compare ) ) ){
$xp = new DOMXPath( $this );
$nodel = $xp->query( $xpath );
$nodea = array();
if ( $nodel->length > 1 ){
$this->compare = $compare;
$this->temp = $xp->query( "./.." , $nodel->item( 0 ) )->item( 0 );
foreach ( $nodel as $node ) $nodea[] = $node;
$this->qsort( $nodea );
}
}
}
/**
* Chama a função do usuário que fará a comparação dos nós
* @param DOMNode $na Nó A
* @param DONNode $nb Nó B
* @return integer Um inteiro no intervalo -1 até 1
*/
private function compare( DOMNode $na , DOMNode $nb ){
$ret = (int) call_user_func( $this->compare , $na , $nb );
if ( ( $ret < -1 ) || ( $ret > 1 ) ){
throw new UnexpectedValueException( sprintf( "A função de comparação deve retornar um valor entre -1 e 1, %d foi retornado." , $ret ) );
}
return( $ret );
}
/**
* Implementação de quicksort para fazer a ordenação
* @param array $m A matriz com os nós encontrados
* @param integer $l Limite inferior da partição
* @param integer $r Limite superior da partição
*/
private function qsort( &$m , $l = 0 , $r = null ){
$r = $r == null ? count( $m ) - 1 : $r;
$i = $l; $j = $r;
$p = $m[ floor( ( $l + $r ) / 2 ) ];
if ( $i < $j ){
while ( $this->compare( $m[ $i ] , $p ) < 0 ) ++$i;
while ( $this->compare( $m[ $j ] , $p ) > 0 ) --$j;
if ( $i <= $j ){
$a = $m[ $i ];
$b = $m[ $j ];
if ( $a !== $b ){
$c = $a->cloneNode( true ); $m[ $i ] = $c;
$d = $b->cloneNode( true ); $m[ $j ] = $d;
$this->temp->replaceChild( $d , $a );
$this->temp->replaceChild( $c , $b );
$t = $m[ $i ]; $m[ $i ] = $m[ $j ]; $m[ $j ] = $t;
}
$i++; $j--;
}
}
if ( $j > $l ) $this->qsort( $m , $l , $j );
if ( $i < $r ) $this->qsort( $m , $i , $r );
}
}>
rsort( $arr );
Legal !!!
Para conseguir ordenar de forma decrescente utilizando a DOM é só inverter a função comparadora:
function crescente( $na , $nb ){
$a = strtotime( $na->getAttribute( "data" ) );
$b = strtotime( $nb->getAttribute( "data" ) );
if ( $a < $b ) return( -1 );
elseif ( $a > $b ) return( 1 );
return( 0 );
}
function decrescente( $na , $nb ){
$a = strtotime( $na->getAttribute( "data" ) );
$b = strtotime( $nb->getAttribute( "data" ) );
if ( $a < $b ) return( 1 );
elseif ( $a > $b ) return( -1 );
return( 0 );
}
$xml = <<<XML
<root>
<item data="2008-05-05" />
<item data="2001-05-05" />
<item data="2003-05-05" />
<item data="2005-05-05" />
<item data="2007-05-05" />
<item data="2002-05-05" />
<item data="2006-05-05" />
<item data="2009-05-05" />
<item data="2004-05-05" />
</root>
XML;
$dom = new DOM();
$dom->loadXML( $xml );
$dom->sort( ".//item" , "crescente" );
echo $dom->saveXML();
$dom->sort( ".//item" , "decrescente" );
echo $dom->saveXML();
Agora um pequeno patch na classe DOM:
No método qsort, as seguintes linhas:
$c = $a->cloneNode(); $m[ $i ] = $c;
$d = $b->cloneNode(); $m[ $j ] = $d;
Devem ser modificadas para:
$c = $a->cloneNode( true ); $m[ $i ] = $c;
$d = $b->cloneNode( true ); $m[ $j ] = $d;
muito bom.
estou postando uma versão simples utilizando "simplexml_load_string"
{$xml = <<<XML
referências:
http://php.net/simplexml_load_string
http://php.net/rsort
http://php.net/str_replace