Home > OS >  Delphi XML ChildNodes.FindNode does not work with CDATA
Delphi XML ChildNodes.FindNode does not work with CDATA

Time:04-06

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