tags:

views:

825

answers:

5

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'&gt;
  <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 &amp; Entertainment</Category>
    <Client>Client1</Client>
    <Title>Get your Free 2</Title>
  </Promotion>
  <Promotion>
    <Category>Arts &amp; Entertainment</Category>
    <Client>Client1</Client>
    <Title>Get your Free 4</Title>
  </Promotion>
  <Promotion>
    <Category>Arts &amp; Entertainment</Category>
    <Client>client1</Client>
    <Title>Get your Free 5</Title>
  </Promotion>
  <Promotion>
    <Category>Community &amp; 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>
A: 

Where did you define mydoc? I bet it's null.

John Saunders
No, i wish it was. I added the code I pop xdoc with to my question.
James Campbell
What happens if you use `new XDocument(new XDeclaration(...), new XElement("Promotions", ...))`? Remove the complexity of the `Add`. Does the document look right if you use `mydoc.ToString()`?
John Saunders
mydoc looks fine at the line it crashes on.
James Campbell
A: 

Did you mean to be creating an XML 2.0 document in your new XDeclaration("2.0","utf-8","true") code? I don't know if that's causing the problem, but it's certainly a strange thing to be doing, since XML 2.0 doesn't exist. Try removing it altogether; you shouldn't need it.

Jacob
Good catch, whether or not that's the problem.
John Saunders
I reverted to 1.0 and omitted it still same issue.
James Campbell
A: 

Because the XML is being built through LINQ to XML, the execution of this code may be deferred:

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)));

Your error is likely taking place in this code. Try placing a breakpoint in your LINQ to step through the XElement creation. Or, to ensure that the execution is not deferred, you could do the following:

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))
    ).ToList()
);
Jacob
If that was the case mydoc would be the issue and it doesn't seem to be as the error does not occur if I use a stringwriter, I will add the contents of the xdoc to question
James Campbell
A: 

It looks like your XSLT is producing an XML fragment containing a list of <Category> elements, rather than a full XML document. And you're trying to write the fragment to an empty XDocument. That would result in an invalid XML document, since you always need one root element in XML. I don't know if that's specifically what's causing your exception, but you should see what happens when you modify your XSLT to look like this:

<xsl:stylesheet version='1.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform'&gt;
  <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>
Jacob
Same error, using the xsl above.
James Campbell
When you tried it, did you also change the conformance level in your XmlWriterSettings or remove the `settings.ConformanceLevel = ConformanceLevel.Fragment;` line?
Jacob
+1  A: 

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 &amp; Entertainment</Category>
    <Client>Client1</Client>
    <Title>Get your Free 2</Title>
  </Promotion>
  <Promotion>
    <Category>Arts &amp; Entertainment</Category>
    <Client>Client1</Client>
    <Title>Get your Free 4</Title>
  </Promotion>
  <Promotion>
    <Category>Arts &amp; Entertainment</Category>
    <Client>client1</Client>
    <Title>Get your Free 5</Title>
  </Promotion>
  <Promotion>
    <Category>Community &amp; 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'&gt;
  <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'&gt;
  <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

Martin Honnen
thank you,this has been driving me crazy/
James Campbell