Home > database >  search string in value and get attribute value in LINQ to XML in VB.NET
search string in value and get attribute value in LINQ to XML in VB.NET

Time:11-18

I have an XML file that I want to search for the value of "val" using the Contains command and get its "index" attribute as well as the "catalog name" attribute.

<list>
    <catalog index="1" name="n1">
        <val index="1">sample text 1</val>
        <val index="2">sample text 2</val>
        <val index="3">sample text 3</val>
    </catalog>
    <catalog index="2" name="n2">
        <val index="1">sample text 0</val>
        <val index="2">sample text 2</val>
        <val index="3">sample text 3</val>
        <val index="4">sample text 1</val>
        <val index="5">sample text 5</val>
        <val index="6">sample text 6</val>
    </catalog>
    <catalog index="3" name="n3">
        <val index="1">sample text 8</val>
        <val index="2">sample text 9</val>
        <val index="3">sample text 10</val>
    </catalog>
</list>

I used

Dim xml_Doc = XDocument.Load(myPath & "list.Xml")

Dim search_result As IEnumerable(Of XElement)
search_result =
        (From c In xml_Doc.Descendants("catalog")
         Where c.Elements("val").Value.Contains("sample text 1")
         Select c.Elements("val").Attributes("index").ToString & c.Attribute("name").Value)

How can I do that?

The output should be as follows:

index:1 , name: n1
index:4 , name: n2

CodePudding user response:

The easiest way to do this looks to be a nested From query over the c.Elements("val") elements like so:

Dim sampleText = "sample text 1"
Dim search_result = 
    (From c In xml_Doc.Descendants("catalog")
    From v in c.Elements("val")
    Where v.Value.Contains(sampleText)
    Select New With {.index = v.Attribute("index").Value, .name = c.Attribute("name").Value })
    
For Each s In search_result
    Console.WriteLine("index:{0} , name:{1}", s.index, s.name)
Next

Which produces:

index:1 , name:n1
index:4 , name:n2
index:3 , name:n3

Notes:

  • Doing it this way allows you to easily filter on the value of the inner element v but then select attributes from both v and the outer element c.

  • index:3 , name:n3 is included in the results because the text of this element, sample text 10, contains the search string sample text 1. If you do not want this element included, change your Where clause to use equality:

     Dim search_result = 
         (From c In xml_Doc.Descendants("catalog")
         From v in c.Elements("val")
         Where v.Value = sampleText
         Select New With {.index = v.Attribute("index").Value, .name = c.Attribute("name").Value })
    

Demo fiddles here and here.

CodePudding user response:

Disclaimer: I love XmlSerialization. Strong typing and reusable objects. I would tackle this problem first by creating some classes to serialize the file to

Imports System.IO
Imports System.Xml.Serialization
<XmlRoot("list")>
Public Class List
    <XmlElement("catalog")>
    Public Property Catalogs As List(Of Catalog)
End Class

Public Class Catalog
    <XmlElement("val")>
    Public Property Vals As List(Of Val)
    <XmlAttribute("index")>
    Public Property Index As Integer
    <XmlAttribute("name")>
    Public Property Name As String
End Class

Public Class Val
    <XmlAttribute("index")>
    Public Property Index As Integer
    <XmlText>
    Public Property Text As String
End Class

Then deserialize the file

Dim list As List
Dim serializer As New XmlSerializer(GetType(List))
Using sr As New StreamReader("list.xml")
    list = CType(serializer.Deserialize(sr), List)
End Using

Now your xml is in .NET classes and you can use LINQ to get what you're after

Dim searchString = "sample text 1"
Dim catalogs As New List(Of Catalog)()
For Each catalog In list.Catalogs
    Dim vals = catalog.Vals.Where(Function(val) val.Text = searchString)
    For Each val In vals
        catalogs.Add(New Catalog() With {.Vals = {val}.ToList(), .Name = catalog.Name, .Index = catalog.Index})
    Next
Next
Dim search_results = catalogs.SelectMany(Function(c) c.Vals.Select(Function(v) $"index:{v.Index}, name: {c.Name}"))
For Each search_result In search_results
    Console.WriteLine(search_result)
Next

Output:

index:1, name: n1
index:4, name: n2

It gets a little unwieldy because you want m items out of an n to m relationship (n catalogs and m vals) so we use SelectMany to project catalogs into the number of vals. But the object list has all your data you can use to other ends either way.

As noted in the other answer, you were searching for strings containing "sample text 1" but that returns "sample text 10", and according to your desired output, you should search for an exact match.

  • Related