Skip to main content

Wouter

Go Search
Home
Contact Me
  

 ‭(Hidden)‬ Admin Links

Houston we have a (workflow) problem.
Call me evil... Yes... it is true. Like many of you, I used to run SharePoint as admin on my developer box, and found out the hard way that this is quite a bad idea when you want to make software that actually deploys well on someone else's machine.
The first beta Wss3Workflow has a small issue with being installed on a least-privy box. We had a stupid idea, which unfortunately wasn't prone to being found in our anti-stupid idea discussions on what and how to implement stuff.
The issue is that when you try and activate our feature, for instance to support bulk tasks, we try and make a config file change using a site collection admin account. Doh!
 
No worries. We are going to solve it before the first release. The config change entails adding a safe control entry. The reason for adding it in an eventhandler is the installation of WSS3 Workflow. When we have no web-app related deployment to do (like registering safe controls), we can not only do an stsadm -o addsolution, but also an stsadm -o deploysolution, all without you having to pick web apps (making our installation easier). To add the safe control (normally done by the deploysolution bit), you just activate the site collection feature (or that was the idea, seems rather stupid when we are thinking of it now).
 
So to fix it, activate the feature with a farm admin account. We'll change our installation procedure before RTM.
 
Now I am off to the corner of shame.
WSS Flash Tools 1.0 beta released
Just wanted to let you know I've polished up the flash tools I use for my blog. You can find the project on CodePlex:
 
Features:
 - Flash Video content type
 - Flash Video Library (applied content type)
 - Flash Viewer Web Part
 - Flash streaming http handler
 
Let me know how you think the project should progress. Some ideas are to add the preview image and add blog support.
 
Hope it helps
WSS3 Workflow Video Guide 1 - Setting up
The first video on WSS3 Workflow is now available. In it you will be shown how to prepare a Visual Studio workflow project for development with WSS3 Workflow.
 
The steps taken are:
- Create a 'Sequential Workflow Library' project
- Sign the DLL / Retrieve public key token
- Create folder structure (RootFiles / TEMPLATE / FEATURES etc)
- Create the feature.xml and workflow.xml
- Initialize the workflow with an OnWorkflowActivated activity
- Create post-build event for easy debugging of your workflow
 
You can watch the video stream here:
 
I'll try and get a SWF flash movie (smaller size, no streaming) available as well.
 
Note that this is still somewhat of a test. Basically I have no idea how my servers will perform with streaming content. I do have a 24mbit upstream, so I hope all goes well. If not, let me know, I can still upgrade to 100mbit for like 10 dollars :) Also, the video area still needs better scaling, but it'll hold up for now.
The first makings of my video blog
Cool stuff! I've created a new SharePoint feature for streaming flash videos using SharePoint. It includes a template for a flash video library, a flash-video web part and a menu-item added to the ECB menu for flash-videos. The feature also includes a default application page for viewing flash videos. The menu-item links here. Streaming is handled with a custom http handler attached to the .flv file type. 
 
I'll post code for this feature soon, first I need to clean the code a little. It'll probably be another CodePlex project.
 
You can see how it works by navigating to the url below, or visit the video library and use the ECB 'View Video' menu item. The webpart is not used here, it's main scenario would be to have separate viewing pages for each video.
 
 
 
Some ideas I have for this code is to integrate it better into the whole blog experience.
Announcing: WSS3Workflow Tools on Steroids

Last year I provided some tooling for developers of workflows in Windows SharePoint Services 3.0 called WSS3Workflow. After a lot of hard work together with my new coding buddy Chris Predeek the tools have now evolved into a great framework for developing SharePoint workflows using a similar methodology as used by MOSS with its InfoPath integration. You can create a full-featured workflow form with just a simple UserControl. There is no need to create complex ASP.NET hosting pages and no more complex SharePoint infrastructure like task content-types. Chris has got a great sample on his blog, with more to follow!

We have abstracted each type of workflow form into base classes which provide two basic methods; Load, and yes, Save too… J, while retaining the option to dive deep into the SharePoint infrastructure. These base classes take care of all the heavy lifting such as performing a workflow association. To give you greater reuse of forms and to save you from the hassle of creating the SharePoint ASPX content you can make use of the hosted control infrastructure. In this case the ASPX page is provided for you by WSS3Workflow. You just create a UserControl and use the metadata in the workflow definition to specify which control to show at what time in the lifecycle.

Note that you use either the form based approach or preferably the control base approach.

In the coming few weeks Chris and I will be writing about all the cool stuff you can do with this library and how it makes your workflow development life so much easier. I've got some videos planned myself. In the mean time, head over to the project page and download the installer. Give it a try and please let us know how you feel about the features we provide. The sample project is a good place to start. It shows a workflow using the hosted control approach.

Here's a screenie from the installed templates that you can now use for your workflow development projects:

Hope it helps!

Embedding repeating elements in a schema-mapped document

A blog post or two ago I introduced the usage of <w:customXml> tags to mark up your documents with business semantics. A second blog post followed up on that displaying the code for mapping an XML data file to your schema-mapped document. In this third and last post on the subject I plan to cover repeating elements which are mapped to a table. This is slightly more complex than mapping simple values since we need to create new rows in the table to accommodate all the data that is provided in the XML data file.

The last blog post left off with a simple method to take a piece of XML data, identified using an XmlNode and an X-Path query, and map that to a <w:customXml> tag, also identified with an XmlNode container and an X-Path query:

static void UpdateDocumentValue(
  string dataSelector, // xpath query into data node
  string customXmlSelector, // xpath query to select customXml tag
  XmlNode dataPart, // the relevant part of the data to query against
  XmlNode documentPart) // the part of the document containing the data
{
  ...
}

In this second code walkthrough a similar method will be created for updating a table as well. One of the cool things is that we can re-use much of the code for the UpdateDocumentValue method, which updates single values. This is because conceptually a row of data consists of single values again, so with the proper factoring the existing code should work great for this situation as well.

The new method that you will call to update the document data is, very creatively, called UpdateDocumentTable (yes, long and hard thought went in to this naming schema). Take for instance the following blob of XML data, in which the <orderItem> is the repeating element that maps to your table:

<invoice xmlns="http://contoso.com/invoice"
    <orderItems
        <orderItem /> 
        <orderItem /> 
        ... 
    </orderItems>
</invoice>

To take this data and update your document, you would use a method call similar to:

UpdateDocumentTable(
  "/iv:invoice/iv:orderItems",
  BuildCustomXMLSelectionQuery(
    XMLNS_IV,
    new string[]
    {
      "invoice",
      "orderItems"
    }),
  data,
  document);

Note that this is largely similar to the UpdateDocumentValue method. The first parameter is the X-Path into the XML data file, relative to the node being passed in as the third parameter. The second parameter is an X-Path selecting the right <w:customXml> tag. Note that this X-Path doesn't select the repeating element itself, but the container element <orderItems>. The last parameter is the XmlNode representing the body of the WordprocessingML document.

The UpdateDocumentTable method

So what are the tasks that we need to perform in the UpdateDocumentTable method? First of all, take a look at a typical document with a mapped table. One important thing to notice is that the table which will contain the order items already has the first row for holding data available.

Assuming that there will also be at least one row of data, the logic that we need to implement is not that complex:

  • Update data in the first row.
  • For 1 to data count
    • Create new row
    • Update data in new row

The first 'complexity' is getting the first data row of the table, so we can update that and use it as a template for the other rows. With a little knowledge of Open XML found in the spec it is not that hard. You first get the <w:customXml> tag which surrounds the entire table using the parameters supplied to the UpdateDocumentTable method. Next you select the <w:customXml> tag which surrounds the row. A row is defined using the <w:tr> tag, so a little X-Path and you're there. Since usually the first row of a table contains the columns headers, this will select the second row of the table. Note that this is obviously not generic enough to work for all documents that you can think of, but I think most of you will do it this way, so it's good enough for me J

XmlNode customXmlTableNode = document.SelectSingleNode(documentSelector, mgr);
XmlNode customXmlRowNode = customXmlTableNode.SelectSingleNode(
 
"w:tbl/w:customXml[w:tr]", mgr);

Now that we have that first row of the table, we can update it with data. First a query is performed into the XML data file, retrieving the container of the <orderItem> elements. Next the first <orderItem> is mapped to the row we have retrieved earlier.

XmlNode dataCollectionNode =
  data.SelectSingleNode(dataXPath, mgr);
UpdateDocumentTableRow(
  customXmlRowNode,
  dataCollectionNode.ChildNodes[0]);

The last part of the UpdateDocumentTable method loops through the rest of the <orderItem> elements in the XML data file and creates a new row for it by cloning the previous row. Like I explained in the first blog post, schema-mapping is a hierarchical model. By just copying the previous row, the hierarchy is maintained correctly.

for (int i = 1; i < dataCollectionNode.ChildNodes.Count; i++)
{
  // clone the Open XML table row
  customXmlRowNode = AddTableRow(  customXmlTableNode, customXmlRowNode);
  // Update the new mapped row
  UpdateDocumentTableRow(
    customXmlRowNode,
    dataCollectionNode.ChildNodes[i]);
}

So a few interesting things yet to be explained are how rows are copied (pretty simple), and how the update of the data in a row is performed.

Creating new table rows

First let's cover the addition of new rows to the table and how this affects table formatting.The copying itself is pretty simple. The <w:customXml> tag for the table is already available in the UpdateDocumentTable method, the <w:customXml> tag for the row is also known. First a deep clone is created from the row. This ensures that all the tags for the mapped cells are copied as well. Next this node is added at the end of the <w:tbl> node, which is the only child of the <w:customXml> tag for the table.

static XmlNode AddTableRow(
  XmlNode customXmlTableNode,
  XmlNode customXmlTableRowNode)
{
  customXmlTableRowNode = customXmlTableRowNode.CloneNode(true);
  customXmlTableNode["tbl", XMLNS_W].AppendChild(customXmlTableRowNode);
  return customXmlTableRowNode;
}

An important note about doing it this way would be table formatting. By now most of us have found out that making the background shading for rows different for odd and even rows improves readability of the table. Doesn't cloning the row break this formatting?

Header Row

Even row

Odd row

Even row

It turns out that it doesn't! This type of formatting is dealt with in a generic way outside of the main table content, using table styles. This means that after you update the table with the XML data, it will look something like

Updating the values in the row

Now we get to the fun of reuse. When you take a look at updating the values in a row, it is remarkably similar to updating a single value.

For the update of a single value we needed to know the X-Path query to select a part of the document, and the part to select from was passed in to the method. For the updating of a single value explained in the previous post on this subject, the XmlNode passed in was the root node of the document body. For the row updates we can pass in the row instead. The X-Path query to select the right <w:customXml> node for the cell we need to update is easily found as well. It is just the name of each child element of the <orderItem> tag. Here is a sample of how the data for an <orderItem> looks. The <orderItem> is passed in to the UpdateDocumentTableRow method, together with the <w:customXml> tag representing the entire row in the document.

<orderItem
    <productID>1</productID
    <productName>Black Pencils</productName
    <quantity>3</quantity
    <unitPrice>2.95</unitPrice>
</orderItem>

The UpdateDocumentTableRow method begins with selecting each child tag of the <orderItem>. The name of the tag can then be used for two things. Selecting the right <w:customXml> tag in the document, and selecting the node in the XML data file. It`s then just a simple call to the method we created in the previous blog post.

foreach (XmlNode dataNode in dataContainerNode.ChildNodes)
{
  // create an xpath query to select the w;customXml node
  // for a single cell
  // "descendant::w:customXml[w:uri='myns' and w:element='productId']"
  string customXmlSelector =
    BuildCustomXMLSelectionQuery(
      dataNode.NamespaceURI,
      new string[] { dataNode.LocalName });
  // Create the xpath query to select the data item
  // "myNs:productId"
  string dataSelector = String.Format(
    "{0}:{1}",
    mgr.LookupPrefix(dataContainerNode.NamespaceURI),
    dataNode.LocalName);
  UpdateDocumentValue(
    dataSelector,
    customXmlSelector,
    dataContainerNode,
    customXmlRowNode);
}

One interesting note is that although the node in the XML data file with a piece of data for the cell is already available in the dataNode variable. However, the UpdateDocumentValue method expects to receive the container of that data element, and not that data element itself. So instead the right X-Path is calculated and the container is passed in for the update of the cell.

Now the last part that needs to be updated for it all to work is the UpdateDocumentValue method. Remember that last time I discussed how you must ensure that the right <p>-<r>-<t> structure is maintained? Same goes for the table cell updates. Since my approach throws away a part of the document to make it easier to maintain the hierarchy (I throw away the child elements of the <w:customXml> tag so you only need to look at your parents), you also need to recreate the <w:tc> table cell elements since they are thrown away. This is because the <w:customXml> tag for a table cell is the parent of the table cell itself, and all the children of the <w:customXml> tag are dropped (impacting formatting if you are not using table styles).

So to make it all work, the last part of the UpdateDocumentValue is updated. The gray part was already there, the last part is new.

if (wmlParentContainer.LocalName == "body")
{
  XmlNode paragraphNode = documentPart.OwnerDocument.CreateElement(
    "w", "p", XMLNS_W);
  paragraphNode.AppendChild(runNode);
  customXmlNode.AppendChild(paragraphNode);
}
// If there is already a paragraph, let's not add an extra one
else if(wmlParentContainer.LocalName == "p")
{
  customXmlNode.AppendChild(runNode);
}
else if (wmlParentContainer.LocalName == "tr")
{
  XmlNode cellNode =
    documentPart.OwnerDocument.CreateElement(
      "w", "tc", XMLNS_W);
  XmlNode paragraphNode =
    documentPart.OwnerDocument.CreateElement(
      "w", "p", XMLNS_W);
  cellNode.AppendChild(paragraphNode);
  paragraphNode.AppendChild(runNode);
  customXmlNode.AppendChild(cellNode);
}

That's it for the last part on updating a schema-mapped document with XML data. I hope it will help you build that new innovative solution that changes the world J

The code shown is in my DevDays 2008 download.

 

Broken ZIP download

I posted the code for my DevDays sessions earlier and someone kindly let me know that the download resulted in a broken zip. The issue was IIS sending the zip as a compressed stream instead of as an octet stream, and it was easily fixed after getting the right pointer. Thanks Leon!

VSTO 3.0 MSI-based Deployment WhitePaper is Published!

With a lot of great help of the Microsoft editors and the VSTO team the whitepaper on how to deploy a Visual Studio Tools for Office 3.0 solution using a Windows Installer package is now available online:

Part 1: http://msdn.microsoft.com/en-us/library/cc563937.aspx

Part 2: http://msdn.microsoft.com/en-us/library/cc616991.aspx

It's a two part thing, given the 50 somewhat pages that Ted and I have written. I hope it helps you create installers for your VSTO 3.0 solutions! Any feedback is of course appreciated. Just send me a mail or fill in the contact form.

Embedding simple values in a schema-mapped document

In my last blog post "Introduction to data and schema-mapped documents" I introduced the way <w:customXml> tags can be used to overlay business semantics on a document and what benefits this model provides compared to using data-bound content-controls (mainly the contextual hierarchy of tags and a way to identify tables). In this second blog post on the subject I will start building the foundations for a data-embedding tool which can act quite generically for embedding XML-based business data into a schema-mapped document. As you might expect the data should follow the mapped schema. I will split the details on how to embed the data in two. This blog post will cover how to embed a simple value and my next one will cover mapped tables, which can reuse some of the code for the simple value updates (since a table is made up of rows with simple values).

To map data to a document you can go two main routes. Either the document mapping is leading, or the data. You either start parsing the document to find <w:customXml> tags and then find the data for each tag, or you start parsing the data, and try to find a <w:customXml> tag for that data. For instance, if you have some repeating data which is mapped to a non-repeating construct like a normal paragraph, what do you do? When the document is leading you would not notice it in your code since you see a non-repeating element, and the data will contain that row (and probably more than one, but who cares?). When the data is leading, you will see the multiple rows of data in the XML and also see that this data is not mapped to a repeating element in the document, in which case this would cause an error (where do you leave the rest of the data rows?)

In my sample, I made the business data leading, so the code will start parsing the data file, lookup the right <w:customXml> tag, or when the data is a repeating element, create the new <w:customXml> tags to accommodate the data. Taking this route does pose one slight difficulty. When the data is put inside the document, you need to ensure that you follow the paragraph-run-text hierarchy of a WordprocessingML document. E.g. you need to find out where the <w:customXml> tag which will store the data is placed inside the document. If it is inside a paragraph, you create a run, if it is within the body of the document, you create a paragraph and a run, and so on.

How to use the code

So, let's begin with the end in mind. Because the model is not entirely generic yet, there is one main method to drive the embedding of data called UpdateDocumentData, and from within it you will call updates to simple values, and later on updates for tables. An update for a single value is done by calling the UpdateDocumentValue method like you can observe in the following sample:

UpdateDocumentValue(
  "/iv:invoice/iv:customer/iv:name",
  BuildCustomXMLSelectionQuery(
    XMLNS_IV,
    new string[]
    {
      "invoice",
      "customer",
      "name"
    }),
  data.DocumentElement,
  document.DocumentElement);

There are four things you need to pass into the UpdateDocumentValue method, two X-Path expressions and two XmlNode objects. The first X-Path expression is the expression that selects the data inside the XML data file. The second parameter, which is the second X-Path expression, selects the <w:customXml> tag inside the body of the document. The third parameter passes in the XML data file to query against, and the last parameter does the same for the body of the WordprocessingML document.

Selecting the right <w:customXml> tags

Now the most interesting bit in this piece of code is the call to the BuildCustomXMLSelectionQuery method. In my previous blog post on this subject I showed how <w:customXml> tags can form a hierarchy:

<w:customXml w:uri="urn:mynamespace" w:element="myElement"
    <w:p
        <w:customXml w:uri="urn:mynamespace" w:element="mySubElement"
            <w:r
                <w:t>Data for myElement</w:t
            </w:r
        </w:customXml
    </w:p>
</w:customXml>

So in order to select the part of the WordprocessingML document we need to walk this hierarchy. As you can see the <w:customXml> tag has two parts, the @w:uri attribute defines the XML namespace of your data file, and the @w:element provides the name of the XML data element. To walk the hierarchy you need to have the combination of namespaces / element names to select each <w:customXml> tag in a hierarchy. My sample only supports the usage of a single XML namespace for the data file, so the first parameter is that namespace (using the XMLNS_IV constant in the project), and of course you will need to support the list of element names to select the right <w:customXML> tag. A call to BuildCustomXMLSelectionQuery for the previous sample would look like:

string xpath = BuildCustomXMLSelectionQuery(
  "urn:mynamespace",
  new string[]
  {
    "myElement",
    "mySubElement"
  });

The resulting X-Path expression will look like the following (note that I formatted the line a little to make it fit):

descendant::w:customXml[
@w:uri='urn:mynamespace' and @w:element='myElement']

/descendant::w:customXml[
@w:uri='urn:mynamespace' and @w:element='mySubElement']

The BuildCustomXMLSelectionQuery method is rather simple. You just need to format the string

"descendant::w:customXml[@w:uri='{0}' and @w:element='{1}']"

for each node name that is passed in to the method. Since you need to add the '/' character in between the formatted strings you need to have a helper function, which we will put into a Func<T, TReturn> delegate using a lambda (note that you can also use an explicit separate helper method, but that makes the code more verbose since there are more methods)

static string BuildCustomXMLSelectionQuery(
  string xmlNamespace,
  string[] nodes)
{
  Func<string, string> buildMethod =
    name => String.Format(
      "descendant::w:customXml[@w:uri='{0}' and @w:element='{1}']",
      xmlNamespace, name);
  // more code to go here
}

In the place where the is displayed, more code is used J. The following block was missing in that spot. It's the code that does the heavy-lifting for building the X-Path.

StringBuilder xpath = new StringBuilder();
string nodeName = nodes[0];
xpath.Append(buildMethod(nodeName));
for (int i = 1; i < nodes.Length; i++)
{
  xpath.Append("/");
  xpath.Append(buildMethod(nodes[i]));
}
return xpath.ToString();

Updating the value in the document

Now we know what to do, and we know how to do it. Without further ado, let's go ahead and do it. All the following blocks of code go inside the UpdateDocumentValue method, you know, the one with the four parameters, two xpaths + two node objects.

static void UpdateDocumentValue(
string dataSelector, // xpath query into data node
string customXmlSelector, // xpath query to select customXml tag
XmlNode dataPart, // the relevant part of the data to query against
XmlNode documentPart) // the part of the document containing the data
{
}

Inside the UpdateDocumentValue method we first need to select the data value, by executing the XPath that is passed in to the method.

XmlNode dataNode = dataPart.SelectSingleNode(
  dataSelector, mgr);

Easy enough, same for the getting the <w:customXml> tag.

XmlNode customXmlNode = documentPart.SelectSingleNode(

customXmlSelector, mgr);

The next step is building a part of the paragraph-run-text hierarchy used by WordprocessingML. The UpdateDocumentValue method cheats a little, by not looking down into the WML hierarchy, but only upwards toward the root. That way we know for sure that we need to create the <w:r> and <w:t> tags ourselves.

XmlNode runNode =
  documentPart.OwnerDocument.CreateElement("w", "r", XMLNS_W);
XmlNode textNode =
  documentPart.OwnerDocument.CreateElement("w", "t", XMLNS_W);
textNode.InnerText = dataNode.InnerText;
runNode.AppendChild(textNode);

To ensure that our content will fit snugly into the WordprocessingML markup, we clear all the content of the <w:customXml> tag. Note that this does also throw away any special formatting that you did. So for a more real-life sample you would need to more complex things, but I think you'll find that this will work well enough for many of your situations.

ClearChildNodes(customXmlNode);

We do need to know if the <w:customXml> is inside a paragraph or inside the document body. If it is inside the document body tag (<w:body>) we need to add an extra paragraph ourselves.

XmlNode wmlParentContainer = FindContainerNode(customXmlNode);
if (wmlParentContainer.LocalName == "body")
{
  XmlNode paragraphNode =
    documentPart.OwnerDocument.CreateElement(
      "w", "p", XMLNS_W);
  paragraphNode.AppendChild(runNode);
  customXmlNode.AppendChild(paragraphNode);
}
else if(wmlParentContainer.LocalName == "p")
{
  customXmlNode.AppendChild(runNode);
}

That is it, and that is that. You can now update simple values into a schema-mapped document by calling a single simple method. In my next blog post I will show how to do the same with tables, and I will post the code for you (note that this was also one of my devdays samples, so you can pick it out of that download too if you want to).

Hope it helps!

 

 

Introduction to data and schema-mapped documents

Like I mentioned in my previous post I wanted to discuss a common requirement, a way to embed and extract business data from an office document. In my session at the DevDays 2008 I mainly focused on how to do it with a WordprocessingML document which has a schema applied to it, and I'll also focus on this particular way of doing things in the next few posts. You can also use data-bound content-controls or bookmarks perhaps. The downside is that it will be hard to do it generically. The code for the schema-mapped document can be implemented in a generic fashion, so the code will easy translate to your particular situation.

The first thing I'd like to discuss a little is how the schema mapping looks in Open XML. It is actually quite easy. There is one tag <w:customXml> which is used through-out for mapping document content such as paragraphs or tables. What you are basically doing is overlaying your own XML structure over the document content. Using this structure you can then take a piece of XML data which follows that structure and apply that to the document. You can also go the other way around and extract XML data from the document, which will conform to the structure that you have defined. Note that you are not allowed to embed arbitrary XML directly into an Open XML document. You can only use <w:customXml> to define the custom XML overlay. Allowing arbitrary XML breaks validation.

The <w:customXml> tag has two important attributes; element and uri. Using this you can define the name of an XML data element, since both the element name and namespace can be combined to form a fully qualified element name. So a basic <w:customXml> tag would look like this:

<w:customXml w:uri="urn:mynamespace" w:element="myElement"/>

To use the custom XML element inside a document you wrap it around the element that will provide the content, such as a paragraph:

<w:customXml w:uri="urn:mynamespace" w:element="myElement">

    <w:p>

        <w:r>

            <w:t>Data for myElement</w:t>

        </w:r>

    </w:p>

</w:customXml>

Which translates to:

<myElement xmlns="urn:mynamespace">

    Data for myElement

</myElement>

Another thing that is important, especially compared to data-bound content controls, is that you can form a hierarchy of these <w:customXml> tags, which translates to the hierarchy of your XML data.

<w:customXml w:uri="urn:mynamespace" w:element="myElement">

    <w:customXml w:uri="urn:mynamespace" w:element="mySubElement">

        <w:p>

            <w:r>

                <w:t>Data for myElement</w:t>

            </w:r>

        </w:p>

    </w:customXml>

</w:customXml>

Which you can extract as:

<myElement xmlns="urn:mynamespace">

    <mySubElement>

        Data for myElement

    </mySubElement>

</myElement>

There are five types of content that you can wrap with a <w:customXml> tag.

  • Paragraphs
  • Runs
  • Tables
  • Table Rows
  • Table Cells

This is an important consideration. Since you can wrap a table, row and cell, you can provide a hierarchical model to the data which can easily be calculated on the fly. Take the following XML, it can be easily used as an data overlay for a table:

<items> <!-- the table -->

    <item> <!-- a row -->

        <tag>A</tag>

        <value>123</value>

    </item>

    <item> <!-- a second row -->

        <tag>B</tag>

        <value>456</value>

    </item>

</items>

Since you can find out through code that the <items> tag is applied to a table, you can then create enough rows in the table to accommodate each item. This information is not present for data-bound content-controls, making a generic approach to binding data to a document more complex. All in all this is an area of improvement in Open XML, or the office application generating it. In both the schema-mapped document and the data-bound content-control approach you need to know about how Open XML works. This is because for both models you will need to add new rows to a table to accommodate each row of data, the actual amount of rows not known until runtime. The coolest would be that you would just be able to say "here's the data in XML form, just apply it yourself pls…", but that is not possible at the moment. So for the time being, you will need to add new rows yourself. Using a little knowledge found in the documentation and a little cheating (not maintaining 100% fidelity when not necessary) it is not that hard.

To close off this first post on this subject, why is it hard to map a data file using data-bound content controls generically? Since there is no explicit hierarchy in the controls, each control is bound separately to a data file, independent of any other control. The binding takes place using an X-Path expression, for instance:

/invoice/items/item[1]/group[2]/someElement

This will make it a little harder to find out which element should be repeated when used in a table for instance, however, it's not impossible of course.. J

In my next post I'll cover how to embed data in a document using schema-mapping.

1 - 10 Next

 Projects

Databinding toolkit for Word 2007Use SHIFT+ENTER to open the menu (new window).
Open XML Activities for Windows Workflow FoundationUse SHIFT+ENTER to open the menu (new window).
Package ExplorerUse SHIFT+ENTER to open the menu (new window).
Windows SharePoint Services 3 Workflow DesignersUse SHIFT+ENTER to open the menu (new window).
Word 2007 Source ViewUse SHIFT+ENTER to open the menu (new window).