We know that a lot of Web services are in XML format, and we can parse our XML by using Xmllistmodel. But in some cases, we might need to parse the XML using JavaScript, which gives us more flexibility in parsing the XML data we need. For example, with a request, we can parse multiple data in the XML result. By comparison, Xmllistmodel can only parse data for the XPath path (defined by the Source property). If you need more than one path, you can query the different paths multiple times. Of course, we may need some way to synchronize these queries (if the final data is connected to each other).
We'll use one of the tutorials we've already had "building your first QML application." In this application, it uses Xmllistmodel to parse the resulting XML data. The API interface is: Http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml
This XML file does not appear to have any style information associated with it. The document tree is shown below.
<gesmes:Envelope xmlns:gesmes="http://www.gesmes.org/xml/2002-08-01" xmlns="http://www.ecb.int/vocabulary/2002-08-01/eurofxref">
<gesmes:subject>Reference rates</gesmes:subject>
<gesmes:Sender>
<gesmes:name>European Central Bank</gesmes:name>
</gesmes:Sender>
<Cube>
<Cube time="2015-04-29">
<Cube currency="USD" rate="1.1002"/>
<Cube currency="JPY" rate="131.20"/>
<Cube currency="BGN" rate="1.9558"/>
<Cube currency="CZK" rate="27.435"/>
<Cube currency="DKK" rate="7.4619"/>
<Cube currency="GBP" rate="0.71610"/>
<Cube currency="HUF" rate="302.55"/>
<Cube currency="PLN" rate="4.0120"/>
<Cube currency="RON" rate="4.4125"/>
<Cube currency="SEK" rate="9.2723"/>
<Cube currency="CHF" rate="1.0491"/>
<Cube currency="NOK" rate="8.3850"/>
<Cube currency="HRK" rate="7.5763"/>
<Cube currency="RUB" rate="56.7850"/>
<Cube currency="TRY" rate="2.9437"/>
<Cube currency="AUD" rate="1.3762"/>
<Cube currency="BRL" rate="3.2467"/>
<Cube currency="CAD" rate="1.3262"/>
<Cube currency="CNY" rate="6.8211"/>
<Cube currency="HKD" rate="8.5278"/>
<Cube currency="IDR" rate="14212.78"/>
<Cube currency="ILS" rate="4.2601"/>
<Cube currency="INR" rate="69.7841"/>
<Cube currency="KRW" rate="1179.14"/>
<Cube currency="MXN" rate="16.8221"/>
<Cube currency="MYR" rate="3.9178"/>
<Cube currency="NZD" rate="1.4310"/>
<Cube currency="PHP" rate="48.743"/>
<Cube currency="SGD" rate="1.4557"/>
<Cube currency="THB" rate="36.142"/>
<Cube currency="ZAR" rate="13.0682"/>
</Cube>
</Cube>
</gesmes:Envelope>
To be able to parse our XML data, we can parse it in the following ways:
function startParse() {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState == XMLHttpRequest.HEADERS_RECEIVED) {
} else if (xhr.readyState == XMLHttpRequest.DONE) {
var doc = xhr.responseXML.documentElement;
showRequestInfo("xhr length: " + doc.childNodes.length );
for (var i = 0; i < doc.childNodes.length; ++i) {
var child = doc.childNodes[i];
for (var j = 0; j < child.childNodes.length; ++j) {
if ( child.nodeName === "Cube") {
var kid = child.childNodes[j];
var length = kid.childNodes.length;
for ( var k = 0; k < length; k ++) {
var cube = kid.childNodes[k];
if ( cube.nodeName === "Cube") {
var len = cube.attributes.length;
var currency = cube.attributes[0].nodeValue;
var rate = cube.attributes[1].nodeValue;
currencies.append({"currency": currency, "rate": parseFloat(rate)})
}
}
}
}
}
}
}
xhr.open("GET", URL);
xhr.send();
}
Here we use "XMLHttpRequest" to send our requests and traverse our XML data through "NodeName" and "NodeValue". Finally, we finish parsing our XML data. In the project, we define the "xmlparser.js" file.
Main.qml
import QtQuick 2.0
import Ubuntu.Components 1.1
import QtQuick.XmlListModel 2.0
import Ubuntu.Components.ListItems 0.1
import Ubuntu.Components.Popups 0.1
import "xmlparser.js" as API
/*!
\brief MainView with a Label and Button elements.
*/
MainView {
id: root
// objectName for functional testing purposes (autopilot-qt5)
objectName: "mainView"
// Note! applicationName needs to match the "name" field of the click manifest
applicationName: "currencyconverterxml.liu-xiao-guo"
/*
This property enables the application to change orientation
when the device is rotated. The default is false.
*/
//automaticOrientation: true
// Removes the old toolbar and enables new features of the new header.
useDeprecatedToolbar: false
property real margins: units.gu(2)
property real buttonWidth: units.gu(9)
width: units.gu(50)
height: units.gu(75)
function convert(from, fromRateIndex, toRateIndex) {
var fromRate = currencies.getRate(fromRateIndex);
if (from.length <= 0 || fromRate <= 0.0)
return "";
return currencies.getRate(toRateIndex) * (parseFloat(from) / fromRate);
}
function update() {
indicator.running = false;
}
Page {
title: i18n.tr("Currency Converter")
ListModel {
id: currencies
ListElement {
currency: "EUR"
rate: 1.0
}
function getCurrency(idx) {
return (idx >= 0 && idx < count) ? get(idx).currency: ""
}
function getRate(idx) {
return (idx >= 0 && idx < count) ? get(idx).rate: 0.0
}
}
ActivityIndicator {
id: indicator
objectName: "activityIndicator"
anchors.right: parent.right
running: true
}
Component {
id: currencySelector
Popover {
Column {
anchors {
top: parent.top
left: parent.left
right: parent.right
}
height: pageLayout.height
Header {
id: header
text: i18n.tr("Select currency")
}
ListView {
clip: true
width: parent.width
height: parent.height - header.height
model: currencies
delegate: Standard {
objectName: "popoverCurrencySelector"
text: model.currency
onClicked: {
caller.currencyIndex = index
caller.input.update()
hide()
}
}
}
}
}
}
Column {
id: pageLayout
anchors {
fill: parent
margins: root.margins
}
spacing: units.gu(1)
Row {
spacing: units.gu(1)
Button {
id: selectorFrom
objectName: "selectorFrom"
property int currencyIndex: 0
property TextField input: inputFrom
text: currencies.getCurrency(currencyIndex)
onClicked: PopupUtils.open(currencySelector, selectorFrom)
}
TextField {
id: inputFrom
objectName: "inputFrom"
errorHighlight: false
validator: DoubleValidator {notation: DoubleValidator.StandardNotation}
width: pageLayout.width - 2 * root.margins - root.buttonWidth
height: units.gu(5)
font.pixelSize: FontUtils.sizeToPixels("medium")
text: '0.0'
onTextChanged: {
if (activeFocus) {
inputTo.text = convert(inputFrom.text, selectorFrom.currencyIndex, selectorTo.currencyIndex)
}
}
// This is more like a callback function
function update() {
text = convert(inputTo.text, selectorTo.currencyIndex, selectorFrom.currencyIndex)
}
}
}
Row {
spacing: units.gu(1)
Button {
id: selectorTo
objectName: "selectorTo"
property int currencyIndex: 1
property TextField input: inputTo
text: currencies.getCurrency(currencyIndex)
onClicked: PopupUtils.open(currencySelector, selectorTo)
}
TextField {
id: inputTo
objectName: "inputTo"
errorHighlight: false
validator: DoubleValidator {notation: DoubleValidator.StandardNotation}
width: pageLayout.width - 2 * root.margins - root.buttonWidth
height: units.gu(5)
font.pixelSize: FontUtils.sizeToPixels("medium")
text: '0.0'
onTextChanged: {
if (activeFocus) {
inputFrom.text = convert(inputTo.text, selectorTo.currencyIndex, selectorFrom.currencyIndex)
}
}
function update() {
text = convert(inputFrom.text, selectorFrom.currencyIndex, selectorTo.currencyIndex)
}
}
}
Button {
id: clearBtn
objectName: "clearBtn"
text: i18n.tr("Clear")
width: units.gu(12)
onClicked: {
inputTo.text = '0.0';
inputFrom.text = '0.0';
}
}
}
Component.onCompleted: {
API.startParse(root);
}
}
}
The source code for the entire project is: Git clone https://gitcafe.com/ubuntu/CurrencyConverterXml.git
How to use JavaScript to parse XML in QML applications