Skip to content

JD Edwards BI Publisher (RTF) Cheat Sheet

A concise reference for working with JD Edwards BI Publisher RTF templates using XPath 1.0 + Oracle XSLT Extensions (XDOXSLT/XDOFX).

🧠 Remember: All logic is XPath 1.0 with Oracle XSLT extensions under the hood.
You write XPath in <? ... ?> tags; BI Publisher compiles it to XSLT / XSL‑FO when generating output.

  • XPath (The XML Path Language) - is used to uniquely identify or address parts of an XML document. An XPath expression can be used to search through an XML document, and extract information from any part of the document, such as an element or attribute (referred to as a node in XML) in it.
  • XSL (Extensible Stylesheet Language) - is a family of XML-based languages that provide a way to transform and format XML documents into other formats, such as HTML or text, or to present them in a specific way.
  • XSLT - Extensible Stylesheet Language Transformations
  • XSL-FO (XSL Formatting Objects) - markup language for XML document formatting

How to read this page

  • Copy–paste the snippets directly into your RTF template (Word + BI Publisher Desktop).
  • Items marked 🛡️ are safe patterns that prevent common runtime errors.
  • Use the Variables pattern to pass values from body to page headers/footers.

Page Numbering

Fields

Press Ctrl + F9 (Windows) or Cmd + F9 (Mac) to insert a field code block → you’ll see { } braces. Inside those braces type:

<!-- Current Page -->
PAGE

<!-- Number of pages -->
NUMPAGES

<!-- Page X of Y -->
Page { PAGE } of { NUMPAGES }

XPath

Page <?page-number?> of <?total-pages?>
Place this in the Word header/footer. With @section + <?initial-page-number:1?>, numbering resets per section.

Custom Formats

Custom Decimal Formats

Define a reusable decimal pattern so European-style separators stay consistent across the template.

<xsl:decimal-format xdofo:ctx="begin" name="myFMT" grouping-separator="." decimal-separator=","/>

Use Format

<!-- Format Field : Gross_Amount -->
<?if:number(Gross_Amount)!=0?><?format-number(Gross_Amount, '#.##0,00', 'myFMT')?><?end if?>

<!-- Create Variable : AmountAging1 -->
<?xdoxslt:set_variable($_XDOCTX,'cust_name', AmountAging1)?>

<!-- Format Variable : AmountAging1 -->
<?format-number(xdoxslt:get_variable($_XDOCTX,''AmountAging1''), '#.##0,00', 'myFMT')?>

Review

Click to expand ## Core Tags
<?field?>                                 <!-- print a node value -->
<?for-each:GROUP?> ... <?end for-each?>   <!-- loop -->
<?for-each@section:GROUP?> ... <?end for-each@section?> <!-- loop + page section -->
<?if: condition?> ... <?end if?>          <!-- conditional -->
<?choose:?> <?when: test?> ... <?otherwise:?> ... <?end choose?> <!-- branching -->
<?sort: FIELD1; order=ascending?>         <!-- sort inside loop -->
<?break?>                                 <!-- exit current for-each -->
??? info "When to use `@section`" Use **`@section`** whenever you need page numbering, headers/footers, or totals **per customer / per document**. It creates a new pagination scope and lets you combine with ``.
<?for-each@section:A_R_Statement_Detail_S9_Group/On_AR_Branch_S224?>
<?initial-page-number:1?>
... body ...
<?end for-each@section?>
--- ## Variables (Pass Data to Header/Footer)
<?xdoxslt:set_variable($_XDOCTX,'cust_name', Remit_to_Name_ID445)?>
<?xdoxslt:get_variable($_XDOCTX,'cust_name')?>
- **Set** variables inside the XML context (body). - **Get** them anywhere (headers/footers have no context). !!! example "Per-customer wrapper with variables"
<?for-each@section:CustomerGroup?>
<?initial-page-number:1?>
<?xdoxslt:set_variable($_XDOCTX,'cust_name', Name)?>
... body ...
<?end for-each@section?>
**Footer**
Customer: <?xdoxslt:get_variable($_XDOCTX,'cust_name')?>  
Page <?page-number?> of <?total-pages?>
--- ## Numbers — Safe Patterns Avoid **“Cannot convert to number.”** by guarding conversions. **Safely format possibly blank or comma’d numbers:**
<?format-number(
  number(concat('0', normalize-space(translate(string(Total), ',', '')))),
  '#,##0.00'
)?>
**Safe comparison to zero:**
<?if: number(concat('0', normalize-space(translate(string(Amount), ',', '')))) != 0 ?>
  ...
<?end if?>
**Shortcut when node may be missing (tolerant):**
<?format-number(sum(Amount), '#,##0.00')?>   <!-- sum() of empty = 0 -->
#### Apply the custom format
<?format-number(var_F03B11_AG_ID142, '##0,00', 'myFMT')?>
#### Apply conditionally
<?if:number(Grand_Total_ID8)!=0?><?format-number(Grand_Total_ID8, '#,##0.00', 'myFMT')?><?end if?>
--- ## Strings
<?xdoxslt:upper_case(Name)?>
<?xdoxslt:lower_case(Name)?>
<?xdoxslt:substring(Text, 1, 10)?>          <!-- 1-based -->
<?xdoxslt:substring_before(Text, '-')?>
<?xdoxslt:substring_after(Text, '-')?>
<?string-length(normalize-space(Text))?>
<?contains(Text, 'ABC')?>                    <!-- often available -->
--- ## Dates
<?xdoxslt:format_date(DateNode, 'YYYY-MM-DD')?>
<?xdoxslt:format_date(sysdate(), 'DD Mon YYYY')?>   <!-- current date -->
--- ## Oracle XDOFX Helpers
<?xdofx:nvl(Field, 'N/A')?>                       <!-- default when null -->
<?xdofx:decode(Status, 'C','Closed','O','Open','?')?> <!-- switch/case -->
<?xdofx:sysdate()?>                                <!-- now (server time) -->
--- ## Totals, Counts, Grouping
<?sum(Invoice/Amount)?>      <!-- inside a group -->
<?count(Invoice)?>
<?min(Amount)?> <?max(Amount)?> <?avg(Amount)?>
### Combine and format totals
<?format-number(sum(Gross_Amount___Display_ID28)+sum(Amount_Due___Display_ID20), '#,##0.00')?>
--- ## Sorting in Loops
<?for-each:Invoice?>
  <?sort: Invoice_Date; data-type=date; order=ascending?>
  <?Invoice_Number?>  <?Invoice_Date?>  <?Amount?>
<?end for-each?>
### Targeted iteration over child nodes
<?for-each:Phase_1___Build_Work_File_S1/Detail_Line_1_Section_S2_Group/On_Payment_Terms_S3/*[self::With_Lot___Main_S39 or self::With_Lot___Lot_S36 or self::Print_Business_Reason_S22]?>
Use XPath predicates to include only the nodes required for the output section. --- ## Conditional Blocks
<?if: normalize-space(TERMS_ID358) != ''?>
  Terms: <?TERMS_ID358?>
<?end if?>
**Validation guards:**
<?if:number(CL__Extended_Amount_ID13)!=0?>
<?if:normalize-space(GRAND_TOTAL_ID22)!=''?>
  ... content ...
<?end if?>
<?end if?>
Use nested `` statements to close each guard. --- ## Match Data Between Nodes (PageFooters ↔ Customer)
<?xdoxslt:set_variable($_XDOCTX,'cust_key', Account___local_ID670)?>
<?for-each:/R5603B5001/PageFooters/Page_Footer_S218?>
  <?if: normalize-space(New_Customer_Field) = xdoxslt:get_variable($_XDOCTX,'cust_key')?>
    <?xdoxslt:set_variable($_XDOCTX,'cust_total', Total_Balance_Due___Summary_ID30)?>
    <?break?>
  <?end if?>
<?end-for-each?>
Use it later:
Total Due: <?xdoxslt:get_variable($_XDOCTX,'cust_total')?>
--- ## Choose / When / Otherwise
<?choose:?>
  <?when: Status = 'C'?>Closed<?end when?>
  <?when: Status = 'O'?>Open<?end when?>
  <?otherwise?>Unknown<?end otherwise?>
<?end choose?>
--- ## Repeat Headers in Tables
<?repeat-header:yes?>
Add to the first header row of a Word table to repeat it on each page. --- ## Bursting Paths Reference the XML path used when configuring bursting definitions.
/R5942565/Phase_1___Build_Work_File_S1/Detail_Line_1_Section_S2_Group/On_Payment_Terms_S3
--- ## Common Pitfalls & Fixes | Problem | Fix | |----------|-----| | “Cannot convert to number.” | Use *safe-number* or `sum()` | | Footer shows wrong customer | Use `set_variable` in body, `get_variable` in footer | | Wrong equality operator | Use `=` not `==` | | Whitespace mismatch | Wrap with `normalize-space()` | | Per-document page totals | Use `@section` + `` | --- ## Quick Copy Blocks **Per-Customer Wrapper**
<?for-each@section:CustomerGroup?>
<?initial-page-number:1?>
<?xdoxslt:set_variable($_XDOCTX,'cust_name', Name)?>
... body ...
<?end for-each@section?>
**Footer**
Customer: <?xdoxslt:get_variable($_XDOCTX,'cust_name')?>  
Page <?page-number?> of <?total-pages?>
**Show Value Only If Present**
<?if: string-length(normalize-space(Field)) > 0?>Label: <?Field?><?end if?>
---