delphi xml childnodes.findnode不适用于CDATA

发布于 2025-01-17 19:42:50 字数 2230 浏览 0 评论 0 原文

我有一个来自 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;

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;

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

游魂 2025-01-24 19:42:50

您的XML文档正在使用命名空间,但是您对 findNode()的使用正在告诉它忽略命名空间。因此,在调用 findNode()时,应指定正确的名称空间。

另外,即使 findNode()成功地找到了节点, ixmlnode.istextlement 属性不支持 cdata content,只有普通文本内容。这甚至是 comporded行为

但是,属性将愉快地返回 cdata content(您可能会或不得不手动解码,我不确定) - 至少在现代版本中( wither版本不支持此)。

尝试更多这样的东西:

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

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;
...
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文