CollapseXML – Documentation

Let’s start this documentation with the architecture.

1. Architecture

CollapseXML requires two things to work:

  • Template
  • Data

1.1. Template

A template describes the resulting XML and is an XML document itself.

CollapseXML defines a namespace and the following XML elements and attributes to describe the output:

  • http://kaisean.com/tools/collapse namespace
  • cx:foreach attribute
  • cx:value element
  • cx:filter attribute
  • cx:filter-* attribute

Each of those elements will be described in much more details later on.

1.2. Data

CollapseXML will ask you for a single CLR object and it will traverse its properties to access data required by the template. The object given to the CollapseXML is called a root object. Every direct reference to a property in the template refers to a property of this root object. If a property of a root object is another object with its own properties, you can access those by using dot-notation, just as you would in most object-oriented languages.

Note that CollapseXML does not traverse functions in the current version. Support for functions may be added in a future version.

For example, the path written like this:

"ContactPerson.Address.City"

refers to the root object’s ContactPerson property which in turn is an object with the Address property, which in turn has the City property. That City property (which is a string in this scenario) will be accessed for data if you use such path in the template.

2. The Anatomy of a Template

There are several rules that must be followed when writing a template.

First of all, a template must be a valid XML document. CollapseXML will throw an exception if you give it an invalid XML document.

2.1. Template’s root element

Second thing is the template’s root element. This element must define the cx namespace, but can be named whatever you want. CollapseXML ignores the name of this element because its only purpose is to define the namespace. It’s usually best to name it “template“.

<?xml version="1.0" encoding="utf-8" ?>
<template xmlns:cx="http://kaisean.com/tools/collapse">
</template>

2.2. Output root element

Within the template’s root element, an output root element must be defined. Output root element is the root element of the resulting XML.

For example, if the following XML is used as a template:

<?xml version="1.0" encoding="utf-8" ?>
<template xmlns:cx="http://kaisean.com/tools/collapse">
  <root>
    <data />
  <root>
</template>

… the result XML will be this:

<?xml version="1.0" encoding="utf-8" ?>
<root>
  <data />
<root>

Use CollapseXML attributes and elements in root element and its descendants to create the template you need.

3. CollapseXML attributes and elements

XML is usually created from a collection of objects. That’s where cx:foreach attribute comes into play.

cx:foreach element

If your root object contains a property whose type implements IEnumerable<object> (in a covariant manner), CollapseXML can export that collection as one XML node for each of its elements.

The format of the attribute looks like this:

<node cx:foreach="item in collection" />

Instead of “item“, you can name your variable whatever you want (unless that identifier is already used). That identifier will be used as a reference to the specific item from the collection during iteration. It is available only in the scope of this node element (similar to foreach behavior in C#).  During XML generation, each generated node will have its own reference from the collection.

There are four ways you can use this reference to output XML content.

  • Use it in a nested cx:foreach
  • Use it in an attribute transformation
  • Using a cx:value element
  • Inlining a (cx|*) code

Each of those will be described later in much more details. But let’s just show an example that shows them all. Note that the example describes another feature of cx:foreach – nesting.

Elements with a cx:foreach attribute can be nested like this:
(with assumption that the root object contains a property called Users, which implements IEnumerable)

<user cx:foreach="user in Users" name="cx|user.Name">
  <friends>
    User (cx|user.Name) has (cx|user.Friends.Count) friend(s).
    <friend cx:foreach="friend in user.Friends" name="cx|friend.Name">
      (cx|user.Name) and <cx:value source="friend.Name" /> are friends.
    </friend>
  </friends>
</user>

In each nested XML cx:foreach element, there’s an identifier which is valid in that scope. An example of resulting XML could be this:

<user name="Mark Johnson">
  <friends>
    User Mark Johnson has 2 friend(s).
    <friend name="Steve Walls">
      Mark Johnson and Steve Walls are friends.
    </friend>
    <friend name="Sandy Brais">
      Mark Johnson and Sandy Brais are friends.
    </friend>
  </friends>
</user>
<user name="Tim">
  <friends>
    User Tim has 1 friend(s).
    <friend name="Sandra Rockster">
      Tim and Sandra Rockster are friends.
    </friend>
  </friends>
</user>

cx:value element and (cx|*) code

XML element can contain another XML element (or more of them), a text, or both (mixed content). It can also contain nothing.

When you need to output a property as a text content of an XML element, you can use two ways:

  • cx:value element
  • inline (cx|*) code

Let’s repeat the previous example:

<user cx:foreach="user in Users" name="cx|user.Name">
  <friends>
    User (cx|user.Name) has (cx|user.Friends.Count) friend(s).
    <friend cx:foreach="friend in user.Friends" name="cx|friend.Name">
      (cx|user.Name) and <cx:value source="friend.Name" /> are friends.
    </friend>
  </friends>
</user>

Look at the line 3. You can see how placeholders for values are created by using (cx|*) inline code.

The format for the code is: (cx|propertyPath), where ‘propertyPath’ is a path to the property whose value will replace the placeholder.

The other way is by using the cx:value XML element, as shown on the line 5.

The format for the element is:

<cx:value source="propertyPath" />

The source attribute must contain a path to the desired property whose value will replace this element. Note that the value is not inserted into the cx:value element, but instead replaces it.

It is usually more convenient to use the inline version. However, since CollapseXML does not do any escaping, there is no way for CollapseXML to differentiate between (cx|*) code and regular text that might contain such string. If unsure, use the cx:value element.

Attribute transformations

Every attribute whose value starts with cx| will be transformed. The full syntax for the attribute is the following:

<element attribute="cx|propertyPath" />

The previous examples have already shown how attribute transformations works to generate the resulting XML for.

Filters – elements

There are situations in which you want elements to be output only if a certain condition is met. CollapseXML can help you with that. When an element with cx:filter attribute is encountered, it will be included in the output only if the filter condition is satisfied.

If a filter is not found, MissingFilterBehavior configuration (set up on CollapseXML construction) determines what happens with the element.

The following example shows how to write filters:

<user cx:foreach="user in Users"
      name="cx|user.Name"
      cx:filter="UserHasFriends | user.Friends.Count">
  <friends>...</friends>
</user>

A filter consists of its name and a list of arguments. Between the name and the arguments is a pipe character (|). Arguments are separated by commas (,).

With the filter set up like this, we expect only those users who have at least one friend to be output to resulting XML.

new Collapse().SetFilter("UserHasFriends", args => args[0].Value<int>() > 0);

That’s it. Only those users that have at least one friend will be included in the output.

Alternatively, since v1.3 you can use named arguments which are easier to read:

<user cx:foreach="user in Users"
      name="cx|user.Name"
      cx:filter="UserHasFriends | count: user.Friends.Count">
  <friends>...</friends>
</user>

And then access them by their name, like this:

new Collapse().SetFilter("UserHasFriends", args => args["count"].Value<int>() > 0);

Filters – attributes

Conditional output of elements is not enough. You need to be able to conditionally output attributes too.

For this exact purpose, you use cx:filter-* attribute.

<user cx:foreach="user in Users"
      name="cx|user.Name"
      cx:filter-name="UserNameTwoWords | name: user.Name">
  <friends>...</friends>
</user>

As you can see in the example, the attribute that is being conditionally output is the name attribute.

In other words, only <user> elements that satisfy the filter will have the name attribute.

new Collapse().SetFilter("UserNameTwoWords",
    args => args["name"].Value<String>().Split(' ').Count() == 2);

Let’s check out the example output.

<user name="Mark Johnson">
  <friends>...</friends>
</user>
<user>
  <friends>...</friends>
</user>

As you can see, the name attribute for Tim has not been output, because he’s got only one word in his name.

Conclusion

We came to the conclusion of this pretty little guide. We hope you have received enough information to make use of CollapseXML. We are eager to hear if you found this library useful for your work. We are also open for any kind of critique or suggestion for future versions.

Please feel free to contact us with feedback :)

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>