Ir para conteúdo

POWERED BY:

Arquivado

Este tópico foi arquivado e está fechado para novas respostas.

João Batista Neto

Ordenando um XML

Recommended Posts

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

Compartilhar este post


Link para o post
Compartilhar em outros sites

muito bom.

 

estou postando uma versão simples utilizando "simplexml_load_string"

 

 

 

<?php
function foo( $src )
{
    $src = simplexml_load_string( $src );
    foreach( $src as $k => $v )
    {
        $arr[] = str_replace(  '-', '', $v->attributes()->data );
    }
    rsort( $arr );
    return $arr;
}

$xml = <<<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>
XML;


print_r( foo( $xml ) );
?>

 

referências:

http://php.net/simplexml_load_string

http://php.net/rsort

http://php.net/str_replace

Compartilhar este post


Link para o post
Compartilhar em outros sites

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;

Compartilhar este post


Link para o post
Compartilhar em outros sites

×

Informação importante

Ao usar o fórum, você concorda com nossos Termos e condições.