I have an XML file from a Garmin device, where the name
tag is not found, because it has a CDATA
in it.
This is not standard, most XML files don't have this CDATA
, and then FindNode()
works normally.
If I look for number
in the example below, it works normally.
How do I get around this?
<trk>
<name><![CDATA[Drawn track]]></name>
<src><![CDATA[MapToaster iOS]]></src>
<number>433385247</number>
LNode := TRKNode.ChildNodes.FindNode('name','');
if (LNode <> nil) and (LNode.IsTextElement) then
begin
AName := LNode.Text;
SLocalLog('(GetTracks) Name= ' AName ' TRKNode.ChildNodes.Count=' IntToStr(TRKNode.ChildNodes.Count));
end;
if AName = '' then
begin
LocalLog('(GetTracks) Name=empty, continue', d_warning);
continue;
end;
EDIT:
The full start of the XML file looks like this:
<?xml version="1.0" encoding="utf-8"?>
<gpx version="1.1" creator="MapToaster for iOS V3.5" xmlns="http://www.topografix.com/GPX/1/1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd">
<metadata>
<time>2022-03-28T07:24:24.207Z</time>
</metadata>
<trk>
<name><![CDATA[Drawn track]]></name>
<src><![CDATA[MapToaster iOS]]></src>
...
EDIT:
The following code works:
function FindNodeEx(ID: string; Nodes: IXMLNodelist):IXMLNode;
var
i: integer;
begin
result := nil;
for i := 0 to Nodes.Count-1 do
begin
SLocalLog(i.ToString ': ' Nodes[i].NodeName);
if Nodes[i].NodeName = 'name' then
begin
SLocalLog('FindNodeEx: ID found: =' Nodes[i].Text);
result := Nodes[i];
if result.NodeType = TNodeType.ntCData then SLocalLog('FindNodeEx: CDATA found'); // This does not happen.
SLocalLog('FindNodeEx: NodeType=' IntToStr(integer(result.NodeType))); // Shows as 'Element'
exit;
end;
end;
SLocalLog('FindNodeEx: ID not found: ' ID,d_warning);
end;
CodePudding user response:
Your XML document is using namespaces, but your use of FindNode()
is telling it to ignore namespaces. So, you should specify the correct namespace when calling FindNode()
.
Also, even if FindNode()
were successful in finding the node, the IXMLNode.IsTextElement
property does not support CDATA
content, only plain text content. This is even documented behavior.
However, the IXMLNode.Text
property will happily return CDATA
content (which you may or may not have to decode manually, I'm not sure) - at least in modern versions (older versions didn't support this).
Try something more like this:
function IsTextOrCDataElement(const ANode: IXMLNode): Boolean;
begin
Result := (ANode.NodeType = ntElement) and
(ANode.DOMNode.childNodes.length = 1) and
(ANode.DOMNode.childNodes[0].nodeType in [TEXT_NODE, CDATA_SECTION_NODE]);
end;
function RemoveCData(const AData: string): string;
begin
if StartsText('<![CDATA[', AData) and EndsText(']]>', AData) then
Result := Copy(AData, 10, Length(AData)-12)
else
Result := AData;
end;
...
LNode := TRKNode.ChildNodes.FindNode('name', 'http://www.topografix.com/GPX/1/1');
if (LNode <> nil) and IsTextOrCDataElement(LNode) then
begin
AName := RemoveCData(LNode.Text);
// or just:
// AName := LNode.Text;
// or:
// AName := LNode.NodeValue;
...
end;
...