Freemarker : Expression inside Expression - freemarker

Is there any way that i can use an expression inside expression in Freemarker?
Example:
XML FIle
<Document>
<Row>
<item_date>01/01/2015</item_date>
</Row>
<Row>
<item_date>02/01/2015</item_date>
</Row>
</Document>
<#list 0..1 as i>
${Document.Row[${i}].item_date}
</#list>
I want to print as below
01/01/2015
02/01/2015
Any idea?
Thanks in Advance

Like this:
${Document.Row[i].item_date}
Note that if you are using an up-to-date version, you get this error message, which explains why:
You can't use "${" here as you are already in
FreeMarker-expression-mode. Thus, instead of ${myExpression}, just
write myExpression. (${...} is only needed where otherwise static text
is expected, i.e, outside FreeMarker tags and ${...}-s.)

Related

XPath as a string variable / property

I retrieve XPath from a database (via DSS) and I need to apply it to the body. Is this somehow possible?
To give an example, let's say I have this xml request
<custom>
<id>24</id>
<text>Some Text</text>
<firstOccurId>123456</firstOccurId>
<secondOccurId>654321</secondOccurId>
</custom>
I take ID (24) and call template, which will return
<replacements>
<row>
<value>ABCDEFG</value>
<xpath>/*/custom/firstOccurId</xpath>
</row>
<row>
<value>GFEDCDBA</value>
<xpath>/*/custom/secondOccurId</xpath>
</row>
</replacements>
now I need to apply xpath to request and change it's value, so the transformed request body looks like this
<custom>
<id>24</id>
<text>Some Text</text>
<firstOccurId>ABCDEFG</firstOccurId>
<secondOccurId>GFEDCDBA</secondOccurId>
</custom>
I tried both evaluate() and put XPath string inside {{}} in Call Template mediator, but both without success.
evaluate() is the correct answer. To be more specific, for this example I need to store custom request in property, get replacements, loop through them and inside this loop I need to store full xpath in property as fn:concat('$body', //el:row/el:xpath, '/text()'), enrich body with original payload and apply stored xpath as evaluate()

How to get parent element with attribute using xpath

I have posted sample XML and expected output kindly help to get the result.
Sample XML
<root>
<A id="1">
<B id="2"/>
<C id="2"/>
</A>
</root>
Expected output:
<A id="1"/>
You can formulate this query in several ways:
Find elements that have a matching attribute, only ascending all the time:
//*[#id=1]
Find the attribute, then ascend a step:
//#id[.=1]/..
Use the fn:id($id) function, given the document is validated and the ID-attribute is defined as such:
/id('1')
I think it's not possible what you're after. There's no way of selecting a node without its children using XPATH (meaning that it'd always return the nodes B and C in your case)
You could achieve this using XQuery, I'm not sure if this is what you want but here's an example where you create a new node based on an existing node that's stored in the $doc variable.
declare variable $doc := <root><A id="1"><B id="2"/><C id="2"/></A></root>;
element {fn:node-name($doc/*)} {$doc/*/#*}
The above returns <A id="1"></A>.
is that what you are looking for?
//*[#id='1']/parent::* , similar to //*[#id='1']/../
if you want to verify that parent is root :
//*[#id='1']/parent::root
https://en.wikipedia.org/wiki/XPath
if you need not just parent - but previous element with some attribute: Read about Axis specifiers and use Axis "ancestor::" =)

Get attribute value from XML

I have this chunk of XML:
<show name="Are We There Yet?">
<sid>24588</sid>
<network>TBS</network>
<title>The Kwandanegaba Children's Fund Episode</title>
<ep>03x31</ep>
<link>
http://www.tvrage.com/shows/id-24588/episodes/1065228407
</link>
</show>
I am trying to get "Are we there yet?" via Nokogiri. It is effectively the 'name' attribute of 'show'. I'm struggling to figure out how to parse this.
xml.at_css('show').value was my best guess but doesn't work.
You can use the following:
xml.at('//show/#name').text
which is XPath expression that returns the name attribute from the show element.
Use:
require 'nokogiri'
xml =<<EOT
<show name="Are We There Yet?">
<sid>24588</sid>
<network>TBS</network>
<title>The Kwandanegaba Children's Fund Episode</title>
<ep>03x31</ep>
<link>
http://www.tvrage.com/shows/id-24588/episodes/1065228407
</link>
</show>
EOT
xml = Nokogiri::XML(xml)
puts xml.at('show')['name']
=> Are We There Yet?
at accepts either CSS or XPath expressions, so feel free to use it for both. Use at_css or at_xpath if you know you need to declare the expression as CSS or XPath, respectively. at returns a Node, so you can simply reference the parameters of the node like you would a hash.

How can I merge two XML files into one?

I need to merge two XML files. I saw this question before, but that poster wanted to simply concatenate the two files. I want to merge based on a specific child element, in this case, id.
I have two XML files that have the following structure:
File #1:
<document>
<row>
<id>1</id>
<data_field1>aaaa</data_field1>
<data_field2>bbbb</data_field2>
</row>
</document>
File #2:
<document>
<row>
<id>1</id>
<data_field3>cccc</data_field3>
</row>
</document>
And I want them to be merged into File #3:
<document>
<row>
<id>1</id>
<data_field1>aaaa</data_field1>
<data_field2>bbbb</data_field2>
<data_field3>cccc</data_field3>
</row>
</document>
Where it uses the id element to join each XML entry.
The code below will do this, using XML::Twig
It will work with more than 2 docs, and work even if not all id's are present in both docs. It will load both files in memory though, if you want to be able to work with documents too big to fit in memory, the code will be a bit more complex. The rows will be in the same order as in the first document, then in the second one (for those that only appear in the second one).
Since it is written as a test, you can make the test case more complex, or add more tests, which would probably be a good idea.
#!/usr/bin/perl
use strict;
use warnings;
use Test::More;
use XML::Twig;
# normally you would read the documents from file,
# but it's easier to write a self-contained test
my $d1='
<document>
<row>
<id>1</id>
<data_field1>aaaa</data_field1>
<data_field2>bbbb</data_field2>
</row>
</document>
';
my $d2='
<document>
<row>
<id>1</id>
<data_field3>cccc</data_field3>
</row>
</document>
';
my $merged=
'<document>
<row>
<id>1</id>
<data_field1>aaaa</data_field1>
<data_field2>bbbb</data_field2>
<data_field3>cccc</data_field3>
</row>
</document>
';
$merged=~ s{\n}{}g; # remove \n's,
# if you want the result indented, look at the pretty_print option
is( merged( $d1, $d2), $merged, 'one test to rule them all');
done_testing();
sub merged
{
my #docs= map { XML::Twig->new->parse( $_) } #_;
my $merged= XML::Twig->new->parse( '<document></document>');
my %row_id; # hash id => row_element
foreach my $doc (#docs)
{ foreach my $row ($doc->root->children( 'row'))
{ my $eid= $row->first_child( 'id');
my $id= $eid->text;
# if the row hasn't been created in the merged doc, do it
if( ! $row_id{$id})
{ $row_id{$id}= $merged->root->insert_new_elt( last_child => 'row');
$row_id{$id}->insert_new_elt( last_child => id => $id);
}
# move the data fields to the end of the row
foreach my $data_field ($eid->next_siblings)
{ $data_field->move( last_child => $row_id{$id}); }
}
}
return $merged->sprint;
}

Limiting an XPath predicate: predicate starting with

I am navigating this office open xml file using XPath 1.0 (extract):
<sheetData ref="A1:XFD108">
<row spans="1:3" r="1">
<c t="s" r="A1">
<is>
<t>FirstCell</t>
</is>
</c>
<c t="s" r="C1">
<is>
<t>SecondCell</t>
</is>
</c>
</row>
<row spans="1:3" r="2">
<c t="s" r="A2">
<is>
<t>ThirdCell</t>
</is>
</c>
<c t="s" r="C2">
<is>
<t>[persons.ID]</t>
</is>
</c>
</row>
</sheetData>
I need to find the cell that says "[persons.ID]", which is a variable. Technically, I need to find the first <row> containing a descendant::t that starts with [ and closes with ]. I currently have:
.//row//t[starts-with(text(), '[') and
substring(text(), string-length(text())) = ']']/ancestor::row
So I filter and then go up again. It works, but I'd like to understand XPath better here - I found no way filter the predicate. Can you point me to a valid equivalent of doing something like .//row[descendant::t[starts-with()...]].
Any help is greatly appreciated.
Technically, I need to find the first
containing a descendant::t that
starts with [ and closes with ].
/sheetData/row[c/is/t[starts-with(.,'[')]
[substring(.,string-length(.))=']']]
[1]
or
/sheetData/row[.//t[starts-with(.,'[') and
substring(.,string-length(.))=']']][1]
or
(//row[.//t[starts-with(.,'[') and
substring(.,string-length(.))=']']])[1]
One option:
.//row[starts-with(descendant::t/text(),'[') and substring(descendant::t/text(), string-length(descendant::t/text())) = ']' ]
This will give you the row, however one significant problem could be if your row has two t elements that would satisfy different conditions, but not both conditions. e.g. one t starts with [, and another ends with ]
Obvsiously, what you have doesn't have this problem
Another option: use translate
.//row[translate(descendant::t/text(),"0123456789","") = "[]"]
That will strip the numeric characters and then it's a simple comparison to the [] characters

Resources