Andres Scarpone
Andres Scarpone

Reputation: 69

How to appens nodes using XPATH

Hi everyone I'm using XPATH with XML.SelectNodes() to extract dome data from an XML file, I wish this data to be in certain order, the XML file is like this:

<?xml version='1.0' encoding='UTF-8'?>
    <ConvenioAladi>
       <Operaciones>
         <Operacion Prioridad='Alta' />
         <Operacion Prioridad='Media' />
         <Operacion Prioridad='Alta' />
         <Operacion Prioridad='Baja' />
         <Operacion Prioridad='Baja' />
         <Operacion Prioridad='Media' />
       </Operaciones>
    </ConvenioAladi>

And wish to obtain an XML like this:

<?xml version='1.0' encoding='UTF-8'?>
    <ConvenioAladi>
       <Operaciones>
         <Operacion Prioridad='Alta' />
         <Operacion Prioridad='Alta' />
         <Operacion Prioridad='Media' />
         <Operacion Prioridad='Media' />
         <Operacion Prioridad='Baja' />
         <Operacion Prioridad='Baja' />
       </Operaciones>
    </ConvenioAladi>

I'm capable of obtaining one of the Prioridad attributes at any moment by giving the XPATH:

'/ConvenioAladi/Operaciones/Operacion[@Prioridad='Alta']',

but if I try something like this: '/ConvenioAladi/Operaciones/Operacion[@Prioridad='Alta' or @Prioridad='Media' or @Prioridad='Baja' ]'

Or: '/ConvenioAladi/Operaciones/Operacion[@Prioridad='Alta'] | /ConvenioAladi/Operaciones/Operacion[@Prioridad='Media'] | /ConvenioAladi/Operaciones/Operacion[@Prioridad='Baja']'

I always get the original XML back, is there anyway of achieving what I mentioned before?. Thanks

Upvotes: 0

Views: 103

Answers (2)

ScalaWilliam
ScalaWilliam

Reputation: 741

XPath in the version you are using matches nodes in order, which you cannot change.

There are several ways to achieve it:

XSLT

One simple way is to perform an XSL Transform

Once you learn XSLT, it becomes really easy to do this sort of thing. Such as:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    version="2.0">
    <xsl:template match="Operaciones">
        <xsl:copy>
            <xsl:apply-templates select="Operacion">
                <xsl:sort select="index-of(('Alta','Media','Baja'), @Prioridad)"/>
            </xsl:apply-templates>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>

And then you can query all your Operaciones easily. If you do it the XSLT route, it means that you can upgrade your queries without having to recompile your software. It is probably the solution you wish to utilise.

Update: Mathias pointed out that we're not looking for an alphabetical sort. I will keep the solutions below for reference, but now I'd advise to go for the correct XSLT solution above.

XPathDocument

The second way is more programatic, which utilises Microsoft's own features:

Assuming doc is an XmlDocument, you can do the following to turn it into an XPathDocument:

XPathDocument xpathDoc = new XPathDocument(new XmlNodeReader(doc));

Once you do that, you can run the following query:

XPathNavigator nav = xpathDoc.CreateNavigator();
XPathExpression expression = XPathExpression.Compile(@"//Operacion");
expression.AddSort(@"@Prioridad", XmlSortOrder.Ascending, XmlCaseOrder.None, "", XmlDataType.Text);
XPathNodeIterator iterator = nav.Select(expression);
foreach (XPathNavigator operation in iterator) {
    Console.WriteLine("Found priority '{0}'",operation.GetAttribute("Prioridad",""));
}

Linq

Third way, using Linq (you might want to check the syntax here):

XDocument xDoc = XDocument.Load(new XmlNodeReader(doc));
var operations = xDoc.Descendants("Operacion").OrderBy(s=>(string)s.Attribute("Prioridad"));
foreach(var operation in operations) {
    Console.WriteLine("hey -> {0}", operation);
}

I suggest you do it via Linq, but if you are transforming data, then go for XSLT every time.

Upvotes: 2

Jesse Alama
Jesse Alama

Reputation: 1

As discussed before, there are a few options here. For the sake of concreteness, here's a complete XSLT (1.0) solution, which has so far only been hinted at:

<?xml version='1.0' encoding='UTF-8'?>

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" indent="yes"/>

  <xsl:template match="*">
    <xsl:variable name="n" select="name (.)"/>
    <xsl:element name="{$n}">
      <xsl:for-each select="@*">
        <xsl:copy-of select="."/>
      </xsl:for-each>
      <xsl:apply-templates select="*"/>
    </xsl:element>
  </xsl:template>

  <xsl:template match="/">
    <xsl:apply-templates select="ConvenioAladi"/>
  </xsl:template>

  <xsl:template match="Operaciones">
    <xsl:apply-templates select="Operacion[@Prioridad = &quot;Alta&quot;]"/>
    <xsl:apply-templates select="Operacion[@Prioridad = &quot;Media&quot;]"/>
    <xsl:apply-templates select="Operacion[@Prioridad = &quot;Baja&quot;]"/>
  </xsl:template>
</xsl:stylesheet>

You can apply this stylesheet to your XML and it sorts the Operacion nodes in the order Alta, Media, Baja. It proceeds rather naively: it simply looks for any Operacion having the value Alta for the Prioridad attribute, and (essentially) copies it to the output. Then Media, then Baja. It would work even if there are no Operacion nodes having value Baja for their Prioridad attribute (and similarly for the other two values). The stylesheet does assume, implicitly, that these three are the only possible values for the Prioridad attribute (if there are such nodes, they are silently ignored), so if you have Operacion nodes that might have other values for their Prioridad attribute, the stylesheet would need to be adjusted.

Upvotes: 0

Related Questions