November 19, 2006

Reporting XSLT compilation errors in .NET

Reporting errors in XSLT stylesheets is a task that almost nobody gets done right. Including me - error reporting in nxslt sucks in a big way. Probably that's because I'm just lazy bastard. But also lets face it - XslCompiledTransform API doesn't help here.

Whenever there are XSLT loading (compilation) errors XslCompiledTransform.Load() method throws an XsltException containing description of the first error encountered by the compiler. But as a matter of fact internally XslCompiledTransform holds list of all errors and warnings (internal Errors property). It's just kept internal who knows why. Even Microsoft own products such as Visual Studio don't use this important information when reporting XSLT errors - Visual Studio's XML editor also displays only first error. That sucks.

Anyway here is a piece of code written by Anton Lapounov, one of the guys behind XslCompiledTransform. It shows how to use internal Errors list via reflection (just remember you would need FullTrust for that) to report all XSLT compilation errors and warnings. The code is in the public domain - feel free to use it.  I'm going to incorporate it into the next nxslt release. I'd modify it a little bit though - when for some reason (e.g. insufficient permissions) errors info isn't available you still have XsltException with at least first error info.

private void Run(string[] args) {
    XslCompiledTransform xslt = new XslCompiledTransform();
    try {
    catch (XsltException) {
        string errors = GetCompileErrors(xslt);
        if (errors == null) {
            // Failed to obtain list of compile errors

// True to output full file names, false to output user-friendly file names
private bool fullPaths = false;

// Cached value of Environment.CurrentDirectory
private string currentDir = null;

/// Returns user-friendly file name. First, it tries to obtain a file name
/// from the given uriString.
/// Then, if fullPaths == false, and the file name starts with the current
/// directory path, it removes that path from the file name.
private string GetFriendlyFileName(string uriString) {
    Uri uri;

    if (uriString == null ||
        uriString.Length == 0 ||
        !Uri.TryCreate(uriString, UriKind.Absolute, out uri) ||
    ) {
        return uriString;

    string fileName = uri.LocalPath;

    if (!fullPaths) {
        if (currentDir == null) {
            currentDir = Environment.CurrentDirectory;
            if (currentDir[currentDir.Length - 1] != Path.DirectorySeparatorChar) {
                currentDir += Path.DirectorySeparatorChar;

        if (fileName.StartsWith(currentDir, StringComparison.OrdinalIgnoreCase)) {
            fileName = fileName.Substring(currentDir.Length);

    return fileName;

private string GetCompileErrors(XslCompiledTransform xslt) {
    try {
        MethodInfo methErrors = typeof(XslCompiledTransform).GetMethod(
            "get_Errors", BindingFlags.NonPublic | BindingFlags.Instance);

        if (methErrors == null) {
            return null;

        CompilerErrorCollection errorColl = 
            (CompilerErrorCollection) methErrors.Invoke(xslt, null);
        StringBuilder sb = new StringBuilder();

        foreach (CompilerError error in errorColl) {
            sb.AppendFormat("{0}({1},{2}) : {3} {4}: {5}",
                error.IsWarning ? "warning" : "error",
        return sb.ToString();
    catch {
        // MethodAccessException or SecurityException may happen 
        //if we do not have enough permissions
        return null;

Feel the difference - here is nxslt2 output:

An error occurred while compiling stylesheet 'file:///D:/projects2005/Test22/Test22/test.xsl': 
System.Xml.Xsl.XslLoadException: Name cannot begin with the '1' character, hexadecimal value 0x31.

And here is Anton's code output:

test.xsl(11,5) : error : Name cannot begin with the '1' character, hexadecimal value 0x31.
test.xsl(12,5) : error : Name cannot begin with the '0' character, hexadecimal value 0x30.
test.xsl(13,5) : error : The empty string '' is not a valid name.
test.xsl(14,5) : error : The ':' character, hexadecimal value 0x3A, cannot be included in a name.
test.xsl(15,5) : error : Name cannot begin with the '-' character, hexadecimal value 0x2D.