1
votes

I am working on a biztalk project and I need to copy (filtered) content from 1 xml to another. I have to do this with xpath, I can't use xsl transformation. So my xpath to get the content from the source xml file is this:

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

Now this returns an xmlNodelist. Is it possible to return a string with all the nodes in it like:

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

If I put string() before my xpath it returns the values, but I want the whole xml in a string (with nodes..), so I could load that string in another xmldocument. I think this is the best method for my problem.

I know I can loop over the xmlnodelist and append the nodes to the new xmldocument, but it's a bit tricky to loop in a biztalk orchestration and I want to avoid this.

The code I can use is C#. I've tried to just assign the nodelist to the xmldocument, but this throws a cast error (obvious..).

The way I see it is that I have 2 solutions:

  • assign the nodelist to the xmldocument without a loop (not possible i think in C#)
  • somehow convert the nodelist to string and load this in the xmldocument
  • load the xpath directly in the new xmldocument (don't know if this is possible since it returns a nodelist)

Thanks for your help

edit:

sample input:

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

Now I want to copy this sample to another xmldocument, but without insurance node 2 and 3 (this is dynamically, so it could be unsurance node 1 and 2 to delete, or 1 and 3...)

So this has to be the output:

<root>
<Patient>
    <PatientId></PatientId>
    <name></name>
</Patient>
<insurance>
    <id>1</id>
    <billing></billing>
</insurance>
 </root>

What I am doing now is use the xpath to get the nodes I want. Then I want to assign the result to the new xmldocument, but this is not possible since I get the castException

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)

I know the syntax is a bit strange, but it is biztalk c# code..

2
@Dimitre, it looks like you removed the XPath tag, and understandably so, but I'm not convinced that there isn't a question of XPath in here. There's a good chance that the XPath expression the OP is using is wrong. - LarsH
@LarsH: I edited the tags because what the OP wants clearly cannot be achieved with XPath alone -- or do you suggest to put it under "not-xpath"? BTW, did you check your email? - Dimitre Novatchev
@Dimitre: I think the xpath tag is appropriate for 2 reasons: 1) the question "Can this be done using XPath?" is a useful question about XPath, even if the answer is "no"; and 2) even though this task cannot be done with XPath alone (not much can), it could be done with XPath in combination with other tools in the environment, in which case it's necessary to get the XPath expression right. (Yes I will respond to email shortly. :-) - LarsH

2 Answers

3
votes

The most straightforward solution would indeed be to "loop over the xmlnodelist and append (import) the nodes to the new xmldocument", but since you can't loop, what other basic things can/can't you do?

To serialize the nodelist, you could try using XmlNodeList.toString(). If that worked, you'd get a strange beast, because it could be duplicating parts of the XML document several times over. Especially since you're explicitly including ancestors and descendants directly in the nodelist. It would not be something that you could parse back in and have a result that resembled the nodelist you started with.

In other words, it would be best to loop over the XmlNodeList and import the nodes to the new XmlDocument.

But even so, I would be really surprised if you wanted to put all these ancestor and descendant nodes:

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

directly into the new XML document. If you post some sample input and the desired output, we can probably help determine if that's the case.

Update:

I see what you're trying to do: copy an XML document, omitting all <insurance> elements (and their descendants) except the one you want.

This can be done without a loop if the output is as simple as your sample output: only one <Patient> and one <insurance> element, with their descendants, under one top-level element.

Something like (I can't test this as I don't have a biztalk server):

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

I confess though, I'm curious why you can't use XSLT. You're approaching tasks that would be more easily done in XSLT than in XPath + C# XmlDocument.

Update: since the xpath() function probably returns an XmlNodeList rather than an XmlNode, I added [0] to the first argument to ImportNode() above. Thanks to @Martin Honnen for alerting me to that.

1
votes

XPath is a query language (only) for XML documents.

It operates on an abstract model -- the XML INFOSET, and cannot either modify the structure of the XML document(s) it operates on or serialize the INFOSET information items back to XML.

Therefore, the only way to achieve such serialization is to use the language that is hosting XPath.

Apart from this, there are obvious problems with yout question, for example these is no element named IN1_Insurance in the provided XML document -- therefore the XPath expression provided:

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

selects all elements in the document.

Note:

The described task is elementary to fulfil using XSLT.

Finally: If you are allowed to use C# then you can use the XslCompiledTransform (or XslTransform) class. Use its Transform() method to carry out the following transformation against the XML document:

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

This produces exactly the wanted result:

<root>
    <Patient>
        <PatientId></PatientId>
        <name></name>
    </Patient>
    <insurance>
        <id>1</id>
        <billing></billing>
    </insurance>
</root>