xml2js: como es la salida?

Estoy tratando de usar el módulo node.js xml2js

Mi código es bastante simple:

function testparse(pathname, callback) { var parser = require('xml2js').Parser(), util = require('util'), fs = require('fs'), fs.readFile(pathname, function (err, data) { parser.parseString(data, function(err, result) { console.log('Complete result:'); console.log(util.inspect(result, {depth: null})); //Work console.log('Try to access element:'); console.log(result.smil.body); //Work console.log(result.smil.body.update); //Undefined }); }); } 

Mi archivo xml es como:

         

La salida me da:

 Complete result: { smil: { head: [''], body: [ { update: [[Object]], stream: [[Object]], playlist: [[Object]] } ] } } Try to access element: [Object] Undefined 

Intenté acceder al cuerpo intentando, pero ahora estoy atascado, ¿hay alguna plantilla o ejemplo de cómo xml2js genera el xml analizado en algún lugar?

xml2js tiene una tarea no envidiable: convertir XML a JSON de una manera que pueda revertirse, sin conocer el esquema de antemano. Parece obvio, al principio:

 Fred → { name: "Fred" }  → { chacha: null } 

Fácil hasta ahora, ¿verdad? ¿Qué tal esto, sin embargo?

 z 

La eliminación de los nombres amigables de los humanos lleva a casa la incertidumbre de xml2js . Al principio, podrías pensar que esto es bastante razonable:

 { x: { y: "z" } } 

Más tarde, tropieza con este texto XML y se da cuenta de que su esquema adivinado estaba equivocado:

 zz2 

UH oh. Tal vez deberíamos haber usado una matriz. Al menos todos los miembros tienen la misma etiqueta:

 { x: [ "z", "z2" ] } 

Inevitablemente, sin embargo, eso resulta ser miope:

 zz2nhappy 

Uh …

 { x: [ { y: "z" }, { y : "z2" }, { m: "n" }, "happy" ] } 

… y luego alguien te pule con algunos atributos y espacios de nombres XML.

La forma de construir un esquema de salida más conciso te resulta evidente. Puede inferir detalles de los nombres de tags y atributos. Tú lo entiendes.

La biblioteca no comparte ese entendimiento.

Si la biblioteca no conoce el esquema, debe utilizar matrices de “uso y abuso”, capas adicionales de objetos, nombres de atributos especiales o los tres.

La única alternativa es emplear un esquema de salida variable. Eso lo mantiene simple al principio, como vimos anteriormente, pero rápidamente se encontrará escribiendo una gran cantidad de código condicional. Considere lo que sucede si los niños con el mismo nombre de etiqueta se contraen en una lista, pero solo si hay más de uno:

 if (Array.isArray(xy)) { processTheYChildren(xy); } else if (typeof(xy) === 'object') { // only one child; construct an array on the fly because my converter didn't processTheYChildren([xy]); } else ... 

TL; DR: es más difícil de lo que parece. Lea la página de Conversión JSON y XML de Open311 para obtener detalles de otras representaciones del lado JSON. Todas las matrices de “uso y abuso”, capas adicionales de objetos, miembros con nombres que no aparecieron en el XML original, o los tres.

Como indica la documentación de xml2js , puede configurar el analizador para no abusar de las matrices, estableciendo la propiedad explicitArray en false (importante: tiene que ser un valor booleano ya que la cadena "false" simplemente no funcionará).

Ejemplo:

 var parser = new xml2js.Parser({explicitArray : false}); 

De esta manera, debería poder acceder a sus propiedades JSON de una manera mucho más fácil. Espero que esto ayude a cualquiera.

El JSON que regresa no es demasiado compatible con JavaScript. He escrito una función de ayuda que puede facilitar el trabajo.

Asegúrese de leerlo antes de usarlo para que entienda lo que hace.

 xml.parseString(xmlString, function(err, results){ if(err) throw err results = cleanXML(results); }); var cleanXML = function(xml){ var keys = Object.keys(xml), o = 0, k = keys.length, node, value, singulars, l = -1, i = -1, s = -1, e = -1, isInt = /^-?\s*\d+$/, isDig = /^(-?\s*\d*\.?\d*)$/, radix = 10; for(; o < k; ++o){ node = keys[o]; if(xml[node] instanceof Array && xml[node].length === 1){ xml[node] = xml[node][0]; } if(xml[node] instanceof Object){ value = Object.keys(xml[node]); if(value.length === 1){ l = node.length; singulars = [ node.substring(0, l - 1), node.substring(0, l - 3) + 'y' ]; i = singulars.indexOf(value[0]); if(i !== -1){ xml[node] = xml[node][singulars[i]]; } } } if(typeof(xml[node]) === 'object'){ xml[node] = cleanXML(xml[node]); } if(typeof(xml[node]) === 'string'){ value = xml[node].trim(); if(value.match(isDig)){ if(value.match(isInt)){ if(Math.abs(parseInt(value, radix)) <= Number.MAX_SAFE_INTEGER){ xml[node] = parseInt(value, radix); } }else{ l = value.length; if(l <= 15){ xml[node] = parseFloat(value); }else{ for(i = 0, s = -1, e = -1; i < l && e - s <= 15; ++i){ if(value.charAt(i) > 0){ if(s === -1){ s = i; }else{ e = i; } } } if(e - s <= 15){ xml[node] = parseFloat(value); } } } } } } return xml; }; 

Ejemplos:

 { queries: { query: [ {}, {}, {} ] } } 

se convierte en

 { queries: [ {}, {}, {} ] } 

y

 { types: { type: [ {}, {}, {} ] } } 

se convierte en

 { types: [ {}, {}, {} ] } 

También convertirá con seguridad enteros / puntos flotantes.

Editar: Reemplazado por ... en con por

Para aquellos que se preguntan, xml2js uso y abuso de matriz

Para mi archivo, el árbol sería:

 .result //Object |_.head //Array |_.body //Array |_.update //Array | |_.$ //Object | |_.fields //Strings | |_.stream //Array | |_.$ //Object | |_.fields //Strings | |_.playlist //Array |_.$ //Object |_.fields //Strings | |_.video //Array |_.$ //Object |_.fields //Strings 

Es posible que desee probar console.log(util.inspect(result, false, null)) , que debería mostrar el resultado completo.

Para mí fue un problema de console.dir o, más exactamente, un problema.

Tuve el mismo resultado cuando consola.dir la salida:

 { TextView: [ [Object] ], ImageView: [ [Object] ] } } 

Pero me sorprendió descubrir que era una limitación de console.dir y que los datos estaban realmente allí. Al parecer, console.dir no muestra más que unos pocos niveles. Cuando consola.dir un nivel más profundo los datos estaban allí:

  console.log(result.RelativeLayout.TextView); 

salida:

  { '$': { 'android:layout_width': 'wrap_content', 'android:layout_height': 'wrap_content', 'android:layout_marginLeft': '10dp', 'android:layout_marginTop': '10dp', 'android:textColor': '#ffffff', 'android:id': '@+id/textView', 'android:text': 'Hello World!' } } 

Comencé a buscar otras bibliotecas para volver atrás e intentarlo de nuevo. Si ayuda a alguien hurra.