XML data

The Cascades framework lets you load and save data in Extensible Markup Language (XML) format. This format is a markup language (the same class of languages as HTML) and is designed to represent data in a form that's readable by both humans and machines. To achieve this, XML stores information semantically (that is, according to the meaning of the information). By contrast, HTML is designed to display information, and focuses on how the information should look.

Unlike other markup languages, XML doesn't include predefined tags. You define the tags that are most useful for the data that you want to store. For example, in an app that stores a list of employees, you might define a tag that represents a single employee, along with other tags that represent the employee's name, salary, and so on.

XML provides a framework that you should follow when you create an XML document, and this framework includes the following components:

Markup and content

The characters that make up an XML document are divided into markup and content. Markup strings either begin with a less-than sign (<) and end with a greater-than sign (>), or begin with an ampersand (&) and end with a semicolon (;). Any string that doesn't fit one of these criteria is considered content.

Tag

A tag is markup that starts with < and ends with >. There are three types of tags:

  • Start tag, which represents the beginning of an XML element (for example, <section>)
  • End tag, which represents the end of an XML element (for example, </section>)
  • Empty-element tag, which represents both the start and end of an XML element (for example, <line-break>)

Element

An element is an XML document component that consists of a start tag and matching end tag, or of only an empty-element tag. The characters between the start tag and end tag are considered the content of the element, and this content can contain markup (even other elements, which are called child elements).

For example, <greeting>Hello, world</greeting> is an element.

Attribute

An attribute is markup that consists of a name/value pair, and is located within a start tag or an empty-element tag. For example, the element  <img src="employee.png" alt="An image of an employee." /> contains two attributes, src and alt.

Here's how you might represent a list of employees in an XML document. Each employee is an element and includes child elements that represent additional information about the employee, such as first name, last name, and employee number.

<employees>
    <employee>
        <firstname>Mike</firstname>
        <lastname>Chepesky</lastname>
        <number>62145936</number>
    </employee>
    <employee>
        <firstname>Westlee</firstname>
        <lastname>Barichak</lastname>
        <number>41236500</number>
    </employee>
</employees>

You can use the  XmlDataAccess  class to parse XML documents and turn the data into Qt C++ objects. Then, you can add these objects to a data model and display them in your app using a list view. The XmlDataAccess class is available only in C++ (not QML), and manages the connection to a source of XML data, such as a file or buffer.

Each element and attribute in an XML document is mapped to a corresponding Qt value type, wrapped in a QVariant. For example, the parent element of an XML document is mapped to a QVariant(QVariantMap) in Qt, and an element with no child elements is mapped to a QVariant(QString). For a full list of these mappings, see the XmlDataAccess class.

In general, XML data is stored as a set of name/value pairs in a hierarchy. The name of an element is used as the key for an entry in the parent map of that element. For example, consider the XML document that was presented above. In that document, the <firstname>Mike</firstname> element is stored in Qt as map["firstname"] = QVariant("Mike"), where map is a QVariantMap that stores all of the child elements of the first employee element. For the root element of an XML document, a special entry (denoted by .root) is added to the top-level Qt map, and this entry contains the name of the root element.

To make the relationships easier to visualize, here's the corresponding Qt data structure of the XML document that was presented above.


Diagram showing the data structure of the XML document discussed above.

Prerequisites

To use the Data APIs in your app, you need to link against the correct library by adding the following line to your project's .pro file:

LIBS += -lbbdata

Loading XML data

To load XML data into your app and start working with it, you create an  XmlDataAccess  object and call  load() while specifying the source of the data. You can also specify an optional query path parameter to load(), which represents the starting element for the subset of XML that you want to return. The returned data will either start with the query path element or, if no query path is provided, the data will start with the root element of the XML document. The load() function returns a QVariant that wraps this starting element (either a QVariantList or QVariantMap). Then, you can add the data to a data model with an associated list view to display the data.

For example, consider the sample XML document with list of two employees (Mike and Westlee) that was presented in the previous section. If this data is included in a file called employees.xml employees.xml (located in your project's assets folder), here's how to load the data and display it using a  GroupDataModel  and a ListView control. The GroupDataModel sorts the data by first name and last name. To learn more about GroupDataModel, see Sorted data models. .

Not applicable

#include "applicationui.hpp"

#include <bb/data/XmlDataAccess>
#include <bb/cascades/GroupDataModel>
#include <bb/cascades/Page>
#include <bb/cascades/ListView>
#include <bb/cascades/Application>
#include <QObject>

using namespace bb::cascades;
using namespace bb::data;

ApplicationUI::ApplicationUI(bb::cascades::Application *app) :
        QObject(app)
{
// Create the root page and list view
Page *root = new Page;
ListView *listView = new ListView;

// Create the data model, specifying sorting keys of "firstName" and "lastName"
GroupDataModel *model = new GroupDataModel(QStringList() << "firstName"
                                           << "lastName");

// Create an XmlDataAccess object and load the .xml file. The QDir::currentPath()
// function returns the current working directory for the app.
XmlDataAccess xda;
QVariant list = xda.load(QDir::currentPath() +
                         "/app/native/assets/employees.xml",
                         "/employees/employee");

// Insert the data into the data model. You can provide a
// QVariantList to a data model directly by using insertList().
model->insertList(list.value<QVariantList>());

// Set the data model for the list view
listView->setDataModel(model);

// Set the content of the page and display it
root->setContent(listView);
app->setScene(root);
}

You might notice that the file path that's specified in load() is constructed using the QDir::currentPath() function. This function returns the current working directory for an app, which is the application's sandbox directory. By adding a relative file path, you can specify the location of the .xml file that you want to load. The path "/app/native/assets/" specifies the location of the assets folder in your project, which is where the employees.xml file is located. To learn more about the directories that your app can access, see File system access.

Saving XML data

To save XML data to a file, you create an XmlDataAccess object and then call  save() , specifying the data that you want to save and the file to save it to.

The data should be a QVariant that wraps the Qt objects that represent your data. For example, if your data is a list of cities, you should pass the save() function a QVariant that wraps a QVariantMap, which itself contains a QVariantList of cities. Each item in the QVariantList would represent a single city, and you could use a structure such as a QVariantMap to encapsulate the properties of each city.

Here's how to save a list of employees to a temporary .xml file. Each employee is represented by a QVariantMap, and these objects are added to a QVariantList, which is then added to a QVariantMap. Finally, this QVariantMap is written to the .xml file.

Not applicable

#include <QDir>
#include <QVariant>

#include <bb/data/XmlDataAccess>
using namespace bb::data;
// Create QVariantMap objects to contain the data for each employee
QVariantMap firstEmployee, secondEmployee, thirdEmployee;
  
// Populate each object with data
firstEmployee["firstName"] = QVariant("Mike");
firstEmployee["lastName"] = QVariant("Chepesky");
firstEmployee["number"] = QVariant("51649875");
secondEmployee["firstName"] = QVariant("Westlee");
secondEmployee["lastName"] = QVariant("Barichak");
secondEmployee["number"] = QVariant("63257801");
thirdEmployee["firstName"] = QVariant("Ian");
thirdEmployee["lastName"] = QVariant("Dundas");
thirdEmployee["number"] = QVariant("25669315");
  
// Add each employee to a QVariantList
QVariantList myEmployeeList = QVariantList() << QVariant(firstEmployee)
                           << QVariant(secondEmployee)
                           << QVariant(thirdEmployee);
                            
// Add the employee list to a top-level QVariantMap, then wrap it in a QVariant
QVariantMap topLevelEmployeeMap;
topLevelEmployeeMap[".root"] = QVariant("employees");
topLevelEmployeeMap["employee"] = QVariant(myEmployeeList);
QVariant myData = QVariant(topLevelEmployeeMap);
  
// Retrieve the working directory, and create a temporary .xml file in that
// location
QDir home = QDir::home();
QTemporaryFile file(home.absoluteFilePath("myXMLFile.xml"));
  
// Open the file that was created
if (file.open()) {
    // Create an XmlDataAccess object and save the data to the file
    XmlDataAccess xda;
    xda.save(myData, &file);
}

Considerations when saving XML data

There are a few considerations that you should keep in mind when you're saving XML data using XmlDataAccess. The XML output that XmlDataAccess generates is well-formed (based on the rules for XML markup), but the output is not necessarily valid because it doesn't conform to any schema definition. An XML schema specifies the rules that an XML document must follow to be considered valid. A schema defines constraints, such as the set of elements that can be used, the order in which these elements appear, which attributes can be applied to elements, and so on.

This behavior results in some limitations, including the following:

  • You can't choose whether to output character data as an attribute (such as name="value") or as an element (such as <name>value</name>).
  • There are no data type constraints for character data.
  • The order of elements and attributes isn't preserved. The XML data that is produced is in alphabetical order, not in the order of the original XML data. However, the order of repeating elements (for example, the <employee> elements in the previous examples) is preserved.

You should consider using XmlDataAccess to store data in XML format only as a temporary method of storing structured data, and a method that you use only inside your own app. The XML data that's generated by XmlDataAccess isn't suitable for communicating with an external system that expects valid XML based on an established schema.

Handling errors

Because XmlDataAccess is very similar to JsonDataAccess, you can use the same approach to handle any errors that might occur during load or save operations. To learn about this approach, see Handling errors on the Working with JSON data page.

Last modified: 2014-09-30



Got questions about leaving a comment? Get answers from our Disqus FAQ.

comments powered by Disqus