2010-11-17 22 views
9

J'essaie de comprendre comment fonctionne QXmlStreamReader pour une application C++ que j'écris. Le fichier XML que je veux analyser est un grand dictionnaire avec une structure alambiquée et beaucoup de caractères Unicode. J'ai donc décidé d'essayer un petit cas de test avec un document plus simple. Malheureusement, je frappe un mur. Voici le fichier xml exemple:Pourquoi ne puis-je pas analyser un fichier XML à l'aide de QXmlStreamReader à partir de Qt?

<?xml version="1.0" encoding="UTF-8" ?> 
<persons> 
    <person> 
     <firstname>John</firstname> 
     <surname>Doe</surname> 
     <email>[email protected]</email> 
     <website>http://en.wikipedia.org/wiki/John_Doe</website> 
    </person> 
    <person> 
     <firstname>Jane</firstname> 
     <surname>Doe</surname> 
     <email>[email protected]</email> 
     <website>http://en.wikipedia.org/wiki/John_Doe</website> 
    </person> 
    <person> 
     <firstname>Matti</firstname> 
     <surname>Meikäläinen</surname> 
     <email>[email protected]</email> 
     <website>http://fi.wikipedia.org/wiki/Matti_Meikäläinen</website> 
    </person> 
</persons> 

... et je suis en train de l'analyser en utilisant ce code:

int main(int argc, char *argv[]) 
{ 
    if (argc != 2) return 1; 

    QString filename(argv[1]); 
    QTextStream cout(stdout); 
    cout << "Starting... filename: " << filename << endl; 

    QFile file(filename); 
    bool open = file.open(QIODevice::ReadOnly | QIODevice::Text); 
    if (!open) 
    { 
     cout << "Couldn't open file" << endl; 
     return 1; 
    } 
    else 
    { 
     cout << "File opened OK" << endl; 
    } 

    QXmlStreamReader xml(&file); 
    cout << "Encoding: " << xml.documentEncoding().toString() << endl; 

    while (!xml.atEnd() && !xml.hasError()) 
    { 
     xml.readNext(); 
     if (xml.isStartElement()) 
     { 
      cout << "element name: '" << xml.name().toString() << "'" 
       << ", text: '" << xml.text().toString() << "'" << endl; 
     } 
     else if (xml.hasError()) 
     { 
      cout << "XML error: " << xml.errorString() << endl; 
     } 
     else if (xml.atEnd()) 
     { 
      cout << "Reached end, done" << endl; 
     } 
    } 

    return 0; 
} 

... je reçois cette sortie:

C:\xmltest\Debug>xmltest.exe example.xml
Starting... filename: example.xml
File opened OK
Encoding:
XML error: Encountered incorrectly encoded content.

Que s'est-il passé? Ce fichier ne pourrait pas être plus simple et il me semble cohérent. Avec mon fichier d'origine, je reçois également une entrée vide pour l'encodage, les noms des entrées() sont affichés, mais hélas, le texte() est également vide. Toutes les suggestions grandement appréciées, personnellement, je suis profondément mystifié. Etes-vous sûr que votre document est codé en UTF-8?

Répondre

11

Je réponds à cette question moi-même car ce problème était lié à trois problèmes, dont deux ont été soulevés par les réponses.

  1. Le fichier n'était pas codé en UTF-8. J'ai changé l'encodage en iso-8859-1 et l'avertissement de codage a disparu.
  2. La fonction text() ne fonctionne pas comme prévu. Je dois utiliser readElementText() pour lire le contenu des entrées.
  3. Lorsque je tente de readElementText() sur un élément qui ne contient pas de texte, comme le haut niveau <personnes> dans mon cas, l'analyseur retourne un « données de caractère attendus » erreur et l'analyse syntaxique est interrompu. Je trouve ce comportement étrange (à mon avis renvoyer une chaîne vide et continuer serait mieux) mais je suppose que tant que la spécification est connue, je peux contourner ce problème et éviter d'appeler cette fonction sur chaque entrée.

La section pertinente du Code qui fonctionne comme prévu ressemble maintenant à ceci:

while (!xml.atEnd() && !xml.hasError()) 
{ 
    xml.readNext(); 
    if (xml.isStartElement()) 
    { 
     QString name = xml.name().toString(); 
     if (name == "firstname" || name == "surname" || 
      name == "email" || name == "website") 
     { 
      cout << "element name: '" << name << "'" 
         << ", text: '" << xml.readElementText() 
         << "'" << endl; 
     } 
    } 
} 
if (xml.hasError()) 
{ 
    cout << "XML error: " << xml.errorString() << endl; 
} 
else if (xml.atEnd()) 
{ 
    cout << "Reached end, done" << endl; 
} 
+0

Intéressant: readElementText() est un peu bogué en général, mais ne fonctionne pas correctement lors de la lecture incrémentielle de données d'un flux où les données peuvent être incomplètes (par exemple une socket), voir http://bugreports.qt.nokia.com/browse/QTBUG-14661 –

+0

Devrais-je signaler cela comme un bug? Je ne suis pas sûr que ce soit est-ce, ou si c'est censé fonctionner de cette façon. – neuviemeporte

+0

@FrankOsterfeld y a-t-il quelque chose de nouveau sur 'readElementText()'? Y at-il une fonction pour vérifier si cela va fonctionner ou non? – Niklas

2

Quel éditeur avez-vous utilisé? Vérifiez à quoi ressemblent les caractères ä si vous visualisez le fichier sans le décoder.

4

Le fichier n'est pas codé en UTF-8. Changez le codage en iso-8859-1 et il analysera sans erreur.

<?xml version="1.0" encoding="iso-8859-1" ?> 
+0

À droite sur. J'ai honte de ne pas y avoir pensé.:( – neuviemeporte

+0

Mais que se passe-t-il si le codage XML n'est pas spécifié? QXmlStreamReader suppose-t-il que c'est UTF-8, c'est-à-dire: ' en haut du fichier XML donne ** "Contenu encodé incorrectement rencontré." ** – bitek

2

A propos de l'encodage: Comme baysmith et hmuelner et dit, votre fichier est probablement mal codé (sauf si l'encodage est perdu lors du collage ici). Essayez de résoudre ce problème avec un éditeur de texte avancé.

Le problème avec votre utilisation de text() est que cela ne fonctionne pas comme prévu. text() renvoie le contenu du jeton en cours s'il est de type Caractères, Commentaire, DTD ou EntityReference. Votre jeton actuel est un StartElement, donc il est vide. Si vous voulez consommer/lire le texte de l'actuel startElement, utilisez readElementText() à la place.

+0

J'ai fait quelques recherches supplémentaires et l'encodage n'est pas vide, apparaissant comme "UTF-8" si je fais un readNext() avant d'accéder à l'encodage() – neuviemeporte

1

Essayez cet exemple je viens recopié de mon projet, il fonctionne pour moi.

void MainWindow::readXML(const QString &fileName) 
{ 


fileName = "D:/read.xml"; 

QFile* file = new QFile(fileName); 
if (!file->open(QIODevice::ReadOnly | QIODevice::Text)) 
{ 
    QMessageBox::critical(this, "QXSRExample::ReadXMLFile", "Couldn't open xml file", QMessageBox::Ok); 
    return; 
} 

/* QXmlStreamReader takes any QIODevice. */ 
QXmlStreamReader xml(file); 
/* We'll parse the XML until we reach end of it.*/ 
while(!xml.atEnd() && !xml.hasError()) 
{ 
    /* Read next element.*/ 
    QXmlStreamReader::TokenType token = xml.readNext(); 
    /* If token is just StartDocument, we'll go to next.*/ 
    if(token == QXmlStreamReader::StartDocument) 
     continue; 

    /* If token is StartElement, we'll see if we can read it.*/ 
    if(token == QXmlStreamReader::StartElement) { 
     if(xml.name() == "email") { 
      ui->listWidget->addItem("Element: "+xml.name().toString()); 
      continue; 
     } 
    } 
} 
/* Error handling. */ 
if(xml.hasError()) 
    QMessageBox::critical(this, "QXSRExample::parseXML", xml.errorString(), QMessageBox::Ok); 

//resets its internal state to the initial state. 
xml.clear(); 
} 

void MainWindow::writeXML(const QString &fileName) 
{ 
fileName = "D:/write.xml"; 
QFile file(fileName); 
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) 
{ 
    QMessageBox::critical(this, "QXSRExample::WriteXMLFile", "Couldn't open anna.xml", QMessageBox::Ok); 
    return; 
} 
QXmlStreamWriter xmlWriter(&file); 
xmlWriter.setAutoFormatting(true); 
xmlWriter.writeStartDocument(); 
//add Elements 
xmlWriter.writeStartElement("bookindex"); 
ui->listWidget->addItem("bookindex"); 
xmlWriter.writeStartElement("Suleman"); 
ui->listWidget->addItem("Suleman"); 

//write all elements in xml filexl 
xmlWriter.writeEndDocument(); 
file.close(); 
if (file.error()) 
    QMessageBox::critical(this, "QXSRExample::parseXML", file.errorString(), QMessageBox::Ok); 


}