1

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>
8
  • Please edit your question, and add a desired XML output Commented Jan 16 at 11:33
  • @Yitzhak Khabinsky I added a result XML. 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 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. 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). Commented Jan 16 at 12:17

3 Answers 3

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>
Sign up to request clarification or add additional context in comments.

Comments

0

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

2 Comments

This XML structure is poor.
@dbasnett Yes it is. This structure is coming directly from IBM. It is part of it's requirement management system and I was also wondering about the poor structure.
0

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.