xmlvb.netsearchlinq-to-xml

Trying to count named XML elements filtered by parent element name and index value


I have been just about managing to manipulate XML in VB.net but I've run into a problem I'm really struggling with, so I'm reaching out for a little guidance please?!

With this example XML...

<?xml version="1.0" encoding="utf-8"?>
<dataset>
  <packages>
    <package index="1">
      <desc>First Package</desc>
      <rmabool>1</rmabool>
      <rmaref>RMACASE1</rmaref>
      <bootfiles>1</bootfiles>
      <image>1</image>
      <driver>3</driver>
      <driver>4</driver>
    </package>
    <package index="2">
      <desc>Second Package</desc>
      <bootfiles>2</bootfiles>
      <image>2</image>
      <driver>3</driver>
    </package>
    <package index="3">
      <desc>Third Package</desc>
      <bootfiles>3</bootfiles>
      <image>2</image>
      <driver>3</driver>
    </package>
  </packages>
</dataset>

...I would like to count how many elements, named 'driver', are contained in an element named 'package' with an index matching "1". I would expect the result to be returned is "2", or if I change the search to match index with "3" then I would expect the result is "1".

Elsewhere I have a simple sub which returns a count of elements only matching a name, below is my effort to modify this but I cannot get it to work...

Public Function CountElementDescendents(ByVal CFGFile As String,_ 'Path to Config.xml
                                        ByVal Parent As String,_ 'Name of element to search descendents eg. 'package'
                                        ByVal ParentIndex As String,_ 'Index value of element to search descendents eg. '1'
                                        ByVal ElementCount As String_ 'Name of descendent elements to count 'eg. 'driver'
                                        ) As Integer 'Return number of matching elements eg. '2'
    Dim ReturnValue As Integer = 0
    Dim Xe As XElement

    Xe = XElement.Load(CFGFile)
    Dim Query As IEnumerable(Of XElement) = Xe.Elements(Parent).Attribute("index="&ParentIndex).Descendants(ElementCount)
    
    ReturnValue = Query.Count()
    CountElementDescendents = ReturnValue
End Function

I'm not sure if this is possible or even if I have taken the best approach of structuring my XML, can someone offer some assistance or advice please? Thanks!

Thanks to @dbasnett and @Craig for their help, you're awesome! I'll mark this as answered.


Solution

  • Give this a try. The LINQ query examines all driver elements and selects those whose parent is a package with an index of 1.

        'for testing use XML literal
        Dim xe As XElement
        xe = <dataset>
                 <packages>
                     <package index="1">
                         <desc>First Package</desc>
                         <rmabool>1</rmabool>
                         <rmaref>RMACASE1</rmaref>
                         <bootfiles>1</bootfiles>
                         <image>1</image>
                         <driver>3</driver>
                         <driver>4</driver>
                     </package>
                     <package index="2">
                         <desc>Second Package</desc>
                         <bootfiles>2</bootfiles>
                         <image>2</image>
                         <driver>3</driver>
                     </package>
                     <package index="3">
                         <desc>Third Package</desc>
                         <bootfiles>3</bootfiles>
                         <image>2</image>
                         <driver>3</driver>
                     </package>
                 </packages>
             </dataset>
    
        ' I would like to count how many elements, named 'driver', 
        '  are contained in an element named 'package' with an index matching "1". 
        '
        Dim ie As IEnumerable(Of XElement)
        ie = From el In xe...<driver>
             Where el.Parent.Name.LocalName = "package" AndAlso el.Parent.@index = "1"
             Select el
    

    edit: As function

    Private Function Srch(ElementToSearch As XElement,
                             ParentName As String,
                             ParentIndex As String) As IEnumerable(Of XElement)
    
        Dim ie As IEnumerable(Of XElement)
        ie = From el In ElementToSearch...<driver>
             Where el.Parent.Name.LocalName = ParentName AndAlso el.Parent.@index = ParentIndex
             Select el
    
        Return ie
    
    End Function
    

    edit 2 - updated function

    Private Function Srch(ElementToSearch As XElement,
                             FindThis As String,
                             ParentName As String,
                             ParentIndex As String) As IEnumerable(Of XElement)
    
        Dim ie As IEnumerable(Of XElement)
    
        ie = From el In ElementToSearch.Descendants(FindThis)
             Where el.Parent.Name.LocalName = ParentName AndAlso el.Parent.@index = ParentIndex
             Select el
    
        Return ie
    
    End Function
    

    Tested with

        Dim rslts As IEnumerable(Of XElement)
        rslts = Srch(xe, "bootfiles", "package", "1")