Background Color:
 
Background Pattern:
Reset
Search
Home Recent Changes Show All Pages

Part 1. Data binding and XPath selectors

Not Rated Yet

This article is the 1st of a 5 part course in complex data-binding with namespaces, making choices, dynamic parameters and dynamic data.

 

Course Contents

 

If there are 2 things that mark scryber as revolutionary when generating PDF's it would have to be Styles and Data Binding. Styles have been covered elsewhere in the documentation, this is all about getting up to speed with binding to a data source and getting some results - especially with namespace qualified documents.

 

Article Contents

 

You will need to know about XML and also a little about XPath for this article. You might also want to brush up on the Table, Row, Cell documentation too, before starting

How to bind Data

 

Scryber has support for a few binding mechanisms, the most common being xpath. When binding a specific attribute or value to an expression in a template you use the standard curly brace notation, followed by the type of binding

{xpath:expression}
An XPath binding expression within a current data context, that is evaluated when the item is data-bound
{item:contextItem[.with.optional.tree]}
An Item binding expression that uses the Document Items collection to derive it's value. The expression is evaluated at the time of binding, but does not need a current data context, but cannot (currently) be looped over or used on decisions - unless it is actually an XPath value itself.
{qs:query-string-parameter}
A query string value taken from the current web request. This is evaluated at load time, rather than data-binding, because it cannot change.

The xpath binding is covered in this document and it's following tutorials. The item and query string bindings are covered in Item and Query String binding later on.

 

Our data source.

 

If we start with a simple XML document that we want to generate a document from - let's take the sample from MSDN for a sales report and call it Sales.xml

<?xml version="1.0" ?>
<sales xmlns='http://sample.xml.com/sales' >
   <summary>
      <heading>Lucerne Publishing</heading> 
      <subhead>Regional Sales Report</subhead> 
      <description>Sales report for the West Coast, Central and East Coast regions.</description> 
   </summary>
   <data>
      <region>
         <name>West Coast</name> 
         <quarter number="1" books_sold="24000" /> 
         <quarter number="2" books_sold="38600" /> 
         <quarter number="3" books_sold="44030" /> 
         <quarter number="4" books_sold="21000" /> 
      </region>
      <region>
         <name>Central</name> 
         <quarter number="1" books_sold="11000" /> 
         <quarter number="2" books_sold="16080" /> 
         <quarter number="3" books_sold="25000" /> 
         <quarter number="4" books_sold="29000" /> 
      </region>
      <region>
         <name>East Coast</name> 
         <quarter number="1" books_sold="27000" /> 
         <quarter number="2" books_sold="31400" /> 
         <quarter number="3" books_sold="40100" /> 
         <quarter number="4" books_sold="30000" /> 
      </region>
   </data>
</sales>

 

Adding a reference to the source

 

If we have saved our XML file in a data folder just below or PDFX template file, then we can add a reference to it with our XMLDataSource.

 

<?xml version="1.0" encoding="utf-8" ?>
<pdf:Document xmlns:pdf="Scryber.Components, Scryber.Components, Version=0.8.0.0, Culture=neutral, PublicKeyToken=872cbeb81db952fe"
          xmlns:style="Scryber.Styles, Scryber.Styles, Version=0.8.0.0, Culture=neutral, PublicKeyToken=872cbeb81db952fe"
          xmlns:data="Scryber.Data, Scryber.Components, Version=0.8.0.0, Culture=neutral, PublicKeyToken=872cbeb81db952fe"
          auto-bind="true">
  <Viewer page-display="Outlines"/>
  <Styles>

  </Styles>
  <Pages>

    <data:XMLDataSource id="MySalesData" source-path="./Data/Sales.xml" >
      <Namespaces>
        <data:Xmlns prefix="s" namespace="http://sample.xml.com/sales"/>
      </Namespaces>
    </data:XMLDataSource>

    <pdf:Page id="MyOutput" >
      <Content>

      </Content>
    </pdf:Page>

  </Pages>
</pdf:Document>

About the Namespaces

Because our data file has a namespace, then we need to ensure we reference this in the XMLDataSource too. Otherwise any selectors we use later on will be referencing empty namespace elements. scryber also enforces the use of namespace prefixes - to reduce ambiguity. You can declare as many namespaces as required in the XMLDataSource/Namespaces element (they don't even have to exist in the referenced document), but it is an all or nothing option. If there are namespaces, then they must all be referenced before they can be used.

 

Binding the titles.

 

Once we have the XMLDataSource in place we can reference it and use the content with a ForEach data component

	<?xml version="1.0" encoding="utf-8" ?>
<pdf:Document xmlns:pdf="Scryber.Components, Scryber.Components, Version=0.8.0.0, Culture=neutral, PublicKeyToken=872cbeb81db952fe"
          xmlns:style="Scryber.Styles, Scryber.Styles, Version=0.8.0.0, Culture=neutral, PublicKeyToken=872cbeb81db952fe"
          xmlns:data="Scryber.Data, Scryber.Components, Version=0.8.0.0, Culture=neutral, PublicKeyToken=872cbeb81db952fe"
          auto-bind="true">
  <Viewer page-display="Outlines"/>
  <Styles>

  </Styles>
  <Pages>

    <data:XMLDataSource id="MySalesData" source-path="./Data/Sales.xml" >
      <Namespaces>
        <data:Xmlns prefix="s" namespace="http://sample.xml.com/sales"/>
      </Namespaces>
    </data:XMLDataSource>

    <pdf:Page id="MyOutput" >
      <Content>

        <data:ForEach select="s:sales" datasource-id="MySalesData" >
          <Template>
            <pdf:H1 id="heading" text="{xpath:s:summary/s:heading}" />
            <pdf:H3 id="subhead" text="{xpath:s:summary/s:subhead}" />
            <pdf:Div id="desc" >
              <pdf:Label text="{xpath:s:summary/s:description}" />
            </pdf:Div>
          </Template>
        </data:ForEach>

      </Content>
    </pdf:Page>

  </Pages>
</pdf:Document>

 

    doc = Scryber.Components.PDFDocument.ParseDocument(path);
    doc.ProcessDocument(outputstream, true);

 


 

Because we have declared the namespace prefix for our file as 's' our select value references the top level sales element with the s: prefix. The source xml file does not declare this prefix, and it would not matter if the source declared a different prefix for the namespace. The 's' prefix is local to our loaded source.

There is only one sales element in our data, so the ForEach loop will only ever generate one heading component when it databinds. If there were multiple, then each would be enumerated over (or we could limit it with the @count and @start-index attributes on the ForEach component)

The @select attribute does not require an {xpath: ... } binding expression, as the value because it is expected to be the xpath expression itself. It is perfectly possible to use a binding expression for the value - but this would be bound and evaluated, and expected to result in a returned valid xpath binding expression.

 

Binding component creation

The headings and labels are all bound to values within the current XML Data using the standard {xpath: ... } notation. The values are not actually set to this expression when the document is parsed, in fact the heading and labels do not exist at all until the ForEach template is generated on the DataBinding operation - see Document Processing.

  1. Once the ForEach encounters a matching xml node based on it's select statement the inner components are parsed and instantiated from the Template contents adding them to the document hierarchy.
  2. Any binding expressions found are linked to that components DataBinding event during the parsing.
  3. Once all the newly created components are created and added, then they are also data-bound with the current data as the reference source.
  4. It is only after the the template component is bound does its property contain the actual text to be rendered with.
  5. The ForEach continues to match the select XPath, repeating as necessary - in our case only once

 

Binding to a Table.

 

Once we have the titles in we can bind the sales figures from the data. For our example we can bind these into a simple table using a nested loop.

 

<data:ForEach select="s:sales" datasource-id="MySalesData" count="1" start-index="0" >
  <Template>
    <pdf:Div id="titles" style:margins="10pt">
      <pdf:H1 id="heading" text="{xpath:s:summary/s:heading}" />
      <pdf:H3 id="subhead" text="{xpath:s:summary/s:subhead}" />
      <pdf:Div id="desc" >
        <pdf:Label text="{xpath:s:summary/s:description}" />
      </pdf:Div>
    </pdf:Div>
    <pdf:Table id="salesgrid" style:full-width="true" style:font-size="14pt" style:margins="10pt" >
      <pdf:Header-Row >
        <pdf:Header-Cell>Region</pdf:Header-Cell>
        <pdf:Header-Cell> Q1</pdf:Header-Cell>
        <pdf:Header-Cell> Q2</pdf:Header-Cell>
        <pdf:Header-Cell> Q3</pdf:Header-Cell>
        <pdf:Header-Cell> Q4</pdf:Header-Cell>
        <pdf:Header-Cell>Total</pdf:Header-Cell>
      </pdf:Header-Row>
      <data:ForEach select="s:data/s:region" >
        <Template>
          <pdf:Row>
            <pdf:Cell>
              <pdf:Label text="{xpath:s:name}" />
            </pdf:Cell>
            <pdf:Cell>
              <pdf:Number value="{xpath:s:quarter[@number='1']/@books_sold}" style:number-format="C" />
            </pdf:Cell>
            <pdf:Cell>
              <pdf:Number value="{xpath:s:quarter[@number='2']/@books_sold}" style:number-format="C" />
            </pdf:Cell>
            <pdf:Cell>
              <pdf:Number value="{xpath:s:quarter[@number='3']/@books_sold}" style:number-format="C" />
            </pdf:Cell>
            <pdf:Cell>
              <pdf:Number value="{xpath:s:quarter[@number='4']/@books_sold}" style:number-format="C" />
            </pdf:Cell>
            <pdf:Cell>
              <pdf:Number value="{xpath:(s:quarter[@number='1']/@books_sold 
	                                           + s:quarter[@number='2']/@books_sold 
	                                           + s:quarter[@number='3']/@books_sold 
	                                           + s:quarter[@number='4']/@books_sold)}" 
	                         style:number-format="C" />
            </pdf:Cell>
          </pdf:Row>
        </Template>
      </data:ForEach>
    </pdf:Table>
  </Template>
</data:ForEach>

 

We create the table and header row, then our nested ForEach takes the current node and then executes the select on that node to find each of the data/region elements. Looping through them in turn.

Even though the total does not exist in the data, we can calculate it using the rather long XPath expression. For anything more complex we would probably need to transform the data before consuming it - Part 2. Multiple Namespaces and Transformations

 


 

See Also.

 

 



  Rating
Rate This Page: Poor Great   |  Rate Content |
Average rating:  No Ratings Yet   
Number of Ratings : 0
  Comments
Add Comment
No Comments Yet