From: Oleg T. <he...@us...> - 2005-10-16 20:07:19
|
Update of /cvsroot/mvp-xml/EXSLT/v2/src/Exslt/MultiOutput In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv12843/v2/src/Exslt/MultiOutput Added Files: MultiXmlTextWriter.cs OutputState.cs Log Message: --- NEW FILE: MultiXmlTextWriter.cs --- #region using using System; using System.Xml; using System.Text; using System.Collections; using System.IO; using System.Security; #endregion namespace Mvp.Xml.Exslt { /// <summary> /// Specifies the redirecting state of the <c>MultiXmlTextWriter</c>. /// </summary> internal enum RedirectState { /// <summary> /// The output is being relayed further (default). /// </summary> Relaying, /// <summary> /// The output is being redirected. /// </summary> Redirecting, /// <summary> /// <c><exsl:document></c> attributes are being written /// </summary> WritingRedirectElementAttrs, /// <summary> /// <c><exsl:document></c> attribute value is being written /// </summary> WritingRedirectElementAttrValue } /// <summary> /// <para><c>MultiXmlTextWriter</c> class extends standard <c>XmlTextWriter</c> class /// and represents an XML writer that provides a fast, /// non-cached, forward-only way of generating multiple output files containing /// either text data or XML data that conforms to the W3C Extensible Markup /// Language (XML) 1.0 and the Namespaces in XML recommendations.</para> /// </summary> /// <remarks> /// <para>Instances of <c>MultiXmlTextWriter</c> class regognize special element /// (<c><exsl:document></c> in <c>"http://exslt.org/common"</c> namespace) as /// instruction to redirect the output of this element's content to another file. /// When using with <c>XslTransform</c> class, <c>MultiXmlTextWriter</c> class /// allows to generate multiple XSL Transfromation results.</para> /// <para><c>MultiXmlTextWriter</c> class extends <c>System.Xml.XmlTextWriter</c> /// class, therefore its instances can be passed directly to the overloaded /// <c>XslTransform.Transform()</c> method, which accepts <c>XmlWriter</c> as /// object to write the transformation result to. All actual XML writing work /// <c>MultiXmlTextWriter</c> class delegates to its base class, but it overrides /// several <c>XmlTextWriter</c> class methods to implement output switching logic /// as follows: once <c><exsl:document></c> element start tag is detected in /// the XML stream, new writer (<c>XmlTextWriter</c> or <c>StreamWriter</c> depending on /// <c>method</c> attribute value) object is created with parameters as specified /// in the attributes of the <c><exsl:document></c> element and the output /// is switched to this newly created writer untill the end tag of the /// <c><exsl:document></c> element is encountered.</para> /// <para><c><exsl:document></c> element syntax is as follows: /// <code> /// <exsl:document /// href = { uri-reference } /// method = { "xml" | "text" } /// encoding = { string } /// omit-xml-declaration = { "yes" | "no" } /// standalone = { "yes" | "no" } /// doctype-public = { string } /// doctype-system = { string } /// indent = { "yes" | "no" } /// <-- Content: template --> /// </exsl:document> /// </code></para> /// <para> /// The <c>href</c> attribute specifies where new result document should be stored, /// it must be an absolute or relative URI. Relative URIs are resolved /// relatively to the parent result document base URI. If the <c>href</c> attribute /// specifies that the output should be redirected to a file in a directory /// and that directory does not exist, it will be created. This allows to create /// directory trees of any complexity.</para> /// <para>Semantics of the rest attributes is as defined in W3C XSLT 1.0 Recommendation /// for <c><xsl:output></c> element, see /// <a href="http://www.w3.org/TR/xslt.html#output">http://www.w3.org/TR/xslt.html#output</a>. /// </para> /// <para><b>Note:</b> <c><exsl:document></c> element namespace prefix must be bound to /// <c>"http://exslt.org/common"</c> namespace URI in order to be recognized by /// <c>MultiXmlTextWriter</c> class as a redirecting instruction. </para> /// </remarks> /// <example>This example shows how to use <c>MultiXmlTextWriter</c> along with /// <c>XslTransform</c> to achieve create multiple result documents in one /// transfromation run: /// <para>In XSL stylesheet document declare <c>"http://exslt.org/common"</c> namespace /// and whenever you want to create new result document make use of <c><exsl:documnet></c> /// element:<br/> /// <c>style.xsl</c> stylesheet fragment: /// <code> /// <![CDATA[ /// <xsl:stylesheet version="1.0" /// xmlns:xsl="http://www.w3.org/1999/XSL/Transform" /// ]]><b>xmlns:exsl= "http://exslt.org/common"</b><![CDATA[ /// ]]><b>exclude-result-prefixes="exsl"</b>><![CDATA[ /// <xsl:template match="book"> /// <!-- Builds frameset --> /// <html> /// <head> /// <title><xsl:value-of select="@title"/></title> /// </head> /// <frameset cols="20%,80%"> /// <frame src="toc.html"/> /// <!-- Builds table of contents output document --> /// ]]><b><exsl:document href="toc.html" indent="yes"></b><![CDATA[ /// <html> /// <head> /// <title>Table of Contents</title> /// </head> /// <body> /// <ul> /// <xsl:apply-templates mode="toc"/> /// </ul> /// </body> /// </html> /// ]]><b></exsl:document></b><![CDATA[ /// <frame src="{chapter[1]/@id}.html" name="body" /> /// <xsl:apply-templates /> /// </frameset> /// </html> /// ... /// /// ]]> /// </code> /// C# code fragment: /// <code> /// XPathDocument doc = new XPathDocument("book.xml"); /// XslTransform xslt = new XslTransform(); /// xslt.Load("style.xsl"); /// MultiXmlTextWriter multiWriter = /// new MultiXmlTextWriter("index.html", Encoding.UTF8); /// multiWriter.Formatting = Formatting.Indented; /// xslt.Transform(doc, null, multiWriter); /// </code> /// </para> /// </example> public class MultiXmlTextWriter : XmlTextWriter { /// <summary> /// This constant is the namespace <c><exsl:document></c> element /// should belong to in order to be recognized as redirect instruction. /// It's <c>"http://exslt.org/common"</c> as defined by /// <a href="http://www.exslt.org">EXSLT community initiative</a>. /// </summary> protected const string RedirectNamespace = "http://exslt.org/common"; /// <summary> /// This constant is the redirect instruction element name. /// It's <c>"document"</c> as defined by /// <a href="http://www.exslt.org">EXSLT community initiative</a>. /// </summary> protected const string RedirectElementName = "document"; // Stack of output states Stack states = null; // Current output state OutputState state = null; // Currently processed attribute name string currentAttributeName; //Redirecting state - relaying by default RedirectState redirectState = RedirectState.Relaying; /// <summary> /// Creates an instance of the <c>MultiXmlTextWriter</c> class using the specified filename and /// encoding. /// Inherited from <c>XmlTextWriter</c>, see <see cref="XmlTextWriter"/>. /// Overridden to set output directory. /// </summary> /// <param name="fileName">The filename to write to. If the file exists, it will truncate it and overwrite it /// with the new content.</param> /// <param name="encoding">The encoding to generate. If encoding is a null reference it writes the file out /// as UTF-8, and omits the encoding attribute from the ProcessingInstruction.</param> /// <exception cref="ArgumentException">The encoding is not supported; the filename is empty, contains only /// white space, or contains one or more invalid characters.</exception> /// <exception cref="UnauthorizedAccessException">Access is denied.</exception> /// <exception cref="ArgumentNullException">The filename is a null reference.</exception> /// <exception cref="DirectoryNotFoundException">The directory to write to is not found.</exception> /// <exception cref="IOException">The filename includes an incorrect or invalid syntax /// for file name, directory name, or volume label syntax.</exception> /// <exception cref="SecurityException">The caller does not have the required permission.</exception> public MultiXmlTextWriter(String fileName, Encoding encoding):base(fileName, encoding) { DirectoryInfo dir = Directory.GetParent(fileName); Directory.SetCurrentDirectory(dir.ToString()); } /// <summary> /// Creates an instance of the <c>MultiXmlTextWriter</c> class using the specified /// <c>TextWriter</c>, see <see cref="TextWriter"/>. /// Inherited from <c>XmlTextWriter</c>, see <see cref="XmlTextWriter"/>. /// </summary> /// <param name="w">The <c>TextWriter</c> to write to. It is assumed that the <c>TextWriter</c> is /// already set to the correct encoding.</param> public MultiXmlTextWriter(TextWriter w):base(w) {} /// <summary> /// Creates an instance of the <c>MultiXmlTextWriter</c> class using the specified /// stream and encoding. /// Inherited from <c>XmlTextWriter</c>, see <see cref="XmlTextWriter"/>. /// </summary> /// <param name="w">The stream to which you want to write.</param> /// <param name="encoding">The encoding to generate. If encoding is a null /// reference it writes out the stream as UTF-8 and omits the encoding attribute /// from the ProcessingInstruction.</param> /// <exception cref="ArgumentException">The encoding is not supported or the stream /// cannot be written to.</exception> /// <exception cref="ArgumentNullException">w is a null reference.</exception> public MultiXmlTextWriter(Stream w, Encoding encoding):base(w, encoding) {} /// <summary> /// Checks possible start of <c><exsl:document></c> element content. /// </summary> /// <remarks> /// When <c><exsl:document></c> element start tag is detected, the beginning of the /// element's content might be detected as any next character data (not attribute /// value though), element start tag, processing instruction or comment. /// </remarks> /// <exception cref="ArgumentNullException">Thrown when <c>href</c> attribute is absent.</exception> /// <exception cref="ArgumentException">Thrown when a document, specified by <c>href</c> attribute is /// opened alreary. Two nested <c><exsl:document></c></exception> elements cannot specify the same /// output URI in their <c>href</c> attributes. private void CheckContentStart() { if (redirectState == RedirectState.WritingRedirectElementAttrs) { //Check required href attribute if (state.Href == null) throw new ArgumentNullException("'href' attribute of exsl:document element must be specified."); //Are we writing to this URI already? foreach (OutputState nestedState in states) if (nestedState.Href == state.Href) throw new ArgumentException("Cannot write to " + state.Href + " two documents simultaneously."); state.InitWriter(); redirectState = RedirectState.Redirecting; } } /// <summary> /// Writes the specified start tag and associates it with the given namespace and prefix. /// Inherited from <c>XmlTextWriter</c>, see <see cref="XmlTextWriter.WriteStartElement"/> /// Overridden to detect <c>exsl:document</c> element start tag. /// </summary> /// <param name="prefix">The namespace prefix of the element.</param> /// <param name="localName">The local name of the element.</param> /// <param name="ns">The namespace URI to associate with the element. If this namespace /// is already in scope and has an associated prefix then the writer will automatically write that prefix also. </param> /// <exception cref="InvalidOperationException">The writer is closed.</exception> public override void WriteStartElement(string prefix, string localName, string ns) { CheckContentStart(); //Is it exsl:document redirecting instruction? if (ns == RedirectNamespace && localName == RedirectElementName) { //Lazy stack of states if (states == null) states = new Stack(); //If we are redirecting already - push the current state into the stack if (redirectState == RedirectState.Redirecting) states.Push(state); //Initialize new state state = new OutputState(); redirectState = RedirectState.WritingRedirectElementAttrs; } else { if (redirectState == RedirectState.Redirecting) { if (state.Method == OutputMethod.Text) { state.Depth++; return; } //Write doctype before the first element if (state.Depth == 0 && state.SystemDoctype != null) if (prefix != String.Empty) state.XmlWriter.WriteDocType(prefix+":"+localName, state.PublicDoctype,state.SystemDoctype, null); else state.XmlWriter.WriteDocType(localName, state.PublicDoctype,state.SystemDoctype, null); state.XmlWriter.WriteStartElement(prefix, localName, ns); state.Depth++; } else base.WriteStartElement(prefix, localName, ns); } } /// <summary> /// Finishes output redirecting - closes current writer /// and pops previous state. /// </summary> internal void FinishRedirecting() { state.CloseWriter(); //Pop previous state if it exists if (states.Count != 0) { state = (OutputState)states.Pop(); redirectState = RedirectState.Redirecting; } else { state = null; redirectState = RedirectState.Relaying; } } /// <summary> /// Closes one element and pops the corresponding namespace scope. /// Inherited from <c>XmlTextWriter</c>, see <see cref="XmlTextWriter.WriteEndElement"/> /// Overridden to detect <c>exsl:document</c> element end tag. /// </summary> public override void WriteEndElement() { CheckContentStart(); if (redirectState == RedirectState.Redirecting) { //Check if that's exsl:document end tag if (state.Depth-- == 0) FinishRedirecting(); else { if (state.Method == OutputMethod.Text) return; state.XmlWriter.WriteEndElement(); } } else base.WriteEndElement(); } /// <summary> /// Closes one element and pops the corresponding namespace scope. /// Inherited from <c>XmlTextWriter</c>, see <see cref="XmlTextWriter.WriteFullEndElement"/> /// Overridden to detect <c>exsl:document</c> element end tag. /// </summary> public override void WriteFullEndElement() { CheckContentStart(); if (redirectState == RedirectState.Redirecting) { //Check if it's exsl:document end tag if (state.Depth-- == 0) FinishRedirecting(); else { if (state.Method == OutputMethod.Text) return; state.XmlWriter.WriteFullEndElement(); } } else base.WriteFullEndElement(); } /// <summary> /// Writes the start of an attribute. /// Inherited from <c>XmlTextWriter</c>, see <see cref="XmlTextWriter.WriteStartAttribute"/> /// Overridden to detect <c>exsl:document</c> attribute names and to redirect /// the output. /// </summary> /// <param name="prefix">Namespace prefix of the attribute.</param> /// <param name="localName">Local name of the attribute.</param> /// <param name="ns">Namespace URI of the attribute.</param> /// <exception cref="ArgumentException"><c>localName</c>c> is either a null reference or <c>String.Empty</c>.</exception> public override void WriteStartAttribute(string prefix, string localName, string ns) { if (redirectState == RedirectState.WritingRedirectElementAttrs) { redirectState = RedirectState.WritingRedirectElementAttrValue; currentAttributeName = localName; } else if (redirectState == RedirectState.Redirecting) { if (state.Method == OutputMethod.Text) return; state.XmlWriter.WriteStartAttribute(prefix, localName, ns); } else base.WriteStartAttribute(prefix, localName, ns); } /// <summary> /// Closes the previous <c>WriteStartAttribute</c> call. /// Inherited from <c>XmlTextWriter</c>, see <see cref="XmlTextWriter.WriteEndAttribute"/> /// Overridden to redirect the output. /// </summary> public override void WriteEndAttribute() { if (redirectState == RedirectState.WritingRedirectElementAttrValue) redirectState = RedirectState.WritingRedirectElementAttrs; else if (redirectState == RedirectState.Redirecting) { if (state.Method == OutputMethod.Text) return; state.XmlWriter.WriteEndAttribute(); } else base.WriteEndAttribute(); } /// <summary> /// Writes out a comment <!--...--> containing the specified text. /// Inherited from <c>XmlTextWriter</c>, see <see cref="XmlTextWriter.WriteComment"/> /// Overriden to redirect the output. /// </summary> /// <param name="text">Text to place inside the comment.</param> /// <exception cref="ArgumentException">The text would result in a non-well formed XML document.</exception> /// <exception cref="InvalidOperationException">The <c>WriteState</c> is Closed.</exception> public override void WriteComment(string text) { CheckContentStart(); if (redirectState == RedirectState.Redirecting) { if (state.Method == OutputMethod.Text) return; state.XmlWriter.WriteComment(text); } else base.WriteComment(text); } /// <summary> /// Writes out a processing instruction with a space between the name /// and text as follows: <?name text?>. /// Inherited from <c>XmlTextWriter</c>, see <see cref="XmlTextWriter.WriteProcessingInstruction"/> /// Overridden to redirect the output. /// </summary> /// <param name="name">Name of the processing instruction.</param> /// <param name="text">Text to include in the processing instruction.</param> /// <exception cref="ArgumentException"><para>The text would result in a non-well formed XML document.</para> /// <para><c>name</c> is either a null reference or <c>String.Empty</c>.</para> /// <para>This method is being used to create an XML declaration after /// <c>WriteStartDocument</c> has already been called.</para></exception> public override void WriteProcessingInstruction(string name, string text) { CheckContentStart(); if (redirectState == RedirectState.Redirecting) { if (state.Method == OutputMethod.Text) return; state.XmlWriter.WriteProcessingInstruction(name, text); } else base.WriteProcessingInstruction(name, text); } /// <summary> /// Writes the given text content. /// Inherited from <c>XmlTextWriter</c>, see <see cref="XmlTextWriter.WriteString"/> /// Overridden to detect <c>exsl:document</c> element attribute values and to /// redirect the output. /// </summary> /// <param name="text">Text to write.</param> /// <exception cref="ArgumentException">The text string contains an invalid surrogate pair.</exception> public override void WriteString(string text) { //Possible exsl:document's attribute value if (redirectState == RedirectState.WritingRedirectElementAttrValue) { switch (currentAttributeName) { case "href": state.Href = text; break; case "method": if (text == "text") state.Method = OutputMethod.Text; break; case "encoding": try { state.Encoding = Encoding.GetEncoding(text); } catch (Exception) {} break; case "indent": if (text == "yes") state.Indent = true; break; case "doctype-public": state.PublicDoctype = text; break; case "doctype-system": state.SystemDoctype = text; break; case "standalone": if (text == "yes") state.Standalone = true; break; case "omit-xml-declaration": if (text == "yes") state.OmitXmlDeclaration = true; break; default: break; } return; } else CheckContentStart(); if (redirectState == RedirectState.Redirecting) { if (state.Method == OutputMethod.Text) state.TextWriter.Write(text); else state.XmlWriter.WriteString(text); } else base.WriteString(text); } } } --- NEW FILE: OutputState.cs --- #region using using System; using System.Xml; using System.Text; using System.IO; #endregion namespace Mvp.Xml.Exslt { /// <summary> /// XSLT output method enumeration, see W3C XSLT 1.0 Recommendation at /// <a href="http://www.w3.org/TR/xslt.html#output">http://www.w3.org/TR/xslt.html#output</a>. /// </summary> /// <remarks>Only <c>xml</c> and <c>text</c> methods are supported by this version of /// the <c>MultiXmlTextWriter</c>.</remarks> internal enum OutputMethod { Xml, Text }; /// <summary> /// This class represents redirected output state and properties. /// </summary> internal class OutputState { private XmlTextWriter xmlWriter; private StreamWriter textWriter; private int depth; private string href; private Encoding encoding; private bool indent; private string publicDoctype; private string systemDoctype; private bool standalone; private string storedDir; private OutputMethod method; private bool omitXmlDecl; /// <summary> /// Creates new <c>OutputState</c> with default properties values: /// UTF8 encoding, no indentation, nonstandalone document, XML output /// method. /// </summary> public OutputState() { encoding = System.Text.Encoding.UTF8; indent = false; standalone = false; omitXmlDecl = false; method = OutputMethod.Xml; } /// <summary> /// Initializes the writer to write redirected output. /// </summary> /// <remarks>Depending on the <c>method</c> attribute value, /// <c>XmlTextWriter</c> or <c>StreamWriter</c> is created. /// <c>XmlTextWriter</c> is used for outputting XML and /// <c>StreamWriter</c> - for plain text. /// </remarks> public void InitWriter() { // Save current directory storedDir = Directory.GetCurrentDirectory(); DirectoryInfo dir = Directory.GetParent(href); if (!dir.Exists) dir.Create(); // Create writer if (method == OutputMethod.Xml) { xmlWriter = new XmlTextWriter(href, encoding); if (indent) xmlWriter.Formatting = Formatting.Indented; if (!omitXmlDecl) { if (standalone) xmlWriter.WriteStartDocument(true); else xmlWriter.WriteStartDocument(); } } else textWriter = new StreamWriter(href, false, encoding); // Set new current directory Directory.SetCurrentDirectory(dir.ToString()); } /// <summary> /// Closes the writer that was used to write redirected output. /// </summary> public void CloseWriter() { if (method == OutputMethod.Xml) { if (!omitXmlDecl) { xmlWriter.WriteEndDocument(); } xmlWriter.Close(); } else textWriter.Close(); // Restore previous current directory Directory.SetCurrentDirectory(storedDir); } /// <summary> /// Specifies whether the result document should be written with /// a standalone XML document declaration. /// </summary> /// <value>Standalone XML declaration as per W3C XSLT 1.0 Recommendation (see /// <a href="http://www.w3.org/TR/xslt.html#output">http://www.w3.org/TR/xslt.html#output</a> /// for more info).</value> /// <remarks>The property does not affect output while output method is <c>text</c>.</remarks> public bool Standalone { get { return standalone; } set { standalone = value; } } /// <summary> /// Specifies output method. /// </summary> /// <value>Output Method as per W3C XSLT 1.0 Recommendation (see /// <a href="http://www.w3.org/TR/xslt.html#output">http://www.w3.org/TR/xslt.html#output</a> /// for more info).</value> public OutputMethod Method { get { return method; } set { method = value; } } /// <summary> /// Specifies the URI where the result document should be written to. /// </summary> /// <value>Absolute or relative URI of the output document.</value> public string Href { get { return href; } set { href = value; } } /// <summary> /// Specifies the preferred character encoding of the result document. /// </summary> /// <value>Output encoding as per W3C XSLT 1.0 Recommendation (see /// <a href="http://www.w3.org/TR/xslt.html#output">http://www.w3.org/TR/xslt.html#output</a> /// for more info).</value> public Encoding Encoding { get { return encoding; } set { encoding = value; } } /// <summary> /// Specifies whether the result document should be written in the /// indented form. /// </summary> /// <value>Output document formatting as per W3C XSLT 1.0 Recommendation (see /// <a href="http://www.w3.org/TR/xslt.html#output">http://www.w3.org/TR/xslt.html#output</a> /// for more info).</value> /// <remarks>The property does not affect output while output method is <c>text</c>.</remarks> public bool Indent { get { return indent; } set { indent = value; } } /// <summary> /// Specifies the public identifier to be used in the document /// type declaration. /// </summary> /// <value>Public part of the output document type definition as per W3C XSLT 1.0 Recommendation (see /// <a href="http://www.w3.org/TR/xslt.html#output">http://www.w3.org/TR/xslt.html#output</a> /// for more info).</value> /// <remarks>The property does not affect output while output method is <c>text</c>.</remarks> public string PublicDoctype { get { return publicDoctype; } set { publicDoctype = value; } } /// <summary> /// Specifies the system identifier to be used in the document /// type declaration. /// </summary> /// <value>System part of the output document type definition as per W3C XSLT 1.0 Recommendation (see /// <a href="http://www.w3.org/TR/xslt.html#output">http://www.w3.org/TR/xslt.html#output</a> /// for more info).</value> /// <remarks>The property does not affect output while output method is <c>text</c>.</remarks> public string SystemDoctype { get { return systemDoctype; } set { systemDoctype = value; } } /// <summary> /// Actual <c>XmlTextWriter</c> used to write the redirected /// result document. /// </summary> /// <value><c>XmlWriter</c>, which is used to write the output document in XML method.</value> public XmlTextWriter XmlWriter { get { return xmlWriter; } } /// <summary> /// Actual <c>TextWriter</c> used to write the redirected /// result document in text output method. /// </summary> /// <value><c>StreamWriter</c>, which is used to write the output document in text method.</value> public StreamWriter TextWriter { get { return textWriter; } } /// <summary> /// Tree depth (used to detect end tag of the <c>exsl:document</c>). /// </summary> /// <value>Current output tree depth.</value> public int Depth { get { return depth; } set { depth = value; } } /// <summary> /// Specifies whether the XSLT processor should output an XML declaration. /// </summary> public bool OmitXmlDeclaration { get { return omitXmlDecl; } set { omitXmlDecl = value; } } } } |