// Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Text; using System.Xml; using System.Xml.Linq; using System.Xml.Schema; namespace OpenXmlPowerTools { public static class PtUtils { public static string NormalizeDirName(string dirName) { string d = dirName.Replace('\\', '/'); if (d[dirName.Length - 1] != '/' && d[dirName.Length - 1] != '\\') return d + "/"; return d; } public static string MakeValidXml(string p) { return p.Any(c => c < 0x20) ? p.Select(c => c < 0x20 ? string.Format("_{0:X}_", (int) c) : c.ToString()).StringConcatenate() : p; } public static void AddElementIfMissing(XDocument partXDoc, XElement existing, string newElement) { if (existing != null) return; XElement newXElement = XElement.Parse(newElement); newXElement.Attributes().Where(a => a.IsNamespaceDeclaration).Remove(); if (partXDoc.Root != null) partXDoc.Root.Add(newXElement); } } public class MhtParser { public string MimeVersion; public string ContentType; public MhtParserPart[] Parts; public class MhtParserPart { public string ContentLocation; public string ContentTransferEncoding; public string ContentType; public string CharSet; public string Text; public byte[] Binary; } public static MhtParser Parse(string src) { string mimeVersion = null; string contentType = null; string boundary = null; string[] lines = src.Split(new[] { Environment.NewLine }, StringSplitOptions.None); var priambleKeyWords = new[] { "MIME-VERSION:", "CONTENT-TYPE:", }; var priamble = lines.TakeWhile(l => { var s = l.ToUpper(); return priambleKeyWords.Any(pk => s.StartsWith(pk)); }).ToArray(); foreach (var item in priamble) { if (item.ToUpper().StartsWith("MIME-VERSION:")) mimeVersion = item.Substring("MIME-VERSION:".Length).Trim(); else if (item.ToUpper().StartsWith("CONTENT-TYPE:")) { var contentTypeLine = item.Substring("CONTENT-TYPE:".Length).Trim(); var spl = contentTypeLine.Split(';').Select(z => z.Trim()).ToArray(); foreach (var s in spl) { if (s.StartsWith("boundary")) { var begText = "boundary=\""; var begLen = begText.Length; boundary = s.Substring(begLen, s.Length - begLen - 1).TrimStart('-'); continue; } if (contentType == null) { contentType = s; continue; } throw new OpenXmlPowerToolsException("Unexpected content in MHTML"); } } } var grouped = lines .Skip(priamble.Length) .GroupAdjacent(l => { var b = l.TrimStart('-') == boundary; return b; }) .Where(g => g.Key == false) .ToArray(); var parts = grouped.Select(rp => { var partPriambleKeyWords = new[] { "CONTENT-LOCATION:", "CONTENT-TRANSFER-ENCODING:", "CONTENT-TYPE:", }; var partPriamble = rp.TakeWhile(l => { var s = l.ToUpper(); return partPriambleKeyWords.Any(pk => s.StartsWith(pk)); }).ToArray(); string contentLocation = null; string contentTransferEncoding = null; string partContentType = null; string partCharSet = null; byte[] partBinary = null; foreach (var item in partPriamble) { if (item.ToUpper().StartsWith("CONTENT-LOCATION:")) contentLocation = item.Substring("CONTENT-LOCATION:".Length).Trim(); else if (item.ToUpper().StartsWith("CONTENT-TRANSFER-ENCODING:")) contentTransferEncoding = item.Substring("CONTENT-TRANSFER-ENCODING:".Length).Trim(); else if (item.ToUpper().StartsWith("CONTENT-TYPE:")) partContentType = item.Substring("CONTENT-TYPE:".Length).Trim(); } var blankLinesAtBeginning = rp .Skip(partPriamble.Length) .TakeWhile(l => l == "") .Count(); var partText = rp .Skip(partPriamble.Length) .Skip(blankLinesAtBeginning) .Select(l => l + Environment.NewLine) .StringConcatenate(); if (partContentType != null && partContentType.Contains(";")) { string thisPartContentType = null; var spl = partContentType.Split(';').Select(s => s.Trim()).ToArray(); foreach (var s in spl) { if (s.StartsWith("charset")) { var begText = "charset=\""; var begLen = begText.Length; partCharSet = s.Substring(begLen, s.Length - begLen - 1); continue; } if (thisPartContentType == null) { thisPartContentType = s; continue; } throw new OpenXmlPowerToolsException("Unexpected content in MHTML"); } partContentType = thisPartContentType; } if (contentTransferEncoding != null && contentTransferEncoding.ToUpper() == "BASE64") { partBinary = Convert.FromBase64String(partText); } return new MhtParserPart() { ContentLocation = contentLocation, ContentTransferEncoding = contentTransferEncoding, ContentType = partContentType, CharSet = partCharSet, Text = partText, Binary = partBinary, }; }) .Where(p => p.ContentType != null) .ToArray(); return new MhtParser() { ContentType = contentType, MimeVersion = mimeVersion, Parts = parts, }; } } public class Normalizer { public static XDocument Normalize(XDocument source, XmlSchemaSet schema) { bool havePSVI = false; // validate, throw errors, add PSVI information if (schema != null) { source.Validate(schema, null, true); havePSVI = true; } return new XDocument( source.Declaration, source.Nodes().Select(n => { // Remove comments, processing instructions, and text nodes that are // children of XDocument. Only white space text nodes are allowed as // children of a document, so we can remove all text nodes. if (n is XComment || n is XProcessingInstruction || n is XText) return null; XElement e = n as XElement; if (e != null) return NormalizeElement(e, havePSVI); return n; } ) ); } public static bool DeepEqualsWithNormalization(XDocument doc1, XDocument doc2, XmlSchemaSet schemaSet) { XDocument d1 = Normalize(doc1, schemaSet); XDocument d2 = Normalize(doc2, schemaSet); return XNode.DeepEquals(d1, d2); } private static IEnumerable NormalizeAttributes(XElement element, bool havePSVI) { return element.Attributes() .Where(a => !a.IsNamespaceDeclaration && a.Name != Xsi.schemaLocation && a.Name != Xsi.noNamespaceSchemaLocation) .OrderBy(a => a.Name.NamespaceName) .ThenBy(a => a.Name.LocalName) .Select( a => { if (havePSVI) { var dt = a.GetSchemaInfo().SchemaType.TypeCode; switch (dt) { case XmlTypeCode.Boolean: return new XAttribute(a.Name, (bool)a); case XmlTypeCode.DateTime: return new XAttribute(a.Name, (DateTime)a); case XmlTypeCode.Decimal: return new XAttribute(a.Name, (decimal)a); case XmlTypeCode.Double: return new XAttribute(a.Name, (double)a); case XmlTypeCode.Float: return new XAttribute(a.Name, (float)a); case XmlTypeCode.HexBinary: case XmlTypeCode.Language: return new XAttribute(a.Name, ((string)a).ToLower()); } } return a; } ); } private static XNode NormalizeNode(XNode node, bool havePSVI) { // trim comments and processing instructions from normalized tree if (node is XComment || node is XProcessingInstruction) return null; XElement e = node as XElement; if (e != null) return NormalizeElement(e, havePSVI); // Only thing left is XCData and XText, so clone them return node; } private static XElement NormalizeElement(XElement element, bool havePSVI) { if (havePSVI) { var dt = element.GetSchemaInfo(); switch (dt.SchemaType.TypeCode) { case XmlTypeCode.Boolean: return new XElement(element.Name, NormalizeAttributes(element, havePSVI), (bool)element); case XmlTypeCode.DateTime: return new XElement(element.Name, NormalizeAttributes(element, havePSVI), (DateTime)element); case XmlTypeCode.Decimal: return new XElement(element.Name, NormalizeAttributes(element, havePSVI), (decimal)element); case XmlTypeCode.Double: return new XElement(element.Name, NormalizeAttributes(element, havePSVI), (double)element); case XmlTypeCode.Float: return new XElement(element.Name, NormalizeAttributes(element, havePSVI), (float)element); case XmlTypeCode.HexBinary: case XmlTypeCode.Language: return new XElement(element.Name, NormalizeAttributes(element, havePSVI), ((string)element).ToLower()); default: return new XElement(element.Name, NormalizeAttributes(element, havePSVI), element.Nodes().Select(n => NormalizeNode(n, havePSVI)) ); } } else { return new XElement(element.Name, NormalizeAttributes(element, havePSVI), element.Nodes().Select(n => NormalizeNode(n, havePSVI)) ); } } } public class FileUtils { public static DirectoryInfo GetDateTimeStampedDirectoryInfo(string prefix) { DateTime now = DateTime.Now; string dirName = prefix + string.Format("-{0:00}-{1:00}-{2:00}-{3:00}{4:00}{5:00}", now.Year - 2000, now.Month, now.Day, now.Hour, now.Minute, now.Second); return new DirectoryInfo(dirName); } public static FileInfo GetDateTimeStampedFileInfo(string prefix, string suffix) { DateTime now = DateTime.Now; string fileName = prefix + string.Format("-{0:00}-{1:00}-{2:00}-{3:00}{4:00}{5:00}", now.Year - 2000, now.Month, now.Day, now.Hour, now.Minute, now.Second) + suffix; return new FileInfo(fileName); } public static void ThreadSafeCreateDirectory(DirectoryInfo dir) { while (true) { if (dir.Exists) break; try { dir.Create(); break; } catch (IOException) { System.Threading.Thread.Sleep(50); } } } public static void ThreadSafeCopy(FileInfo sourceFile, FileInfo destFile) { while (true) { if (destFile.Exists) break; try { File.Copy(sourceFile.FullName, destFile.FullName); break; } catch (IOException) { System.Threading.Thread.Sleep(50); } } } public static void ThreadSafeCreateEmptyTextFileIfNotExist(FileInfo file) { while (true) { if (file.Exists) break; try { File.WriteAllText(file.FullName, ""); break; } catch (IOException) { System.Threading.Thread.Sleep(50); } } } #if !NET35 internal static void ThreadSafeAppendAllLines(FileInfo file, string[] strings) { while (true) { try { File.AppendAllLines(file.FullName, strings); break; } catch (IOException) { System.Threading.Thread.Sleep(50); } } } #endif public static List GetFilesRecursive(DirectoryInfo dir, string searchPattern) { var fileList = new List(); GetFilesRecursiveInternal(dir, searchPattern, fileList); return fileList; } private static void GetFilesRecursiveInternal(DirectoryInfo dir, string searchPattern, List fileList) { fileList.AddRange(dir.GetFiles(searchPattern).Select(file => file.FullName)); foreach (DirectoryInfo subdir in dir.GetDirectories()) GetFilesRecursiveInternal(subdir, searchPattern, fileList); } public static List GetFilesRecursive(DirectoryInfo dir) { var fileList = new List(); GetFilesRecursiveInternal(dir, fileList); return fileList; } private static void GetFilesRecursiveInternal(DirectoryInfo dir, List fileList) { fileList.AddRange(dir.GetFiles().Select(file => file.FullName)); foreach (DirectoryInfo subdir in dir.GetDirectories()) GetFilesRecursiveInternal(subdir, fileList); } public static void CopyStream(Stream source, Stream target) { const int bufSize = 0x4096; var buf = new byte[bufSize]; int bytesRead; while ((bytesRead = source.Read(buf, 0, bufSize)) > 0) target.Write(buf, 0, bytesRead); } } public static class PtExtensions { public static XElement GetXElement(this XmlNode node) { var xDoc = new XDocument(); using (XmlWriter xmlWriter = xDoc.CreateWriter()) node.WriteTo(xmlWriter); return xDoc.Root; } public static XmlNode GetXmlNode(this XElement element) { var xmlDoc = new XmlDocument(); using (XmlReader xmlReader = element.CreateReader()) xmlDoc.Load(xmlReader); return xmlDoc; } public static XDocument GetXDocument(this XmlDocument document) { var xDoc = new XDocument(); using (XmlWriter xmlWriter = xDoc.CreateWriter()) document.WriteTo(xmlWriter); XmlDeclaration decl = document.ChildNodes.OfType().FirstOrDefault(); if (decl != null) xDoc.Declaration = new XDeclaration(decl.Version, decl.Encoding, decl.Standalone); return xDoc; } public static XmlDocument GetXmlDocument(this XDocument document) { var xmlDoc = new XmlDocument(); using (XmlReader xmlReader = document.CreateReader()) { xmlDoc.Load(xmlReader); if (document.Declaration != null) { XmlDeclaration dec = xmlDoc.CreateXmlDeclaration(document.Declaration.Version, document.Declaration.Encoding, document.Declaration.Standalone); xmlDoc.InsertBefore(dec, xmlDoc.FirstChild); } } return xmlDoc; } public static string StringConcatenate(this IEnumerable source) { return source.Aggregate( new StringBuilder(), (sb, s) => sb.Append(s), sb => sb.ToString()); } public static string StringConcatenate(this IEnumerable source, Func projectionFunc) { return source.Aggregate( new StringBuilder(), (sb, i) => sb.Append(projectionFunc(i)), sb => sb.ToString()); } public static IEnumerable PtZip( this IEnumerable first, IEnumerable second, Func func) { using (IEnumerator ie1 = first.GetEnumerator()) using (IEnumerator ie2 = second.GetEnumerator()) while (ie1.MoveNext() && ie2.MoveNext()) yield return func(ie1.Current, ie2.Current); } public static IEnumerable> GroupAdjacent( this IEnumerable source, Func keySelector) { TKey last = default(TKey); var haveLast = false; var list = new List(); foreach (TSource s in source) { TKey k = keySelector(s); if (haveLast) { if (!k.Equals(last)) { yield return new GroupOfAdjacent(list, last); list = new List { s }; last = k; } else { list.Add(s); last = k; } } else { list.Add(s); last = k; haveLast = true; } } if (haveLast) yield return new GroupOfAdjacent(list, last); } private static void InitializeSiblingsReverseDocumentOrder(XElement element) { XElement prev = null; foreach (XElement e in element.Elements()) { e.AddAnnotation(new SiblingsReverseDocumentOrderInfo { PreviousSibling = prev }); prev = e; } } [SuppressMessage("ReSharper", "PossibleNullReferenceException")] public static IEnumerable SiblingsBeforeSelfReverseDocumentOrder( this XElement element) { if (element.Annotation() == null) InitializeSiblingsReverseDocumentOrder(element.Parent); XElement current = element; while (true) { XElement previousElement = current .Annotation() .PreviousSibling; if (previousElement == null) yield break; yield return previousElement; current = previousElement; } } private static void InitializeDescendantsReverseDocumentOrder(XElement element) { XElement prev = null; foreach (XElement e in element.Descendants()) { e.AddAnnotation(new DescendantsReverseDocumentOrderInfo { PreviousElement = prev }); prev = e; } } [SuppressMessage("ReSharper", "PossibleNullReferenceException")] public static IEnumerable DescendantsBeforeSelfReverseDocumentOrder( this XElement element) { if (element.Annotation() == null) InitializeDescendantsReverseDocumentOrder(element.AncestorsAndSelf().Last()); XElement current = element; while (true) { XElement previousElement = current .Annotation() .PreviousElement; if (previousElement == null) yield break; yield return previousElement; current = previousElement; } } private static void InitializeDescendantsTrimmedReverseDocumentOrder(XElement element, XName trimName) { XElement prev = null; foreach (XElement e in element.DescendantsTrimmed(trimName)) { e.AddAnnotation(new DescendantsTrimmedReverseDocumentOrderInfo { PreviousElement = prev }); prev = e; } } [SuppressMessage("ReSharper", "PossibleNullReferenceException")] public static IEnumerable DescendantsTrimmedBeforeSelfReverseDocumentOrder( this XElement element, XName trimName) { if (element.Annotation() == null) { XElement ances = element.AncestorsAndSelf(W.txbxContent).FirstOrDefault() ?? element.AncestorsAndSelf().Last(); InitializeDescendantsTrimmedReverseDocumentOrder(ances, trimName); } XElement current = element; while (true) { XElement previousElement = current .Annotation() .PreviousElement; if (previousElement == null) yield break; yield return previousElement; current = previousElement; } } public static string ToStringNewLineOnAttributes(this XElement element) { var settings = new XmlWriterSettings { Indent = true, OmitXmlDeclaration = true, NewLineOnAttributes = true }; var stringBuilder = new StringBuilder(); using (var stringWriter = new StringWriter(stringBuilder)) using (XmlWriter xmlWriter = XmlWriter.Create(stringWriter, settings)) element.WriteTo(xmlWriter); return stringBuilder.ToString(); } public static IEnumerable DescendantsTrimmed(this XElement element, XName trimName) { return DescendantsTrimmed(element, e => e.Name == trimName); } public static IEnumerable DescendantsTrimmed(this XElement element, Func predicate) { Stack> iteratorStack = new Stack>(); iteratorStack.Push(element.Elements().GetEnumerator()); while (iteratorStack.Count > 0) { while (iteratorStack.Peek().MoveNext()) { XElement currentXElement = iteratorStack.Peek().Current; if (predicate(currentXElement)) { yield return currentXElement; continue; } yield return currentXElement; iteratorStack.Push(currentXElement.Elements().GetEnumerator()); } iteratorStack.Pop(); } } public static IEnumerable Rollup( this IEnumerable source, TResult seed, Func projection) { TResult nextSeed = seed; foreach (TSource src in source) { TResult projectedValue = projection(src, nextSeed); nextSeed = projectedValue; yield return projectedValue; } } public static IEnumerable Rollup( this IEnumerable source, TResult seed, Func projection) { TResult nextSeed = seed; int index = 0; foreach (TSource src in source) { TResult projectedValue = projection(src, nextSeed, index++); nextSeed = projectedValue; yield return projectedValue; } } public static IEnumerable SequenceAt(this TSource[] source, int index) { int i = index; while (i < source.Length) yield return source[i++]; } public static IEnumerable SkipLast(this IEnumerable source, int count) { var saveList = new Queue(); var saved = 0; foreach (T item in source) { if (saved < count) { saveList.Enqueue(item); ++saved; continue; } saveList.Enqueue(item); yield return saveList.Dequeue(); } } public static bool? ToBoolean(this XAttribute a) { if (a == null) return null; string s = ((string) a).ToLower(); switch (s) { case "1": return true; case "0": return false; case "true": return true; case "false": return false; case "on": return true; case "off": return false; default: return (bool) a; } } private static string GetQName(XElement xe) { string prefix = xe.GetPrefixOfNamespace(xe.Name.Namespace); if (xe.Name.Namespace == XNamespace.None || prefix == null) return xe.Name.LocalName; return prefix + ":" + xe.Name.LocalName; } private static string GetQName(XAttribute xa) { string prefix = xa.Parent != null ? xa.Parent.GetPrefixOfNamespace(xa.Name.Namespace) : null; if (xa.Name.Namespace == XNamespace.None || prefix == null) return xa.Name.ToString(); return prefix + ":" + xa.Name.LocalName; } private static string NameWithPredicate(XElement el) { if (el.Parent != null && el.Parent.Elements(el.Name).Count() != 1) return GetQName(el) + "[" + (el.ElementsBeforeSelf(el.Name).Count() + 1) + "]"; else return GetQName(el); } public static string StrCat(this IEnumerable source, string separator) { return source.Aggregate(new StringBuilder(), (sb, i) => sb .Append(i.ToString()) .Append(separator), s => s.ToString()); } public static string GetXPath(this XObject xobj) { if (xobj.Parent == null) { var doc = xobj as XDocument; if (doc != null) return "."; var el = xobj as XElement; if (el != null) return "/" + NameWithPredicate(el); var xt = xobj as XText; if (xt != null) return null; // //the following doesn't work because the XPath data //model doesn't include white space text nodes that //are children of the document. // //return // "/" + // ( // xt // .Document // .Nodes() // .OfType() // .Count() != 1 ? // "text()[" + // (xt // .NodesBeforeSelf() // .OfType() // .Count() + 1) + "]" : // "text()" // ); // var com = xobj as XComment; if (com != null && com.Document != null) return "/" + ( com .Document .Nodes() .OfType() .Count() != 1 ? "comment()[" + (com .NodesBeforeSelf() .OfType() .Count() + 1) + "]" : "comment()" ); var pi = xobj as XProcessingInstruction; if (pi != null) return "/" + ( pi.Document != null && pi.Document.Nodes().OfType().Count() != 1 ? "processing-instruction()[" + (pi .NodesBeforeSelf() .OfType() .Count() + 1) + "]" : "processing-instruction()" ); return null; } else { var el = xobj as XElement; if (el != null) { return "/" + el .Ancestors() .InDocumentOrder() .Select(e => NameWithPredicate(e)) .StrCat("/") + NameWithPredicate(el); } var at = xobj as XAttribute; if (at != null && at.Parent != null) return "/" + at .Parent .AncestorsAndSelf() .InDocumentOrder() .Select(e => NameWithPredicate(e)) .StrCat("/") + "@" + GetQName(at); var com = xobj as XComment; if (com != null && com.Parent != null) return "/" + com .Parent .AncestorsAndSelf() .InDocumentOrder() .Select(e => NameWithPredicate(e)) .StrCat("/") + ( com .Parent .Nodes() .OfType() .Count() != 1 ? "comment()[" + (com .NodesBeforeSelf() .OfType() .Count() + 1) + "]" : "comment()" ); var cd = xobj as XCData; if (cd != null && cd.Parent != null) return "/" + cd .Parent .AncestorsAndSelf() .InDocumentOrder() .Select(e => NameWithPredicate(e)) .StrCat("/") + ( cd .Parent .Nodes() .OfType() .Count() != 1 ? "text()[" + (cd .NodesBeforeSelf() .OfType() .Count() + 1) + "]" : "text()" ); var tx = xobj as XText; if (tx != null && tx.Parent != null) return "/" + tx .Parent .AncestorsAndSelf() .InDocumentOrder() .Select(e => NameWithPredicate(e)) .StrCat("/") + ( tx .Parent .Nodes() .OfType() .Count() != 1 ? "text()[" + (tx .NodesBeforeSelf() .OfType() .Count() + 1) + "]" : "text()" ); var pi = xobj as XProcessingInstruction; if (pi != null && pi.Parent != null) return "/" + pi .Parent .AncestorsAndSelf() .InDocumentOrder() .Select(e => NameWithPredicate(e)) .StrCat("/") + ( pi .Parent .Nodes() .OfType() .Count() != 1 ? "processing-instruction()[" + (pi .NodesBeforeSelf() .OfType() .Count() + 1) + "]" : "processing-instruction()" ); return null; } } } public class ExecutableRunner { public class RunResults { public int ExitCode; public Exception RunException; public StringBuilder Output; public StringBuilder Error; } public static RunResults RunExecutable(string executablePath, string arguments, string workingDirectory) { RunResults runResults = new RunResults { Output = new StringBuilder(), Error = new StringBuilder(), RunException = null }; try { if (File.Exists(executablePath)) { using (Process proc = new Process()) { proc.StartInfo.FileName = executablePath; proc.StartInfo.Arguments = arguments; proc.StartInfo.WorkingDirectory = workingDirectory; proc.StartInfo.UseShellExecute = false; proc.StartInfo.RedirectStandardOutput = true; proc.StartInfo.RedirectStandardError = true; proc.OutputDataReceived += (o, e) => runResults.Output.Append(e.Data).Append(Environment.NewLine); proc.ErrorDataReceived += (o, e) => runResults.Error.Append(e.Data).Append(Environment.NewLine); proc.Start(); proc.BeginOutputReadLine(); proc.BeginErrorReadLine(); proc.WaitForExit(); runResults.ExitCode = proc.ExitCode; } } else { throw new ArgumentException("Invalid executable path.", "executablePath"); } } catch (Exception e) { runResults.RunException = e; } return runResults; } } public class SiblingsReverseDocumentOrderInfo { public XElement PreviousSibling; } public class DescendantsReverseDocumentOrderInfo { public XElement PreviousElement; } public class DescendantsTrimmedReverseDocumentOrderInfo { public XElement PreviousElement; } public class GroupOfAdjacent : IGrouping { public GroupOfAdjacent(List source, TKey key) { GroupList = source; Key = key; } public TKey Key { get; set; } private List GroupList { get; set; } IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable) this).GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable) GroupList).GetEnumerator(); } } public static class PtBucketTimer { private class BucketInfo { public int Count; public TimeSpan Time; } private static string LastBucket = null; private static DateTime LastTime; private static Dictionary Buckets; public static void Bucket(string bucket) { DateTime now = DateTime.Now; if (LastBucket != null) { TimeSpan d = now - LastTime; if (Buckets.ContainsKey(LastBucket)) { Buckets[LastBucket].Count = Buckets[LastBucket].Count + 1; Buckets[LastBucket].Time += d; } else { Buckets.Add(LastBucket, new BucketInfo() { Count = 1, Time = d, }); } } LastBucket = bucket; LastTime = now; } public static string DumpBucketsByKey() { StringBuilder sb = new StringBuilder(); foreach (var bucket in Buckets.OrderBy(b => b.Key)) { string ts = bucket.Value.Time.ToString(); if (ts.Contains('.')) ts = ts.Substring(0, ts.Length - 5); string s = bucket.Key.PadRight(60, '-') + " " + string.Format("{0:00000000}", bucket.Value.Count) + " " + ts; sb.Append(s + Environment.NewLine); } TimeSpan total = Buckets .Aggregate(TimeSpan.Zero, (t, b) => t + b.Value.Time); var tz = total.ToString(); sb.Append(string.Format("Total: {0}", tz.Substring(0, tz.Length - 5))); return sb.ToString(); } public static string DumpBucketsByTime() { StringBuilder sb = new StringBuilder(); foreach (var bucket in Buckets.OrderBy(b => b.Value.Time)) { string ts = bucket.Value.Time.ToString(); if (ts.Contains('.')) ts = ts.Substring(0, ts.Length - 5); string s = bucket.Key.PadRight(60, '-') + " " + string.Format("{0:00000000}", bucket.Value.Count) + " " + ts; sb.Append(s + Environment.NewLine); } TimeSpan total = Buckets .Aggregate(TimeSpan.Zero, (t, b) => t + b.Value.Time); var tz = total.ToString(); sb.Append(string.Format("Total: {0}", tz.Substring(0, tz.Length - 5))); return sb.ToString(); } public static void Init() { Buckets = new Dictionary(); } } public class XEntity : XText { public override void WriteTo(XmlWriter writer) { if (Value.Substring(0, 1) == "#") { string e = string.Format("&{0};", Value); writer.WriteRaw(e); } else writer.WriteEntityRef(Value); } public XEntity(string value) : base(value) { } } public static class Xsi { public static XNamespace xsi = "http://www.w3.org/2001/XMLSchema-instance"; public static XName schemaLocation = xsi + "schemaLocation"; public static XName noNamespaceSchemaLocation = xsi + "noNamespaceSchemaLocation"; } }