Please take a look at the following code, as I get the error at this line:
xslt.Transform(mydoc.CreateReader(), writer);
Error:
Step into: Stepping over non-user code 'System.Xml.Linq.XNode.CreateReader' A first chance exception of type 'System.NullReferenceException' occurred in System.Data.SqlXml.dll
((System.NullReferenceException)(ex))
PromotionsDataContext db = new PromotionsDataContext();
//XmlDocument myxml;
XElement Categories =
new XElement("Promotions",
from b in db.GetPromotions()
select new XElement("Promotion",
new XElement("Category", b.CategoryName),
new XElement("Client", b.ClientName),
new XElement("Title", b.Title)));
XDocument mydoc = new XDocument();
mydoc.Add(Categories);
try
{
XDocument newTree = new XDocument();
XmlWriterSettings settings = new XmlWriterSettings();
settings.OmitXmlDeclaration = true;
settings.ConformanceLevel = ConformanceLevel.Fragment;
settings.CloseOutput = false;
//using (XmlWriter writer = newTree.CreateWriter())
using (XmlWriter writer = XmlWriter.Create(newTree.CreateWriter(), settings))
{
// Load the style sheet.
XslCompiledTransform xslt = new XslCompiledTransform();
xslt.Load(XmlReader.Create(new FileStream(@"C:\1\TransList.xslt", System.IO.FileMode.Open)));
// Execute the transform and output the results to a writer.
xslt.Transform(mydoc.CreateReader(), writer);
}
Console.WriteLine(newTree);
}
catch (Exception ex)
{
Console.Write(ex);
}
Here is the XSLT:
<xsl:stylesheet version='1.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>
<xsl:output method='xml' />
<xsl:key name='categories' match='Category' use='.' />
<xsl:template match='/'>
<xsl:for-each select="/Promotions/Promotion/Category[
generate-id(.) = generate-id(key('categories', .)[1])
]">
<xsl:variable name='cname' select='.' />
<Category title='{.}'>
<xsl:for-each select='/Promotions/Promotion[Category=$cname]'>
<Title>
<xsl:value-of select='Title' />
</Title>
</xsl:for-each>
</Category>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
The error:
System.NullReferenceException: Object reference not set to an instance of an object. at System.Xml.Xsl.Runtime.XmlMergeSequenceWriter.StartTree(XPathNodeType rootType, IXmlNamespaceResolver nsResolver, XmlNameTable nameTable) at System.Xml.Xsl.Runtime.XmlQueryOutput.StartTree(XPathNodeType rootType) at System.Xml.Xsl.Runtime.XmlQueryOutput.WriteStartRoot() at Root(XmlQueryRuntime {urn:schemas-microsoft-com:xslt-debug}runtime) at Execute(XmlQueryRuntime {urn:schemas-microsoft-com:xslt-debug}runtime) at System.Xml.Xsl.XmlILCommand.Execute(Object defaultDocument, XmlResolver dataSources, XsltArgumentList argumentList, XmlWriter writer, Boolean closeWriter) at System.Xml.Xsl.XmlILCommand.Execute(XmlReader contextDocument, XmlResolver dataSources, XsltArgumentList argumentList, XmlWriter results) at System.Xml.Xsl.XslCompiledTransform.Transform(XmlReader input, XsltArgumentList arguments, XmlWriter results) at Promo.Page_Load(Object sender, EventArgs e) in c:\1\promo.ascx.cs:line 144
Now if I do this it works:
StringWriter sw = new StringWriter();
xslt.Transform(mydoc.CreateReader(),null, sw);
What am I doing wrong with the XmlWriter?
Value of xdoc right before crash:
<Promotions>
<Promotion>
<Category>Arts & Entertainment</Category>
<Client>Client1</Client>
<Title>Get your Free 2</Title>
</Promotion>
<Promotion>
<Category>Arts & Entertainment</Category>
<Client>Client1</Client>
<Title>Get your Free 4</Title>
</Promotion>
<Promotion>
<Category>Arts & Entertainment</Category>
<Client>client1</Client>
<Title>Get your Free 5</Title>
</Promotion>
<Promotion>
<Category>Community & Neighborhood</Category>
<Client>Client2</Client>
<Title>Get your Free 1</Title>
</Promotion>
<Promotion>
<Category>Education</Category>
<Client>Client3</Client>
<Title>Get Your Free 3</Title>
</Promotion>
</Promotions>
I think the issue is indeed that you seem to want to create an XML fragment with your stylesheet and the XmlWriter while the LINQ to XML object model (i.e. System.Xml.Linq.XDocument/XNode) does not have any class representing fragments.
The code works flawlessly for me if I take your XML input (e.g.
<Promotions>
<Promotion>
<Category>Arts & Entertainment</Category>
<Client>Client1</Client>
<Title>Get your Free 2</Title>
</Promotion>
<Promotion>
<Category>Arts & Entertainment</Category>
<Client>Client1</Client>
<Title>Get your Free 4</Title>
</Promotion>
<Promotion>
<Category>Arts & Entertainment</Category>
<Client>client1</Client>
<Title>Get your Free 5</Title>
</Promotion>
<Promotion>
<Category>Community & Neighborhood</Category>
<Client>Client2</Client>
<Title>Get your Free 1</Title>
</Promotion>
<Promotion>
<Category>Education</Category>
<Client>Client3</Client>
<Title>Get Your Free 3</Title>
</Promotion>
</Promotions>
) and the second stylesheet posted (e.g.
<xsl:stylesheet version='1.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>
<xsl:output method='xml' />
<xsl:key name='categories' match='Category' use='.' />
<xsl:template match='/'>
<Categories>
<!-- Added a root element here -->
<xsl:for-each select="/Promotions/Promotion/Category[
generate-id(.) = generate-id(key('categories', .)[1])
]">
<xsl:variable name='cname' select='.' />
<Category title='{.}'>
<xsl:for-each select='/Promotions/Promotion[Category=$cname]'>
<Title>
<xsl:value-of select='Title' />
</Title>
</xsl:for-each>
</Category>
</xsl:for-each>
</Categories>
</xsl:template>
</xsl:stylesheet>
) and then use the following C# code:
XDocument mydoc = XDocument.Load(@"..\..\XMLFile1.xml");
XDocument newTree = new XDocument();
using (XmlWriter writer = newTree.CreateWriter())
{
XslCompiledTransform xslt = new XslCompiledTransform();
xslt.Load(@"..\..\XSLTFile2.xslt");
xslt.Transform(mydoc.CreateReader(), writer);
writer.Close();
}
Console.WriteLine(newTree);
When I use the original stylesheet (e.g.
<xsl:stylesheet version='1.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>
<xsl:output method='xml' />
<xsl:key name='categories' match='Category' use='.' />
<xsl:template match='/'>
<xsl:for-each select="/Promotions/Promotion/Category[
generate-id(.) = generate-id(key('categories', .)[1])
]">
<xsl:variable name='cname' select='.' />
<Category title='{.}'>
<xsl:for-each select='/Promotions/Promotion[Category=$cname]'>
<Title>
<xsl:value-of select='Title' />
</Title>
</xsl:for-each>
</Category>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
) I get an InvalidOperationException saying "Token StartElement in state EndRootElement would result in an invalid XML document. Make sure that the ConformanceLevel setting is set to ConformanceLevel.Fragment or ConformanceLevel.Auto if you want to write an XML fragment.". I suspect you tried to counter that by wrapping your XmlWriter into another one with ConformanceLevel.Fragment, as you have in your C# code, but that does not work I think, it only results in a different exception.
What should work in my view is to use CreateWriter() on an XElement instance as it should be possible to add a fragment to an XElement. My test however still throws an exception therefore I have filed a bug on that, see https://connect.microsoft.com/VisualStudio/feedback/details/530052/xslt-stylesheet-writing-an-xml-fragment-to-an-xmlwriter-created-with-xelementinstance-createwriter-causes-nullreferenceexception