Ant task to check that xml node exists in xml file - ant

I have xml file inside that I want to add xml say
<car name="BMW">
<color>Red</color>
<model>x3</model>
</car>
I wish to check if node already exists then I want to update this otheriwse wanted to add new.
I am very new to ant xmltask so my question might be very simple.
With regards,
Avinash Nigam

using an additional root tag <foo></foo> for your example (needed for insert operation), with xmltask you may use =
<!-- edit file in place, use other dest if you need to create a new file -->
<xmltask source="path/to/file.xml" dest="path/to/file.xml">
<!-- create property if car node with name='BMW' exists -->
<copy path="//car[#name='BMW']/text()" property="modelexists"/>
<!-- insert new car node if car node with name='BMW' doesn't exist -->
<insert path="/foo" unless="modelexists">
<![CDATA[
<car name="BMW">
<color>Red</color>
<model>x3</model>
</car>
]]>
</insert>
<!-- replace car node if car node with name='BMW' exists -->
<replace path="//car[#name='BMW']" if="modelexists">
<![CDATA[
<car name="BMW">
<color>Blue</color>
<model>x4</model>
</car>
]]>
</replace>
</xmltask>

Related

How to remove an entire node from multiple XML files based on the text of a child node?

I have a set of xml files in a directory and as part of my build process using Ant I would like to remove an entire node based on the text inside a child node. An example is like this:
<?xml version="1.0" encoding="UTF-8"?>
<CustomObject xmlns="http://soap.sforce.com/2006/04/metadata">
...
...
<fields>
<fullName>agf__ADM_Work__c</fullName>
<deleteConstraint>SetNull</deleteConstraint>
<deprecated>false</deprecated>
...
...
</fields>
...
...
</CustomObject>
In this example I want to remove all nodes completely if the text inside begins with agf__
There are many node that qualify for this in many files, and the number of child nodes under varies.
I have used 'replacedregexp' of ant before and matched the entire mode, all children in the past and this works like:
<replaceregexp match="<fieldPermissions>\s*<editable>(.+?)</editable>\s*<field>(.*).agf__(.*)</field>\s*<readable>(.+?)</readable>\s*</fieldPermissions>\s*" replace="" flags="gm" byline="false">
<fileset dir="${basedir}/src/permissionsets" includes="*.permissionset"/>
</replaceregexp>
which will remove something like:
<fieldPermissions>
<editable>false</editable>
<field>something.agf__something</field>
<readable>true</readable>
</fieldPermissions>
But i am not sure how to accomplish this when the markup of the node to be removed changes.
So then I tried using xmltask, which I managed to get to work using the exact field text like this:
<xmltask todir="${basedir}/src/objects">
<fileset dir="${basedir}/src/objects" includes="*.object" />
<remove path="//:CustomObject/:fields[:fullName/text()='agf__ADM_Work__c']"/>
</xmltask>
This works and remove the node in the first example on any of the files in the 'objects' directory the end in .object
However I believe the remove task of the xmltask does not take wild cards or regexp's to match agf__*. The values of fullName that begin with agf__ are many and vary.
I hope I was clear enough and hope some one can assist me. Thanks

Ant xmlproperty task fails due to validation error

I want to extract an application version from a DITA map file. The ditamap file is valid and looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE map PUBLIC "-//OASIS//DTD DITA Map//EN" "map.dtd">
<map id="user-manual">
<title><ph keyref="product"/> User Manual</title>
<topicmeta>
<prodinfo>
<prodname><keyword keyref="product"/></prodname>
<vrmlist>
<vrm version="4" release="3" modification="0"/>
</vrmlist>
</prodinfo>
</topicmeta>
<!--
[...]
-->
</map>
The information I want to get is in the <vrm> element.
"Easy peasy," I think to myself. So I use Ant's <xmlproperty> task to just load this XML file.
<project default="test">
<!-- notice #validate -->
<xmlproperty file="path/to/user-manual.ditamap" validate="false"/>
<target name="test">
<echo>${map.topicmeta.prodinfo.vrmlist.vrm(version)}</echo>
</target>
</project>
I don't want it to validate because Ant isn't going to find map.dtd.
Loading the file returns an error:
java.io.FileNotFoundException: /home/user/user-manual/map.dtd (No such file or directory)
If I remove the <!DOCTYPE> declaration or add a nested <xmlcatalog> with the path to the DTD, the file loads and I can use the properties from it.
I tested this with Ant 1.7.1 and 1.9.4. Is this a bug with Ant, or am I misunderstanding how Ant loads XML properties and the purpose of the validate attribute?
How can I make Ant obey my will?
I recommend to not use the <xmlproperty> for this. Please have a look at the docs:
For example, with semantic attribute processing enabled, this XML
property file:
<root>
<properties>
<foo location="bar"/>
<quux>${root.properties.foo}</quux>
</properties>
</root>
is roughly equivalent to the following fragments in a build.xml file:
<property name="root.properties.foo" location="bar"/>
<property name="root.properties.quux" value="${root.properties.foo}"/>
So the name of the properties you set is generated using their paths to the root element, so they rely on the structure of your DITA Map. But many elements in DITA may be set at different positions on your DITA Map. That means, if you move your metadata to another parent element, the property name changes and your build fails. This is probably not, what you want.
I'd recommend to grab those values via XSLT and than set the properties. That way, you could, for example, say, "give me the first occurance of that element with a simple //foo[1] XPath selector. Further on, you have the power of XSLT and XPath to slice values, format dates and so on before setting a property.
Update
You can use the oops consultancy Ant xmltask for that. It is very easy to set a property using <copy>:
<copy path="//critdates/created/#date"
property="document.date"
append="false"/>

Using ant to modify a file containing values in another file

I'm currently using ant to remove lines from a file if the line matches any of a list of email addresses, and output a new file without these email addresses as follows:
<copy file="src/emaillist.tmp2" tofile="src/emaillist.txt">
<filterchain>
<linecontains negate="true"><contains value="paultaylor#hotmail.com"/>
</linecontains>
<linecontains negate="true"><contains value="paultaylor2#hotmail.com"/>
</linecontains>
........
........
</filterchain>
</copy>
But I already have a file containing a list of the invalid email addresses (invalidemail.txt) I want to remove, so I want my ant file to read the list of invalid email addresses from this file rather than having to add a element for each email I don't want. Cannot work out how to do this.
Ant has some useful resource list processing tasks. Here's a 'prototype' solution.
Load the input and exclusion lists to two resource collections, tokenized by default on line breaks.
<tokens id="input.list">
<file file="src/emaillist.tmp2"/>
</tokens>
<tokens id="invalid.list">
<file file="invalidemail.txt"/>
</tokens>
Do some set arithmetic to produce a resource list of clean emails:
<intersect id="to_be_removed.list">
<resources refid="input.list"/>
<resources refid="invalid.list"/>
</intersect>
<difference id="clean.list">
<resources refid="input.list"/>
<resources refid="to_be_removed.list"/>
</difference>
Here's some diagnostics that may be useful:
<echo message="The input list is: ${ant.refid:input.list}" />
<echo message="Emails to be removed: ${ant.refid:to_be_removed.list}" />
<echo message="The clean list is: ${ant.refid:clean.list}" />
Use the pathconvert task to reformat the clean list into one entry per line:
<pathconvert property="clean.prop" refid="clean.list"
pathsep="${line.separator}" />
Finally, output to a file:
<echo file="src/emaillist.txt">${clean.prop}
</echo>

Apache Ant add header & footer to EVERY file in concat job

I'm sure that this is trivial - but have been bashing my head against a wall
I'm trying to take a directory full of mustache templates (html files essentially) and combine them into one file - wrapping each one with a tag
Example:
File1 = <a>This is a Link</a>
File2 = <b>This is in bold</b>
I want the output to look like:
<script type="text/mustache" id="File1">
<a>This is a Link</a>
</script>
<script type="text/mustache" id="File2">
<b>This is in bold</b>
</script>
I'm using a concat task
<concat destfile="mustache.js" fixlastline="yes">
<fileset dir="." includes="**/*.mustache"/>
</concat>
but can't figure out how to get the script blocks to display
At first i thought about using concat somehow with header and footer but didn't find a working solution.
If you not shy away from using some Ant addon, here's a solution based on Flaka =
<project name="demo" xmlns:fl="antlib:it.haefelinger.flaka">
<!-- make standard ant tasks understand EL expressions -->
<fl:install-property-handler />
<!-- we use path instead of pure fileset because we need
absolute filenames for loadfile later in for loop -->
<path id="foo">
<fileset dir="/some/path" includes="**/*.mustache"/>
</path>
<!-- iterate over the path/fileset -->
<fl:for var="file" in="split('${toString:foo}', ':')">
<!-- unset property for next loop -->
<fl:unset>content</fl:unset>
<!-- load file contents to property -->
<loadfile property="content" srcFile="#{file}"/>
<echo file="/some/path/foobar/mustache.js" append="true">
<!-- the id attribute gets filled with the basename of the current fileitem -->
<![CDATA[<script type="text/mustache" id="#{replace(file, '$1' , '.+?(\w+)\..+' )}">
#{trim('${content}')}
</script>]]></echo>
</fl:for>
</project>
Note : 1. my leftmost notation within the echo task to avoid unnecessary blanks in the resulting file ! just write as in my example above and your file will look like your wanted output
2. the <![CDATA[...]]> is needed, otherwise you'll get some error like "echo doesn't support the nested "script" element."

Multiple ANT properties (from file) into the one property

I use task to run a target for all values from the list, taken from one property.
<foreach list="val1,val2" delimiter="," target="my.target" param="param_name"/>
Now, I want to put those values to the separate properties file as there is a lot of them.
So the question is: how can I read multiple (don't know how many) properties (lines in file in fact) from the file into one property?
The property file should look like this:
val1
val2
anothervalue
foobar
And the output should be:
"val1,val2,anothervalue,foobar"
be put in one property.
You can achieve this using LineTokenizer filter with loadfile. For example:
<target name="t">
<loadfile property="data_range" srcFile="ls.txt">
<filterchain> <!-- this filter outputs lines delimited by "," -->
<tokenfilter delimoutput=","/>
</filterchain>
</loadfile>
<foreach list="${data_range}" param="line" delimiter="," target="print" />
</target>
<target name="print">
<echo>line [${line}]</echo> <!-- you can do anything here -->
</target>

Resources