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