Delphi XML ChildNodes.FindNode 不适用于 CDATA

Delphi XML ChildNodes.FindNode does not work with CDATA

我有一个来自 Garmin 设备的 XML 文件,其中找不到 name 标签,因为其中有一个 CDATA

这不标准,大多数XML文件没有这个CDATA,然后FindNode()正常工作。

如果我在下面的示例中查找 number,它可以正常工作。

我该如何解决这个问题?

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

编辑:

XML 文件的完整开头如下所示:

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

编辑:

以下代码有效:

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;

您的 XML 文档正在使用命名空间,但您对 FindNode() 的使用告诉它忽略命名空间。因此,您应该在调用 FindNode().

时指定正确的命名空间

另外,即使FindNode()成功找到节点,IXMLNode.IsTextElement属性不支持CDATA内容,只支持纯文本内容。这甚至是documented behavior.

但是,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).

试试像这样的东西:

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