web-dev-qa-db-fra.com

xml2js: comment va la sortie?

J'essaie d'utiliser le module node.js xml2js

Mon code est assez 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
        });
    });
}

Mon fichier XML est comme:

<?xml version="1.0"?>
<smil>
    <head/>
    <body>
        <update /*some field*//>
        <stream name="name"/>
        <playlist /*some field*/>
            <video /*some field*//>
            <video /*some field*//>
            <video /*some field*//>
        </playlist>
    </body>
</smil>

La sortie me donne:

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

J'ai réussi à accéder à body en essayant, mais maintenant je suis bloqué, existe-t-il un modèle ou un exemple de la façon dont xml2js produit le xml analysé quelque part?

21
DrakaSAN

Pour ceux qui se demandent, xml2js utilise et abuse de array

Pour mon fichier, l'arbre serait:

.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
4
DrakaSAN

xml2js a une tâche non enviable: convertir XML en JSON de manière à pouvoir être inversé, sans connaître le schéma à l’avance. Cela semble évident, au début:

<name>Fred</name> → { name: "Fred" }
<chacha /> → { chacha: null }

Facile jusqu'ici, non? Qu'en est-il de cela, cependant?

<x><y>z</y><x>

En supprimant les noms conviviaux, l’incertitude à laquelle est confronté xml2js a été mise en évidence. Au début, vous pourriez penser que c'est tout à fait raisonnable: 

{ x: { y: "z" } }

Plus tard, vous trébuchez sur ce texte XML et réalisez que votre schéma deviné est erroné:

<x><y>z</y><y>z2</y></x>

Euh oh. Nous aurions peut-être dû utiliser un tableau. Au moins tous les membres ont la même étiquette:

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

Inévitablement, cependant, cela se révèle être à courte vue:

<x><y>z</y><y>z2</y><m>n</m>happy</x>

Euh ...

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

... puis quelqu'un vous peaufine avec des attributs et des espaces de noms XML. 

La manière de construire un schéma de sortie plus concis vous semble évidente. Vous pouvez déduire des détails à partir des noms de balise et d'attribut. Vous comprenez. 

La bibliothèque ne partage pas cette compréhension. 

Si la bibliothèque ne connaît pas le schéma, elle doit "utiliser et abuser" des tableaux, des couches supplémentaires d'objets, des noms d'attributs spéciaux, ou des trois. 

La seule alternative est d'utiliser un schéma de sortie variable. Cela reste simple au début, comme nous l’avons vu ci-dessus, mais vous vous retrouverez rapidement en train d’écrire beaucoup de code conditionnel. Considérez ce qui se passe si des enfants avec le même nom de balise sont réduits dans une liste, mais uniquement s'il y en a plusieurs:

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

TL; DR: c'est plus difficile qu'il n'y paraît. Lisez la page Open311 Conversion JSON et XML pour obtenir des détails sur les autres représentations côté JSON. Tous les tableaux "d'utilisation et d'utilisation abusive", les couches supplémentaires d'objets, les membres dont le nom ne figure pas dans le code XML d'origine, ou les trois. 

47
Garth Kidd

Comme indiqué dans la documentation de xml2js , vous pouvez configurer l'analyseur pour ne pas abuser des tableaux, en définissant la propriété explicitArray sur false (important: il doit s'agir d'une valeur booléenne, car la chaîne "false" ne fera que pas !)

Exemple:

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

De cette façon, vous devriez pouvoir accéder à vos propriétés JSON de manière beaucoup plus simple. J'espère que cela aide quelqu'un.

36
Clint Eastwood

Le JSON qui revient n'est pas trop convivial avec JavaScript. J'ai écrit une fonction d'aide qui facilite le travail.

Assurez-vous de le lire avant de l'utiliser afin de bien comprendre ce qu'il fait.

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

Exemples:

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

devient

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

et

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

devient

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

Il va également en toute sécurité convertir des entiers/points flottants.

Edit: Remplacé pour ... dans avec pour

6
Tristian

Vous voudrez peut-être essayer console.log(util.inspect(result, false, null)), qui devrait afficher le résultat complet.

1
Stucco

Pour moi, c'était un problème console.dir ou plus précisément un non-problème.

J'ai eu le même résultat quand je console.dir la sortie:

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

Mais j'ai été surpris de découvrir qu'il s'agissait d'une limitation console.dir et que les données étaient réellement là. Apparemment, console.dir ne montre pas plus que quelques niveaux. Quand j'ai consolé.dir un niveau plus profond, les données étaient là:

 console.log(result.RelativeLayout.TextView);

sortie:

 { '$':
 { '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!' } }

J'ai commencé à chercher d'autres bibliothèques seulement pour revenir en arrière et essayer à nouveau. Si cela aide quelqu'un, bravo.

0
Guy