I'm trying to navigate an TXMLDocument in C++ Builder 11 (Alexandria). Everything has been working great, but I'm at a standstill. I've distilled the issue down to simplify it for purposes of this question, but can't for the life of me understand what's going on
// code snippet..
ParentNode = node->ChildNodes->FindNode(Name);
String recipientNodeName = "Recipient"; // This comes from a variable in the original code. Debugger shows that it does not change in value between the two following calls.
_di_IXMLNode RecipientNode = ParentNode->ChildNodes->FindNode(recipientNodeName); // This works, RecipientNode is properly found
_di_IXMLNodeList RecipientNodes = ParentNode->ChildNodes->Nodes[recipientNodeName]; // This does *not* work, RecipientNodes is NULL
From all the documentation I can find, these two should both find the same node. The first one finds it fine. The second one (which should generate a list of one or more nodes - in this case it's just one), fails, and doesn't make a single item list.
Anyone have any clue why this might be happening? Am I misunderstanding how to navigate the DOM? Thanks in advance for any help you might provide.
I've tried walking the tree recursively, and it looks exactly how it should. It just doesn't seem to be creating the list, even though I'm using the same Nodename (the namespace is the same).
NEW: I wrote this simple program, and while it iterates each node correctly (I see two nodes call "Recipient"), recipientNodes is nonetheless NULL ("No Recipient nodes found") triggers. I think I have to assume that there's something wrong with my environment, or RAD Studio 11.4 that causes this to fail. I'll just iterate the nodes to get repeating data. If anyone sees a fundamental flaw in what I'm trying to do, please let me know, as I'd prefer to use the documented function instead of having to write my own. Thanks for the help so far, and any to come. (code follows)
// Create a new XML Document
_di_IXMLDocument xmlDoc = NewXMLDocument();
// XML string to parse
UnicodeString xmlStr = L"<CommunicationMetaData>"
L"<ApplicationReferenceID>12345</ApplicationReferenceID>"
L"<Recipient>ID1</Recipient>"
L"<Recipient>ID2</Recipient>"
L"<SomeOtherNode>Data</SomeOtherNode>"
L"</CommunicationMetaData>";
xmlDoc->LoadFromXML(xmlStr);
xmlDoc->Active = true; // Ensure the document is active
// Get the root node (CommunicationMetaData)
_di_IXMLNode rootNode = xmlDoc->DocumentElement;
if (rootNode)
{
ShowMessage("Root node loaded successfully.");
// Debugging: Output the names of all child nodes under the root node
for (int i = 0; i < rootNode->ChildNodes->Count; i++) {
_di_IXMLNode childNode = rootNode->ChildNodes->Nodes[i];
ShowMessage("Child node name: " + childNode->NodeName);
}
// Attempt to retrieve all Recipient nodes
_di_IXMLNodeList recipientNodes = rootNode->ChildNodes->Nodes["Recipient"];
if (recipientNodes && recipientNodes->Count > 0)
{
ShowMessage("Found " + IntToStr(recipientNodes->Count) + " Recipient nodes.");
// Iterate through each Recipient node
for (int i = 0; i < recipientNodes->Count; i++)
{
_di_IXMLNode recipientNode = recipientNodes->Nodes[i];
if (recipientNode)
{
ShowMessage("Recipient: " + recipientNode->Text);
}
else
{
ShowMessage("Failed to load Recipient node at index: " + IntToStr(i));
}
}
}
else
{
ShowMessage("No Recipient nodes found.");
}
}
else
{
ShowMessage("Failed to load the root node.");
}
}
catch (const Exception& e)
{
ShowMessage("Exception occurred: " + e.Message);
}
// Attempt to retrieve all Recipient nodes _di_IXMLNodeList recipientNodes = rootNode->ChildNodes->Nodes["Recipient"];
This is wrong. The Nodes[]
property DOES NOT return a list of matching nodes, as you are expecting. It returns a single node only (_di_IXMLNode
).
Per the documentation (emphasis added):
__property _di_IXMLNode Nodes[const System::OleVariant IndexOrName] = {read=GetNode/*, default*/}; ^^^^^^^^^^^^
Read
Nodes
to access a specified node in the list.
IndexOrName
identifies the desired node. It can be:
The index of the node, where 0 is the index of the first node, 1 is the index of the second node, and so on. The
Count
property provides an upper bound on the indexes you can specify.The
LocalName
property of a node in the list.
In other words, when you request a node by name from Nodes[]
, only the 1st matching node is returned. NOT a list of matching nodes.
You are taking that returned _di_IXMLNode
and assigning it to a _di_IXMLNodeList
variable. So the node is queried for the IXMLNodeList
interface, which fails as a single node is not a list of nodes. That is why you end up with a NULL pointer.
That being said, the IXMLNode
/IXMLNodeList
interfaces simply do not have the functionality you are looking for. To accomplish what you are attempting, you need to instead use an XPath query. You can query the parent IXMLNode
's DOMNode
property for the IDOMNodeSelect
interface, and if successful (as not all DOMVendor
s support XPath in every Delphi version) then call its selectNodes()
method, which returns an IDOMNodeList
of matching IDOMNode
s.
// Attempt to retrieve all Recipient nodes
_di_IDOMNodeSelect xpath = rootNode->DOMNode;
if (xpath) {
_di_IDOMNodeList recipientNodes = xpath->selectNodes(L"Recipient"); // or whatever query you need...
...
}
Otherwise, you will just have to iterate the IXMLNode
s manually, as you have already discovered.