| /*************************************************************************** |
| |
| Copyright (c) Microsoft Corporation 2012-2015. |
| |
| This code is licensed using the Microsoft Public License (Ms-PL). The text of the license can be found here: |
| |
| http://www.microsoft.com/resources/sharedsource/licensingbasics/publiclicense.mspx |
| |
| Published at http://OpenXmlDeveloper.org |
| Resource Center and Documentation: http://openxmldeveloper.org/wiki/w/wiki/powertools-for-open-xml.aspx |
| |
| Developer: Eric White |
| Blog: http://www.ericwhite.com |
| Twitter: @EricWhiteDev |
| Email: eric@ericwhite.com |
| |
| Version: 2.6.00 |
| * Enhancements to support HtmlConverter.cs |
| |
| ***************************************************************************/ |
| |
| 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<XAttribute> 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<string> GetFilesRecursive(DirectoryInfo dir, string searchPattern) |
| { |
| var fileList = new List<string>(); |
| GetFilesRecursiveInternal(dir, searchPattern, fileList); |
| return fileList; |
| } |
| |
| private static void GetFilesRecursiveInternal(DirectoryInfo dir, string searchPattern, List<string> fileList) |
| { |
| fileList.AddRange(dir.GetFiles(searchPattern).Select(file => file.FullName)); |
| foreach (DirectoryInfo subdir in dir.GetDirectories()) |
| GetFilesRecursiveInternal(subdir, searchPattern, fileList); |
| } |
| |
| public static List<string> GetFilesRecursive(DirectoryInfo dir) |
| { |
| var fileList = new List<string>(); |
| GetFilesRecursiveInternal(dir, fileList); |
| return fileList; |
| } |
| |
| private static void GetFilesRecursiveInternal(DirectoryInfo dir, List<string> 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<XmlDeclaration>().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<string> source) |
| { |
| return source.Aggregate( |
| new StringBuilder(), |
| (sb, s) => sb.Append(s), |
| sb => sb.ToString()); |
| } |
| |
| public static string StringConcatenate<T>(this IEnumerable<T> source, Func<T, string> projectionFunc) |
| { |
| return source.Aggregate( |
| new StringBuilder(), |
| (sb, i) => sb.Append(projectionFunc(i)), |
| sb => sb.ToString()); |
| } |
| |
| public static IEnumerable<TResult> PtZip<TFirst, TSecond, TResult>( |
| this IEnumerable<TFirst> first, |
| IEnumerable<TSecond> second, |
| Func<TFirst, TSecond, TResult> func) |
| { |
| using (IEnumerator<TFirst> ie1 = first.GetEnumerator()) |
| using (IEnumerator<TSecond> ie2 = second.GetEnumerator()) |
| while (ie1.MoveNext() && ie2.MoveNext()) |
| yield return func(ie1.Current, ie2.Current); |
| } |
| |
| public static IEnumerable<IGrouping<TKey, TSource>> GroupAdjacent<TSource, TKey>( |
| this IEnumerable<TSource> source, |
| Func<TSource, TKey> keySelector) |
| { |
| TKey last = default(TKey); |
| var haveLast = false; |
| var list = new List<TSource>(); |
| |
| foreach (TSource s in source) |
| { |
| TKey k = keySelector(s); |
| if (haveLast) |
| { |
| if (!k.Equals(last)) |
| { |
| yield return new GroupOfAdjacent<TSource, TKey>(list, last); |
| |
| list = new List<TSource> { s }; |
| last = k; |
| } |
| else |
| { |
| list.Add(s); |
| last = k; |
| } |
| } |
| else |
| { |
| list.Add(s); |
| last = k; |
| haveLast = true; |
| } |
| } |
| if (haveLast) |
| yield return new GroupOfAdjacent<TSource, TKey>(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<XElement> SiblingsBeforeSelfReverseDocumentOrder( |
| this XElement element) |
| { |
| if (element.Annotation<SiblingsReverseDocumentOrderInfo>() == null) |
| InitializeSiblingsReverseDocumentOrder(element.Parent); |
| XElement current = element; |
| while (true) |
| { |
| XElement previousElement = current |
| .Annotation<SiblingsReverseDocumentOrderInfo>() |
| .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<XElement> DescendantsBeforeSelfReverseDocumentOrder( |
| this XElement element) |
| { |
| if (element.Annotation<DescendantsReverseDocumentOrderInfo>() == null) |
| InitializeDescendantsReverseDocumentOrder(element.AncestorsAndSelf().Last()); |
| XElement current = element; |
| while (true) |
| { |
| XElement previousElement = current |
| .Annotation<DescendantsReverseDocumentOrderInfo>() |
| .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<XElement> DescendantsTrimmedBeforeSelfReverseDocumentOrder( |
| this XElement element, XName trimName) |
| { |
| if (element.Annotation<DescendantsTrimmedReverseDocumentOrderInfo>() == null) |
| { |
| XElement ances = element.AncestorsAndSelf(W.txbxContent).FirstOrDefault() ?? |
| element.AncestorsAndSelf().Last(); |
| InitializeDescendantsTrimmedReverseDocumentOrder(ances, trimName); |
| } |
| |
| XElement current = element; |
| while (true) |
| { |
| XElement previousElement = current |
| .Annotation<DescendantsTrimmedReverseDocumentOrderInfo>() |
| .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<XElement> DescendantsTrimmed(this XElement element, |
| XName trimName) |
| { |
| return DescendantsTrimmed(element, e => e.Name == trimName); |
| } |
| |
| public static IEnumerable<XElement> DescendantsTrimmed(this XElement element, |
| Func<XElement, bool> predicate) |
| { |
| Stack<IEnumerator<XElement>> iteratorStack = new Stack<IEnumerator<XElement>>(); |
| 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<TResult> Rollup<TSource, TResult>( |
| this IEnumerable<TSource> source, |
| TResult seed, |
| Func<TSource, TResult, TResult> projection) |
| { |
| TResult nextSeed = seed; |
| foreach (TSource src in source) |
| { |
| TResult projectedValue = projection(src, nextSeed); |
| nextSeed = projectedValue; |
| yield return projectedValue; |
| } |
| } |
| |
| public static IEnumerable<TResult> Rollup<TSource, TResult>( |
| this IEnumerable<TSource> source, |
| TResult seed, |
| Func<TSource, TResult, int, TResult> 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<TSource> SequenceAt<TSource>(this TSource[] source, int index) |
| { |
| int i = index; |
| while (i < source.Length) |
| yield return source[i++]; |
| } |
| |
| public static IEnumerable<T> SkipLast<T>(this IEnumerable<T> source, int count) |
| { |
| var saveList = new Queue<T>(); |
| 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<T>(this IEnumerable<T> 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<XText>() |
| // .Count() != 1 ? |
| // "text()[" + |
| // (xt |
| // .NodesBeforeSelf() |
| // .OfType<XText>() |
| // .Count() + 1) + "]" : |
| // "text()" |
| // ); |
| // |
| var com = xobj as XComment; |
| if (com != null && com.Document != null) |
| return |
| "/" + |
| ( |
| com |
| .Document |
| .Nodes() |
| .OfType<XComment>() |
| .Count() != 1 |
| ? "comment()[" + |
| (com |
| .NodesBeforeSelf() |
| .OfType<XComment>() |
| .Count() + 1) + |
| "]" |
| : "comment()" |
| ); |
| |
| var pi = xobj as XProcessingInstruction; |
| if (pi != null) |
| return |
| "/" + |
| ( |
| pi.Document != null && pi.Document.Nodes().OfType<XProcessingInstruction>().Count() != 1 |
| ? "processing-instruction()[" + |
| (pi |
| .NodesBeforeSelf() |
| .OfType<XProcessingInstruction>() |
| .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<XComment>() |
| .Count() != 1 |
| ? "comment()[" + |
| (com |
| .NodesBeforeSelf() |
| .OfType<XComment>() |
| .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<XText>() |
| .Count() != 1 |
| ? "text()[" + |
| (cd |
| .NodesBeforeSelf() |
| .OfType<XText>() |
| .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<XText>() |
| .Count() != 1 |
| ? "text()[" + |
| (tx |
| .NodesBeforeSelf() |
| .OfType<XText>() |
| .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<XProcessingInstruction>() |
| .Count() != 1 |
| ? "processing-instruction()[" + |
| (pi |
| .NodesBeforeSelf() |
| .OfType<XProcessingInstruction>() |
| .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<TSource, TKey> : IGrouping<TKey, TSource> |
| { |
| public GroupOfAdjacent(List<TSource> source, TKey key) |
| { |
| GroupList = source; |
| Key = key; |
| } |
| |
| public TKey Key { get; set; } |
| private List<TSource> GroupList { get; set; } |
| |
| IEnumerator IEnumerable.GetEnumerator() |
| { |
| return ((IEnumerable<TSource>) this).GetEnumerator(); |
| } |
| |
| IEnumerator<TSource> IEnumerable<TSource>.GetEnumerator() |
| { |
| return ((IEnumerable<TSource>) 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<string, BucketInfo> 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<string, BucketInfo>(); |
| } |
| } |
| |
| 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"; |
| } |
| } |