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;
...
