When you perform a query against BDB XML, you receive a results set
in the form of an XmlResults
object. To
examine the results, you iterate over this result set, retrieving
each element of the set as an XmlValue
object.
Once you have an individual result element, you can obtain the data
encapsulated in the XmlValue
object in a
number of ways. For example, you can obtain the information as a
string object using
XmlValue::asString()
.
Alternatively, you could obtain the data as an
XmlDocument
object using
XmlValue::asDocument()
.
It is also possible to use DOM-like navigation on the
XmlValue
object since that class offers
navigational methods such as
XmlValue::getFirstChild()
,
XmlValue::getNextSibling()
,
XmlValue::getAttributes()
,
and so forth. For details on these and other
XmlValue
attributes, see the
BDB XML C++ API Reference documentation.
For example, the following code fragment performs a query and then
loops over the result set, obtaining and displaying the document's
name from an XmlDocument
object before
displaying the document itself.
#include "DbXml.hpp" ... using namespace DbXml; ... // Get a manager object. XmlManager myManager; // Open a container XmlContainer myContainer = myManager.openContainer("exampleData.dbxml"); // Get a query context XmlQueryContext context = myManager.createQueryContext(); // Declare a namespace context.setNamespace("fruits", "http://groceryItem.dbxml/fruits"); // Declare the query string. Find all the product documents // in the fruits namespace. std::string myQuery = "collection('exampleData.dbxml')/fruits:product"; // Perform the query. XmlResults results = myManager.query(myQuery, context); // Show the size of the result set std::cout << "Found " << results.size() << " documents for query: '" << myQuery << "'" << std::endl; // Display the result set XmlValue value; while (results.next(value)) { XmlDocument theDoc = value.asDocument(); std::string docName = theDoc.getName(); std::string docString = value.asString(); std::cout << "Document " << docName << ":" << std::endl; std::cout << docString << std::endl; std::cout << "===============================\n" << std::endl; }
It is frequently useful to retrieve a document from BDB XML and then
perform follow-on queries to retrieve individual values from the
document itself. You do this by creating and executing a query,
except that you pass the specific
XmlValue
object that you want to query to the
XmlQueryExpression::execute()
method. You must then iterate over a result set exactly as you would
when retrieving information from a container.
For example, suppose you have an address book product that manages individual contacts using XML documents such as:
<contact> <familiarName>John</familiarName> <surname>Doe</surname> <phone work="555 555 5555" home="555 666 777" /> <address> <street>1122 Somewhere Lane</street> <city>Nowhere</city> <state>Minnesota</state> <zipcode>11111</zipcode> </address> </contact>
Then you could retrieve individual documents and pull data off of them like this:
#include "DbXml.hpp" ... using namespace DbXml; ... // Get a manager object. XmlManager myManager; // Open a container XmlContainer myContainer = myManager.openContainer("exampleData.dbxml"); // Declare the query string. Retrieves all the documents // for people with the last name 'Doe'. std::string myQuery = "collection('exampleData.dbxml')/contact"; // Query to get the familiar name from the // document. std::string fn = "distinct-values(/contact/familiarName)"; // Query to get the surname from the // document. std::string sn = "distinct-values(/contact/surname)"; // Work phone number std::string wrkPhone = "distinct-values(/contact/phone/@work)"; // Get the context for the XmlManager query XmlQueryContext managerContext = myManager.createQueryContext(); // Get a context for the document queries XmlQueryContext documentContext = myManager.createQueryContext(); // Prepare the XmlManager query XmlQueryExpression managerQuery = myManager.prepare(myQuery, managerContext); // Prepare the individual document queries XmlQueryExpression fnExpr = myManager.prepare(fn, documentContext); XmlQueryExpression snExpr = myManager.prepare(sn, documentContext); XmlQueryExpression wrkPhoneExpr = myManager.prepare(wrkPhone, documentContext); // Perform the query. XmlResults results = managerQuery.execute(managerContext, 0); // Display the result set XmlValue value; while (results.next(value)) { // Get the individual values XmlResults fnResults = fnExpr.execute(value, documentContext); XmlResults snResults = snExpr.execute(value, documentContext); XmlResults phoneResults = wrkPhoneExpr.execute(value, documentContext); std::string fnString; XmlValue fnValue; if (fnResults.size() > 0) { fnResults.next(fnValue); fnString = fnValue.asString(); } else { continue; } std::string snString; XmlValue snValue; if (snResults.size() > 0) { snResults.next(snValue); snString = snValue.asString(); } else { continue; } std::string phoneString; XmlValue phoneValue; if (phoneResults.size() > 0) { phoneResults.next(phoneValue); phoneString = phoneValue.asString(); } else { continue; } std::cout << fnString << " " << snString << ": " << phoneString << std::endl; }
Note that you can use the same basic mechanism to pull information out
of very long documents, except that in this case you need to maintain
the query's focus; that is, the location in the document that the result
set item is referencing. For example suppose you have a document
with 2,000 contact
nodes and you want to get the
name
attribute from some particular contact
in the document.
There are several ways to perform this query. You could, for example, ask for the node based on the value of some other attribute or element in the node:
/document/contact[category='personal']
Or you could create a result set that holds all of the
document's contact
nodes:
/document/contact
Regardless of how you get your result set, you can then go ahead and query each value in the result set for information contained in the value. To do this:
Iterate over the result set as normal.
Query for document information as described above. However, in this case change the query so that you reference the self access. That is, for the surname query described above, you would use the following query instead so as to reference nodes relative to the current node (notice the self-access (.) in use in the following query):
distinct-values(./surname)
When you retrieve a document from BDB XML, there are two ways to
examine the metadata associated with that document. The first is to
use
XmlDocument::getMetaData()
.
Use this form if you want to examine the value for a specific
metadata value.
The second way to examine metadata is to obtain an XmlMetaDataIterator
object using
XmlDocument::getMetaDataIterator()
.
You can use this mechanism to loop over and display every piece of
metadata associated with the document.
For example:
#include "DbXml.hpp" ... using namespace DbXml; ... // Get a manager object. XmlManager myManager; // Open a container XmlContainer myContainer = myManager.openContainer("exampleData.dbxml"); // Get a query context XmlQueryContext context = myManager.createQueryContext(); // Declare a namespace context.setNamespace("fruits", "http://groceryItem.dbxml/fruits"); // Declare the query string. Find all the product documents // in the fruits namespace. std::string myQuery = "collection('exampleData.dbxml')/fruits:product"; // Perform the query. XmlResults results = myManager.query(myQuery, context); // Display the result set XmlValue value; while (results.next(value)) { XmlDocument theDoc = value.asDocument(); // Display all of the metadata set for this document XmlMetaDataIterator mdi = theDoc.getMetaDataIterator(); std::string returnedURI; std::string returnedName; XmlValue returnedValue; std::cout << "For document '" << theDoc.getName() << "' found metadata:" << std::endl; while (mdi.next(returnedURI, returnedName, returnedValue)) { std::cout << "\tURI: " << returnedURI << ", attribute name: " << returnedName << ", value: " << returnedValue << std::endl; } // Display a single metadata value: std::string URI = "http://dbxmlExamples/timestamp"; std::string attrName = "timeStamp"; XmlValue newRetValue; bool gotResult = theDoc.getMetaData(URI, attrName, newRetValue); if (gotResult) { std::cout << "For URI: " << URI << ", and attribute " << attrName << ", found: " << newRetValue << std::endl; } std::cout << "===============================\n" << std::endl; }
When you create an XmlResults
object by executing a query, the
object actually references database objects. That is, the object is non-transient which
means that if the objects in the database are modified or deleted in some way, then the
contents of your XmlResults
object can also be modified or
deleted.
One way to guard against this is to use tansactions to provide isolation guarantees for
your XmlResults
objects. Transactions are described in the
Berkeley DB XML Getting Started with Transaction Processing guide.
Another way to guard against this is to create a transient copy of your
XmlResults
object. You do this by using the
XmlResults::copyResults()
method. This method causes all of the XmlValue
objects contained
in the results set to no longer be references to database objects. As a result, you can
safely use the result set outside of a transaction, and you can modify the copied results set
without concern that you are modifying the container.
This method simply returns a new XmlResults
object, which you can
use in the same way you would use any XmlResults
object.
... XmlResults results = myManager.query(myQuery, context); XmlResults transResults = results.copyResults(); ...
It is also possible to concatenate two results sets together using the
XmlResults::concatResults()
method. This method is can only be used with transient results sets (that is,
XmlResults
objects created using
the copyResults()
method. In this case, the results set
provided as an argument to the concatResults()
method is
concatenated to the XmlResults
object that the method is called
on.
... XmlResults results1 = myManager.query(myQuery1, context); XmlResults results2 = myManager.query(myQuery2, context); XmlResults transResults1 = results1.copyResults(); XmlResults transResults2 = results2.copyResults(); // Concatenate results2 to results1 transResults1.concatResults(transResults2); ...
Once you have retrieved a document or node, you can examine that retrieved item using an event reader. Event readers provide a pull iterface that allows you to move through a document, or a portion of a document, using an iterator-style interface.
When you iterate over a document or node using an
event reader, you are examing individual objects in the
document. In this, the event reader behaves much like a
SAX parser in that it allows you to discover what sort
of information you are examining (for example, a start
element, an end element, whitespace, characters, and so
forth), and then retrieve relevant information about
that data. (Note, however, that the event reader
interface differs significantly from SAX in that SAX is a push
interface while XmlEventReader
is a pull interface.)
The document events for which you can test using the event reader are:
StartElement
EndElement
Characters
CDATA
Comment
Whitespace
StartDocument
EndDocument
StartEntityReference
EndEntityReference
ProcessingInstruction
DTD
In addition for testing for specific portions of a document, you can also retrieve information about those portions of the document. For example, if you are examining a starting element, you can retrieve the name of that element. You can also retrieve an attribute count on that element, and then retrieve information about each attribute based on it's indexed value in the start node. That is, suppose you have the following document stored in a container:
<a> <b a1="one" b2="two">b node</b> <c>c node</c> </a>
Then you can examine this document as follows:
try { // Container declaration and open omitted for brevity ... std::string docname = "doc1"; XmlDocument xdoc = container.getDocument(docname); // Get an XmlEventReader XmlEventReader &reader = xdoc.getContentAsEventReader(); // Now iterate over the document elements, examining only // those of interest to us: while (reader.hasNext()) { XmlEventType type = reader.next(); if (type == StartElement) { std::cout << "Found start node: " << reader.getLocalName() << std::endl; std::cout << "There are " << reader.getAttributeCount() << " attributes on this node." << endl; // Show all the attributes on the start element node for (int i = 0; i < reader.getAttributeCount(); i++) { std::cout << "Attribute '" << reader.getAttributeLocalName(i) << "' has a value of '" << reader.getAttributeValue(i) << "'" << endl; } } } // When we are done, we close the reader to free-up resources. reader.close(); } catch (XmlException &e) { std::cout << "Exception: " << e.what() << std::endl; }
Running this code fragment yields:
Found start node: a There are 0 attributes on this node. Found start node: b There are 2 attributes on this node. Attribute 'a1' has a value of 'one' Attribute 'b2' has a value of 'two' Found start node: c There are 0 attributes on this node.
Note that you can also use event readers on XmlValue
objects, provided that the object is an element node. For example:
try { // Container declaration and open omitted for brevity // As are the manager, query and XmlQueryContext // declarations. ... XmlResults res = mgr.query(myquery, context); XmlValue val; while ((val = res.next()) != NULL) { if (val.isNode() && (val.getNodeType() == XmlManager.ELEMENT_NODE)) { XmlEventReader &reader = val.asEventReader(); // Now iterate over the document elements while (reader.hasNext()) { XmlEventType type = reader.next(); // Handle each event type as required by your // application. } // When we are done, we close the reader to free-up resources. reader.close(); } } } catch (XmlException &e) { std::cout << "Exception: " << e.what() << std::endl; }