From: Oleg T. <he...@us...> - 2005-11-07 13:58:40
|
Update of /cvsroot/mvp-xml/Common/v2/src/Xsl In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv16810/v2/src/Xsl Added Files: IXmlTransform.cs XslReader.cs Log Message: --- NEW FILE: IXmlTransform.cs --- #region using using System; using System.IO; using System.Xml; using System.Xml.Xsl; using System.Xml.XPath; #endregion namespace Mvp.Xml.Common.Xsl { public interface IXmlTransform { void Transform(XmlInput defaulDocument, XsltArgumentList args, XmlOutput output); XmlWriterSettings DefaultWriterSettings { get; } } public class XmlInput { internal object source ; internal XmlResolver resolver; public XmlInput(XmlReader reader, XmlResolver resolver) { this.source = reader; this.resolver = resolver; } public XmlInput(TextReader reader, XmlResolver resolver) { this.source = reader; this.resolver = resolver; } public XmlInput(Stream stream, XmlResolver resolver) { this.source = stream; this.resolver = resolver; } public XmlInput(String uri , XmlResolver resolver) { this.source = uri ; this.resolver = resolver; } public XmlInput(XmlReader reader) : this(reader, new XmlUrlResolver()) {} public XmlInput(TextReader reader) : this(reader, new XmlUrlResolver()) {} public XmlInput(Stream stream) : this(stream, new XmlUrlResolver()) {} public XmlInput(String uri ) : this(uri , new XmlUrlResolver()) {} public XmlInput(IXPathNavigable nav ) { this.source = nav ; } // We can add set of implicit constructors. // I am not shre that this will be for good, so I commented them for now. //public static implicit operator XmlInput(XmlReader reader) { return new XmlInput(reader); } //public static implicit operator XmlInput(TextReader reader) { return new XmlInput(reader); } //public static implicit operator XmlInput(Stream stream) { return new XmlInput(stream); } //public static implicit operator XmlInput(String uri ) { return new XmlInput(uri ); } //public static implicit operator XmlInput(XPathNavigator nav ) { return new XmlInput(nav ); } // the trick doesn't work with interfaces } public class XmlOutput { internal object destination; public XmlOutput(XmlWriter writer) { this.destination = writer; } public XmlOutput(TextWriter writer) { this.destination = writer; } public XmlOutput(Stream strm ) { this.destination = strm ; } public XmlOutput(String uri ) { this.destination = uri ; } // We will add overrides with XmlOutputResolver here later to support multiple output documents (<xsl:result-document>) } } --- NEW FILE: XslReader.cs --- #region using using System; using System.Collections.Generic; using System.Text; using System.IO; using System.Xml; using System.Xml.Xsl; using System.Xml.XPath; using System.Diagnostics; using System.Threading; #endregion namespace Mvp.Xml.Common.Xsl { public class XslReader : XmlReader { static string NsXml = "http://www.w3.org/XML/1998/namespace"; static int defaultBufferSize = 256; XmlNameTable nameTable; TokenPipe pipe; BufferWriter writer; ScopeManager scope; Thread thread; XslCompiledTransform xslCompiledTransform; bool multiThread = false; int initialBufferSize; private static XmlReaderSettings ReaderSettings; static XslReader() { ReaderSettings = new XmlReaderSettings(); ReaderSettings.ProhibitDtd = true; } // Transform Parameters XmlInput defaulDocument; XsltArgumentList args; public XslReader(XslCompiledTransform xslTransform, bool multiThread, int initialBufferSize) { this.xslCompiledTransform = xslTransform; this.multiThread = multiThread; this.initialBufferSize = initialBufferSize; nameTable = new NameTable(); pipe = this.multiThread ? new TokenPipeMultiThread(initialBufferSize) : new TokenPipe(initialBufferSize); writer = new BufferWriter(pipe, nameTable); scope = new ScopeManager(nameTable); SetUndefinedState(ReadState.Initial); } public XslReader(XslCompiledTransform xslTransform) : this(xslTransform, true, defaultBufferSize) { } public XmlReader Transform(XmlInput input, XsltArgumentList args) { this.defaulDocument = input; this.args = args; Start(); return this; } private void Start() { if (thread != null && thread.IsAlive) { // We can also reuse this thread or use ThreadPool. For simplicity we create new thread each time. // Some problem with TreadPool will be the need to notify transformation thread when user calls new Start() befor previous transformation completed thread.Abort(); thread.Join(); } this.writer.Reset(); this.scope.Reset(); this.pipe.Reset(); this.depth = 0; SetUndefinedState(ReadState.Initial); if (multiThread) { this.thread = new Thread(new ThreadStart(this.StrartTransform)); this.thread.Start(); } else { StrartTransform(); } } private void StrartTransform() { try { while (true) { XmlReader xmlReader = defaulDocument.source as XmlReader; if (xmlReader != null) { xslCompiledTransform.Transform(xmlReader, args, writer, defaulDocument.resolver); break; } IXPathNavigable nav = defaulDocument.source as IXPathNavigable; if (nav != null) { xslCompiledTransform.Transform(nav, args, writer); break; } string str = defaulDocument.source as string; if (str != null) { using (XmlReader reader = XmlReader.Create(str, ReaderSettings)) { xslCompiledTransform.Transform(reader, args, writer, defaulDocument.resolver); } break; } Stream strm = defaulDocument.source as Stream; if (strm != null) { using (XmlReader reader = XmlReader.Create(strm, ReaderSettings)) { xslCompiledTransform.Transform(reader, args, writer, defaulDocument.resolver); } break; } TextReader txtReader = defaulDocument.source as TextReader; if (txtReader != null) { using (XmlReader reader = XmlReader.Create(txtReader, ReaderSettings)) { xslCompiledTransform.Transform(reader, args, writer, defaulDocument.resolver); } break; } throw new Exception("Unexpected XmlInput"); } writer.Close(); } catch (Exception e) { if (multiThread) { // we need this exception on main thread. So pass it through pipe. pipe.WriteException(e); } else { throw; } } } public XslCompiledTransform XslCompiledTransform { get { return this.xslCompiledTransform; } set { this.xslCompiledTransform = value; } } public int InitialBufferSize { get { return initialBufferSize; } set { initialBufferSize = value; } } #region XmlReader Implementation int attOffset = 0; // 0 - means reader is positioned on element, when reader potitionrd on the first attribute attOffset == 1 int attCount; int depth; XmlNodeType nodeType = XmlNodeType.None; ReadState readState = ReadState.Initial; QName qname; string value; void SetUndefinedState(ReadState readState) { this.qname = writer.QNameEmpty; this.value = string.Empty; this.nodeType = XmlNodeType.None; this.attCount = 0; this.readState = readState; } bool IsWhitespace(string s) { // Because our xml is presumably valid only and all ws chars <= ' ' foreach (char c in s) { if (' ' < c) { return false; } } return true; } public override bool Read() { // Leave Current node switch (nodeType) { case XmlNodeType.None : if (readState == ReadState.EndOfFile || readState == ReadState.Closed) { return false; } readState = ReadState.Interactive; break; case XmlNodeType.Attribute: attOffset = 0; depth--; goto case XmlNodeType.Element; case XmlNodeType.Element: pipe.FreeTokens(1 + attCount); depth++; break; case XmlNodeType.EndElement : scope.PopScope(); pipe.FreeTokens(1); break; case XmlNodeType.Text : if (attOffset != 0) { // We are on text node inside of the attribute attOffset = 0; depth -= 2; goto case XmlNodeType.Element; } pipe.FreeTokens(1); break; case XmlNodeType.ProcessingInstruction : case XmlNodeType.Comment: case XmlNodeType.SignificantWhitespace: case XmlNodeType.Whitespace: pipe.FreeTokens(1); break; default : throw new InvalidProgramException("Internal Error: unexpected node type"); } Debug.Assert(attOffset == 0); Debug.Assert(readState == ReadState.Interactive); attCount = 0; // Step on next node pipe.Read(out nodeType, out qname, out value); if (nodeType == XmlNodeType.None) { SetUndefinedState(ReadState.EndOfFile); return false; } switch (nodeType) { case XmlNodeType.Element: for (attCount = 0; true; attCount ++) { XmlNodeType attType; QName attName; string attText; pipe.Read(out attType, out attName, out attText); if (attType != XmlNodeType.Attribute) { break; // We are done with attributes for this element } if (attName == writer.QNameXmlLang) { scope.AddLang(attText); } else if (attName == writer.QNameXmlSpace) { scope.AddSpace(attText); } } scope.PushScope(qname); break; case XmlNodeType.EndElement : qname = scope.Name; depth--; break; case XmlNodeType.Comment: case XmlNodeType.ProcessingInstruction : break; case XmlNodeType.Text: if (IsWhitespace(value)) { nodeType = XmlSpace == XmlSpace.Preserve ? XmlNodeType.SignificantWhitespace : XmlNodeType.Whitespace; } break; default : throw new InvalidProgramException("Internal Error: unexpected node type"); } return true; } public override int AttributeCount { get { return attCount; } } // issue: What should be BaseURI in XslReader? xslCompiledTransform.BaseURI ? public override string BaseURI { get { return string.Empty; } } public override XmlNameTable NameTable { get { return nameTable; } } public override int Depth { get { return depth; } } public override bool EOF { get { return ReadState == ReadState.EndOfFile; } } public override bool HasValue { get { return 0 != (/*HasValueBitmap:*/0x2659C & (1 << (int)nodeType)); } } public override XmlNodeType NodeType { get { return nodeType; } } // issue: We may want return true if element doesn't have content. Iteresting to know what public override bool IsEmptyElement { get { return false; } } public override string LocalName { get { return qname.Local; } } public override string NamespaceURI { get { return qname.NsUri; } } public override string Prefix { get { return qname.Prefix; } } public override string Value { get { return value; } } public override ReadState ReadState { get { return readState; } } public override void Close() { SetUndefinedState(ReadState.Closed); } public override string GetAttribute(int i) { if (IsInsideElement()) { if (0 <= i && i < attCount) { QName attName; string attValue; pipe.GetToken(i + 1, out attName, out attValue); return value; } } throw new ArgumentOutOfRangeException("i"); } static char[] qnameSeparator = new char[] { ':' }; private int FindAttribute(string name) { if (IsInsideElement()) { string prefix, local; string[] strings = name.Split(qnameSeparator, StringSplitOptions.None); switch(strings.Length) { case 1: prefix = string.Empty; local = name; break; case 2: if (strings[0].Length == 0) { return 0; // ":local-name" } prefix = strings[0]; local = strings[1]; break; default : return 0; } for (int i = 1; i <= attCount; i++) { QName attName; string attValue; pipe.GetToken(i, out attName, out attValue); if (attName.Local == local && attName.Prefix == prefix) { return i; } } } return 0; } public override string GetAttribute(string name) { int attNum = FindAttribute(name); if (attNum != 0) { return GetAttribute(attNum - 1); } return null; } public override string GetAttribute(string name, string ns) { if (IsInsideElement()) { for (int i = 1; i <= attCount; i++) { QName attName; string attValue; pipe.GetToken(i, out attName, out attValue); if (attName.Local == name && attName.NsUri == ns) { return attValue; } } } return null; } public override string LookupNamespace(string prefix) { return scope.LookupNamespace(prefix); } public override bool MoveToAttribute(string name) { int attNum = FindAttribute(name); if (attNum != 0) { MoveToAttribute(attNum - 1); return true; } return false; } public override void MoveToAttribute(int i) { if (IsInsideElement()) { if (0 <= i && i < attCount) { ChangeDepthToElement(); attOffset = i + 1; depth++; pipe.GetToken(attOffset, out qname, out value); nodeType = XmlNodeType.Attribute; } } throw new ArgumentOutOfRangeException("i"); } public override bool MoveToAttribute(string name, string ns) { if (IsInsideElement()) { for (int i = 1; i <= attCount; i ++) { QName attName; string attValue; pipe.GetToken(i , out attName, out attValue); if (attName.Local == name && attName.NsUri == ns) { ChangeDepthToElement(); nodeType = XmlNodeType.Attribute; attOffset = i; qname = attName; depth++; value = attValue; } } } return false; } private bool IsInsideElement() { return ( nodeType == XmlNodeType.Element || nodeType == XmlNodeType.Attribute || nodeType == XmlNodeType.Text && attOffset != 0 ); } private void ChangeDepthToElement() { switch (nodeType) { case XmlNodeType.Attribute : depth--; break; case XmlNodeType.Text : if (attOffset != 0) { depth -= 2; } break; } } public override bool MoveToElement() { if ( nodeType == XmlNodeType.Attribute || nodeType == XmlNodeType.Text && attOffset != 0 ) { ChangeDepthToElement(); nodeType = XmlNodeType.Element; attOffset = 0; pipe.GetToken(0, out qname, out value); return true; } return false; } public override bool MoveToFirstAttribute() { ChangeDepthToElement(); attOffset = 0; return MoveToNextAttribute(); } public override bool MoveToNextAttribute() { if (attOffset < attCount) { ChangeDepthToElement(); depth++; attOffset++; pipe.GetToken(attOffset, out qname, out value); nodeType = XmlNodeType.Attribute; return true; } return false; } public override bool ReadAttributeValue() { if (nodeType == XmlNodeType.Attribute) { nodeType = XmlNodeType.Text; depth++; return true; } return false; } public override void ResolveEntity() { throw new InvalidOperationException(); } public override string XmlLang { get { return scope.Lang; } } public override XmlSpace XmlSpace { get { return scope.Space; } } #endregion // XmlReader Implementation #region ------------------------------- Supporting classes ------------------------------ // QName is imutable. private class QName { string local; string nsUri; string prefix; public QName(string local, string nsUri, string prefix) { this.local = local ; this.nsUri = nsUri ; this.prefix = prefix; } public string Local { get { return this.local ; } } public string NsUri { get { return this.nsUri ; } } public string Prefix { get { return this.prefix; } } public override string ToString() { return (Prefix != null && Prefix.Length != 0) ? (Prefix + ':' + Local) : Local; } } // BufferWriter records information written to it in sequence of WriterEvents: [DebuggerDisplay("{NodeType}: name={Name}, Value={Value}")] private struct XmlToken { public XmlNodeType NodeType; public QName Name ; public string Value ; // it seams that it faster to set fields of structure in one call. // This trick is workaround of the C# limitation of declaring variable as ref to a struct. public static void Set(ref XmlToken evnt, XmlNodeType nodeType, QName name, string value) { evnt.NodeType = nodeType; evnt.Name = name ; evnt.Value = value ; } public static void Get(ref XmlToken evnt, out XmlNodeType nodeType, out QName name, out string value) { nodeType = evnt.NodeType; name = evnt.Name ; value = evnt.Value ; } } private class BufferWriter : XmlWriter { QNameTable qnameTable; TokenPipe pipe; string firstText; StringBuilder sbuilder; QName curAttribute; public QName QNameXmlSpace; public QName QNameXmlLang; public QName QNameEmpty; public BufferWriter(TokenPipe pipe, XmlNameTable nameTable) { this.pipe = pipe; this.qnameTable = new QNameTable(nameTable); this.sbuilder = new StringBuilder(); QNameXmlSpace = qnameTable.GetQName("space", NsXml, "xml"); QNameXmlLang = qnameTable.GetQName("lang" , NsXml, "xml"); QNameEmpty = qnameTable.GetQName("", "", ""); } public void Reset() { this.firstText = null; this.sbuilder.Length = 0; } private void AppendText(string text) { if (firstText == null) { Debug.Assert(sbuilder.Length == 0); firstText = text; } else if (sbuilder.Length == 0) { sbuilder.Append(firstText); } sbuilder.Append(text); } private string MergeText() { if (firstText == null) { return string.Empty; // There was no text ouptuted } if (sbuilder.Length != 0) { // merge content of sbuilder into firstText Debug.Assert(firstText != null); firstText = sbuilder.ToString(); sbuilder.Length = 0; } string result = firstText; firstText = null; return result; } private void FinishTextNode() { string text = MergeText(); if (text.Length != 0) { pipe.Write(XmlNodeType.Text, QNameEmpty, text); } } public override void WriteComment(string text) { FinishTextNode(); pipe.Write(XmlNodeType.Comment, QNameEmpty, text); } public override void WriteProcessingInstruction(string name, string text) { FinishTextNode(); pipe.Write(XmlNodeType.ProcessingInstruction, qnameTable.GetQName(name, string.Empty, string.Empty), text); } public override void WriteStartElement(string prefix, string name, string ns) { FinishTextNode(); pipe.Write(XmlNodeType.Element, qnameTable.GetQName(name, ns, prefix), ""); } public override void WriteEndElement() { FinishTextNode(); pipe.Write(XmlNodeType.EndElement, QNameEmpty, ""); } public override void WriteStartAttribute(string prefix, string name, string ns) { curAttribute = qnameTable.GetQName(name, ns, prefix); } public override void WriteEndAttribute(){ pipe.Write(XmlNodeType.Attribute, curAttribute, MergeText()); } public override void WriteString(string text) { AppendText(text); } public override void WriteFullEndElement() { WriteEndElement(); } public override void WriteRaw(string data) { WriteString(data); // In XslReader output we ignore disable-output-escaping } public override void Close() { FinishTextNode(); pipe.Close(); } public override void Flush() { } // XsltCompiledTransform never calls these methods and properties: public override void WriteStartDocument() { throw new NotSupportedException(); } public override void WriteStartDocument(bool standalone) { throw new NotSupportedException(); } public override void WriteEndDocument() { throw new NotSupportedException(); } public override void WriteDocType(string name, string pubid, string sysid, string subset) { throw new NotSupportedException(); } public override void WriteEntityRef(string name) { throw new NotSupportedException(); } public override void WriteCharEntity(char ch) { throw new NotSupportedException(); } public override void WriteSurrogateCharEntity(char lowChar, char highChar) { throw new NotSupportedException(); } public override void WriteWhitespace(string ws) { throw new NotSupportedException(); } public override void WriteChars(char[] buffer, int index, int count) { throw new NotSupportedException(); } public override void WriteRaw(char[] buffer, int index, int count) { throw new NotSupportedException(); } public override void WriteBase64(byte[] buffer, int index, int count) { throw new NotSupportedException(); } public override void WriteCData(string text) { throw new NotSupportedException(); } public override string LookupPrefix(string ns) { throw new NotSupportedException(); } public override WriteState WriteState { get { throw new NotSupportedException(); } } public override XmlSpace XmlSpace { get { throw new NotSupportedException(); } } public override string XmlLang { get { throw new NotSupportedException(); } } private class QNameTable { // This class atomizes QNames. XmlNameTable nameTable; Dictionary<string, List<QName>> qnames = new Dictionary<string, List<QName>>(); public QNameTable(XmlNameTable nameTable) { this.nameTable = nameTable; } public QName GetQName(string local, string nsUri, string prefix) { nsUri = nameTable.Add(nsUri ); prefix = nameTable.Add(prefix); List<QName> list; if (! qnames.TryGetValue(local, out list)) { list = new List<QName>(); qnames.Add(local, list); } else { foreach(QName qn in list) { Debug.Assert(qn.Local == local, "Atomization Failure: '" + local + "'"); if (RefEquals(qn.Prefix, prefix) && RefEquals(qn.NsUri, nsUri)) { return qn; } } } QName qname = new QName(nameTable.Add(local), nsUri, prefix); list.Add(qname); return qname; } private static string Atomize(string s, Dictionary<string, string> dic) { string atom; if (dic.TryGetValue(s, out atom)) { return atom; } else { dic.Add(s, s); return s; } } public static bool RefEquals(string strA, string strB) { Debug.Assert( ((object) strA == (object) strB) || ! String.Equals(strA, strB), "Atomization Failure: '" + strA + "'" ); return (object) strA == (object) strB; } } } private class ScopeManager { // We need the scope for the following reasons: // 1. Report QName on EndElement (local, nsUri, prefix ) // 2. Keep scope of Namespaces (null , nsUri, prefix ) // 3. Keep scope of xml:lang (null , lang , "lang" ) // 4. Keep scope of xml:space (null , space, "space") // On each StartElement we adding record(s) to the scope, // Its convinient to add QName last becuase in this case it will be directly available for EndElement static string atomLang = new String("lang" .ToCharArray()); static string atomSpace = new String("space".ToCharArray()); XmlNameTable nameTable; string stringEmpty; QName[] records = new QName[32]; int lastRecord; XmlSpace currentSpace; string currentLang; public ScopeManager(XmlNameTable nameTable) { this.nameTable = nameTable; this.stringEmpty = nameTable.Add(string.Empty); this.currentLang = this.stringEmpty; this.currentSpace = XmlSpace.None; Reset(); } public void Reset() { lastRecord = 0; records[lastRecord++] = new QName(null , nameTable.Add(NsXml), nameTable.Add("xml")); // xmlns:xml="http://www.w3.org/XML/1998/namespace" records[lastRecord++] = new QName(null , stringEmpty, stringEmpty); // xml="" records[lastRecord++] = new QName(stringEmpty, stringEmpty, stringEmpty); // -- lookup barier } public void PushScope(QName qname) { Debug.Assert(qname.Local != null, "Scope is Element Name"); AddRecord(qname); } public void PopScope() { Debug.Assert(records[lastRecord - 1].Local != null, "LastRecord in each scope is expected to be ElementName"); do { lastRecord--; Debug.Assert(0 < lastRecord, "Push/Pop balance error"); QName record = records[lastRecord-1]; if (record.Local != null) { break; // this record is Element QName } if ((object) record.Prefix == (object) atomLang) { currentLang = record.NsUri; } else if ((object) record.Prefix == (object) atomSpace) { currentSpace = Str2Space(record.NsUri); } } while (true); } private void AddRecord(QName qname) { if (lastRecord == records.Length) { QName[] temp = new QName[records.Length * 2]; records.CopyTo(temp, 0); records = temp; } records[lastRecord++] = qname; } public void AddNamespace(string prefix, string uri) { Debug.Assert(prefix != null); Debug.Assert(uri != null); prefix = nameTable.Add(prefix); uri = nameTable.Add(uri ); Debug.Assert( (object)prefix != (object)atomLang && (object)prefix != (object)atomSpace, "This assumption is important to distinct NsDecl from xml:space and xml:lang" ); AddRecord(new QName(null, uri, prefix)); } public void AddLang(string lang) { Debug.Assert(lang != null); lang = nameTable.Add(lang); if ((object) lang == (object) currentLang) { return; } AddRecord(new QName(null, currentLang, atomLang)); currentLang = lang; } public void AddSpace(string space) { Debug.Assert(space != null); XmlSpace xmlSpace = Str2Space(space); if (xmlSpace == XmlSpace.None) { throw new Exception("Unexpected value for xml:space attribute"); } if (xmlSpace == currentSpace) { return; } AddRecord(new QName(null, Space2Str(currentSpace), atomSpace)); currentSpace = xmlSpace; } private string Space2Str(XmlSpace space) { switch(space) { case XmlSpace.Preserve : return "preserve"; case XmlSpace.Default : return "default"; default : return "none"; } } private XmlSpace Str2Space(string space) { switch(space) { case "preserve" : return XmlSpace.Preserve; case "default" : return XmlSpace.Default; default : return XmlSpace.None; } } public string LookupNamespace(string prefix) { Debug.Assert(prefix != null); prefix = nameTable.Get(prefix); for (int i = lastRecord - 2; 0 <= i; i -- ) { QName record = records[i]; if (record.Local == null && (object)record.Prefix == (object)prefix) { return record.NsUri; } } return null; } public string Lang { get { return currentLang; } } public XmlSpace Space { get { return currentSpace; } } public QName Name { get { Debug.Assert(records[lastRecord-1].Local != null, "Element Name is expected"); return records[lastRecord - 1]; } } } private class TokenPipe { protected XmlToken[] buffer; protected int writePos; // position after last wrote token protected int readStartPos; // protected int readEndPos; // protected int mask; // used in TokenPipeMultiThread public TokenPipe(int bufferSize) { /*BuildMask*/ { if (bufferSize < 2) { bufferSize = defaultBufferSize; } // To make or round buffer work bufferSize should be == 2 power N and mask == bufferSize - 1 bufferSize--; mask = bufferSize; while ((bufferSize = bufferSize >> 1) != 0) { mask |= bufferSize; } } this.buffer = new XmlToken[mask + 1]; } public virtual void Reset() { readStartPos = readEndPos = writePos = 0; } public virtual void Write(XmlNodeType nodeType, QName name, string value) { Debug.Assert(writePos <= buffer.Length); if (writePos == buffer.Length) { XmlToken[] temp = new XmlToken[buffer.Length * 2]; buffer.CopyTo(temp, 0); buffer = temp; } Debug.Assert(writePos < buffer.Length); XmlToken.Set(ref buffer[writePos], nodeType, name, value); writePos++; } public virtual void WriteException(Exception e) { throw e; } public virtual void Read(out XmlNodeType nodeType, out QName name, out string value) { Debug.Assert(readEndPos < buffer.Length); XmlToken.Get(ref buffer[readEndPos], out nodeType, out name, out value); readEndPos++; } public virtual void FreeTokens(int num) { readStartPos += num; readEndPos = readStartPos; } public virtual void Close() { Write(XmlNodeType.None, null, null); } public virtual void GetToken(int attNum, out QName name, out string value) { Debug.Assert(0 <= attNum && attNum < readEndPos - readStartPos - 1); XmlNodeType nodeType; XmlToken.Get(ref buffer[readStartPos + attNum], out nodeType, out name, out value); Debug.Assert(nodeType == (attNum == 0 ? XmlNodeType.Element : XmlNodeType.Attribute), "We use GetToken() only to access parts of start element tag."); } } private class TokenPipeMultiThread : TokenPipe { Exception exception; public TokenPipeMultiThread(int bufferSize) : base(bufferSize) {} public override void Reset() { base.Reset(); exception = null; } private void ExpandBuffer() { // Buffer is too smal for this amount of attributes. Debug.Assert(writePos == readStartPos + buffer.Length, "no space to write next token"); Debug.Assert(writePos == readEndPos, "all tokens ware read"); int newMask = (mask << 1) | 1; XmlToken[] newBuffer = new XmlToken[newMask + 1]; for (int i = readStartPos; i < writePos; i ++) { newBuffer[i & newMask] = buffer[i & mask]; } buffer = newBuffer; mask = newMask; Debug.Assert(writePos < readStartPos + buffer.Length, "we should have now space to next write token"); } public override void Write(XmlNodeType nodeType, QName name, string value) { lock (this) { Debug.Assert(readEndPos <= writePos && writePos <= readStartPos + buffer.Length); if (writePos == readStartPos + buffer.Length) { if (writePos == readEndPos) { ExpandBuffer(); } else { Monitor.Wait(this); } } Debug.Assert(writePos < readStartPos + buffer.Length); XmlToken.Set(ref buffer[writePos & mask], nodeType, name, value); writePos++; if (readStartPos + buffer.Length <= writePos) { // This "if" is some heuristics, it may wrk or may not: // To minimize task switching we wakeup reader ony if we wrote enouph tokens. // So if reader already waits, let it sleep before we fill up the buffer. Monitor.Pulse(this); } } } public override void WriteException(Exception e) { lock (this) { exception = e; Monitor.Pulse(this); } } public override void Read(out XmlNodeType nodeType, out QName name, out string value) { lock (this) { Debug.Assert(readEndPos <= writePos && writePos <= readStartPos + buffer.Length); if (readEndPos == writePos) { if (readEndPos == readStartPos + buffer.Length) { ExpandBuffer(); Monitor.Pulse(this); } Monitor.Wait(this); } if (exception != null) { throw new XsltException("Exception happened during transformation. See inner exception for details:\n", exception); } } Debug.Assert(readEndPos < writePos); XmlToken.Get(ref buffer[readEndPos & mask], out nodeType, out name, out value); readEndPos++; } public override void FreeTokens(int num) { lock (this) { readStartPos += num; readEndPos = readStartPos; Monitor.Pulse(this); } } public override void Close() { Write(XmlNodeType.None, null, null); lock (this) { Monitor.Pulse(this); } } public override void GetToken(int attNum, out QName name, out string value) { Debug.Assert(0 <= attNum && attNum < readEndPos - readStartPos - 1); XmlNodeType nodeType; XmlToken.Get(ref buffer[(readStartPos + attNum) & mask], out nodeType, out name, out value); Debug.Assert(nodeType == (attNum == 0 ? XmlNodeType.Element : XmlNodeType.Attribute), "We use GetToken() only to access parts of start element tag."); } } #endregion ------------------------------- Supporting classes ------------------------------ } } |