Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Frustrated with TXMLDocument.

171 views
Skip to first unread message

Jason Cipriani

unread,
Mar 2, 2008, 12:57:31 AM3/2/08
to
So I've used a TXMLDocument to construct an XML file that sort of looks like
this:

<?xml version="1.0" standalone="yes"?>
<project version="1">
... bunch of stuff ...
</project>

Now I want to load the file back in and perform the following logic:

1) Find the top-level project node, making sure it exists, fail if it
doesn't.
2) Read it's version attribute.
3) Compare it to 1 and fail if it's not 1.

How do I do this using TXMLDocument? I'm having a lot of difficulty figuring
out the difference between Node, and DocumentElement, and FindNode, and
FindChild, and ChildNodes, and Nodes, and GetNode, and whatever. The latest
of many things I've tried has been this (XML is a TXMLDocument) (when I use
XML->DocumentElement->ChildNodes->Nodes["project"] it does not find the node
and returns NULL):

XML->LoadFromFile(filename);
IXMLNode *node = XML->DocumentElement->ChildNodes->GetNode("project");
if (node == NULL)
throw Exception("Invalid configuration file (no <project>).");
if ((int)(node->Attributes["version"]) != 1)
throw Exception("Invalid configuration file (unsupported
version).");

I am explicitly casting Attributes["version"] to an int because if I don't,
it complains about an ambiguity between bool and __int64. That is as close
as I got it to working, except here it the (int) cast throws an exception
stating:

Could not convert a Variant of type (Null) into type (Integer).

But I don't know why it would be null; that attribute exists. The same thing
happens if I try node->AttributeNodes->Nodes["version"]->NodeValue instead
of node->Attributes["version"]. Now the following:

IXMLNode *an = node->AttributeNodes->FindNode("version");
if (an == NULL)
throw Exception("no version node");
if (an->NodeValue != "1")
throw Exception("invalid version");

Throws "invalid version". The "version" attribute node does indeed exist. I
can also compare NodeValue to a string. But if I do this:

AnsiString as = an->NodeValue;
OutputDebugString(as.c_str());

Then it throws the "can't convert (Null) to (String)" exception. I can't
expand the IXMLNode structure in the debugger to see what's in it, either.
Also, in the above example, I added a watch on "an->NodeValue" and managed
to break the debugger to the point where I had to restart the IDE to get it
working again... but let's not talk about that right now because I need to
dig into it more to figure out what went wrong.

Anyways, I can't figure out how to do what I'm trying to do here. It's
starting to make me angry. Writing the data was extremely easy:

IXMLNode *node = XML->AddChild("project");
node->Attributes["version"] = 1;

I want to just do the opposite of that.

Thanks,
Jason


Jason Cipriani

unread,
Mar 2, 2008, 2:48:03 AM3/2/08
to
"Jason Cipriani" <jason.c...@nospam-gmail.com> wrote in message
news:47ca41d2$1...@newsgroups.borland.com...

> IXMLNode *node = XML->DocumentElement->ChildNodes->GetNode("project");

I got it, I think. XML->DocumentElement (XML is a TXMLDocument) returns the
root "<project>" node; getting it's ChildNode is incorrect here. That seems
to be the source of most of the problems. I'm still not sure what returns
NULL and what returns null-type Variants and what just returns empty
strings, but I'll figure that out sooner or later I guess. There was mention
in the documentation about "comment nodes" and things, is
XML->DocumentElement *always* going to be the root data node? Is that
somehow different than XML->Node?

Jason


Clayton Arends

unread,
Mar 2, 2008, 10:35:17 AM3/2/08
to
"Jason Cipriani" <jason.c...@nospam-gmail.com> wrote in message
news:47ca41d2$1...@newsgroups.borland.com...

> How do I do this using TXMLDocument?

Firstly, I don't use TXMLDocument. I use IXMLDocument. TXMLDocument is a
wrapper for IXMLDocument that can be dropped on a form. I have never found
a need to drop one though.

From your follow-up post it seems you have figured some things out but I
would like to take some time to answer your original post.

> 1) Find the top-level project node

As you found out DocumentElement references the top-level node. You can
reference the first node one of many ways, here are three:

_di_IXMLNode node = XML->DocumentElement;
_di_IXMLNode node = XML->ChildNodes->Last(); // [1]
_di_IXMLNode node = XML->ChildNodes->Nodes["project"];

[1] Note that First() in your example would be the "<?xml" node.

> making sure it exists, fail if it doesn't.

if (!node) // or if (node == NULL)
throw ...

> 2) Read it's version attribute.

if (!node->HasAttribute("version"))
throw ...

> 3) Compare it to 1 and fail if it's not 1.

if (node->Attributes["version"].operator int() != 1) // [1]
throw ...

> IXMLNode *node = XML->DocumentElement->ChildNodes->GetNode("project");

Don't use the interfaces directly. Use the DelphiInterface wrappers instead
(also, the code has been fixed to do what you want). They provide for
reference counting and ensure that the node object stays in memory until you
are finished with it. In this case you want to use _di_IXMLNode. The
classe methods all return and expect these types:

_di_IXMLNode node = XML->DocumentElement;

> I am explicitly casting Attributes["version"] to an int because if I
> don't, it complains about an ambiguity between bool and __int64.

This is one of those RAD2007 bugs with implicit conversions. In past
versions of C++Builder the explicit cast wouldn't be needed. Now, you must
do *something* to the left hand side (a Variant) to get the correct type.
I've had to change a *lot* of my code because of this. Here is what I do
(since this is what the compiler should be doing for us):

if (node->Attributes["version"].operator int() != 1)

In the future, if CodeGear ever fixes (or enhances) this behavior then it is
a lot easier to search for ".operator" than it is to search for each
specialized explicit cast (String, int, Widestring, TDateTime, Currency,
etc).

Okay, so here's the full converted code:

XML->LoadFromFile(filename);
_di_IXMLNode node = XML->DocumentElement;
if (node == NULL || node->NodeName != "project")


throw Exception("Invalid configuration file (no <project>).");

if (!node->HasAttribute("version") ||
node->Attributes["version"].operator int() != 1)


throw Exception("Invalid configuration file (unsupported version).");

> Anyways, I can't figure out how to do what I'm trying to do here. It's

> starting to make me angry. Writing the data was extremely easy:
>
> IXMLNode *node = XML->AddChild("project");
> node->Attributes["version"] = 1;

Again, use _di_IXMLNode instead of IXMLNode*

HTH,
Clayton

Clayton Arends

unread,
Mar 2, 2008, 10:42:21 AM3/2/08
to
"Clayton Arends" <nospam_cla...@hotmail.com> wrote in message
news:47cac937$1...@newsgroups.borland.com...

> Now, you must do *something* to the left hand side (a Variant) to get
> the correct type.

Correction -- the variant types used by IXMLDocument and IXMLNode are
OleVariant.

Clayton

Clayton Arends

unread,
Mar 2, 2008, 10:56:13 AM3/2/08
to
"Jason Cipriani" <jason.c...@nospam-gmail.com> wrote in message
news:47ca...@newsgroups.borland.com...

> I'm still not sure what returns NULL

Pointers can be NULL.

_di_IXMLNode node = xmldoc->DocumentElement;
_di_IXMLNode attrnode = node->AttributeNodes->Nodes["version"];
if (attrnode == NULL)

> and what returns null-type Variants

The variants can be Null(). To check for those you would use
Variant::IsNull().

Variant value = node->Attributes["version"];
if (value.IsNull())

> and what just returns empty strings,

As far as attributes are concerned there is a setting to control this. If
you would rather work with Null() values you can use the default options.
If you'd rather work with "empty string" values then change your XML options
to remove the doAttrNull option.

XML->Options = XML->Options >> doAttrNull;

For more on this see "TXMLDocOption" in the help.

> is XML->DocumentElement *always* going to be the root data node?

Yes.

> Is that somehow different than XML->Node?

Yes. IXMLDocument::Node is basically the IXMLNode interface to the entire
IXMLDocument. The following two lines of code are functionally equivalent:

XML->ChildNodes->First();
XML->Node->ChildNodes->First();

The help has an okay explanation on this. The short of it is you won't need
to use the IXMLDocument::Node member.

HTH,
Clayton

Remy Lebeau (TeamB)

unread,
Mar 3, 2008, 12:38:39 PM3/3/08
to

"Jason Cipriani" <jason.c...@nospam-gmail.com> wrote in message
news:47ca41d2$1...@newsgroups.borland.com...

> Now I want to load the file back in and perform the following logic:
>
> 1) Find the top-level project node, making sure it exists, fail if it
> doesn't.
> 2) Read it's version attribute.
> 3) Compare it to 1 and fail if it's not 1.
>
> How do I do this using TXMLDocument?

XMLDocument1->LoadFromFile("...");
_di_IXMLNode Doc = XMLDocument1->DocumentElement;
if( (Doc) && (Doc->NodeName == "project") )
{
AnsiString version = Doc->Attributes["version"];
if( version != "1" )
//...
}

> The latest of many things I've tried has been this (XML is a TXMLDocument)
> (when I use XML->DocumentElement->ChildNodes->Nodes["project"]
> it does not find the node and returns NULL):

Of course not, because the "project" node is the DocumentElement node, not a
child of it.

> the (int) cast throws an exception stating:
>
> Could not convert a Variant of type (Null) into type (Integer).

You are reading an attribute that does not exist, so a Variant with a value
of null is being returned.

> But I don't know why it would be null; that attribute exists.

Not on the node you are reading it from, it doesn't.

> IXMLNode *an = node->AttributeNodes->FindNode("version");
> if (an == NULL)
> throw Exception("no version node");
> if (an->NodeValue != "1")
> throw Exception("invalid version");
>
> Throws "invalid version". The "version" attribute node does indeed exist.

No, it doesn't. However, the TXMLDocument::Options property has the
doAttrNull flag enabled by default, so that is why Null is returned for any
attribute you read from that does not exist.


Gambit


Jason Cipriani

unread,
Mar 4, 2008, 8:19:20 PM3/4/08
to
Thanks for the great response and explanations, this clears a lot of stuff
up. Sorry about the delayed response, it's been hectic.

"Clayton Arends" <nospam_cla...@hotmail.com> wrote in message
news:47cac937$1...@newsgroups.borland.com...

>> I am explicitly casting Attributes["version"] to an int because if I
>> don't, it complains about an ambiguity between bool and __int64.
>
> This is one of those RAD2007 bugs with implicit conversions. In past
> versions of C++Builder the explicit cast wouldn't be needed. Now, you
> must do *something* to the left hand side (a Variant) to get the correct
> type. I've had to change a *lot* of my code because of this. Here is what
> I do (since this is what the compiler should be doing for us):
>
> if (node->Attributes["version"].operator int() != 1)

It seems to have an issue with AnsiString too; because there's a few
conversion paths it can take (one through AnsiString's wchar_t*
constructor). I seem to remember having to do this:

node->Attributes["version"].operator AnsiString()

Instead of this:

(AnsiString)node->Attributes["version"]

Because of that particular ambiguity (it ended up not being able to decide
between a couple of different AnsiString constructors, but really it seems
like it should have just used the OleVariant casting operator instead).

> XML->LoadFromFile(filename);
> _di_IXMLNode node = XML->DocumentElement;
> if (node == NULL || node->NodeName != "project")
> throw Exception("Invalid configuration file (no <project>).");
> if (!node->HasAttribute("version") ||
> node->Attributes["version"].operator int() != 1)
> throw Exception("Invalid configuration file (unsupported version).");

Ah, changing all these IXMLNode *'s to _di_IXMLNode's sure is fun! Live and
learn, I guess. :)

> HTH,

Definitely; I was able to clean up a lot of my code and make loading the
data back in as simple as writing it out was.

Thanks again,
Jason


Jason Cipriani

unread,
Mar 4, 2008, 8:21:08 PM3/4/08
to
"Clayton Arends" <nospam_cla...@hotmail.com> wrote in message
news:47cace1f$1...@newsgroups.borland.com...

>> Is that somehow different than XML->Node?
>
> Yes. IXMLDocument::Node is basically the IXMLNode interface to the entire
> IXMLDocument. The following two lines of code are functionally
> equivalent:
>
> XML->ChildNodes->First();
> XML->Node->ChildNodes->First();

Thanks for clearing all that up. With the DocumentElement thing, too, I
didn't realize the <?xml was a node that it parsed, I always just assumed
that was some kind of XML meta data that the parser would use but never pass
back to you.

Jason


0 new messages