I am having difficulty adding a node deeper in an xml structure. I am missing something between and node and nodeList. Any help would be greatly appreciated.
def xml='''<Root id="example" version="1" archived="false">
<Item name="one" value="test"/>
<Item name="two" value="test2"/>
<Item name="three" value="test3"/>
<AppSettings Name="foo" Id="foo1">
<roles>foo</roles>
</AppSettings>
<AppSettings Name="bar" Id="bar1">
<Item name="blue" value=""/>
<Item name="green" value=""/>
<Item name="yellow" value=""/>
<Roles>
<Role id="A"/>
<Role id="B"/>
<Role id="C"/>
</Roles>
</AppSettings>
</Root>'''
root = new XmlParser().parseText(xml)
def appSettings = root.'AppSettings'.find{it.#Name == "bar"}.'Roles'
appSettings.appendNode('Role', [id: 'D'])
def writer = new StringWriter()
def printer = new XmlNodePrinter(new PrintWriter(writer))
printer.preserveWhitespace = true
printer.print(root)
String result = writer.toString()
println result
Error
groovy.lang.MissingMethodException: No signature of method: groovy.util.NodeList.appendNode() is applicable for argument types: (java.lang.String, java.util.LinkedHashMap) values: [Role, [id:D]]
This line here:
def appSettings = root.'AppSettings'.find{it.#Name == "bar"}.'Roles'
is returning you a NodeList (containing a single node), so you want to call appendNode on the contents of this list, not on the list itself.
This can be done either by:
appSettings*.appendNode('Role', [id: 'D'])
Which will call appendNode on every element of the list, or by:
appSettings[0]?.appendNode('Role', [id: 'D'])
Which will call appendNode on the first element of the list (if there is a first element thanks to the null-safe operator ?).
Related
1) i want to read below mentioned XML file and access the values, i already tried in many ways but not able to access, for example i want 'NightRaidPerformanceCPUScore' value and that is from which passIndex.
<?xml version='1.0' encoding='utf8'?>
<benchmark>
<results>
<result>
<name />
<description />
<passIndex>-1</passIndex>
<sourceId>C:\Users\dgadhipx\Documents\3DMark\3dmark-autosave-20200401155825.3dmark-result</sourceId>
<NightRaidPerformance3DMarkScore>2066</NightRaidPerformance3DMarkScore>
<NightRaidPerformanceCPUScore>1454</NightRaidPerformanceCPUScore>
<NightRaidPerformanceGraphicsScore>2233</NightRaidPerformanceGraphicsScore>
<benchmarkRunId>8045dec5-e97c-452b-abeb-54af187fd50a</benchmarkRunId>
</result>
<result>
<name />
<description />
<passIndex>0</passIndex>
<sourceId>C:\Users\dgadhipx\Documents\3DMark\3dmark-autosave-20200401155825.3dmark-result</sourceId>
<NightRaidPerformanceCPUScoreForPass>1454</NightRaidPerformanceCPUScoreForPass>
<NightRaidPerformance3DMarkScoreForPass>2066</NightRaidPerformance3DMarkScoreForPass>
<NightRaidPerformanceGraphicsScoreForPass>2233</NightRaidPerformanceGraphicsScoreForPass>
<NightRaidPerformanceGraphicsTest1>9.57</NightRaidPerformanceGraphicsTest1>
<NightRaidPerformanceGraphicsTest2>12.18</NightRaidPerformanceGraphicsTest2>
<NightRaidCpuP>395.2</NightRaidCpuP>
<benchmarkRunId>8045dec5-e97c-452b-abeb-54af187fd50a</benchmarkRunId>
</result>
</results>
</benchmark>
You can use BeautifulSoup as fellow:
with open(file_path, "r") as f:
content = f.read()
xml = BeautifulSoup(content, 'xml')
elements = xml.find_all("NightRaidPerformanceCPUScore")
for i in elements:
print(i.text)
That will print you the values of all "NightRaidPerformanceCPUScore" tags.
I've been working with this much of the day but haven't been able to find a solution. I have a fairly large XML file that I need to strip of some data. The 'fields' are annotated using attributes by Id (field name) and Num (unique number for the field name).
<?xml version="1.0" encoding="UTF-8"?>
<Data>
<Item Id="Type" Num="30">3</Item>
<Item Id="Version" Num="50">180</Item>
<Owner>
<Item Id="IdNumber" Num="20">00000000</Item>
<Item Id="race1" Num="160">01</Item>
<Item Id="race2" Num="161">88</Item>
<Item Id="race3" Num="162">88</Item>
<Dog>
<Item Id="Breed" Num="77">Mutt</Item>
<Item Id="Weight" Num="88">88</Item>
</Dog>
<Dog>
<Item Id="Breed" Num="77">Retriever</Item>
<Item Id="Weight" Num="88">77</Item>
</Dog>
</Owner>
<Owner>
<Item Id="IdNumber" Num="20">00033000</Item>
<Item Id="race1" Num="160">03</Item>
<Item Id="race2" Num="161">88</Item>
<Item Id="race3" Num="162">88</Item>
<Dog>
<Item Id="Breed" Num="77">Poodle</Item>
<Item Id="Weight" Num="88">21</Item>
</Dog>
</Owner>
</Data>
Here's the pretty simple python code that I assumed would do the trick, but it's stripping out the data as expected.
#!/usr/bin/env python3
import xml.etree.ElementTree as ET
# list of "Nums" to drop from each Owner and Dog
drops = ['160', '161', '162', '88']
# read in XML file
with open('dogowners.xml') as xmlin:
tree = ET.parse(xmlin)
root = tree.getroot()
for x in root:
for y in x:
# checking to make sure it's an Owner
if y.attrib:
# if the value for attribute Num is in the list of drops then remove it
if y.attrib["Num"] in drops:
x.remove(y)
# finally output new tree
tree.write('output.xml')
output.xml
The issue I'm running into is it's not removing all the drops/Nums listed. In the case with this small XML, it's only doing the first and third value on the Owner level, which is consistent with my large file because it appears to be only removing every other one.
<Data>
<Item Id="Type" Num="30">3</Item>
<Item Id="Version" Num="50">180</Item>
<Owner>
<Item Id="IdNumber" Num="20">00000000</Item>
<Item Id="race2" Num="161">88</Item>
<Dog>
<Item Id="Breed" Num="77">Mutt</Item>
<Item Id="Weight" Num="88">88</Item>
</Dog>
<Dog>
<Item Id="Breed" Num="77">Retriever</Item>
<Item Id="Weight" Num="88">77</Item>
</Dog>
</Owner>
<Owner>
<Item Id="IdNumber" Num="20">00033000</Item>
<Item Id="race2" Num="161">88</Item>
<Dog>
<Item Id="Breed" Num="77">Poodle</Item>
<Item Id="Weight" Num="88">21</Item>
</Dog>
</Owner>
</Data>
I feel like I might be missing something kind of obvious, but XML parsing is not my forte and I've been fighting with this for a couple of hours. Any help is greatly appreciated.
This seems like a good candidate for the identiy transform pattern. The following will copy the xml document, but will exclude the Item elements that match the empty template at the end of the xsl string.
owner-dog.py
#!/usr/bin/env python3
from lxml import etree
# list of "Nums" to drop from each Owner and Dog
drops = ('160', '161', '162', '88')
# we turn it into an xsl attribute pattern:
# #Num = '160' or #Num = '161' or #Num = '162' or #Num = '88'
attr_vals = list(map(lambda n: f'#Num = \'{n}\'', drops))
attr_expr = ' or '.join(attr_vals)
xsl = etree.XML('''<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent='yes'/>
<xsl:strip-space elements="*" />
<!-- copy all nodes ... -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<!-- ... except for any elements matching the following template -->
<xsl:template match="//Item[ {attr_vals} ]" />
</xsl:stylesheet>'''.format(attr_vals=attr_expr))
transform = etree.XSLT(xsl)
with open('owner-dog.xml') as xml:
print(transform(etree.parse(xml)))
Output
<?xml version="1.0"?>
<Data>
<Item Id="Type" Num="30">3</Item>
<Item Id="Version" Num="50">180</Item>
<Owner>
<Item Id="IdNumber" Num="20">00000000</Item>
<Dog>
<Item Id="Breed" Num="77">Mutt</Item>
</Dog>
<Dog>
<Item Id="Breed" Num="77">Retriever</Item>
</Dog>
</Owner>
<Owner>
<Item Id="IdNumber" Num="20">00033000</Item>
<Dog>
<Item Id="Breed" Num="77">Poodle</Item>
</Dog>
</Owner>
</Data>
Comparing the original xml with the ouput
diff <(xmllint --format owner-dog.xml) <(./owner-dog.py)
1c1
< <?xml version="1.0" encoding="UTF-8"?>
---
> <?xml version="1.0"?>
7,9d6
< <Item Id="race1" Num="160">01</Item>
< <Item Id="race2" Num="161">88</Item>
< <Item Id="race3" Num="162">88</Item>
12d8
< <Item Id="Weight" Num="88">88</Item>
16d11
< <Item Id="Weight" Num="88">77</Item>
21,23d15
< <Item Id="race1" Num="160">03</Item>
< <Item Id="race2" Num="161">88</Item>
< <Item Id="race3" Num="162">88</Item>
26d17
< <Item Id="Weight" Num="88">21</Item>
29a21
>
<Shape ID="1" NameU="Start/End" Name="Start/End" Type="Shape" Master="2">
....</Shape>
<Shape ID="2" NameU="Start/End" Name="Start/End" Type="Shape" Master="5">
....</Shape>
I have to return the Master value for every ID value.
How can i achieve it by using LINQ to XMl.
You didn't really present how your XML document looks like, so I assumed it's as follow:
<Shapes>
<Shape ID="1" NameU="Start/End" Name="Start/End" Type="Shape" Master="2">
</Shape>
<Shape ID="2" NameU="Start/End" Name="Start/End" Type="Shape" Master="5">
</Shape>
</Shapes>
You can simply get Master attribute value for all different ID like that:
var xDoc = XDocument.Load("Input.xml");
var masters = xDoc.Root
.Elements("Shape")
.ToDictionary(
x => (int)x.Attribute("ID"),
x => (int)x.Attribute("Master")
);
masters will be Dictionary<int, int> where key is your ID and value is corresponding Master attribute value.
I have a numer, "n" in a property in MSBuild. I also have a string "Str" that needs to be duplicated n-times to achieve a final string that is the repetition of "Str" n times.
Eg. If n is 3 and Str is "abc", what I want to obtain is "abcabcabc"
Since one cannot loop in MSBuild, I don't know how to achieve this. Perhaps with an item group, but how do I create one based on a property containing an "n" count?
Thanks!
To create a String repeated n times, you can also do this (at least in MSBuild Tools v4.0):
<SomeRepeatedString>$([System.String]::New("-", 40))</SomeRepeatedString>
usually for things like this I resolve to using inline C#, as it costs me less time than searching all over the internet to find a 'true' msbuild solution; here you go:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<MyString>abc</MyString>
<Count>3</Count>
</PropertyGroup>
<UsingTask TaskName="RepeatString" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
<ParameterGroup>
<s ParameterType="System.String" Required="true" />
<n ParameterType="System.Int32" Required="true" />
<result ParameterType="System.String" Output="true" />
</ParameterGroup>
<Task>
<Code Type="Fragment" Language="cs"><![CDATA[
result = string.Concat( Enumerable.Repeat( s, n ) );
]]></Code>
</Task>
</UsingTask>
<Target Name="doit">
<RepeatString s="$(MyString)" n="$(Count)">
<Output PropertyName="result" TaskParameter="result" />
</RepeatString>
<Message Text="Result = $(result)"/>
</Target>
</Project>
I am unable to run the code for moving file, when i use the ant macrodef attribute in groovy task inside the macrodef.
<macrodef name="dirmove">
<attribute name="todir" />
<attribute name="fromdir" />
<attribute name="includes" default="*" />
<sequential>
<var name="todir" value="#{todir}" />
<var name="fromdir" value="#{fromdir}" />
<var name="includes" value="#{includes}" />
<groovy>
File dir1 = new File(properties.'fromdir');
File dir2 = new File(properties.'todir');
def pattern = properties.get('includes')
println pattern;
dir1.eachFileMatch ~/pattern/, {
f->
boolean fileMoved = f.renameTo(new File(dir2, f.getName()));
//assert f.name == '1.txt' //**because File object is immutable, so I am just checking for the existing of previous file name. It is still there.
println fileMoved;
}
</groovy>
</sequential>
</macrodef>
This code correctly prints the value of pattern, which is coming from attribute value. But the eachFileMatch function doesn't picks up the spec
dir1.eachFileMatch ~/pattern/, {
should be
dir1.eachFileMatch ~/${pattern}/, {
As pattern is a String variable, so you need to add it in to the regex pattern as such.
Previously, you were just searching for all files called pattern