关于c#:xpath返回字符串而不是nodelist

xpath return string instead of nodelist

我正在处理一个Biztalk项目,我需要将经过筛选的内容从一个XML复制到另一个XML。我必须使用XPath来完成这项工作,我不能使用XSL转换。所以从源XML文件获取内容的xpath是:

1
//*[not(ancestor-or-self::IN1_Insurance)]|//IN1_Insurance[2]/descendant-or-self::*

现在返回一个xmlnodelist。是否可以返回包含所有节点的字符串,如:

1
"<root><node>text</node></root>"

如果我将string()放在xpath之前,它将返回值,但我希望整个XML包含在一个字符串中(包含nodes..),这样我就可以将该字符串加载到另一个xml文档中。我认为这是解决我问题的最好方法。

我知道我可以循环使用xmlnodelist并将节点附加到新的xmldocument中,但是在一个biztalk编排中循环有点困难,我希望避免这种情况。

我可以使用的代码是C。我尝试将nodelist分配给xmldocument,但这会引发一个强制转换错误(显然是..)。

我认为我有两个解决方案:

  • 在没有循环的情况下将nodelist分配给xmldocument(我认为在C中不可能)
  • 不知何故,将nodelist转换为字符串,并将其加载到xmldocument中
  • 直接在新的xml文档中加载xpath(不知道是否可能,因为它返回一个nodelist)

谢谢你的帮助

编辑:

样本输入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<root>
<Patient>
    <PatientId></PatientId>
    <name></name>
</Patient>
<insurance>
    <id>1</id>
    <billing></billing>
</insurance
<insurance>
    <id>2</id>
    <billing></billing>
</insurance>
<insurance>
    <id>3</id>
    <billing></billing>
</insurance>
   </root>

现在,我想将这个示例复制到另一个XML文档,但是没有保险节点2和3(这是动态的,因此可以删除不确定的节点1和2,或者1和3…)

所以这必须是输出:

1
2
3
4
5
6
7
8
9
10
<root>
<Patient>
    <PatientId></PatientId>
    <name></name>
</Patient>
<insurance>
    <id>1</id>
    <billing></billing>
</insurance>
 </root>

我现在要做的是使用xpath获取我想要的节点。然后我想将结果分配给新的xml文档,但这是不可能的,因为我得到了castexception

1
2
3
string xpath ="//*[not(ancestor-or-self::IN1_Insurance)]|//IN1_Insurance[2]/descendant-or-self::*";
xmlDoc = new System.Xml.XmlDocument();
xmlDoc = xpath(sourceXml, strXpath);   <= cast error (cannot cast xmlnodelist to xmldocuemnt)

我知道语法有点奇怪,但它是Biztalk C代码。


最简单的解决方案确实是"循环遍历xmlnodelist并将节点附加(导入)到新的xmlmdocument",但是由于不能循环,您还可以/不能做什么其他基本的事情?

要序列化节点列表,可以尝试使用xmlnodelist.toString()。如果成功的话,您会遇到一个奇怪的怪兽,因为它可能会多次复制XML文档的某些部分。尤其是因为你明确地将祖先和后代直接包括在节点列表中。它不会是您可以重新解析并得到与您开始使用的节点列表类似的结果的东西。

换句话说,最好在xmlnodelist上循环并将节点导入到新的xmlDocument。

但即便如此,如果你想把所有这些祖先和后代节点都放在

1
//*[not(ancestor-or-self::IN1_Insurance)]|//IN1_Insurance[2]/descendant-or-self::

直接进入新的XML文档。如果您发布一些示例输入和所需的输出,我们可能会帮助确定是否是这种情况。

更新:

我看到了您要做的:复制一个XML文档,省略除您想要的元素以外的所有元素(及其后代)。

如果输出与示例输出一样简单,那么可以不使用循环来完成此操作:在一个顶级元素下,只有一个和一个元素及其后代。

例如(我无法测试此项,因为我没有BizTalk Server):

1
2
3
4
5
6
7
8
9
10
string xpathPatient ="/*/Patient";
string xpathInsuran ="/*/insurance[id =" + insId +"]"; // insId is a parameter
xmlDoc = new System.Xml.XmlDocument();
xmlPatient = xpath(sourceXml, xpathPatient);
xmlInsuran = xpath(sourceXml, xpathInsuran);
XmlElement rootNode  = xmlDoc.CreateElement("root");
xmlDoc.AppendChild(rootNode);
//**Update: use [0] to get an XmlNode from the returned XmlNodeList (presumably)
rootNode.AppendChild(xmlDoc.ImportNode(xmlPatient[0], true));
rootNode.AppendChild(xmlDoc.ImportNode(xmlInsuran[0], true));

不过,我承认,我很好奇为什么你不能使用XSLT。您正在处理的任务在XSLT中比在xpath+c_xmldocument中更容易完成。

更新:由于xpath()函数可能返回xmlnodelist而不是xmlnode,所以我将[0]添加到上面importnode()的第一个参数中。感谢@martin honnen提醒我。


xpath是XML文档的查询语言(仅限)。

它在抽象模型(XML信息集)上操作,不能修改它所操作的XML文档的结构,也不能将信息集信息项序列化回XML。

因此,实现这种序列化的唯一方法是使用承载xpath的语言。

除此之外,您的问题也存在明显的问题,例如,在提供的XML文档中,这些元素不是名为IN1_Insurance的元素,因此,xpath表达式提供了:

1
//*[not(ancestor-or-self::IN1_Insurance)]|//IN1_Insurance[2]/descendant-or-self::*

选择文档中的所有元素。

注:

所描述的任务是使用XSLT完成的基本任务。

最后:如果允许您使用C,那么您可以使用XslCompiledTransform(或XslTransform)类。使用其transform()方法对XML文档执行以下转换:

1
2
3
4
5
6
7
8
9
10
11
12
13
<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

   <xsl:template match="node()|@*">
       <xsl:copy>
           <xsl:apply-templates select="node()|@*"/>
       </xsl:copy>
   </xsl:template>

   <xsl:template match="insurance[not(id=1)]"/>
</xsl:stylesheet>

这就产生了想要的结果:

1
2
3
4
5
6
7
8
9
10
<root>
    <Patient>
        <PatientId></PatientId>
        <name></name>
    </Patient>
    <insurance>
        <id>1</id>
        <billing></billing>
    </insurance>
</root>