Main

XSL/XPATH Archives

May 26, 2006

Dynamic Sorting or Sort by Parameter

When you create a simple report that contains for example a single table, many users would like to have a single template that gets the column by which the table is sorted as a parameter - so that the end user can easily change it. Lately, I got this functionality working  and thought I share it.First you will need to define a parameter:

<xsl:param name="order" select="'SALARY'" xdofo:ctx="begin"/>

Next you will create a table that is sorted for example by name This can be accomplished easily with the table wizard. The resulting for-each and sort statements are:

<?for-each:ROW?>
<sort:NAME;'ascending';data-type='text'?>

So in a first attempt we would replace NAME with $order to use the parameter to sort:

<?for-each:ROW?>
<?sort:$order;'ascending';data-type='text'?>

This does not work? Why? Well, $order contains a string, while the <?sort:$order ?> statement which is translated into a <xsl:sort select=?$order?/> expect a node as a parameter and not a name. So how do we get the SALARY node from a variable that contains the text 鉄ALARY??

We can get the node by adding a predicate using the [] syntax. We first use the expression that matches any childnode: ./*. Now we add the condition that the name of the child node equals the content of the variable $order. The resulting expression is:

./*[name(.) = $order].

So we replace the NAME not with $order but with ./*[name(.) = $order] and achieve

<?for-each:ROW?>
<?sort:./*[name(.) = $order];'ascending';data-type='text'?>

This worked fine, with one caveat: It always sorts alphanumerical and not numerical - meaning 1000 comes before 2. You will need to change the data-type to achieve correct sorting. You could achieve that with an if statement to set the data-type depending on the field name and run into some other issues or pass the type as a parameter.

If you want to use a parameter (e.g. with the name type) in for the data-type, you need to know that this field expects a string and not a node. If you write:

<?for-each:ROW?>
<?sort:./*[name(.) = $order];'ascending';data-type='$type'?>

your sorting will fail, because it will use the name of the variable $type as the data type and not its content. So you will need to user {} around the variable to substitute an XPATH expression or a variable inside the text. The final expression is:

<?for-each:ROW?>
<?sort:./*[name(.) = $order];'ascending';data-type='{$type}'?>

and your dynamic sorting should work like a charm. Unless you want to use an if statement to set the data-type depending on the parameter name - but that again is a different story to be told at a different time.

Klaus Fabian

November 1, 2007

My Total Follow Up

Dave M followed up with a question on the regrouping totals post from a day or two ago.


Would it be possible to reference the group sum values from outside of the loop? For instance, if you wanted to have a separate table at the bottom of the report that contained the department totals for each department.


Of course the answer is 'Yes', but of course there are several ways you could tackle it. You could use our updatable variables to track totals against each department as you move through the XML tree. Not ideal and probably expensive.


You could loop through everything again in the summary section.


<?for-each-group:ROW;DNAME?> 
<?DNAME?> <?sum(current-group()/SAL)?>
<?end for-each-group?>


Its another pass thru the data which could be expensive on a very large report. The fastest way to get the summary is going to be to have the summarization at the extract layer. Another alternative might be to build a dynamic XML tree and then use that to render the values. I think this would be just as expensive as the solution above thou - I might be wrong thou.


 


 

December 5, 2007

Multi Column Row Woes

Through streaming eyes and a nose that wont stop running, its a doozy of a cold/flu. I found a nice question yesterday on the forum from Chris (chrish6):


Hi All

I have just spent the last couple of hours trying to do something which I was hoping would be fairly simple - the only thing I have figured out is that it is not simple.

Heres my problem, I have data structured like so

<HORSE>
 <HORSE_NAME>Chris One</HORSE_NAME>
</HORSE>
<HORSE>
 <HORSE_NAME>Chris Two</HORSE_NAME>
</HORSE>
<HORSE>
 <HORSE_NAME>Chris Three</HORSE_NAME>
</HORSE>
<HORSE>
 <HORSE_NAME>Chris Four</HORSE_NAME>
</HORSE>

I want the data to come out in RTF data like this

Chris One Chris Two
Chris Three Chris Four

I really want to avoid changing the data file.
So I don't come across as a lazy boy I have looked into a couple of things....
Tims blog on Rows and Columns - this is the direction I need to go but I don't get how to force a new line - I tried it and got all records on one line.
I've read various forum posts a blog by another guy doing invoice stuff - which seems over complicated for what I need.
Tim - any chance you could do a follow up to your rows and columns blog??
I'm running latest BIP,
my data can have x amount of records and may be an odd or even amount of records.


Not sure on the horse reference, maybe Chris has a race horse stable and wants to generate address labels for them. I came up with a solution that requires you to specify the number of columns you want in the table. In Chris' case its 2, it relies on a small piece of XPATH code to get specific records or horses to show up.


All we have in the template is a 2 celled table, inside each we have a complete for-each loop with the HORSE_NAME and an end loop.


AddrLabel1:


The contents of the F fields are :


<?for-each:HORSE[position() mod 2=1]?>   -   <?for-each:HORSE[position() mod 2=0]?>


the [position() mod 2 =1] expression tests the result of dividing the current record number by two and check the remainder. This puts the odd records into the first column and even records into the second column.


AddrLabel2:


Now your not tied to two columns, just add a column and adjust the 'mod' calculation to have


<?for-each:HORSE[position() mod 3=1]?> 
- <?for-each:HORSE[position() mod 3=2]?> 
- <?for-each:HORSE[position() mod 3=0]?>


to get this output


AddrLabel4:


Sample template and data here.
You can keep going as long as you wish, just need to work out the 'mod' calcs and of course if you dont want horses but maybe address labels, just add the fields you need and you're 'off the races' as it were.

January 10, 2008

More Colorful Groupings

I knew I had jumped the gun ... the devil is in the detail as it were - Bill followed up yesterday on the forum with a little more detail on what was needed.


Thank you for your fast response, actually I should have made my question clearer. Let's take your sample data, what I need is like this:

For group 1 Nuts and Bolts Limited, make background yellow,
981110 10-NOV-04 Standard EUR 122
100000 28-MAY-04 Standard FIM 122
100001 28-MAY-04 Standard FIM 200
1 03-JUN-04 Standard FIM 400
100004 28-MAY-04 Standard FIM 100
00s 07-JUN-04 Standard FIM 100
FI1009 10-MAY-04 Standard FIM 1220
Stop make background yellow.

For group 2 Tick Tock Clocks, make background blue,
100002 28-MAY-04 Standard DEM 235
100003 28-MAY-04 Standard DEM 100
2001 01-JUN-04 Standard EUR 1000
2002 01-JUN-04 Standard EUR 1500
2003 01-JUN-04 Standard DEM 1000
2004 01-JUN-04 Standard DEM 2000
3001 04-JUN-04 Standard EUR 1000
FI1011 25-MAY-04 Standard DEM 2000
Stop make background blue.

For group 3, make background back to yellow
Big Bike Motorsports
3001 01-JUN-04 Standard SEK 1000
FI1013 02-JUN-04 Standard USD 25000
Stop make background back to yellow

For group 4, make background back to blue
Second Cars
FI1010 20-MAY-04 Standard FIM 2440
Stop make background back to blue


We're mixing 'Groups' and 'records' here, in my original post I colored fonts and backgrounds based on the group or 'for-each'. What Bill is decribing above is to alternate the coloring of records within a group. Still doable, still not 'disappearing an elephant' but maybe 'pulling a bunch of flowers out of your sleeve' - OK OK I'll stop with the magician analogies.


Heres the template, looks similar to the ones from yesterday but without the shading.


Colors5:


The only addition is the IFColor field - inside there is some code to manipulate the table fill color based of some boolean expression.


<?choose:?>
 <?when:position() mod 2=0?>
 <?attribute@incontext:background-color;'Yellow'?>
 <?end
when?>
 <?otherwise:?>
 <?attribute@incontext:background-color;'Red'?>
 <?end
otherwise?>
<?end choose?>


This relies on the position() and 'mod' functions to allow us to, in this case alternate between two colors we can increase the mod value to alternate between more if we wish. If you are going to stick with just 2 then we can simplify further.


Colors6:


Just set a color background on the table and in the formfield use:


<?if:position() mod 2=0?><?attribute@incontext:background-color;'Red'?><?end if?>


So by default the color will be yellow but when the 'mod' condition is satified ie every even row you'll get a red background. Of course standard HTML color names are supported along with RGB HEX values.


 

February 8, 2008

Summing Nulls

Good question from Remc0 on the forum recently.

When the following in a group:
<?sum(current-group()/_saw_8_[.!=鋳])?>

It returns a 0 when all values are null. But 0 means smaller then 0,5 and null means it is not there. Now my question is: how can I sum the values and make a different between 0 and null?

Its a good question and there is a reply to use the updateable variables - I have to admit an aversion to updateable variables - they are expensive and are not a feature of native XSL - there are other ways around using the 'variables'


Remco's basic problem is that the code above that checks and eliminates null values in a sum function returns a zero value when all values are null. For example with the following XML.

<AMOUNTS>
<AMOUNT type="INV"></AMOUNT>
<AMOUNT type="INV"></AMOUNT>
<AMOUNT type="CM"></AMOUNT>
<AMOUNT type="CM">100</AMOUNT>
</AMOUNTS>


The sum of the amounts of the type 'CM' using the XPATH null elimination:


<?sum(AMOUNT[@type='CM' and .!=""])?>


as an aside, notice we're combining XPATH expressions with an 'and' - we want to sum all AMOUNT values where the type attribute is CM and the values are not 'null'. In this case we get '100' returned - thats good. If we sum the INV types:


<?sum(AMOUNT[@type='INV' and .!=""])?>


we get a zero returned - which as Remco notes is not actually correct - well not for his requirement, he wants to see an empty string.


My approach was to use our inline 'if' statement.


<?xdoxslt:ifelse(sum(AMOUNT[@type='CM' and .!=""]) = 0,'',sum(AMOUNT[@type='CM' and .!=""]))?>


The if statement tests the returned value and if its a zero ie the XML values are null then an empty string (two single quotes) is inserted otherwise it returns the calculated amount. This is fine if all the values are null but if the returned sum value happens to be '0' then an empty string will be returned.


In the interest of audience participation, here's a challenge. Can anyone come up with the XPATH necessary to return the real zero values and handle the null case too?

February 25, 2008

Calculating Pages

There is a good and apparently simple question on the forum today from mysterious user613190 - dont be a number, be a free person to mangle a phrase. It was actually posted yesterday but a man has to rest a little.


Hi,

In my output I am getting page numbers like

1 of 3 (when output has 3 pages)
2 of 3
3 of 3

But if output has only one page then I need to show like below

1

Could you help me in this?

Its a simple question, how do I conditionalize (is that a new word I just made up?) the page numbers? Seems straightforward to the uninitiated XSLFO template creator.
'Why cant I count the number of pages in the template and then render one string or another?'
'Well, you can't !'
'Why Not?'
'Because I said so !'
Hang on, hang on, this is sounding like a conversation between my son and I; except it would be about why he can not go to the movies, or hang out at the skate park or maybe ride his dirtbike - it's a power thang and I get irrational sometimes OK!


Getting back to a rational answer ... you still can't and its not because I said so, its because the XSL standards say so. Well that and the fact that the rendering engine at runtime decides how much data/information can fit on a page. To understand the process a little more, when we apply a template against some data, the flow (for the case of PDF) and assume your RTF template has been converted to XSLFO is as follows.


XML + XSLFO  -------> FO ------> PDF
__________             ____
          |                        |
 XSLT Engine          FO Renderer


That FO stage is the Formatting Object - its a big piece of XML that looks similar to extremely detailed HTML with pixel perfect placement of objects on the resulting page. To understand why you can not place 'page' logic in the template you need to know the processes going on. To get to the formatting object its an XSL transformation - at this stage all thats happening is the format and placement of data is applied to the XML data but there is no notion of a 'page' or how much data will fit on that page. The second stage, taking the FO to the PDF output is carried out by the FO renderer - at this point the engine will start to lay the data out on pages, calculating what will fit and then rendering it.


So, you see that any logic around pages in the template is not going to work, its lost and can not be executed in the FO layer, by then all calculations and conditional formatting will have been executed and because there is no notion of what will fit on a page or how many pages there are going to be - by then its a matter of fitting the data onto the page.


What are the options then I hear you ask? Well, we already have some extensions to the standard we have put in to our engine around page rendering. Those of you using the dynamic column solution to get a Z printing format on your PDF output are unwittingly using one of those extensions. We are therefore looking into building a page calculation extension to allow such functionality as is being asked for above - it's coming, not in the next release but some time after that.
What about now, I need a solution! - well one option to is to control the number of lines you want to show per page. If you have that in place then you can comfortably calculate in your template whether you are going to get 1 page or 100 pages of output and then act accordingly. I have covered fixed number of lines per page before and that article should get you on your way. If folks out there have found others, please feel free to share.

June 10, 2008

Hard Core Sorting

Arrived in California today to a beautiful warm and sunny day, great to see the team. With the morning's meetings out of the way it was off to the 600 cafe to get a much missed veggie burrito - its not so much the burrito (although it was as good as ever) it was the fantastic lady who serve's you. Sadly, I have forgotten her name, but she looks so gruff and barks questions at you, what kind of tortilla do you want, beans? black or refried? jalapanos? salsa - hot? Its a fantastic tongue lashing setting you up to savor the flavor of the burrito even more ... forget Qdoba get over to the Oracle cafe in the bottom of 600!


So to todays rather strangely titled entry - hard core sorting. I say hard core purely because of the requirement. Here's the XML:


<REP> 
<G_1>
<NAME_1>Peter</NAME_1>
<SORT_1>6</SORT_1>
</G_1>
<G_1>
<NAME_1>Anna</NAME_1>
<SORT_1>3</SORT_1>
<G_2>
<NAME_2>Paul</NAME_2>
<SORT_2>1</SORT_2>
</G_2>
<G_2>
<NAME_2>Mary</NAME_2>
<SORT_2>11</SORT_2>
</G_2>
</G_1>
<G_1>
<NAME_1>John</NAME_1>
<SORT_1>2</SORT_1>
</G_1>
<G_3>
<NAME_3>Jim</NAME_3>
<SORT_3>4</SORT_3>
</G_3>
<G_3>
<NAME_3>Frank</NAME_3>
<SORT_3>12</SORT_3>
</G_3>
</REP>


Looks innocuous enough until you see the requirement on how it should render ...


Paul 1
John 2
Anna 3
Jim 4
Peter 6
Mary 11
Frank 12

Hmmmm ... well Klaus and Hok Min rose to the challenge and came up with the following:


<?for-each://*[starts-with(name(.),'G_')]?> 

<?sort:./*[starts-with(name(.),'SORT_')];'descending';data-type='number'?>

<?./*[starts-with(name(.),'NAME_')]?>, <?./*[starts-with(name(.),'SORT_')]?>

<?end for-each?>



Why am I sharing this ? Couple of reasons:


1. Its a neat solution to a tough problem - that, I would have pushed back on before answering. Why not get the extraction routine to do all the heavy lifting. I've said many a time, sorting in the template comes at a price. This type of sorting comes at a bigger price, you are sorting on a partial string comparison ... not good! If you can get the query to do the work, get it to do the work!


2. If we ignore the sorting and focus on the 'for-each'. You can see that you are not looping over a given element in the XML but you are looping over a partial string match on the element name. Tres Cool!

  starts-with(ELEMENT, string ) is an XSL function
  name(.) - another XSL function that returns the element name itself, not its contents.


So we are matching on all element names that start with 'G_' - this enables us to treat the nested 'G_2's as if they were at the same level as the G_1 and G_3 elements.
Again, I would question the XML structure and ask why its so badly formed, but I suspect we did not get to see all of it.


I'll try and expand on this theme of not just 'element looping' in more articles, you can create some damn fine reports with an Oracle burrito inside you!

July 1, 2008

PO Line Description Quandary

Welcome back .... new look, same old (hopefully) useful articles - will talk about the new look another day. We're back with a bang, at least for those of you struggling with the Purchase Order Oracle Report. Dave summed up the problem on the BIP forum last week:

I have the following XML:

<PO_DATA>
 <LINES>
  <LINES_ROW>
   <LINE_DESCRIPTION>LINE 1</LINE_DESCRIPTION>
   <PO_LINE_ID>2467</PO_LINE_ID>
  </LINES_ROW>
  <LINES_ROW>
   <LINE_DESCRIPTION>LINE 2</LINE_DESCRIPTION>
   <PO_LINE_ID>2468</PO_LINE_ID>
  </LINES_ROW>
<LINES_ROW> <LINE_DESCRIPTION>LINE 3</LINE_DESCRIPTION> <PO_LINE_ID>2469</PO_LINE_ID> </LINES_ROW> </LINES>
<LINE_ATTACHMENTS> <TEXT>Detailed item description</TEXT> <ID>2467</ID> <TEXT>Detailed item description2</TEXT> <ID>2468</ID> <TEXT>Detailed item description3</TEXT> <ID>2469</ID> </LINE_ATTACHMENTS> </PO_DATA>
Output should be the line details including the attachment matched by the ID:
LINE 1, Detailed item description
LINE 2, Detailed item description2
LINE 3, Detailed item description3


Is this possible?

Notice the line attachment section? We have a link back to the LINE_ROW section but no nice structure to loop over!

We have to get into some more serious XSL/XPATH expressions to get the description to appear with the rest of the line information.

With some help from a relatively new member of the BIP dev team, Yaoguang, I managed to come up with a solution.

Where you are looping over the LINES_ROW, just inside the for-each field add a new field:

<?variable@incontext:PoLnID;'PO_LINE_ID'?>


Every time the loop goes over it assigns the current PO_LINE_ID to a local variable 'PoLnID'


Where ever you want the description to appear on the row create a field that contains

<?/PO_DATA/LINE_ATTACHMENTS/ID[child::text()=$PoLnID]/preceding-sibling::TEXT[1]?>


Its a bit of a mouthful but its basically looking for a child element that matches the PoLnID value - then its looking for the first [1] occurrence of the TEXT value that is a sibling to that ID.


Works real nice!

Of course it would be better to get the PO team to restructure the XML but this will do nicely. Example template here

July 18, 2008

JDE XML Fun and Games

I have been helping a JD Edwards customer over the last few days. Nothing too tough but the XML that JDE spits out can have its own 'issues'. In JDE you can use their App engine to generate reports, a UBE. It has been modified to spit out XML too but the structure can be a bit daunting.

Here's a PO XML structure:

<R5543500>
<Properties>
<Purchase_Order_Print_S42_Column_Headings>
<PageHeaders>
<Purchase_Order_Print_S42_Group>
<On_Ship_To_S45>
<OLN_S47>
<Purchase_Order_Print_S42>
<Grand_Total_S50>
<Buyer_S51>

Not too bad on the surface but the XML is generated in 'pages' - the engine in its former use generates the layout of the report in a page fashion for the rendering engine to generate the output.
This means things can get a little funky in the template because we dont care about pages in the data - the BIP rendering engine will handle all that for you.

Mac at GW Electronics has been getting to grips with RTF templates and the XML, he had also started down the path of using XPATH to navigate around the XML to get the data he needed for various parts of the report.
He had also alighted on the use of our updatable variables to help get the right element. He had things like:

<?/R5543500/Purchase_Order_Print_S42_Group/On_Ship_To_S45/OLN_S47[xdoxslt:get_variable($_XDOCTX, 'x')]/DescriptionLine2_ID24?>

where the variable was being updated as the template looped over the XML groups. It was not working very well. It's nasty and will come at a cost, albeit a small one.

Im not a fan of our updatable variables - talk to an XSL purist and they will tell you we have committed blasphemy for providing them in our engine. My beef with them is that they are too verbose and expensive in terms of resources.

Looking at Mac's template and having the benefit of having a decent XML editor (XMLSpy) on my machine I was able to spot that he could use.

<?../DescriptionLine2_ID24?>

To get to the same data item - much simpler no? The takeaway from this? Arm yourself with a decent XML editor - and learn the data structure you are working with. XMLSpy will cost you but there are others out there that can help with working out navigation paths through the XML and stay away from those updateable variables if you can

August 21, 2008

Last Position

I have seen quite a few templates recently that are using our updatable variables to keep a track of the current record being processed. Regular readers will know my dislike for the updatable variables. Don't get me wrong, they have their place ... sometimes. It might be the way you set the dang things up that annoys me so much and I should take some blame for not chasing the dev team up to simplify the declaration and retrievel. Here's the code to define and assign a value to a variable x:

<?xdoxslt:set_variable($_XDOCTX, ’x’, 10)?>

now lets update the variable, adding 10 and then show it

<?xdoxslt:set_variable($_XDOCTX, 'x', xdoxslt:get_variable($_XDOCTX, 'x')+10)?>
<?xdoxslt:get_variable($_XDOCTX, 'x')?>

Too much right? Imagine using them to track the number of the current record. Folks are doing this in their templates. There is no need - the underlying standard for RTF templates is XSL and it has some neat native functions that can be used to track the current record and the last record.

position()
- will return the current record counter. In XSL it starts at 1
last()
- will return the last record counter

For some examples I indebted to Chris from UK Oracle Consulting. Chris will be out at Oracle World again this year so if you would like to hear how the Queen's English should be spoken drop by the demo grounds. I seem to have developed some mid-atlantic drawl - my family in the UK think Im lost to those danged Yankees! I do still pronounce butter with t's rather than d's but my kids are struggling to understand me when I ask for a 'glass of water' rather than 'wadder'. Enough language mangling already! Back to Chris' examples. He came up with the following XML data.

<ROOT>
  <LIST_G_ORDER_HEADERS>
    <G_ORDER_HEADERS>
      <HEADER_ID>123</HEADER_ID>
    </G_ORDER_HEADERS>
    <G_ORDER_HEADERS>
      <HEADER_ID>456</HEADER_ID>
    </G_ORDER_HEADERS>
    <G_ORDER_HEADERS>
      <HEADER_ID>789</HEADER_ID>
    </G_ORDER_HEADERS>
  </LIST_G_ORDER_HEADERS>
</ROOT>

and some examples of position() and last()

.//LIST_G_ORDER_HEADERS/G_ORDER_HEADERS[HEADER_ID = '123']/position() RETURNS '1'
//LIST_G_ORDER_HEADERS/G_ORDER_HEADERS[position() = last()]/HEADER_ID RETURNS '789'
.//LIST_G_ORDER_HEADERS/G_ORDER_HEADERS[last()]/HEADER_ID RETURNS '789'
.//LIST_G_ORDER_HEADERS/G_ORDER_HEADERS[position() != last()]/HEADER_ID RETURNS '123' and '456'
.//LIST_G_ORDER_HEADERS/G_ORDER_HEADERS[2]/HEADER_ID RETURNS '456' 

You'll have noticed the [ ] - these contain what are know as XPATH expressions. They are very powerful and well worth taking a look at - I'll try and cover XPATH more than we do in the documentation.
Update
===========
Leslie, our fab doc writer has just pointed out that I should have stated:
I will work with the doc writer to get some more in the documentation

I'll also try and get some more in here in the meantime :)

You can of course use them both in a conditional statement too:

<?if:position() = last()?>
 Last Record!
<? end if ?>

Chris' third example is doing something similar but its using the condition within an XPATH expression, G_ORDER_HEADERS[position() != last()] - inside those square braces you do not need the 'if'.

So, throw out those updatable variable whenever you can and get native!

August 28, 2008

XSLT Extensions

Got a request to come up with a list of all the extension functions we provide in Publisher. I finally got around to generating some javadoc for the XSLTFunctions.java.

You can get the zipped java doc for the 5.6.3 release here.
You can get the zipped java doc for the 10.1.3.4 release here

Should be enough in there for you to work out whats needed in terms of parameters.

To use the functions you need to prefix them with

<?xdoxslt: function_name(parameters) ?>

Enjoy!

September 4, 2008

Cool String Sorting

Got a good internal question from Nikos one of our Product managers this week, it was a sorting question but a tough one. He had the following data:

<ROWSET>
  <ROW>
   <YEAR>Total Year</YEAR>
   <MARKET>East</MARKET>
   <PRODUCT>Audio</PRODUCT>
   <SALES>75241.0</SALES>
  </ROW>
  <ROW>
   <YEAR>Total Year</YEAR>
   <MARKET>East</MARKET>
   <PRODUCT>Visual</PRODUCT>
   <SALES>143054.0</SALES>
  </ROW>
  <ROW>
   <YEAR>Total Year</YEAR>
   <MARKET>West</MARKET>
   <PRODUCT>Audio</PRODUCT>
   <SALES>108053.0</SALES>
  </ROW>
  <ROW>
   <YEAR>Total Year</YEAR>
   <MARKET>West</MARKET>
   <PRODUCT>Visual</PRODUCT>
   <SALES>171242.0</SALES>
  </ROW>
  <ROW>
   <YEAR>Total Year</YEAR>
   <MARKET>South</MARKET>
   <PRODUCT>Visual</PRODUCT>
   <SALES>110312.0</SALES>
  </ROW>
  <ROW>
   <YEAR>Qtr1</YEAR>
   <MARKET>East</MARKET>
   <PRODUCT>Audio</PRODUCT>
   <SALES>18132.0</SALES>
  </ROW>
  <ROW>
   <YEAR>Qtr1</YEAR>
   <MARKET>East</MARKET>
   <PRODUCT>Visual</PRODUCT>
   <SALES>32847.0</SALES>
  </ROW>
  ...
  </ROWSET>

You get the picture, unsorted sales data with funky values for the YEAR. What he wanted to do was get an output that sorted by Market then Product then by the YEAR value ie Jan, Feb,Mar, Qtr1 ... Total Year. He then wanted to highlight the Qtr and Year rows with other background colors. Something like this:

Stringsort1.jpg

The first two criteria are easy, just use the for-each-group functionality. The last is a bit tougher, you could hard code the YEAR values into a table, that would get what he wanted but not very satisfying from a 'Templaters' point of view.


A quick Google and I had a plan - hat tips to XSL miesters, Michael Kay and Jeni Tennison.Still a little hard coding but nothing like the other hard coding option and a bit more satisfying. It relies some string calculations to come up with the sort order. Here's the sort command:


 <?sort: string-length(
substring-before ('JanFebMarQtr1AprMayJunQtr2JulAugSepQtr3OctNovDecQtr4Total Year'
,YEAR));
'ascending';
data-type='number'?>


You need to list out all the possible YEAR values in the order in which you want them sorted ie JanFebMarQtr1...DecQtr4Total Year.Then you find current YEAR value that you are currently looping over, in that string and then find the length of the string before that position. So you get :


Length
Period
0 Jan
3 Feb
6 Mar
9 Qtr1
13 Apr
16 May
19 Jun
22 Qtr2


then you sort by the string length ... coool! Now you just plug that into the template.

<?for-each:current-group()?>
<?sort: string-length(
         substring-before ('JanFebMarQtr1AprMayJunQtr2JulAugSepQtr3OctNovDecQtr4Total Year'
          ,YEAR))
        ;'ascending';data-type='number'?>

Hok Min from the dev team made a good point here. To avoid that ugly and potentially unmanageable (if you wanted to use it again in the template) sort string. Just create a variable to hold the string and then reference it in the sort command:

<xsl:variable name="mnthtbl">JanFebMarQtr1AprMayJunQtr2JulAugSepQtr3OctNovDecQtr4Total Year</xsl:variable>

Add to the sort by:

<?sort:string-length(substring-before($mnthtbl, ./YEAR));'ascending';data-type='number'?>

For the conditional highlighting of the 'Qtr' and 'Total Year' rows we can use the string manipulation again.

For the QtrX

<?if@row:substring(YEAR,1,1)='Q'?><?attribute@incontext:background-color;'#80FFFF'?><?end if?>

For the Total Year

<?if@row:substring(YEAR,1,1)='T'?><?attribute@incontext:background-color;'#00CACA'?><?end if?>

All Im doing is testing the first character of the YEAR value and looking for a 'Q' or a 'T'. Using the @row gets the formatting applied to the whole row. If you want the source files you can get them here.
You can of course apply this sort to any set of strings, its going to come at a small price thou. Its far far better to sort in the XML extract if at all possible. So what have we learned? this XSL stuff is cool! Google is a 'Templaters' friend and there is always an easier way!

September 11, 2008

Pad Your Reports

Im not talking about filling your reports with fluff. I can remember, mistakenly filling my high school essays with fluff and padding. Why use 5 words when 10 can say it more eloquently and fill the word count more quickly. Sadly, there was no pulling the wool over my teacher's eyes - just had to learn more facts!

I have just been helping out on a demo for a customer proof of concept. They wanted the following report output.

Padding1.jpg

Nothing fancy there on the surface, building the report is straightforward enough. We can get this output very quickly.

Padding2.jpg

But have you noticed in the original, there is some space after each set of categories.

We can achieve this by manipulating the 'padding' in the first cell of the row to effectively increase the height of the whole row. This increases the space between the last row of the group and the beginning of the next.

Just insert the following in a form field in the first cell:

<?if:LAST='Y'?>
<?attribute@incontext:padding-bottom;'10.0pt'?>
<?end if?>

The LAST element was in the XML to define whether the current member of a group is the last. You could equally use a position() based if statement, you just need to have some means of identifying the last member of a give grouping. The next line just manipulates the 'padding-bottom' attribute - here we are making it 10pts. This effectively provides us the row height we need to provide the spacing between the rows we need. There are of course other padding attributes:

  • padding-top
  • padding-bottom
  • padding-start
  • padding-end

XSL uses 'end' and 'start' rather than 'right' and 'left' for internationalization purposes. With the extra conditional field we can now generate the required output.

Padding3.jpg

this post kinda begs the question, 'how the heck do you know what you can manipulate?' Well there is the XSL-FO spec available, if you want to wade through it. Personally, I dont very often. My best friend is the Template Builder for Word -> Tools > Export > XSL-FO Stylesheet. this will dump your current RTF template into the browser. There you can check out whats going on in the template. Its not that tough - it's bit like complex HTML, there are tables, rows, cells, etc and each have attributes that you can override to your hearts content. Yes, you have to write some code at the moment, but the extra power in the formatting is worth it I think. I have not touched Oracle Reports (in anger) in a while but I can not think how I would achieve the layout above, not off the top of my head anyway. So get padding!

July 6, 2009

Extension Testing

A few weeks back I documented an extension I built for a potential customer that passed a chunk of the XML tree out and passed back some formatted text.

Well I have another coming up, more of that later this week. One problem was how to test the extension in the template builder. It was a bit of a pain, having to merge the extension class and any supporting classes into an existing zip or jar that the builder knew about.

Today, I actually bothered to reach out to 'template builder meister' Junichi and ask if I could add a classpath command or in some way include my extension jar. Ask and you shall receive ... simple stuff, this communicating with people :0)

Just open RTF2PDF.jar under jlib directory, for me thats 'D:\Program Files\Oracle\BIP\BIP Desktop\Template Builder for Word\jlib'. You will see MANIFEST.MF file inside. Back up that files and then add a file path to class-path in the manifest file, the new jar/zip will be used when you run the preview.

The manifest file looks like this out of the box

Manifest-Version: 1.0
Class-Path:  ./activation.jar ./mail.jar ./xdochartstyles.jar ./bicmn.
 jar ./jewt4.jar ./share.jar ./bipres.jar ./xdoparser.jar ./xdocore.ja
 r ./xmlparserv2.jar ./xmlparserv2-904.jar  ./i18nAPI_v3.jar ./version
 info.jar
Main-Class: RTF2PDF

With my extension and supporting library I now have

Manifest-Version: 1.0
Class-Path:  ./activation.jar ./mail.jar ./xdochartstyles.jar ./bicmn.
 jar ./jewt4.jar ./share.jar ./bipres.jar ./xdoparser.jar ./xdocore.ja
 r ./xmlparserv2.jar ./xmlparserv2-904.jar  ./i18nAPI_v3.jar ./version
 info.jar D:/Work/idauto/idfontextension.zip D:/Work/idauto/LinearBarCode.jar
Main-Class: RTF2PDF 


Update



Typo in the file above. you can only use relative folder references ie the file above will not work. I just dropped my jars and zips into the same jlib folder and used

Manifest-Version: 1.0
Class-Path:  ./activation.jar ./mail.jar ./xdochartstyles.jar ./bicmn.
 jar ./jewt4.jar ./share.jar ./bipres.jar ./xdoparser.jar ./xdocore.ja
 r ./xmlparserv2.jar ./xmlparserv2-904.jar  ./i18nAPI_v3.jar ./version
 info.jar ./idfontextension.zip ./LinearBarCode.jar
Main-Class: RTF2PDF

as my file entry ... works great!

When I now hit preview for any of the formats supported my extension is pulled in without the need for all that merging.
If you are now wondering how I opened the RTF2PDF.jar file, I used winzip or zip or you could use the java tools.
Thanks for the tip Junichi. Big prizes for anyone guessing what my latest extension project has been. OK there's no prize but you'll get a mention :0)

August 19, 2009

Removin` duplicates

I wrote about removing duplicate data a while back, I hit it as a problem again this week. Lets just say I was using an almost undocumented feature of the data extraction engine that gave me some unexpected results. Thats all Im saying cos Im not supposed to be using it.

I ended up with a bunch of duplicated rows which was anoying but because I was not supposed to be using it I could not moan or bug the dev boys and gals. So I needed a work around, thankfully the RTF template came to the rescue.
I combined a BIP function, xdoxslt:distinct_values and XSLs fabulous concept of a variable to get rid of my duplicate rows.

As I have mentioned before variables in XSL are strange things, they are not updatable which is a drag for most of us but they can hold complete chunks of the data tree which is a big plus for many of us.

All I had in my template was:

<?variable:metrics;xdosxlt:distinct_values(G_METRICS)?>

you could also go with this neat XPATH solution which checks an entry's siblings for duplicates.

<?variable:metrics;/APCARS/LIST_G_METRICS/G_METRICS[not(HOUR=preceding-sibling::G_METRICS/HOUR)]>>

We now have a variable that has the data we need. Now all we do is loop over it just like a regular repeating node

<?for-each:$metrics?> ... <? end for-each?>

Ideally I`d have that bug fixed so we get the right data set presented to the template but its not a bug cos Im not meant to be using that feature right :0)

November 5, 2009

Indenting Trees

The snow has nearly all gone, just in time for the next batch! It was a douzy of a storm, fun driving conditions, amazing how much you forget over the hot dry 'roaded' summer. My newly licensed son was itching to get out there. Mom wouldn't let him, I had to agree, our insurance couldn't take it and we have way too many trees near our house. The trees looked very pretty thou and speaking of trees (what a segue) I have been asked numerous times if BIP can generate a tree structure, the more ambitious ask for an org chart. I plan to take a look at how to do the org chart another time, for now, how about a nice tree structure.

Between the two of us, Rob (colleague and data modeler extraordinaire) came up with a solution for a customer to generate this:

Tree1.jpg

Its a tree of sorts, nothing fancy, we just wanted to get the structure right, so there are no details under the headings. Its getting the indenting correct that was the trick. The customer had a pretty fixed data structure and did not want to use nested tables.

The template is pretty simple, just a funky trick to get the indenting working. I mentioned the customer had a pretty fixed data structure. It made life easier, they only had 3 levels to worry about so the code to handle the indenting could be pretty straightforward and somewhat 'hard coded' for the data set.

tree4.jpg

It also has the advantage that the tree is in the right order ie

Level1
-Level2
-Level2
-- Level3
-Level2
Level1
Level1
...

The template looks like this

Tree2.jpg

the fields contain

Tree3.jpg

Of course the most interesting field is the C

<?if:number(LEVELS)=1?>
<?ORG_LONG_NAME?>
<?end if?>
<?if:number(LEVELS)=2?>
<?attribute@inblock:start-indent;'20.0pt'? > <?ORG_LONG_NAME?>
<?end if?>
<?if:number(LEVELS)=3?>
<?attribute@inblock:start-indent;'30.0pt'?> <?ORG_LONG_NAME?>
<?end if?>

A chhose statement would have worked just as well. You can see the dependence on the data order and the structure or more specifically LEVELS element. We need to check that to know how big the indentation should be.
The @block is also important, we need that to get the indentation to show only on the current text and not affect anything else. If we had been using tables we could have gotten around that.
Now we could have been a little smarter and written something to handle any number of levels ... answers on the back on a new Mac Book Pro 15-inch: 2.8GHz 4Gb 500Gb HD :0)

September 18, 2009

Formatting Concatenated Datasources

When you are building reports inside BIP standalone, do you concatenate or data template? If you need to use multiple queries in your report, either in the same database or separate and you need to join that data together in the output, what do you use? Or maybe some other system is spitting out a flattened hierarchy XML.

You have a choice, either use a data template to bring the data together simplifying the template or use the concatenated data source and use some funky tricks in the layout to bring the separate data together.

Let's assume I have vendors in an Oracle db and their purchase orders in a SQL server database. I need to generate a listing report showing vendors and their POs. I have covered data templates elsewhere in this blog and I will follow up with a multi database example next week. Today, lets deal with the concatenated data source. Say I have the following data:

<DATA>
<SUPPLIERLIST>
 <G_VENDOR_NAME>
  <VENDOR_NAME>Nuts and Bolts Limited</VENDOR_NAME>
  <ADDRESS>1 El Camino Real, Redwood City, CA 94065</ADDRESS>
  <VENDOR_ID>1</VENDOR_ID>		
 </G_VENDOR_NAME>
 <G_VENDOR_NAME>
  <VENDOR_NAME>Big Bike Motorsports</VENDOR_NAME>
  <ADDRESS>10 Downing Street, London, SW1, UK</ADDRESS>
  <VENDOR_ID>2</VENDOR_ID>		
 </G_VENDOR_NAME>
</SUPPLIERLIST>
<PO_LIST>
 <G_PO>
  <PO_VENDOR>1</PO_VENDOR>
  <PO_NUM>123</PO_NUM>
  <PO_ITEM>TV</PO_ITEM>
  <PO_AMT>100</PO_AMT>
 </G_PO>
 <G_PO>
  <PO_VENDOR>1</PO_VENDOR>
  <PO_NUM>124</PO_NUM>
  <PO_ITEM>Monitor</PO_ITEM>
  <PO_AMT>300</PO_AMT>
  </G_PO>
 <G_PO>
  <PO_VENDOR>1</PO_VENDOR>
  <PO_NUM>125</PO_NUM>
  <PO_ITEM>PC</PO_ITEM>
  <PO_AMT>400</PO_AMT>
 </G_PO>
 <G_PO>
  <PO_VENDOR>2</PO_VENDOR>
  <PO_NUM>233</PO_NUM>
  <PO_ITEM>TV</PO_ITEM>
  <PO_AMT>1000</PO_AMT>
 </G_PO>
 <G_PO>
  <PO_VENDOR>2</PO_VENDOR>
  <PO_NUM>234</PO_NUM>
  <PO_ITEM>Receiver</PO_ITEM>
  <PO_AMT>340</PO_AMT>
 </G_PO>
 <G_PO>
  <PO_VENDOR>2</PO_VENDOR>
  <PO_NUM>235</PO_NUM>
  <PO_ITEM>Phone</PO_ITEM>
  <PO_AMT>150</PO_AMT>
 </G_PO>
</PO_LIST>
</DATA>

You'll notice both the vendors and the PO are at the same level in the XML hierarchy. Looks tough to bring them together in the output to get

VENDOR1
PO1
PO2
VENDOR2
PO1
PO2.

With a very simple template approach we get

ConcatDS1.jpg

Not what you need right?

Looking back at the data, you might also have noticed that both the vendor and PO share the vendor ID - we can use that. In our RTF template we are going to loop over the vendors and inside loop over the PO but checking vendor ID to ensure we are getting the right POs.

There are two pieces to the solution that are different to a regular template loop over or re-grouping over a regular hierarchical or completely flat data set.

ConcatDS3.jpg


Variable - as we loop over the vendors we need some way of tracking what vendor record we are currently processing. In my template just after the for-each for the G_VENDOR group I have field with

<?variable@incontext:v_id;VENDOR_ID?<

Its not really a variable, you can not update with a value, you can just reference it or re-declare it. Im using the @incontext command to keep the variable localized to the vendor loop.

The other piece of interest is the loop command for the POs.

<&?for-each:/DATA/PO_LIST/G_PO[PO_VENDOR= $v_id]?>

Two things of note in here:

  1. The for-each is not using 'G_PO' as you might expect. Im providing the complete path to the PO group. Why? Because Im currently looping over the vendors in an outer loop. Remember POs are at the same level as the vendors so <?for-each:G_PO?> will not find any data.

  2. There is an XPATH expression associated with the for-each, '[PO_VENDOR= $v_id]' - this is instructing the rendering engine to only show PO records that have a vendor id equal to the parameter, 'v_id' - remember that was set and re-set every time you hit a new vendor in that loop.

That's it, those two pieces of 'code' allow you to generate a hierarchical output from the concatenated data set.

ConcatDS2.jpg


If you need the template and some data, get em here.

Yep, its a simple demo but you get the idea. Of the two options, layout or data template - Id go with the data template every time. Get the extraction engine to do the heavy lifting and KISS on the layout template :0) Complex layout templates mean scared business users that will not touch the template even if it is in friendly ol' MSWord. Im excluding version 2007 from that term of endearment, I have had it 6 months and I still don't like it!

Enjoy yer weekend, after my week, I'll be sleeping!

October 16, 2009

Row Filler Followup

Since my post last week or was it the week before. The weeks are blurring by at the moment. Last week, I could swear it was warm and sunny here in Colorado, the fish were biting along with the mozzies and sitting on our deck you could here the 'crickets' chirping into the night. Back in July, we got nightly visits from a huge bear which I managed to capture on film. He was a sausage stealing, trash tipping pain in the butt but we were not going to argue :0)

bear.jpg

Now its 27F, sorry 'Rest of the Worlders', I have been assimilated. Its imperial measurements for me all the way. I even know what a 'quart' is, I think in inches and cups for measuring. America is sometimes staggering in its arrogance for the rest of the world! Maybe a bit strong but you know what I mean.
Yep, its freezing cold the wind is howling, the windshield (another Americanism along with tail pipe, thats a good one) blower on the car can not keep up with the freezing rain and I have to scrape it to see. Come back Summer, maybe not the bear thou!

Anyhoo, my last post on row filling and the added requirement that I did not cover got some folks into action. Most notably, Lakshmanan and the Ice Man aka Ike Wiggins. Both of them were way ahead of me. I guess from a need, born of necessity i.e. get it done for a customer. Rather than my approach of, 'when I can find time', they have independently come up with solutions to handle the row wrapping issue I mentioned at the end of the last post.

Shout outs are due and if you use their methods, please drop a quick mail of thanks.

Lakshmanan's solution is available here. Some other great posts on EBS related tidbits (another PC'ed American term) available on his blog.

Ike's solution is available here Ike has also posted sample template and data here. Like the blog's new look and feel Ike, very suave!

About XSL/XPATH

This page contains an archive of all entries posted to Oracle BI Publisher Blog in the XSL/XPATH category. They are listed from oldest to newest.

Charting is the previous category.

Many more can be found on the main index page or by looking through the archives.

Top Tags

Powered by
Movable Type and Oracle