PHP: ¿Cómo agregar las etiquetas faltantes en cada grupo de elementos en XML?

Este es el XML que tenemos ahora:

<persons>
    <person>
        <firstname>John</firstname>
        <surname>Doe</surname>
        <age></age>
    </person>
    <person>
        <firstname>Jane</firstname>
        <surname>Doe</surname>
        <age></age>
        <sex>Female</sex>
    </person>
</persons>

Como puede ver, el primer grupo de elementos solo tiene tres etiquetas, a saber, nombre, apellido y edad, mientras que el segundo grupo tiene una etiqueta adicional de sexo.

Lo que necesitamos es hacer que todos los grupos de elementos en el XML contengan todas las etiquetas que tiene cada grupo, en este caso el primer grupo también debe contener la etiqueta de sexo pero en un estado en blanco así:

<persons>
    <person>
        <firstname>John</firstname>
        <surname>Doe</surname>
        <age></age>
        <sex></sex>
    </person>
    <person>
        <firstname>Jane</firstname>
        <surname>Doe</surname>
        <age></age>
        <sex>Female</sex>
    </person>
</persons>

Además, ¿qué pasa si hay un tercer, cuarto o en el grupo 50 que tiene otra etiqueta nueva llamada apodo? En este caso, todo el grupo debería tener el apodo de la etiqueta también, pero en un estado en blanco.

¿Cómo podría hacer esto de manera eficiente en PHP?

preguntado el 27 de agosto de 11 a las 18:08

¿Tienes que tenerlos en XML o tienes que tenerlos en el modelo de dominio? ¿Cualquier otra cosa que no sea su aplicación leerá y escribirá en el XML? -

En un XML, no se utiliza ningún modelo de dominio aquí. Una aplicación diferente está escribiendo este tipo de XML y nos gustaría actualizar el archivo XML antes de usarlo en nuestro sistema y luego almacenarlo. -

3 Respuestas

Usar SimpleXML, el script realiza dos pasadas: una para encontrar todas las etiquetas posibles, la otra para crear elementos vacíos:

$str = <<<STR

<persons>
    <person>
        <firstname>John</firstname>
        <surname>Doe</surname>
        <age></age>
    </person>
    <person>
        <firstname>Jane</firstname>
        <surname>Doe</surname>
        <age></age>
        <sex>Female</sex>
    </person>
</persons>

STR;

$xml = simplexml_load_string($str);

// Create an array of all the possible tags
$tags = array();
foreach($xml->person as $person)
{
    $current_tags = array_keys(get_object_vars($person));
    $tags = array_unique(array_merge($tags, $current_tags));
}

// Add empty tags to elements who don't have them
foreach($xml->person as $person)
{
    foreach($tags as $tag)
    {
        if(!property_exists($person, $tag))
        {
            $person->$tag = '';
        }
    }
}

// Output the new XML
echo $xml->asXML();

Respondido 27 ago 11, 23:08

Esto funciona bien, agregando las etiquetas de forma dinámica. Puede que me equivoque, pero parece que las versiones XSLT en las otras respuestas requieren que agregue manualmente nuevas etiquetas al XSL. Esto lo hace automáticamente, que es lo que pedía la pregunta. - Herbert

@Herbert: Es cierto que las soluciones XSLT requieren un ajuste si cambia el esquema. Si la solución debe ser dinámica, la solución de Tim funcionará. De lo contrario, las soluciones XSLT funcionarían. - Daniel Trebbien

@Herbert Con un enfoque dinámico, solo obtendrá nodos que existan al menos una vez en el XML de origen. Si falta un nodo esperado en todos los elementos, simplemente no aparecerá, lo que podría provocar efectos no deseados. El XSLT (! = Schema por cierto) le permite mantener el formato de destino exactamente como lo necesita. Funciona como un adaptador y se asegura de que los cambios externos no afecten a su aplicación. También funciona como documentación para el formato esperado en su aplicación y debería ser significativamente más rápido. - Gordon

@Herbert, si no sabe qué esperar, no usa el servicio :) Quiero decir, ¿cuál es el punto de consumir datos aleatorios arbitrarios? Tu aplicación tiene que conocer al menos algunas estructuras en ella, para hacer algo significativo con ella. Desafortunadamente, officeboi no dice qué está haciendo con los datos. Definitivamente es posible inferir el esquema a partir del XML, pero requiere mucho más esfuerzo (y aún necesitaría un procesamiento adicional para rellenar los elementos faltantes después de eso). Ver kore-nordmann.de/blog/0104_generating_xml_schemas_from_xml.html - pero si lo anterior funciona para el OP: bien :) - Gordon

@Gordon: Una vez más, estoy totalmente de acuerdo. Personalmente, no soñaría con utilizar un servicio que produzca datos arbitrarios. Pero ... lo que quiere el OP ... bueno ... eso es lo que obtiene el OP. :) Por supuesto, ni siquiera dice que provenga de un servicio. Tal vez fue codificado a mano o tal vez los monos lo vomitaron o tal vez ... Supongo que ahora es un punto discutible. Simplemente apoyé la solución porque responde a la pregunta del OP. :) Veo tu punto sobre inferir el esquema. Si proviene de un servicio, debería haber un esquema en su lugar o al menos un DTD para llorar en voz alta. - Herbert

Lo más fácil para hacer que esto se pueda mantener durante mucho tiempo (por ejemplo, anticipando más campos nuevos y cosas así) sería procesar el XML con un XSLT que contenga todos los campos obligatorios:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  <xsl:output method="xml" indent="yes"/>

  <xsl:template match="/persons">
    <persons>
      <xsl:apply-templates select="person"/>
    </persons>
  </xsl:template>

  <xsl:template match="person">
    <person>
        <firstname><xsl:value-of select="firstname"/></firstname>
        <surname><xsl:value-of select="surname"/></surname>
        <age><xsl:value-of select="age"/></age>
        <sex><xsl:value-of select="sex"/></sex>
    </person>
  </xsl:template>

</xsl:stylesheet>

Luego, siempre que obtenga una nueva copia del servicio de generador, haga (manual)

$dom = new DOMDocument;
$dom->load('people.xml');
$xsl = new DOMDocument;
$xsl->load('people.xsl');
$proc = new XSLTProcessor;
$proc->importStyleSheet($xsl);
echo $proc->transformToXML($dom);

Esto producirá (manifestación)

<?xml version="1.0"?>
<persons>
  <person>
    <firstname>John</firstname>
    <surname>Doe</surname>
    <age/>
    <sex/>
  </person>
  <person>
    <firstname>Jane</firstname>
    <surname>Doe</surname>
    <age/>
    <sex>Female</sex>
  </person>
</persons>

Respondido 27 ago 11, 23:08

Gracias por esto, pero necesitamos que sea automatizado ya que no sabremos si hay una nueva etiqueta agregada en el archivo XML que se nos dio. ¡Pero aprendí algo nuevo aquí gracias a ti! - officeboi101

Estoy de acuerdo con @Gordon en que la mejor solución aquí es XSLT. Sin embargo, sugiero usar un XSL ligeramente diferente:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="person">
        <person>
            <xsl:apply-templates select="@* | *[not(self::sex)]"/>
            <sex><xsl:value-of select="sex"/></sex>
        </person>
    </xsl:template>
</xsl:stylesheet>

I probado con el evaluador XSLT en línea de W3Schools y funciona según lo solicitado.

Respondido 27 ago 11, 23:08

¿En qué se diferencia esto de la solución de Gordon? ¿Puede agregar campos dinámicamente sin agregarlos manualmente al archivo XSL? Eso parece ser lo que pide officeboi. Mi voto va a la solución de @ Tim. - Herbert

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.