Juni 2010 - Einträge

XSD.exe /c - Klassengenerierung richtig gemacht
Samstag, 26. Juni 2010 15:07
Einleitung:
XSD.exe hat so seine Tücken, ich glaube das zeigt auch der große Unmut, der im Internet zu finden ist. Dabei muss das doch nicht sein Big Smile

Ich habe festgestellt, dass wenn man XSD verwenden möchte, dass es sinnvoll ist den Berg zum Propheten zu tragen. Im Folgenden erkläre ich, was das Problem eigentlich so dabei ist, und wie man das geschickt umschiffen kann.

Problembeschreibung:
XSD generiert lediglich aus den Teilen Klassen und Felder, von denen es denkt, dass diese zu gebrauchen sind. Dabei beachtet es nur die Elemente in den Schemas, Complex- und SimpleTypes für sich betrachtet lässt es außen vor.

Beispiel:
<xs:schema targetNamespace="http://MyTestSchemas/XMLSchema.xsd" elementFormDefault="qualified" xmlns="http://MyTestSchemas/XMLSchema.xsd" xmlns:mstns="http://tempuri.org/XMLSchema.xsd" xmlns:xs="http://www.w3.org/2001/XMLSchema">
  
  <!-- Ein SimpleElement, das ebenfalls in der Klassengeneration beachtet werden soll -->
  <xs:simpleType name="SimpleRootElement">
    <xs:restriction base="xs:string">
      <xs:enumeration value="TestVal1" />
      <xs:enumeration value="TestVal2" />
    </xs:restriction>
  </xs:simpleType>

</xs:schema>

Hier meldet XSD die Fehlermeldung
Warning: cannot generate classes because no top-level elements with complex type were found.

Genauso sieht es aus, wenn das Schema so aussehen würde:
<?xml version="1.0" encoding="utf-8"?>
<xs:schema targetNamespace="http://MyTestSchemas/XMLSchema.xsd" elementFormDefault="qualified" xmlns="http://MyTestSchemas/XMLSchema.xsd" xmlns:mstns="http://tempuri.org/XMLSchema.xsd" xmlns:xs="http://www.w3.org/2001/XMLSchema">
  
  <!-- Ein normales Element, das kein ComplexType verwendet -->
  <xs:element name="CommonRootElement" type="xs:string" />

</xs:schema>


Und wiederum folgendes Schema, das ebenfalls die o.g. Fehlermeldung provoziert:
<xs:schema targetNamespace="http://MyTestSchemas/XMLSchema.xsd" elementFormDefault="qualified" xmlns="http://MyTestSchemas/XMLSchema.xsd" xmlns:mstns="http://tempuri.org/XMLSchema.xsd" xmlns:xs="http://www.w3.org/2001/XMLSchema">
  
  <!-- Ein normales Element, das kein ComplexType verwendet -->
  <xs:element name="CommonRootElement" type="xs:string" />
  
  <!-- Ein ComplexType, aus dem eine Klasse generiert werden soll -->
  <xs:complexType name="ComplexTypeWhichIsReferenced">
    <xs:sequence>
      <xs:element name="CommonInnerElement1" type="xs:string" />
    </xs:sequence>
  </xs:complexType>

</xs:schema>


Was will das "doofe" XSD denn überhaupt, warum erzeugt es mir aus dem Schema keine Klassen?

Problemerklärung:
XSD will zwingend ein ElementType, das im Root des Xml-Baumes des Schemas liegt. Weiterhin will es zwingend mindestens einen ComplexType definiert wissen, welches von solch einem Root-Element referenziert wird. Also folgendes Schema ist als Basis für XSD ausreichend, so dass es mit einer Klassengenerierung beginnt:

<xs:schema targetNamespace="http://MyTestSchemas/XMLSchema.xsd" elementFormDefault="qualified" xmlns="http://MyTestSchemas/XMLSchema.xsd" xmlns:mstns="http://tempuri.org/XMLSchema.xsd" xmlns:xs="http://www.w3.org/2001/XMLSchema">
  
  <!-- Ein weiterer ComplexType, aus dem eine Klasse generiert werden soll 
       Hier gibt es allerdings ein Top level element, das auf den komplexen Typ 'ComplexTypeWhichIsReferenced2' veweist-->
  <xs:element name="TestElement2" type="ComplexTypeWhichIsReferenced2" />
  <xs:complexType name="ComplexTypeWhichIsReferenced2">
    <xs:sequence>
      <xs:element name="CommonInnerElement2" type="xs:string" />
    </xs:sequence>
  </xs:complexType>

</xs:schema>


Ergebnis (Kommentare und Attribute entfernt):
public partial class ComplexTypeWhichIsReferenced2 {
    
    private string commonInnerElement2Field;
    
    /// <remarks/>
    public string CommonInnerElement2 {
        get {
            return this.commonInnerElement2Field;
        }
        set {
            this.commonInnerElement2Field = value;
        }
    }
}


Na das ist doch soweit super, oder? Jetzt hat XSD endlich ein Ergebnis produziert, das auch einem prüfenden Blick Stand hält.
Aber...

Erweiterte Problembeschreibung:
Wenn jetzt Kombinationen aus den oben genannten Schemas und deren Fehlermeldungen mit dem funktionierenden Beispiel zusammenspielen, dann wird es gefährlich. Denn dann wird XSD keine Fehlermeldung ausspucken und aber nur einen Teil für die Klassengenerierung heranziehen. Das ist denke ich das größte Manko, das diesem Tool angelastet wird. So denkt der Benutzer, dass das Tool eine gewisse undurchschaubare Eigenintelligenz verwendet.

Ich habe hier einmal ein kleines Schema entwickelt, das sehr viele Probleme auf einmal betrachtet:
<xs:schema targetNamespace="http://MyTestSchemas/XMLSchema.xsd" elementFormDefault="qualified" xmlns="http://MyTestSchemas/XMLSchema.xsd" xmlns:mstns="http://tempuri.org/XMLSchema.xsd" xmlns:xs="http://www.w3.org/2001/XMLSchema">
  
  <!-- Ein SimpleElement, das ebenfalls in der Klassengeneration beachtet werden soll -->
  <xs:simpleType name="SimpleRootElement">
    <xs:restriction base="xs:string">
      <xs:enumeration value="TestVal1" />
      <xs:enumeration value="TestVal2" />
    </xs:restriction>
  </xs:simpleType>
  
  <!-- Ein normales Element, das kein ComplexType verwendet -->
  <xs:element name="CommonRootElement" type="xs:string" />
  
  <!-- Ein ComplexType, aus dem eine Klasse generiert werden soll -->
  <xs:complexType name="ComplexTypeWhichIsReferenced">
    <xs:sequence>
      <xs:element name="CommonInnerElement1" type="xs:string" />
    </xs:sequence>
  </xs:complexType>
  
  <!-- Ein weiterer ComplexType, aus dem eine Klasse generiert werden soll 
       Hier gibt es allerdings ein Top level element, das auf den komplexen Typ 'ComplexTypeWhichIsReferenced2' veweist-->
  <xs:element name="TestElement2" type="ComplexTypeWhichIsReferenced2" />
  <xs:complexType name="ComplexTypeWhichIsReferenced2">
    <xs:sequence>
      <xs:element name="CommonInnerElement2" type="xs:string" />
    </xs:sequence>
  </xs:complexType>
  
  <!-- Ein weiterer ComplexType, aus dem eine Klasse generiert werden soll 
       Hier gibt es ein inneres Element, das auf den komplexen Typ 'ComplexTypeWhichIsReferenced' veweist-->
  <xs:complexType name="TestInnerLink">
    <xs:sequence>
      <xs:element name="ComplexInnerElement" type="ComplexTypeWhichIsReferenced" />
    </xs:sequence>
  </xs:complexType>
</xs:schema>


Wenn man das so mittels XSD durch den Klassengenerator jagt, dann entsteht folgender C# Code:
public partial class ComplexTypeWhichIsReferenced2 {
    
    private string commonInnerElement2Field;
    
    /// <remarks/>
    public string CommonInnerElement2 {
        get {
            return this.commonInnerElement2Field;
        }
        set {
            this.commonInnerElement2Field = value;
        }
    }
}


Was unschwer zu erkennen ist: Das ist genau das Gleiche, wie in dem oben genannten funktionierenden Minischema. Also hier hat XSD alles andere als das TestElement2 ignoriert!

Lösung:
Die Lösung ist aus der oben erwähnten Fehlermeldung heraus begründet. XSD betrachtet nur den Inhalt zur Klassengenerierung, welcher als Root-Element auf andere Complex-Typen verweist.
Das könnte man nun manuell machen, oder es automatisiert anwenden. Ich habe hier ein kleines Tool geschrieben, das die notwendigen Elemente einfach dem Schema hinzufügt. Die Logik im Schema ändert sich quasi nicht, aber XSD ist glücklich und das Ergebnis sieht sehr viel besser aus.
Das Tool macht von der Logik her folgendes (Codeausschnitt):
        /// <summary>
        /// Methode, die ein XML Schema erweitert, so dass keine ClassType ohne Element existieren,
        /// das darauf verweist.
        /// </summary>
        /// <remarks>Verhindert die Warnung:
        /// cannot generate classes because no top-level elements with complex type were found.</remarks>
        /// <param name="xsdFullFilename">Der voll qualifizierte Dateiname zur XSD Schemadatei.</param>
        /// <param name="throwError">Definiert, ob im Fehlerfall eine Exception geworfen werden soll.</param>
        /// <returns>True, wenn die Erweiterung der Schemadatei erfolgreich war, andernfalls False.</returns>
        public static bool GenerateDummyElementsForClassTypes(string xsdFullFilename, bool throwError)
        {
            XmlDocument schemaDoc = null;
            bool retVal = false;
            List<string> rootElements = new List<string>();
            Dictionary<string, string> rootElementsToAddToSchema = new Dictionary<string, string>();

            try
            {
                // Überprüfung, ob Schemadatei vorhanden
                if (!System.IO.File.Exists(xsdFullFilename))
                {
                    throw new System.IO.FileNotFoundException(String.Format("Konnte die Datei '{0}' nicht finden!", xsdFullFilename));
                }

                // Versuch das Schema in ein XmlDokument zu laden
                schemaDoc = new XmlDocument();
                schemaDoc.Load(xsdFullFilename);

                // Alle Root-Elements herausfinden
                ExtractAllElements(schemaDoc, rootElements);

                // Alle Root-ComplexTypes herausfinden
                ExtractAllClassTypes(schemaDoc, rootElements, rootElementsToAddToSchema);

                // Alle vorgemerkten Root-Elements dem Schema hinzufügen
                AddNecessaryRootElementsToSchemafile(xsdFullFilename, schemaDoc, rootElementsToAddToSchema);

                retVal = true;
            }
            catch
            {
                // wenn gewünscht, dann Fehler schmeißen, ansonsten nur den
                // Rückgabewert auf "False" setzen.
                if (throwError)
                {
                    throw;
                }
                retVal = false;
            }

            return retVal;
        }

Es gibt einen vollständigen Download des VisualStudio 2010 Projektes hier: XsdPrepare.zip

Durch Anwendung der Methode GenerateDummyElementsForClassTypes entsteht aus dem kompletten Schema oben folgendes:
<xs:schema targetNamespace="http://MyTestSchemas/XMLSchema.xsd" elementFormDefault="qualified" xmlns="http://MyTestSchemas/XMLSchema.xsd" xmlns:mstns="http://tempuri.org/XMLSchema.xsd" xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <!-- Ein SimpleElement, das ebenfalls in der Klassengeneration beachtet werden soll -->
  <xs:simpleType name="SimpleRootElement">
    <xs:restriction base="xs:string">
      <xs:enumeration value="TestVal1" />
      <xs:enumeration value="TestVal2" />
    </xs:restriction>
  </xs:simpleType>
  <!-- Ein normales Element, das kein ComplexType verwendet -->
  <xs:element name="CommonRootElement" type="xs:string" />
  <!-- Ein ComplexType, aus dem eine Klasse generiert werden soll -->
  <xs:complexType name="ComplexTypeWhichIsReferenced">
    <xs:sequence>
      <xs:element name="CommonInnerElement1" type="xs:string" />
    </xs:sequence>
  </xs:complexType>
  <!-- Ein weiterer ComplexType, aus dem eine Klasse generiert werden soll 
       Hier gibt es allerdings ein Top level element, das auf den komplexen Typ 'ComplexTypeWhichIsReferenced2' veweist-->
  <xs:element name="TestElement2" type="ComplexTypeWhichIsReferenced2" />
  <xs:complexType name="ComplexTypeWhichIsReferenced2">
    <xs:sequence>
      <xs:element name="CommonInnerElement2" type="xs:string" />
    </xs:sequence>
  </xs:complexType>
  <!-- Ein weiterer ComplexType, aus dem eine Klasse generiert werden soll 
       Hier gibt es ein inneres Element, das auf den komplexen Typ 'ComplexTypeWhichIsReferenced' veweist-->
  <xs:complexType name="TestInnerLink">
    <xs:sequence>
      <xs:element name="ComplexInnerElement" type="ComplexTypeWhichIsReferenced" />
    </xs:sequence>
  </xs:complexType>
  <xs:element name="RootElement0" type="SimpleRootElement" />
  <xs:element name="RootElement1" type="TestInnerLink" />
</xs:schema>


Und wenn man dann XSD nochmal drüberlaufen lässt, dann entsteht folgender Code:
public partial class ComplexTypeWhichIsReferenced2 {
    
    private string commonInnerElement2Field;
    
    /// <remarks/>
    public string CommonInnerElement2 {
        get {
            return this.commonInnerElement2Field;
        }
        set {
            this.commonInnerElement2Field = value;
        }
    }
}

public partial class ComplexTypeWhichIsReferenced {
    
    private string commonInnerElement1Field;
    
    /// <remarks/>
    public string CommonInnerElement1 {
        get {
            return this.commonInnerElement1Field;
        }
        set {
            this.commonInnerElement1Field = value;
        }
    }
}

public enum SimpleRootElement {
    
    /// <remarks/>
    TestVal1,
    
    /// <remarks/>
    TestVal2,
}

public partial class TestInnerLink {
    
    private ComplexTypeWhichIsReferenced complexInnerElementField;
    
    /// <remarks/>
    public ComplexTypeWhichIsReferenced ComplexInnerElement {
        get {
            return this.complexInnerElementField;
        }
        set {
            this.complexInnerElementField = value;
        }
    }
}


Wenn man jetzt ein letztes Mal genau drauf schaut, dann hat XSD den Fall CommonRootElement nicht betrachtet, aber hier muss man zu dessen Schutz sagen, hier wüsste ich auch nicht, wie man hieraus eine Klasse basteln soll.

Aber mit meinem kleinen Helfertool gehen nun (hoffentlich) keine Inhalte mehr durch die Klassengenerierung verloren, und so kann man das auch in automatisierten Prozessen dann einsetzen.

Ich hoffe, dass die nähere Betrachtung hilfreich war, und dass ihr etwas mit dem Tool anfangen könnt. Über Kommentare und Verbesserungsvorschläge würde ich mich freuen.

Es gibt einen vollständigen Download des VisualStudio 2010 Projektes hier: XsdPrepare.zip
Mock Objekt - Wie generiere ich eine SQLException ohne Datenbank?
Freitag, 4. Juni 2010 09:27
Folgendes Problem: Wie kann man für Unit Tests bestimmte SQL Exceptions provozieren? Vielleicht sogar noch ohne Datenbank? Bei mir war das Problem, dass wir standardisierte Retry-Mechanismen verwenden, die aber bei bestimmten Fehlerszenarien keinen Sinn machen. So haben wir diverse SQL Fehlermeldungen / Errorcodes definiert, bei denen kein Retry stattfinden soll. Wie testet man nun so etwas? Hier die Antwort:

Mit Hilfe der folgenden Klasse kann man beliebige SqlExceptions erzeugen lassen, und somit Datanbankfehlerszenarien testen, ohne die Fehler mit einer Datenbank reproduzieren zu lassen.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.SqlClient;
using System.Reflection;

namespace Testing.MockObjects
{
    /// <summary>
    /// Hilfsklasse, um SQL Exceptions zu generieren. Diese Klasse bietet nur statische
    /// Methoden an.
    /// </summary>
    public class MockSqlException
    {
        #region Constructor

        /// <summary>
        /// Standard Konstruktor, nicht öffentlich, da nur statische Methoden verfügbar sein sollen.
        /// </summary>
        protected MockSqlException()
        {}

        #endregion

        #region Public methods

        /// <summary>
        /// Erstellt eine SqlException über Reflection.
        /// </summary>
        /// <param name="errorMessage">Die Fehlermeldung, die für die SqlException.Message
        /// Eigenschaft vorgesehen werden soll.</param>
        /// <param name="errorNumber">Die Sqlspezifische Fehlernummer.</param>
        /// <returns>Die generierte SqlException.</returns>
        public static SqlException CreateSqlException(string errorMessage, int errorNumber)
        {
            // Eine SqlException enthält immer eine SqlErrorCollection
            SqlErrorCollection collection = GetErrorCollection();

            // Wir brauchen nur einen Fehler, den wir hiermit generieren
            SqlError error = GetError(errorNumber, errorMessage);

            // Dieser Fehler wird der Collection über die Eigenschaft "add" hinzugefügt
            MethodInfo addMethod = collection.GetType().GetMethod("Add", BindingFlags.NonPublic | BindingFlags.Instance);
            addMethod.Invoke(collection, new object[] { error });

            // Hier wird der Konstruktor der SqlException aufgerufen und die SqlErrorCollection
            // als Parameter übergeben (Standard Konstruktor)
            Type[] types = new Type[] { typeof(string), typeof(SqlErrorCollection) };
            object[] parameters = new object[] { errorMessage, collection };
            ConstructorInfo constructor = typeof(SqlException).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, types, null);
            SqlException exception = (SqlException)constructor.Invoke(parameters);

            // Die SQLException wird zurückgegeben
            return exception;
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Generiert über Reflection einen SqlError, der einer SqlErrorCollection hinzugefügt werden kann.
        /// </summary>
        /// <param name="errorCode">Die Sqlspezifische Fehlernummer.</param>
        /// <param name="message">>Die Fehlermeldung, die für die SqlException.Message
        /// Eigenschaft vorgesehen werden soll.</param>
        /// <returns>Das generierte SqlError Objetkt.</returns>
        private static SqlError GetError(int errorCode, string message)
        {
            // generiert über den Konstruktor und dessen Argumente eine SqlError Instanz
            object[] parameters = new object[] { errorCode, (byte)0, (byte)10, "server", message, "procedure", 0 };
            Type[] types = new Type[] { typeof(int), typeof(byte), typeof(byte), typeof(string), typeof(string), typeof(string), typeof(int) };
            ConstructorInfo constructor = typeof(SqlError).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, types, null);
            
            return  (SqlError)constructor.Invoke(parameters);
        }
        
        /// <summary>
        /// Generiert über Reflection eine leere SqlErrorCollection Instanz.
        /// </summary>
        /// <returns>Die generierte SqlErrorCollection Instanz.</returns>
        private static SqlErrorCollection GetErrorCollection()
        {
            // generiert über den Konstruktor eine SqlErrorCollection Instanz
            ConstructorInfo constructor = typeof(SqlErrorCollection).
            GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[] { }, null);
            SqlErrorCollection collection = (SqlErrorCollection)constructor.Invoke(new object[] { });

            return collection;
        }

        #endregion
    }
}


Wie aus dem Code gut zu erkennen ist, wird der ganze Aufbau der SqlException über Reflection getätigt, weil die SqlException leider nicht öffentlich zugänglich ist.

Die SqlException ist auch immer so aufgebaut, dass sie eine ErrorCollection entgegennimmt, deshalb der umständliche Weg über die Collection. Weiterhin wichtig ist, dass die Eigenschaft SqlException.ErrorCode NICHT den ErrorCode vom SqlServer liefert, sondern den HRESULT, mit dem meist nichts anzufangen ist. Für den SqlErrorCode muss man die Eigenschaft SqlException.Number abfragen. Witzigerweise entspricht der SqlErrorcode der SqlException immer des SqlErrorCodes des ersten SqlErrors aus der Collection (hardcoded!).

Ein Anwendungsbeispiel ist dann z.b. folgender:
        static void Main(string[] args)
        {
            try
            {
                throw MockSqlException.CreateSqlException(
                    "The conversion from datetime data type to smalldatetime data type resulted in a smalldatetime overflow error",
                    298);
            }
            catch (SqlException sqlex)
            {
                int test = sqlex.ErrorCode;
                Console.WriteLine(String.Format("Eine SQL Exception ist aufgetreten: ErrorCode: {0} Message {1}", sqlex.Number, sqlex.Message));
            }

            Console.WriteLine("If this is displayed, a sql exception was thrown, and everything is fine ;-)");
            Console.ReadKey();
        }


Übrigens eine Auflistung aller SQL Fehlermeldungen findet man in der Datenbank selbst:
In SQL 2005: sys.messages
In SQL 2000: master.dbo.sysmessages

Mit Hilfe des Verfahrens lassen sich Datenbank-Negativtests erheblich beschleunigen, weil keine Verbindung zu einer Datenbank notwendig ist.
[deprecated] XslTransform wird zu XslCompiledTransform -> Umstellungsprobleme
Mittwoch, 2. Juni 2010 02:59
Mit unserer Umstellung auf das neue Framework habe ich mich mit dem Problem beschäftigt, dass XslTransform veraltet ist. Die Anweisung, dass nun XslCompiledTransform zu verwenden ist, ist zwar schön und gut, aber wir stießen dabei auf 2 markante Probleme:

Fehler 1:
For security reasons DTD is prohibited in this XML document. To enable DTD processing set the DtdProcessing property on XmlReaderSettings to Parse and pass the settings into XmlReader.Create method.

Fehler 2:
The 'xsl:stylesheet' element is not declared.

Im Folgenden erkläre ich nun Schritt für Schritt, worin die Probleme lagen. Hierfür soll folgendes Ausgangsszenario gelten:

Ausgangssituation:

Gegeben sei folgender Quellcode:
        static void Main(string[] args)
        {
            XPathDocument doc = new XPathDocument("PocTest1_log.xml");
            StreamWriter swriter = new StreamWriter("PocTest1_log.html");

            XslTransform trans = new XslTransform();
            trans.Load("XSLTFile1.xslt");
            trans.Transform(doc, null, swriter);
        }

dabei ist das zu transformierende Dokument "PocTest1_log.xml" erst einmal unwichtig, es enthält ein valides und vollständiges Xml DOM Objekt.
Interessant ist das XSLT Stylesheet, das für eine Transformation zu einem HTML Dokument verwendet werden soll. Die Besonderheit ist, dass dieses einen DTD Teil definiert, also eine Entity mit definiert. Zu Demozwecken, und damit den Fehler zu provozieren, genügt folgendes Dummy Stylesheet:
<!DOCTYPE xsl:stylesheet [ <!ENTITY jquerysrc SYSTEM 'test.js'> ]>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:output method="xml" indent="yes"/>

    <xsl:template match="@* | node()">
        <xsl:copy>
            <xsl:apply-templates select="@* | node()"/>
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>

Wenn nun der o.g. Code ausgeführt wird läuft alles zunächst wunderbar, außer, dass die Warnung XslTransform ist deprecated ausgegeben wird.

Umstellung auf XslCompiledTransform Schritt 1:

Eine Umstellung auf XslCompiledTransform ergibt zunächst folgender Code:
        static void Main(string[] args)
        {
            XPathDocument doc = new XPathDocument("PocTest1_log.xml");
            StreamWriter swriter = new StreamWriter("PocTest1_log.html");

            XslCompiledTransform trans = new XslCompiledTransform();
            trans.Load("XSLTFile1.xslt");
            trans.Transform(doc, null, swriter);
        }

Die Laufzeit ergibt den o.g. Fehler 1 (Zeile trans.Load): "For security reasons DTD is prohibited in this XML document ..."

Umstellung auf XslCompiledTransform Schritt 2 (Lösung zu Schritt 1):

Im Netz findet sich hier die bereits etablierte Lösung, dass man mittels einem XmlReader und der darin enthaltenen Möglichkeit XmlReaderSettings mitzugeben die Lösung enthalten sei:
        static void Main(string[] args)
        {
            XPathDocument doc = new XPathDocument("PocTest1_log.xml");
            StreamWriter swriter = new StreamWriter("PocTest1_log.html");

            XmlReaderSettings xmlReadSet = new XmlReaderSettings();
            xmlReadSet.ValidationType = ValidationType.DTD;
            xmlReadSet.DtdProcessing = DtdProcessing.Parse;

            XslCompiledTransform trans = new XslCompiledTransform();
            trans.Load(XmlReader.Create("XSLTFile1.xslt", xmlReadSet));
            trans.Transform(doc, null, swriter);
        }

Übrigens, die Eigenschaft XmlReaderSettings.ProhibitDtd ist ebenso veraltet, hierzu kann man wie im Code angegeben die Eigenschaft XmlReaderSettings.DtdProcessing verwenden.

Die Laufzeit ergibt allerdings den o.g. Fehler 2 (Zeile trans.Load): "The 'xsl:stylesheet' element is not declared."

Diese Fehlermeldung ist alles andere als selbsterklärend, und total irreführend. Ich habe lange herumgespielt, aber im Netz war nichts zu finden, und auch ein intensives Debugging brachte leider keine Lösung. Ich habe nur folgende Erkenntnisse gehbt (zunächst):
- Entfernung des DTD (DOCTYPE Knoten) -> Code funktioniert
- Verwendung von XsltSettings mit diversen Einstellmöglichkeiten (z.B. TrustedXslt) -> keine Veränderung, Code funktioniert nicht
- XsltArgumentList kann nicht verwendet werden, da das Problem schon bei Load auftritt, nicht erst bei Transform
- ...

Umstellung auf XslCompiledTransform vollständige Lösung:
Ich weiß nicht warum ich versucht habe noch einmal mit den XmlReaderSettings herumzuspielen, aber die Änderung des ValidationType brachte dann die funktionierende Lösung:
        static void Main(string[] args)
        {
            XPathDocument doc = new XPathDocument("PocTest1_log.xml");
            StreamWriter swriter = new StreamWriter("PocTest1_log.html");

            XmlReaderSettings xmlReadSet = new XmlReaderSettings();
            xmlReadSet.DtdProcessing = DtdProcessing.Parse;
            xmlReadSet.ValidationType = ValidationType.Schema;

            XslCompiledTransform trans = new XslCompiledTransform();
            trans.Load(XmlReader.Create("XSLTFile1.xslt", xmlReadSet));
            trans.Transform(doc, null, swriter);
        }

So ganz kann ich die Fehlermeldung nicht nachvollziehen, ich wüsste nicht, wenn ich das Stylesheet als DTD behandle, was darin syntaktisch verkehrt wäre, und warum eine Validierung fehlschlägt, aber eine Umstellung auf Schema brachte dann schlußendlich die Lösung.