xml - How to modify a feature tag attribute depending on a value of a different attribute with LINQ - Stack Overflow

admin2025-04-25  2

I have the following source as XML:

<?xml version="1.0"?>
<report>
    <feature tag="Config"/>
    <feature tag="Runtime">
        <feature tag="Metadata">
            <property name="date" value="16.01.2025"/>
            <property name="time" value="09:31:34"/>
        </feature>
        <feature tag="Templates">
            <feature tag="Template">
                <property name="username" value="myself"/>
                <property name="password" value="something"/>
                <feature tag="Data sources">
                    <feature tag="Source">
                        <property name="name" value="modules"/>
                        <property name="driver" value="eval"/>
                    </feature>
                    <feature tag="Source">
                        <property name="name" value="Artifact"/>
                        <property name="driver" value="eval"/>
                    </feature>
                    <feature tag="Source">
                        <property name="name" value="Comments"/>
                        <property name="driver" value="eval"/>
                        </feature>
                    </feature>
                </feature>
            </feature>
        </feature>
    </feature>
</report>

I want to modify the value of the driver property (below the feature tag Source), but only if the value of the name property (below the feature tag Source) equals the word "modules".

I tried to use the following function only to extract the feature tags Source. I think it is possible to modify the properties in the way I want in one LINQ command, but I don't know how to formulate this if construct in XPath.

Private Function ModifyXml(ByVal xml As String) As Boolean
     Try
         Dim xdoc As New XDocument
         xdoc = XDocument.Parse(xml)

         Dim query As String = "/report/feature[@tag='Runtime']/feature[@tag='Templates']/feature[@tag='Template']/feature[@tag='Data sources']/feature[@tag='Source']"
         xdoc.XPathSelectElements(query).ToList()

         xdoc.Save("c:\temp\myFile.xml")

         Return True
     Catch ex As Exception
         Return False
     End Try
 End Function

The result should look like this:

I need a driver name in the properties as value. This driver name depends on the value of the name properties.

<?xml version="1.0"?>
<report>
    <feature tag="Config"/>
    <feature tag="Runtime">
        <feature tag="Metadata">
            <property name="date" value="16.01.2025"/>
            <property name="time" value="09:31:34"/>
        </feature>
        <feature tag="Templates">
            <feature tag="Template">
                <property name="username" value="myself"/>
                <property name="password" value="something"/>
                <feature tag="Data sources">
                    <feature tag="Source">
                        <property name="name" value="modules"/>
                        <property name="driver" value="somedriver1"/>
                    </feature>
                    <feature tag="Source">
                        <property name="name" value="Artifact"/>
                        <property name="driver" value="somedriver2"/>
                    </feature>
                    <feature tag="Source">
                        <property name="name" value="Comments"/>
                        <property name="driver" value="somedriver3"/>
                        </feature>
                    </feature>
                </feature>
            </feature>
        </feature>
    </feature>
</report>

I have the following source as XML:

<?xml version="1.0"?>
<report>
    <feature tag="Config"/>
    <feature tag="Runtime">
        <feature tag="Metadata">
            <property name="date" value="16.01.2025"/>
            <property name="time" value="09:31:34"/>
        </feature>
        <feature tag="Templates">
            <feature tag="Template">
                <property name="username" value="myself"/>
                <property name="password" value="something"/>
                <feature tag="Data sources">
                    <feature tag="Source">
                        <property name="name" value="modules"/>
                        <property name="driver" value="eval"/>
                    </feature>
                    <feature tag="Source">
                        <property name="name" value="Artifact"/>
                        <property name="driver" value="eval"/>
                    </feature>
                    <feature tag="Source">
                        <property name="name" value="Comments"/>
                        <property name="driver" value="eval"/>
                        </feature>
                    </feature>
                </feature>
            </feature>
        </feature>
    </feature>
</report>

I want to modify the value of the driver property (below the feature tag Source), but only if the value of the name property (below the feature tag Source) equals the word "modules".

I tried to use the following function only to extract the feature tags Source. I think it is possible to modify the properties in the way I want in one LINQ command, but I don't know how to formulate this if construct in XPath.

Private Function ModifyXml(ByVal xml As String) As Boolean
     Try
         Dim xdoc As New XDocument
         xdoc = XDocument.Parse(xml)

         Dim query As String = "/report/feature[@tag='Runtime']/feature[@tag='Templates']/feature[@tag='Template']/feature[@tag='Data sources']/feature[@tag='Source']"
         xdoc.XPathSelectElements(query).ToList()

         xdoc.Save("c:\temp\myFile.xml")

         Return True
     Catch ex As Exception
         Return False
     End Try
 End Function

The result should look like this:

I need a driver name in the properties as value. This driver name depends on the value of the name properties.

<?xml version="1.0"?>
<report>
    <feature tag="Config"/>
    <feature tag="Runtime">
        <feature tag="Metadata">
            <property name="date" value="16.01.2025"/>
            <property name="time" value="09:31:34"/>
        </feature>
        <feature tag="Templates">
            <feature tag="Template">
                <property name="username" value="myself"/>
                <property name="password" value="something"/>
                <feature tag="Data sources">
                    <feature tag="Source">
                        <property name="name" value="modules"/>
                        <property name="driver" value="somedriver1"/>
                    </feature>
                    <feature tag="Source">
                        <property name="name" value="Artifact"/>
                        <property name="driver" value="somedriver2"/>
                    </feature>
                    <feature tag="Source">
                        <property name="name" value="Comments"/>
                        <property name="driver" value="somedriver3"/>
                        </feature>
                    </feature>
                </feature>
            </feature>
        </feature>
    </feature>
</report>
Share Improve this question edited Jan 16 at 13:45 Yitzhak Khabinsky 22.5k2 gold badges19 silver badges23 bronze badges asked Jan 16 at 11:19 Mark MullenMark Mullen 271 silver badge7 bronze badges 8
  • Please edit your question, and add a desired XML output – Yitzhak Khabinsky Commented Jan 16 at 11:33
  • @Yitzhak Khabinsky I added a result XML. – Mark Mullen Commented Jan 16 at 11:42
  • 1 XPath queries, it doesn't modify. As the name suggests, XPathSelectElements returns the nodes you want. You'll have to process each one of them and modify the correct child elements. The unconventional XML schema makes this harder than needed -Templates,Source, Comments etc should all be elements, not values in a generic attribute. No flexibility is gained by the current format – Panagiotis Kanavos Commented Jan 16 at 11:55
  • 1 LINQ is also a query, not a modification language, it doesn't modify. No matter what language you use you'll have to iterate over the feature elements they produce and for each one, retrieve the name or driver children and modify their value attribute. – Panagiotis Kanavos Commented Jan 16 at 12:02
  • 1 You can use LINQ to XML to produce a new XDocument with either the original or new elements. That's similar to using XSLT. Instead of breaking the modifications into multiple templates though, you can break them into multiple methods. This is shown in the docs, in How to transform the shape of an XML tree (LINQ to XML). – Panagiotis Kanavos Commented Jan 16 at 12:17
 |  Show 3 more comments

3 Answers 3

Reset to default 2

You could use a simple xslt to do the job:

<?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"/>

  <!-- Identity template to copy the rest of the XML as is -->
  <xsl:template match="@*|node()">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
  </xsl:template>

  <!-- 
    A template match on with this xpath for the correct attribute to change:
    feature[property/@value='modules']/property[@name='driver']/@value
  -->
  <xsl:template match="feature[property/@value='modules']/property[@name='driver']/@value">
    <xsl:attribute name="value">somedriver1</xsl:attribute>
  </xsl:template> 

  <!-- 
    A template match on with this xpath for the correct attribute to change:
    feature[property/@value='Artifact']/property[@name='driver']/@value
  -->
  <xsl:template match="feature[property/@value='Artifact']/property[@name='driver']/@value">
    <xsl:attribute name="value">somedriver2</xsl:attribute>
  </xsl:template>

  <!-- 
    A template match on with this xpath for the correct attribute to change:
    feature[property/@value='Comments']/property[@name='driver']/@value
  -->
  <xsl:template match="feature[property/@value='Comments']/property[@name='driver']/@value">
    <xsl:attribute name="value">somedriver3</xsl:attribute>
  </xsl:template>

</xsl:stylesheet>

XPath queries, it doesn't modify. As the name suggests, XPathSelectElements returns the nodes you want. You'll have to process each one of them and modify the correct child elements. The unconventional XML schema makes this harder than needed -Templates,Source, Comments etc should all be elements, not values in a generic attribute. No flexibility is gained by the current format.

LINQ is also a query, not a modification language, it doesn't modify. No matter what language you use you'll have to iterate over the feature elements they produce and for each one, retrieve the name or driver children and modify their value attribute.

Comment from Panagiotis Kanavos

Using this data

    Dim docXE As XElement
    'test data
    docXE = <report>
                <feature tag="Config"/>
                <feature tag="Runtime">
                    <feature tag="Output">
                        <feature tag="Target">
                            <property name="type" value="Html"/>
                            <property name="driver" value="Telelogic.Html.Driver"/>
                        </feature>
                        <feature tag="Target">
                            <property name="type" value="Word"/>
                            <property name="driver" value="Telelogic.Word.Driver"/>
                        </feature>
                        <feature tag="Target">
                            <property name="type" value="PDF"/>
                            <property name="driver" value="Telelogic.Pdf.Driver"/>
                        </feature>
                    </feature>
                    <feature tag="Metadata">
                        <property name="date" value="16.01.2025"/>
                        <property name="time" value="09:31:34"/>
                    </feature>
                    <feature tag="Templates">
                        <feature tag="Template">
                            <property name="username" value="myself"/>
                            <property name="password" value="something"/>
                            <feature tag="Data sources">
                                <feature tag="Source">
                                    <property name="name" value="modules"/>
                                    <property name="driver" value="eval"/>
                                </feature>
                                <feature tag="Source">
                                    <property name="name" value="Artifact"/>
                                    <property name="driver" value="eval"/>
                                </feature>
                                <feature tag="Source">
                                    <property name="name" value="Comments"/>
                                    <property name="driver" value="eval"/>
                                </feature>
                            </feature>
                        </feature>
                    </feature>
                </feature>
            </report>

Try this code.

    Dim selMod As List(Of XElement)
    selMod = (From el In docXE...<feature>
                Where el.@tag = "Source" AndAlso
                 el.<property>.@value = "modules"
                     From src In el.<property>
                        Where src.@name = "driver"
                        Select src).ToList

    For Each el As XElement In selMod
        el.@name = "FOODriver"
    Next
转载请注明原文地址:http://anycun.com/QandA/1745535111a90891.html