iReport sub-report with Xpath expression - xpath

This should be very easy, but my sub report report stays empty.
my xml :
<?xml version="1.0" encoding="UTF-8"?>
<document>
<settings></settings>
<header></header>
<lines>
<line>
<product_level>1</product_level>
<product_description>description</product_description>
</line>
<line>
<product_level>2</product_level>
<product_description>description lvl2</product_description>
</line>
<line>
<product_level>3</product_level>
<product_description>description lvl3</product_description>
</line>
</lines>
</document>
my main document uses Xpath /document to select the data
the sub report uses Xpath /document/lines/line to select the data
the problem starts with the subreport where I only want to select certain values, say where
is 1 ( 1 )
using :
((net.sf.jasperreports.engine.data.JRXmlDataSource)$P{REPORT_DATA_SOURCE}).subDataSource("/document/lines/line/product_level[text()=3]")
results in a empty part.
If I leave out the /product_level[text()=3] it returns every line ( as expected )...
I am guessing it is something easy, but can't figure out what.

This is the correct XPath:
/document/lines/line/product_level[text()='3']
Here's a free tool to help in the future: http://www.bubasoft.net/product/xpath-builder/

Related

Count values in a list that are represented as values in XML elements.

I have some XML like this:
<lines>
<line>
<entity>H</entity>
<account>1002</account>
<taxcode>ESCR04</taxcode>
</line>
<line>
<entity>H</entity>
<account>1003</account>
<taxcode>ESCR04</taxcode>
</line>
<line>
<entity>H</entity>
<account>1004</account>
<taxcode>ESCR04</taxcode>
</line>
<line>
<entity>H</entity>
<account>1005</account>
<taxcode>ESCR0</taxcode>
</line>
<line>
<entity>H</entity>
<account>1002</account>
<taxcode>ESCR0</taxcode>
</line>
</lines>
And a list of tax codes like this:
<Codes>
<Code>ESCR0</Code>
<Code>ESCR04</Code>
<Code>ESCR10</Code>
<Code>ESCR21</Code>
</Codes>
And I need to determine the number of Code elements in Codes that are represented in 'lines' as a value in lines>line>taxcode. In this example it would be 2.
I understand how to find the number of 'line' elements with one of the listed Codes, but I'm having trouble figuring out how to even approach it the other way around. Any ideas?
Thanks
Using XPath 2.0 and later you can use count(/Codes/Code[. = doc('lines.xml')/lines/line/taxcode]).

Xpath: Get certain values

Take a xml-file like this:
<games>
<game>
<place>xxx</place>
<date>2013-10-02</date>
</game>
<game>
<place>yyy </place>
<date>2013-10-03</date>
</game>
<game>
<place>zzz</place>
<date>2013-10-03</date>
</game>
<game>
<place>aaa</place>
<date>2013-10-03</date>
<status>1</status>
</game>
<game>
<place>bbb</place>
<date>2013-10-03</date>
<status>9</status>
</game>
</games>
Now, not only do I need to know, which "game" does have a tag named "status", but also what value do this tags have (in this example: 1 and 9 ).
//game/status
only leeds me to all the nodes with a tag "status", but I can't figure out, how to fetch the specific tag, just to ask for the value.
Can anybody help?
Thanks
The below expression will give you the result:
/games/game[status]/status/text()
/games/game[status] will give you all the game node which has status node. Then /games/game[status]/status will you give you only the status nodes from the selected game node. Then finally top mentioned will help you to extract the text values within status node.

How do I force parsing an XML node as hash array?

This is my simplified myXML:
<?xml version="1.0" encoding="utf-8"?>
<ShipmentRequest>
<Message>
<MemberId>A00000001</MemberId>
<MemberName>Bruce</MemberName>
<Line>
<LineNumber>3.1</LineNumber>
<Item>fruit-004</Item>
<Description>Peach</Description>
</Line>
<Line>
<LineNumber>4.1</LineNumber>
<Item>fruit-001</Item>
<Description>Peach</Description>
</Line>
</Message>
</ShipmentRequest>
When I parse it with the Crack gem myHash is:
{
"MemberId"=>"A00000001",
"MemberName"=>"Bruce",
"Line"=>[
{"LineNumber"=>"3.1", "Item"=>"A0001", "Description"=>"Apple"},
{"LineNumber"=>"4.1", "Item"=>"A0002", "Description"=>"Peach"}
]
}
The Crack gem creates the hash Line as an array, because there two <Line> nodes in myXML. But if myXML would contain only one <Line> node, the Crack gem would not parse it as an array:
{
"MemberId"=>"ABC0001",
"MemberName"=>"Alan",
"Line"=> {"LineNumber"=>"4.1", "Item"=>"fruit-004", "Description"=>"Apple"}
}
I want to see it still as an array no matter if there's only one node:
{
"MemberId"=>"ABC0001",
"MemberName"=>"Alan",
"Line"=> [{"LineNumber"=>"4.1", "Item"=>"fruit-004", "Description"=>"Apple"}]
}
After you convert the XML document to a hash you could do this:
myHash["Line"] = [myHash["Line"]] if myHash["Line"].kind_of?(Hash)
It will ensure that the Line node will be wrapped in Array.
The problem is, you're relying on code to do what you really should do. Crack has no idea that you want a single node to be an array of a single element, and that behavior makes it a lot more difficult for you when trying to dive into that portion of the data.
Parsing XML isn't hard, and, by parsing it yourself, you'll know what to expect, and will avoid the hassle of dealing with the "sometimes it's an array and sometimes it's not" returned by Crack.
require 'nokogiri'
doc = Nokogiri::XML(<<EOT)
<?xml version="1.0" encoding="utf-8"?>
<ShipmentRequest>
<Message>
<MemberId>A00000001</MemberId>
<MemberName>Bruce</MemberName>
<Line>
<LineNumber>3.1</LineNumber>
<Item>fruit-004</Item>
<Description>Peach</Description>
</Line>
<Line>
<LineNumber>4.1</LineNumber>
<Item>fruit-001</Item>
<Description>Peach</Description>
</Line>
</Message>
</ShipmentRequest>
EOT
That sets up the DOM, so it can be navigated:
hash = {}
message = doc.at('Message')
hash[:member_id] = message.at('MemberId').text
hash[:member_name] = message.at('MemberName').text
lines = message.search('Line').map do |line|
line_number = line.at('LineNumber').text
item = line.at('Item').text
description = line.at('Description').text
{
:line_number => line_number,
:item => item,
:description => description
}
end
hash[:lines] = lines
message = doc.at('Message') finds the first <Message> node.
message.at('MemberId').text finds the first <MemberID> node inside <Message>.
message.at('MemberName').text is similar to the above step.
message.search('Line') looks for all <Line> nodes inside <Message>.
From those descriptions you can figure out the rest.
After running, hash looks like:
{:member_id=>"A00000001",
:member_name=>"Bruce",
:lines=>
[{:line_number=>"3.1", :item=>"fruit-004", :description=>"Peach"},
{:line_number=>"4.1", :item=>"fruit-001", :description=>"Peach"}]}
If I remove one of the <Line> blocks from the XML, and re-run, I get:
{:member_id=>"A00000001",
:member_name=>"Bruce",
:lines=>[{:line_number=>"3.1", :item=>"fruit-004", :description=>"Peach"}]}
Using search to locate the <Line> nodes is the trick. search returns a NodeSet, which is akin to an Array, so by iterating over it using map it'll return an array of hashes of the contents of <Line> tags.
Nokogiri is a great tool for parsing HTML and XML, then allowing us to search, add, change or remove nodes. It supports CSS and XPath accessors, so if you are used to jQuery or how CSS works, or XPath expressions, you'll be off and running quickly. The tutorials for Nokogiri are a good starting place to learn how it works.

Find string in NodeSet with XPath (Nokgiri)

I have this XML:
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE pdf2xml SYSTEM "pdf2xml.dtd">
<pdf2xml>
<page number="1">
<text top="91">Rapport</text>
<text top="102">foo</text>
</page>
<page number="2">
<text top="91">Rapport</text>
<text top="102">bar</text>
</page>
<page number="3">
<text top="91">Rapport</text>
<text top="102">asdf</text>
</page>
</pdf2xml>
which I'm doing this with:
require 'nokogiri'
doc = Nokogiri::XML(File.read("file.xml"))
pages = doc.xpath("//page")
nodeset = pages[0].xpath("./text") + pages[1].xpath("./text")
I want to find a node by string in nodeset, like this
irb(main):011:0> nodeset.at_xpath("//text[text()[contains(., 'bar')]]")
=> #<Nokogiri::XML::Element:0x3fea6a4821d4 name="text" attributes=[#<Nokogiri::XML::Attr:0x3fea6a482170 name="top" value="102">] children=[#<Nokogiri::XML::Text:0x3fea6a481cac "bar">]>
but I don't want to use //
I have managed to do this
irb(main):018:0> nodeset.at_xpath("text()[contains(., 'bar')]")
=> #<Nokogiri::XML::Text:0x3fea6a481cac "bar">
but I want the whole <text> node.
What should my xpath query on nodeset look like?
For selecting parent of the current node you can use .. For example,
/pdf2xml/page[1]
points to the first <page> node. If you want to select its parent again you can write
/pdf2xml/page[1]/..
This will select <pdf2xml> node which is the parent of <page>.
On the similar lines you can use .. for selecting parent node in your example.
For more information you can refer this
Hope this helps.
Simpler than selecting the text() node and then selecting the parent node is to just select the node you want in the first place:
pages = doc.xpath("//page")
puts pages.xpath("text[contains(.,'bar')]")
#=> <text top="102">bar</text>
If it makes you feel better, you could alternatively explicitly test the text() child node of the text element instead of using the text equivalent for the element:
pages.xpath("text[contains(text(),'bar')]")
I just discovered that
nodeset.at_xpath("../text[text()[contains(., 'bar')]]")
works too.
Edit: But I think this is slower than /...

XPath in BizTalk returns concatenated string values. Want Elements AND Values

I have a message which looks like this for example, but could have many more invoices contained within it:
<ns1:InvoicesEnvelope xmlns:ns1="http://Test.Schemas.InvoiceEnvelope_1"xmlns:ns0="http://Test.Schemas.Invoices">
<Invoices>
<ns0:Invoice>
<Header>
<BatchID>1311</BatchID>
<InvoiceNo>3400055151</InvoiceNo>
<CustomerName>CUSNAME1</CustomerName>
<TotalInvoiceLines>2</TotalInvoiceLines>
</Header>
<Lines>
<Line>
<TaxCode>S15</TaxCode>
<InvoiceAmt>12.77</InvoiceAmt>
</Line>
<Line>
<TaxCode>S15</TaxCode>
<InvoiceAmt>1.92</InvoiceAmt>
</Line>
</Lines>
</ns0:Invoice>
<ns0:Invoice>
<Header>
<BatchID>1311</BatchID>
<InvoiceNo>3400055152</InvoiceNo>
<CustomerName>CUSNAME2</CustomerName>
<TotalInvoiceLines>2</TotalInvoiceLines>
</Header>
<Lines>
<Line>
<TaxCode>S15</TaxCode>
<InvoiceAmt>12.77</InvoiceAmt>
</Line>
<Line>
<TaxCode>S15</TaxCode>
<InvoiceAmt>1.92</InvoiceAmt>
</Line>
</Lines>
</ns0:Invoice>
</Invoices>
</ns1:InvoicesEnvelope>
All I want to do is get the 2nd Invoice from the original message using xpath
Here is my Xpath:
msgInvoice = xpath(msgInvoicesEnvelope, "string (//ns1:InvoicesEnvelope/Invoices/ns0:Invoice[position() = 2])”);
All it returns though are the actual string values concatenated together like so:
13113400055152CUSNAME22S1512.77S151.92
What I want is the element tags aswell so it can be put into a new single invoice message. This is what I expect to get:
<ns0:Invoice>
<Header>
<BatchID>1311</BatchID>
<InvoiceNo>3400055152</InvoiceNo>
<CustomerName>CUSNAME2</CustomerName>
<TotalInvoiceLines>2</TotalInvoiceLines>
</Header>
<Lines>
<Line>
<TaxCode>S15</TaxCode>
<InvoiceAmt>12.77</InvoiceAmt>
</Line>
<Line>
<TaxCode>S15</TaxCode>
<InvoiceAmt>1.92</InvoiceAmt>
</Line>
</Lines>
</ns0:Invoice>
</Invoices>
What am I doing wrong?
I found the solution to the problem. It was very simple.
It concernes the XPath expression being used.
Instead of saying
msgInvoice = xpath(msgInvoicesEnvelope, "string (//ns1:InvoicesEnvelope/Invoices/ns0:Invoice[position() = 2])”);
Omit the string and the values along with their elements are returned.
msgInvoice = xpath(msgInvoicesEnvelope, "//ns1:InvoicesEnvelope/Invoices/ns0:Invoice[position() = 2]”);
Your XPath is right; your problem is probably way you're asking for XML data;
For instance, in C#, if I run your XPath and ask for .InnerText property, I'll get same bogus result; but, if I take that result as a XmlElement I can handle it correctly.
HTH

Resources