The Engine Room
The engine room is a place for people to contribute ideas on the development of Sharepoint, Visual Studio and information technology.

Creating Invoice Reports using Open XML Format

Recently I was responsible for generating an Invoice report using Open XML format of Office 2007. The report was developed using Word 2007, Word 2007 Content Control Toolkit, VS.Net 2005 (.Net 3.0), and SQL Server 2005. Most of the data was dynamically added to the invoice report using content controls and XML data manipulation.

This was part of a migration exercise with one of our internal applications where we replaced all of our Active Reports reports to SQL Server Reporting Services (SSRS) except one. That report was created in Word 2007.

One of the reasons for using Open XML format for this report is that the SSRS didn't provide us with enough flexibility of using custom fonts and formatting the data. Another reason was to avoid upgrading license fee for Active Reports for .Net 2.0 (which I believe should be free).
If you like an introduction to the new Open XML format in Office 2007 then please read this article.

Content controls
Content controls were introduced in Word 2007 to give developers the flexibility of creating editable placeholders. These content controls can be mapped to the data in Word's Custom XML file for pre-populating the document with dynamic data. There are different types of content controls available in Word 2007 like Text Box (including Rich Text), Picture, Combo and Drop down list, Calendar and ActiveX controls. These controls provide us with enough flexibility for generating a basic Word document dynamically. You can find out more about adding content controls to a Word 2007 document here.
This image shows the design view of a word document with few Content controls.

Content Control Toolkit
I came across this very handy tool called Content Control Toolkit which enabled me to map the content controls to the fields in Custom XML files of Word 2007 as easy as drag and drop. You can read more about this toolkit here. This toolkit does not require Word 2007 to be installed on your machine because it operates directly on the new Open XML file format using System.IO.Packaging library available in .Net 3.0 (you just need to have .Net 3.0 installed on the server). But it is recommended that you have Word 2007 installed to see any changes you make to the document.
The System.IO.Packaging library available in .Net 3.0 is very useful for manipulating the data in any Office 2007 documents. You can use the package and packagepart objects to access the XML documents in the Word 2007.

The Process
The process of generating the invoice report involved using content controls and manipulating the XML data. A template was created with all the content controls placed in a desired format. A copy of this template was used every time a new invoice report was generated.
The content controls were used for non-repeating fields like Client Name, Invoice Amount, and Invoice Number etc. You can find out more about adding content controls and mapping them to the custom XML file here. The XML data in custom XML file was then updated with the respective data retrieved from the database.
One of the challenges we faced during the process was that Word 2007 doesn't have repeating content controls. As a work around we used data manipulation method for repeating contents like line items. A table was added to the template. The table had one row for line items with some placeholder text in each cell. The row was then programmatically accessed by looping through the tables in the document and searching for the row containing the placeholder string.
Before you even start coding you need to

  • Prepare an invoice template in Word 2007
  • Add content controls to a Word 2007
  • Add a table for invoice line items
  • Map the content controls to the custom XML, preferably using content control toolkit
Once you've done that, you basically apply the following logic to create an invoice report programmatically
  • Make a copy of the invoice template and open it using Package object
  • Access the document's XML files using PackagePart object
  • Create line items by adding rows to the line item table
  • Update the document's Custom XML file to pre-populate the data in content controls
  • Save the changes made to all XML files

The following is a brief step by step guide on how to create an invoice report. I am not saying this is the only way to do it but this is what worked for me. Please note that I've included only the important steps and have left out easy tasks for you to figure out.

Opening a Word document
The document can be opened by using the Package object. A package is a collection of package parts consisting of XML files (like document.xml, item1.xml etc).
Open the Word document from a specified path sFileName file as below
// Given a file name, retrieve the officeDocument part.
const string sDocumentRelationshipType = "http://schemas.openxmlformats.org/
officeDocument/2006/relationships/officeDocument";
// Open the package with read/write access.
using (Package pkgDocPackage = Package.Open(sFileName, FileMode.Open, FileAccess.ReadWrite))
{
// Get the main document part (workbook.xml, document.xml, presentation.xml).
foreach (System.IO.Packaging.PackageRelationship rlsRelationship in pkgDocPackage.GetRelationshipsByType
(sDocumentRelationshipType))

{
//Do the document formatting fisrt (in this case populate the line items)
//Do the custom xml formatting (in this case replace the custom xml with invoice details)
} }

Accessing XML files
Once the Word document is opened, the XML files in the document structure can then be accessed and manipulated by instantiating a Package Part object. A Package Part can be instantiated using the URI stored in the PackageRelationship object.
Load the content of document.xml into an XmlDocument object as below
// Get the Package Part for this document
Uri uMainDocumentUri = PackUriHelper.ResolvePartUri(new Uri("/", UriKind.Relative), rlsRelationship.TargetUri);
PackagePart ppDocPkgPart = pkgDocPackage.GetPart(uMainDocumentUri);
// Load the xml to a XmlDocument by streaming the package part
XmlDocument xmlMainXMLDoc = new XmlDocument();
xmlMainXMLDoc.Load(ppDocPkgPart.GetStream(FileMode.Open, FileAccess.Read));
Make sure that you use the appropriate URI path for the files you are trying to access. For example, use new Uri("/customXML/item.xml", UriKind.Relative) if you are trying to access any of the custom XML files. A "/" points to the document.xml file.
Assign namespaces to the xml document as below
XmlNamespaceManager nsmgr = CreateNamespaceManager(xmlMainXMLDoc);

Namespaces
There are bunch of namespaces that you will be using while accessing the Open XML format of typical Word 2007 document. Below are some of the namespaces I had used.
public XmlNamespaceManager CreateNamespaceManager(XmlDocument xmlDoc)
{
// Define all the namespaces
XmlNamespaceManager nmNamespaceMgr = new XmlNamespaceManager(xmlDoc.NameTable);
nmNamespaceMgr.AddNamespace("ve", "http://schemas.openxmlformats.org/
markup-compatibility/2006"
);

nmNamespaceMgr.AddNamespace("o", "urn:schemas-microsoft-com:office:office");
nmNamespaceMgr.AddNamespace("r", "http://schemas.openxmlformats.org/
officeDocument/2006/relationships"
);

nmNamespaceMgr.AddNamespace("m", "http://schemas.openxmlformats.org/
officeDocument/2006/math"
);

nmNamespaceMgr.AddNamespace("v", "urn:schemas-microsoft-com:vml");
nmNamespaceMgr.AddNamespace("wp", "http://schemas.openxmlformats.org/
drawingml/2006/wordprocessingDrawing"
);

nmNamespaceMgr.AddNamespace("w10", "urn:schemas-microsoft-com:office:word");
nmNamespaceMgr.AddNamespace("w", "http://schemas.openxmlformats.org/
wordprocessingml/2006/main"
);

nmNamespaceMgr.AddNamespace("ds", "http://schemas.openxmlformats.org/
officeDocument/2006/customXml"
);

nmNamespaceMgr.AddNamespace("wne", "http://schemas.microsoft.com/
office/word/2006/wordml"
);

nmNamespaceMgr.AddNamespace("vt", "http://schemas.openxmlformats.org/
officeDocument/2006/docPropsVTypes"
);

nmNamespaceMgr.AddNamespace("", "http://schemas.openxmlformats.org/
officeDocument/2006/extended-properties"
);

/* These namespaces are not required however can be added if required at a later stage.
nsNamespaceMgr.AddNamespace("o12", "http://schemas.microsoft.com/
office/2004/7/core");

nsNamespaceMgr.AddNamespace("m", "http://schemas.microsoft.com/
office/omml/2004/12/core");

nsNamespaceMgr.AddNamespace("wp", "http://schemas.openxmlformats.org/
drawingml/2006/3/wordprocessingDrawing");

nsNamespaceMgr.AddNamespace("a", "http://schemas.openxmlformats.org/
drawingml/2006/3/main");

nsNamespaceMgr.AddNamespace("pic", "http://schemas.openxmlformats.org/
drawingml/2006/3/picture");
*/
return nmNamespaceMgr;
}

Create line items
The document template contains a table for line items. There is only one row of data for line items consisting of placeholder text in each cell. Now, programmatically find the row containing the placeholder string by looping through all the tables in the document.
// Looop thru all the rows in all tables until you find what you are looking for
foreach (XmlNode xnNode in xmlMainXMLDoc.SelectNodes("//w:body/w:tbl/w:tr", nsmgr))
{
// check if the current node is what you are after
if (xnNode.InnerText == "InvoiceLinePlaceholderText")
{
// if yes, Add line items to the document by passing the placeholder row node xnNode.
}
}
Note: Looping through each table is a work around for not having the ability to assign IDs to tables in Word 2007. This process is a bit expensive but not critical when you don't have a huge document.
Let's assume that we have loaded the data for invoice lines in a variable called xnRow.
Add invoice line items to the document by manipulating the data in the document.
//replace the existing text with line item data
xnNode.SelectNodes("w:tc/w:p/w:r/w:t", nmNamespaceMgr).Item(0).InnerText = xnRow.SelectSingleNode("@InvoiceLineDescription").InnerText;
xnNode.SelectNodes("w:tc/w:p/w:r/w:t", nmNamespaceMgr).Item(1).InnerText = xnRow.SelectSingleNode("@Amount").InnerText;
Note: For clarity purpose I haven't included that code that creates a copy of the placeholder node, if you have more than one line item. Make sure you do that. Loop through all line items and use the copy of the placeholder node and append it to the document.
Save the document.xml file
// Save the changes made to the xml document.
xmlMainXMLDoc.Save(ppDocPkgPart.GetStream(FileMode.Create, FileAccess.Write));

Content controls and custom XML
Use the custom XML files to manage the data in content controls. The custom XML files, if any, can be found under customXML folder of the document structure. Accessing these custom XML files is similar to that of any other document as explained above.
Let's assume that we have loaded the custom XML in a variable called xmlCustomXMLDoc of type XmlDocument. Replace the content in custom XML file with the actual data retrieved from the GetInvoiceReport() method.
// Get the invoice report xml and replace it with the current custom xml
XmlNode xnReport = GetInvoiceReport();
xmlCustomXMLDoc.SelectSingleNode("root").InnerText = "";
xmlCustomXMLDoc.AppendChild
(xmlCustomXMLDoc.OwnerDocument.ImportNode(xnReport, true));

Save the custom XML document as below
// Save the changes made to the xml document.

xmlCustomXMLDoc.Save(ppDocPkgPart.GetStream(FileMode.Create, FileAccess.Write));

Note: Word 2007 is inconsistent while mapping the content controls to custom XML files. For example, sometimes Word 2007 maps the content controls to /CustomXML/item2.xml file instead of /CustomXML/item1.xml. The work around is to use datastoreItem id of the custom XML to find the appropriate file. The datastoreitem id remains constant irrespective of the custom XML filename. I'll write more on this in another post.

Tools for Debugging JavaScript in Firefox.

One of the serious limitations of using IE when debugging in my opinion is the web developer's toolbar. It simply cannot compete with the functionality that the Firefox add-on Web developer introduces. The toolbar allows you to disable features on a web page, such as caching, Java, JavaScript, redirects, colours and more. I also find it helpful for terminating state, setting specific window sizes and viewing source on pop-up's in an external editor... Coupled with Firebug (another Firefox developer's tool) it also makes tedious JavaScript errors A LOT easier to define and fix, giving meaningful error messages and code pointers rather than vague ramblings IE generates.

To enable Firefox debugging is pretty simple. Firstly I would suggest downloading the latest add-on's such as Web Developer and Firebug.


Then to setup VS to enable Firefox debugging or any other browser for that matter follow these simple steps:

Select your application in the solution explorer and go File | Browse with..

You then need to add your browser:


Once added you select Firefox and set as default. You can then debug using Firefox in exactly the same manner you would using IE except you get all the added functionality of the Firefox add-ons.

For example when debugging using Firefox a misspelled function call generates the following error in firebug:



It should be mentioned also you can view the source of a popup in an external editor whereas the following example debugging using IE provides you with a rather meaningless error message and doesn't allow you to view the source:



Gadgeting!

One of the cool features in Vista is gadgets. Gadgets are mini applications with lightweight UI and functionality that can provide a lot of usefulness to any user.

The gadgets can be found on the Vista’s sidebar. To create a gadget one simply use HTML and CSS for presentation and, JavaScript/VbScript and gadget object model for providing the functionality.

Sophisticated gadgets can be developed using activex controls and also using the Windows Presentation Framework.  

Good starting link for understanding and creating gadgets:

Gadget overview and object model

Caching using HttpRuntime.Cache

I was recently working on a project where we had a search field that would filter a list of codes as you type each character. There were two separate lists of codes on two pages that both had a search field. The search functionality worked by firing an AJAX call to retrieve and display search results on every key press in the search field. We were worried about the potential performance problems caused by executing a separate SQL query on every key press for every session using the application. Our solution was to cache both lists of codes in memory on the server and query this list using XPath to find codes where the start of the code matches the search text field.

For caching I looked into using HttpContext.Current.Cache but after reading other blogs I found that caching using HttpContext uses HttpRuntime.Cache to do the actual caching. The advantage of using HttpRuntime directly is that it is always available, for example, in Console applications and in Unit tests.

Using HttpRuntime.Cache is simple. Objects can be stored in the cache and are indexed by a string. Along with a key and the object to cache the other important parameter is the expiry time. This parameter sets the time before the object is dropped from the cache. There are other options you can use with the add method, but I have kept it simple for this example.

Below is a statement to cache the xml document xmlCodes with the key “akey”. I have set the expiry time to one minute in the future.

HttpRuntime.Cache.Add(“akey”, xmlCodes, null,

      DateTime.UtcNow.AddMinutes(1.0),

      System.Web.Caching.Cache.NoSlidingExpiration,

      System.Web.Caching.CacheItemPriority.Normal, null);

 

The statement to access this cached document is even simpler.

xmlCodes = (XmlDocument) HttpRuntime.Cache.Get( “akey” );

 

Putting this together, I have created a method below that could be found in a business class. The method takes a string parameter which is used to retrieve a set of items from the database. The method first checks to see if an XmlDocument indexed by the parameter key is in the cache. If it is, it simply returns this object, if not it will have to grab it from the database. After it has retrieved the document from the database it then places it into the cache. If this method is called again within the next minute, it will get the object from the cache instead of the database.

 

public static XmlDocument GetStuff(string sKey) 

{

XmlDocument xmlCodes;

xmlCodes = (XmlDocument) HttpRuntime.Cache.Get( sKey );

if (xmlCodes == null)

{

      xmlCodes = SqlHelper.ExecuteXml(new dn("Nodes", "Node"), "Get_Stuff_From_Database", sKey);

      HttpRuntime.Cache.Add(sKey, xmlCodes, null,

      DateTime.UtcNow.AddMinutes(1.0),

      System.Web.Caching.Cache.NoSlidingExpiration,

      System.Web.Caching.CacheItemPriority.Normal, null);

}

return xmlCodes;

}

Generic TryParse Type Conversion

With web-based applications, it is often necessary to convert retrieved strings into dates, amounts, or quantities etc.  This generally requires the creation of numerous utility methods which parse strings into a variety of formats; for ie, ToDateTime(), ToInteger(), ToDouble().

.NET 2.0 introduced the TryParse() method, and implemented it as a class method on all primitive data types.  This allows us to call DateTime.TryParse(), as follows:

DateTime result;

      DateTime.TryParse(sDate, out result);

      return result;

The main advantages of the TryParse method include speedy performance (when compared to using the older parse()), as well as removing the necessity of exception handling in try / catch blocks.

Unfortunately, to implement this method for common usage, it is likely that utility classes offering conversion methods will simply be rewritten utilising TryParse, over older parsing methods; this will require custom conversion methods to be written for every conversion that is required, for ie: ToDateTime(), to Double().  This creates a high degree of code redundancy, as well as requiring customised conversion methods to be created for every type conversion that is required.  The alternative is to scrap any utility class, and simply inline the TryParse() method in the calling class.  Nevertheless, this still requires a degree of redundant, repetitive code to be written.

So, how do we utilise conversion methods which satisfy the following conditions:

-          Simple, one line, calling code syntax

-          Easily extendable parsing of newly required types

-          The least amount of redundant utility method code repetition

One solution, is to utilise generics and reflection to create a simple conversion method.  This conversion method may be placed in a utility class, and may be called using one simple line of code.  Likewise, it would be easily extensible, and would work with any primitive data type – as well as any user defined type which implements a tryParse() method.

Such a method could be constructed as follows:

 

Create a generic method, which takes an object to parse, and constrains the type parameter (T) to require data types (using the ‘where’ syntax of the generic method definition). 

using System.Reflection;

...

1     static public T ToType<T>(object sCurrent) where T : struct

2     {

 

 

Next, iterate through the class methods available on the type parameter, and return the first TryParse method available.  This uses reflection to retrieve the method, as a MethodInfo object.

 

3            try

4            {

 

5                int iNumberOfMethods = typeof(T).GetMethods().Length;

6                MethodInfo mTryParse = null;

              

7                int iCounter = 0;

8                for (iCounter = 0; iCounter <= iNumberOfMethods; iCounter++)

9               {

 

10                    MethodInfo mCurrent = (MethodInfo)typeof(T).GetMethods().GetValue(iCounter);

11                    if (mCurrent.Name.Equals("TryParse"))

12                    {

13                        mTryParse = mCurrent;

14                        break;

15                    }

 

16                }

 

 

 

Create a new object, setting its initial value to the default value of the type parameter (T).  In order to invoke the mTryParse method, you will need to create an object array of the arguments required for the TryParse() method.  Although the TryParse method requires the second parameter to be defined as out, the use of an object array ensures that the parameter is still accessible after the method invocation; the array of objects passes the parameters by reference – they are therefore available via their index in the object array (line 22). 

 

17                if (mTryParse != null)

18                {

 

19                    T result = default(T);

20                    object[] args = { sCurrent, result };

21                    mTryParse.Invoke(null, args);

22                    result = (T)args[1];

 

23                    return result;

 

24                }

 

25            }

 

27            catch { }

 

28            return default(T);

 

29     }

 

This method may be now be invoked by any calling code, supplying a valid type parameter; for ie:

DateTime result = UtilityClass.ToType<DateTime>(objectToParse);

 

Bool result = UtilityClass.ToType<Bool>(objectToParse);

 

Int result = UtilityClass.ToType<Int>(objectToParse);

 

 

The TryParse() method implemented on all primitive data types is often useful in the context of web applications.  When combined with the power of reflection and generics, we can maximise flexibility and extensibility, while minimising the code required in utility conversion methods.

How to restore a SQL 2005 Database in SQL 2000

The short version of the story is that you can't. If try to do it you will get the following error:

"Error 3169: The backed-up database has on-disk structure version 611. The server supports version 539 and cannot restore or upgrade this database. RESTORE FILELIST is terminating abnormaly"

 So the only way to fully restore a SQL 2005 DB in SQL 2000 is to follow 3 steps:

  1. Script and recreate the objects without foreign keys
  2. Transfer the data
  3. Recreate the foreign keys.

For full details on how to do it, check this MSDN forum post

XSLT and Templates

XSLT has two main styles of template that you can use. One is a named template and one is a match.

When I was new to XSLT I wasn’t sure which style I should be using.

 

So when should you be using a named template vs. a match template?

 

Syntax

 

A named template has the following syntax:

 

<xsl:template name=”MyTemplate”>   

…. All of the template code including any parameters …

</xsl:template>

 

And you call it with the following code:

 

<xsl:call-template name=”MyTemplate” />

 

A match template has the following syntax:

 

<xsl:template match=”NodeName” mode=”MyMode”>

      … All of the template code including any parameters …

</xsl:template>

 

And you call it with the following code:

 

<xsl:apply-templates select=”Nodes” mode=”MyMode” />

 

NOTE: The mode attribute is optional for both types of templates.

 

Discussion

 

For the new user the named template is very easy to understand and use. I fell into the trap of pretty much using this type of template exclusively. You also don’t need to know much XPath to be able to use this type of template. But although named templates have their place, a new user should take their time to understand how the match template style works.

 

The match template is not quite so easy to understand, but the flexibility of this style of template is far greater than a named template. To use a match template you first need to have a good understanding of XPath.

 

Match templates are all about the underlying XML document that you are using to get your data from. The template matches to a particular node in that XML and the select statement says which specific nodes you want to match to. Named templates are about templates that have nothing to do with XML Nodes. For example, a Header template for a website, or a Footer template. Or you might have a specific paragraph of text that you need to display in various parts of website.

 

Using Match Templates to display XML Data

 

The following are some examples of using match templates. For these examples I am using an underlying XML document that has a whole lot of nodes describing books.

 

Say I want to display the books. I would write a template to display the book.

 

<xsl:template match=”Book”>

      <!-- code to display the book such as -->

      <h3><xsl:value-of select=”Title” /></h3>

      <h4><xsl:value-of select=”Author /><h4>

      <p><xsl:value-of select =”Description” /><p>

</xsl:template>

 

I would then use the power of XPath to select the books from the xml data that I wanted to display.

If I wanted to display them all I would just write:

 

<xsl:apply-templates select=”//Book” />

 

But if I just wanted to select the books by a certain author I would use

 

<xsl:apply-templates select=”//Book[Author=’Authors Name’]” />

 

I can use XPath to select one particular book, a selection of books, the first 3 books, all the books etc. The possibilities are only limited to what you can do with XPath.

 

But what if you want to display novels one way and textbooks another way?

Easy. That is what the mode attribute is for.

 

You create two match templates, each matching a book XmlNode, but one having a mode of “Novels” and the other a mode of “Textbooks”.

 

<xsl:template match=”Book” mode=”Novels”>

      … code to display the book ….

</xsl:template>

 

And

 

<xsl:template match=”Book” mode=”Textbooks”>

      … different code to display the book ….

</xsl:template>

 

Now to display all the text books I would call

 

<xsl:apply-templates select=”//Book[BookType=’TextBook’]” mode=”Textbooks” />

 

Whereas to display a novel I would call

 

<xsl:apply-templates select=”//Book[Title=’Name of Book’]” mode=”Novels” />

 

Now what if you have XML describing Books, and you also had XML describing Videos but you wanted to display them on the page using the same template code as although the main node name was different (one node name being “Book” and the other node name being “Video”) they had the same describing nodes.

You can do that to.

 

A match attribute can match to more than one node through the use of “|”.

For example:

 

<xsl:template match=”Book|Video” mode=”General”>

      … the display code …

</xsl:template>

 

 

Conclusion

 

Whenever you are displaying data related to the underlying XML a match style template should be used. A named template should only be used when it doesn’t relate to a series of xml fields.

Using FOR XML PATH, a primer

/*

This is a a script to introduce the FOR XML PATH clause in Sql Server 2005 in a step by step manner.

I have often found people unfamiliar with the T-SQL are often thrown by the FOR XML PATH clause and what it does and so have written this

to the FOR XML PATH's basic elements.

 

Follow through this tutorial by running each statement and comparing with previous statements to see the effect of each change.

--these sql statments can be run on the AdventureWorks database in 2005

*/

 

--This statment brings back suppliers into XML with a default - <row> for each entry.

--Note: The syntax for creating an attribute is an '@' in the column name and a separate node is '/' in column name.

SELECT TOP 10 PERCENT

      pv.VendorID AS '@VendorID',

      pv.AccountNumber AS '@AccountNumber',

      pv.Name,

      pv.ActiveFlag AS 'Details/@ActiveFlag',

      pv.CreditRating AS 'Details/CreditRating',

      pv.PreferredVendorStatus AS 'Details/PreferredVendorStatus'

FROM

Purchasing.Vendor pv

FOR XML PATH

 

--As the xml is written out in a linear fashion, any attributes for a node must be written before the nodes.

--otherwise you get the error 'Attribute-centric column '[ColumnName]' must not come after a non-attribute-centric sibling in XML hierarchy in FOR XML PATH.'

--See the error when the following is run

SELECT TOP 10 PERCENT

      pv.VendorID AS '@VendorID',

      pv.AccountNumber AS '@AccountNumber',

      pv.Name,

      pv.CreditRating AS 'Details/CreditRating',

      pv.ActiveFlag AS 'Details/@ActiveFlag',

      pv.PreferredVendorStatus AS 'Details/PreferredVendorStatus'

FROM

Purchasing.Vendor pv

FOR XML PATH

 

 

--Nodes should be kept together,

--Otherwise they will appear twice in the list. See below

SELECT TOP 10 PERCENT

      pv.VendorID AS '@VendorID',

      pv.AccountNumber AS '@AccountNumber',

      pv.PreferredVendorStatus AS 'Details/PreferredVendorStatus',

      pv.Name,

      pv.ActiveFlag AS 'Details/@ActiveFlag',

      pv.CreditRating AS 'Details/CreditRating'

FROM

Purchasing.Vendor pv

FOR XML PATH

 

--This brings back vendors in <Vendor> nodes instead of <row> nodes

--Before: FOR xml path

--After: FOR xml path ('Vendor')

SELECT TOP 10 PERCENT

      pv.VendorID AS '@VendorID',

      pv.AccountNumber AS '@AccountNumber',

      pv.Name,

      pv.ActiveFlag AS 'Details/@ActiveFlag',

      pv.CreditRating AS 'Details/CreditRating',

      pv.PreferredVendorStatus AS 'Details/PreferredVendorStatus'

FROM

Purchasing.Vendor pv

FOR XML PATH('Vendor')

 

 

--This change wraps all nodes in a root <Vendors> node

--Before: FOR xml path ('Vendor')

--After: FOR xml path ('Vendor'), root('Vendors')

SELECT TOP 10 PERCENT

      pv.VendorID AS '@VendorID',

      pv.AccountNumber AS '@AccountNumber',

      pv.Name,

      pv.ActiveFlag AS 'Details/@ActiveFlag',

      pv.CreditRating AS 'Details/CreditRating',

      pv.PreferredVendorStatus AS 'Details/PreferredVendorStatus'

FROM

Purchasing.Vendor pv

FOR XML PATH('Vendor'), root('Vendors')

 

 

--This demonstrates adding products to the query the wrong way,

--involving a join.

--Note that the <Vendor> is repeated for each <Product>

SELECT TOP 10 PERCENT

      pv.VendorID AS '@VendorID',

      pv.AccountNumber AS '@AccountNumber',

      pv.Name,

      pv.ActiveFlag AS 'Details/@ActiveFlag',

      pv.CreditRating AS 'Details/CreditRating',

      pv.PreferredVendorStatus AS 'Details/PreferredVendorStatus',

      pp.ProductID AS 'Product/@ProductID',

      pp.Name AS 'Product/Name',

      pp.ProductNumber AS 'Product/ProductNumber'

FROM

Purchasing.Vendor pv

INNER JOIN

Purchasing.ProductVendor ppv ON ppv.VendorID = pv.VendorID

INNER JOIN

Production.Product pp ON pp.ProductID = ppv.ProductID

ORDER BY pv.VendorID, pp.ProductID

FOR XML PATH('Vendor')

 

--As repeating the <Vendor> node for each <Product> is far less than ideal,

--we can add products in a nested select.

SELECT TOP 10 PERCENT

      pv.VendorID AS '@VendorID',

      pv.AccountNumber AS '@AccountNumber',

      pv.Name,

      pv.ActiveFlag AS 'Details/@ActiveFlag',

      pv.CreditRating AS 'Details/CreditRating',

      pv.PreferredVendorStatus AS 'Details/PreferredVendorStatus',

      (

      SELECT

            pp.ProductID AS '@ProductID',

            pp.Name,

            pp.ProductNumber

      FROM 

      Purchasing.ProductVendor ppv

      INNER JOIN

      Production.Product pp ON pp.ProductID = ppv.ProductID

      WHERE

      ppv.VendorID = pv.VendorID   

      FOR XML PATH('Product')

      ) AS Products

FROM

Purchasing.Vendor pv

ORDER BY pv.VendorID

FOR XML PATH('Vendor')

 

--Eeeeuugh, but thats a nasty looking products node

--A little change will clear that up though.

--Before: FOR xml path('Product')

--After: FOR xml path('Product'), type

SELECT TOP 10 PERCENT

      pv.VendorID AS '@VendorID',

      pv.AccountNumber AS '@AccountNumber',

      pv.Name,

      pv.ActiveFlag AS 'Details/@ActiveFlag',

      pv.CreditRating AS 'Details/CreditRating',

      pv.PreferredVendorStatus AS 'Details/PreferredVendorStatus',

      (

      SELECT

            pp.ProductID AS '@ProductID',

            pp.Name,

            pp.ProductNumber

      FROM 

      Purchasing.ProductVendor ppv

      INNER JOIN

      Production.Product pp ON pp.ProductID = ppv.ProductID

      WHERE

      ppv.VendorID = pv.VendorID   

      FOR XML PATH('Product'), type

      ) AS Products

FROM

Purchasing.Vendor pv

ORDER BY pv.VendorID

FOR XML PATH('Vendor')

 

--We are now done with this little primer.

--I hope it has been useful to you.

 

Sreedhar, Thanks for your comment. I've uploaded another sample here that I hope will answer your question. Also check: http://msdn2.microsoft.com/en-us/library/ms189885.aspx