PtUtil.cs 44 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248
  1. // Copyright (c) Microsoft. All rights reserved.
  2. // Licensed under the MIT license. See LICENSE file in the project root for full license information.
  3. using System;
  4. using System.Collections;
  5. using System.Collections.Generic;
  6. using System.Diagnostics;
  7. using System.Diagnostics.CodeAnalysis;
  8. using System.IO;
  9. using System.Linq;
  10. using System.Text;
  11. using System.Xml;
  12. using System.Xml.Linq;
  13. using System.Xml.Schema;
  14. namespace OpenXmlPowerTools
  15. {
  16. public static class PtUtils
  17. {
  18. public static string NormalizeDirName(string dirName)
  19. {
  20. string d = dirName.Replace('\\', '/');
  21. if (d[dirName.Length - 1] != '/' && d[dirName.Length - 1] != '\\')
  22. return d + "/";
  23. return d;
  24. }
  25. public static string MakeValidXml(string p)
  26. {
  27. return p.Any(c => c < 0x20)
  28. ? p.Select(c => c < 0x20 ? string.Format("_{0:X}_", (int) c) : c.ToString()).StringConcatenate()
  29. : p;
  30. }
  31. public static void AddElementIfMissing(XDocument partXDoc, XElement existing, string newElement)
  32. {
  33. if (existing != null)
  34. return;
  35. XElement newXElement = XElement.Parse(newElement);
  36. newXElement.Attributes().Where(a => a.IsNamespaceDeclaration).Remove();
  37. if (partXDoc.Root != null) partXDoc.Root.Add(newXElement);
  38. }
  39. }
  40. public class MhtParser
  41. {
  42. public string MimeVersion;
  43. public string ContentType;
  44. public MhtParserPart[] Parts;
  45. public class MhtParserPart
  46. {
  47. public string ContentLocation;
  48. public string ContentTransferEncoding;
  49. public string ContentType;
  50. public string CharSet;
  51. public string Text;
  52. public byte[] Binary;
  53. }
  54. public static MhtParser Parse(string src)
  55. {
  56. string mimeVersion = null;
  57. string contentType = null;
  58. string boundary = null;
  59. string[] lines = src.Split(new[] { Environment.NewLine }, StringSplitOptions.None);
  60. var priambleKeyWords = new[]
  61. {
  62. "MIME-VERSION:",
  63. "CONTENT-TYPE:",
  64. };
  65. var priamble = lines.TakeWhile(l =>
  66. {
  67. var s = l.ToUpper();
  68. return priambleKeyWords.Any(pk => s.StartsWith(pk));
  69. }).ToArray();
  70. foreach (var item in priamble)
  71. {
  72. if (item.ToUpper().StartsWith("MIME-VERSION:"))
  73. mimeVersion = item.Substring("MIME-VERSION:".Length).Trim();
  74. else if (item.ToUpper().StartsWith("CONTENT-TYPE:"))
  75. {
  76. var contentTypeLine = item.Substring("CONTENT-TYPE:".Length).Trim();
  77. var spl = contentTypeLine.Split(';').Select(z => z.Trim()).ToArray();
  78. foreach (var s in spl)
  79. {
  80. if (s.StartsWith("boundary"))
  81. {
  82. var begText = "boundary=\"";
  83. var begLen = begText.Length;
  84. boundary = s.Substring(begLen, s.Length - begLen - 1).TrimStart('-');
  85. continue;
  86. }
  87. if (contentType == null)
  88. {
  89. contentType = s;
  90. continue;
  91. }
  92. throw new OpenXmlPowerToolsException("Unexpected content in MHTML");
  93. }
  94. }
  95. }
  96. var grouped = lines
  97. .Skip(priamble.Length)
  98. .GroupAdjacent(l =>
  99. {
  100. var b = l.TrimStart('-') == boundary;
  101. return b;
  102. })
  103. .Where(g => g.Key == false)
  104. .ToArray();
  105. var parts = grouped.Select(rp =>
  106. {
  107. var partPriambleKeyWords = new[]
  108. {
  109. "CONTENT-LOCATION:",
  110. "CONTENT-TRANSFER-ENCODING:",
  111. "CONTENT-TYPE:",
  112. };
  113. var partPriamble = rp.TakeWhile(l =>
  114. {
  115. var s = l.ToUpper();
  116. return partPriambleKeyWords.Any(pk => s.StartsWith(pk));
  117. }).ToArray();
  118. string contentLocation = null;
  119. string contentTransferEncoding = null;
  120. string partContentType = null;
  121. string partCharSet = null;
  122. byte[] partBinary = null;
  123. foreach (var item in partPriamble)
  124. {
  125. if (item.ToUpper().StartsWith("CONTENT-LOCATION:"))
  126. contentLocation = item.Substring("CONTENT-LOCATION:".Length).Trim();
  127. else if (item.ToUpper().StartsWith("CONTENT-TRANSFER-ENCODING:"))
  128. contentTransferEncoding = item.Substring("CONTENT-TRANSFER-ENCODING:".Length).Trim();
  129. else if (item.ToUpper().StartsWith("CONTENT-TYPE:"))
  130. partContentType = item.Substring("CONTENT-TYPE:".Length).Trim();
  131. }
  132. var blankLinesAtBeginning = rp
  133. .Skip(partPriamble.Length)
  134. .TakeWhile(l => l == "")
  135. .Count();
  136. var partText = rp
  137. .Skip(partPriamble.Length)
  138. .Skip(blankLinesAtBeginning)
  139. .Select(l => l + Environment.NewLine)
  140. .StringConcatenate();
  141. if (partContentType != null && partContentType.Contains(";"))
  142. {
  143. string thisPartContentType = null;
  144. var spl = partContentType.Split(';').Select(s => s.Trim()).ToArray();
  145. foreach (var s in spl)
  146. {
  147. if (s.StartsWith("charset"))
  148. {
  149. var begText = "charset=\"";
  150. var begLen = begText.Length;
  151. partCharSet = s.Substring(begLen, s.Length - begLen - 1);
  152. continue;
  153. }
  154. if (thisPartContentType == null)
  155. {
  156. thisPartContentType = s;
  157. continue;
  158. }
  159. throw new OpenXmlPowerToolsException("Unexpected content in MHTML");
  160. }
  161. partContentType = thisPartContentType;
  162. }
  163. if (contentTransferEncoding != null && contentTransferEncoding.ToUpper() == "BASE64")
  164. {
  165. partBinary = Convert.FromBase64String(partText);
  166. }
  167. return new MhtParserPart()
  168. {
  169. ContentLocation = contentLocation,
  170. ContentTransferEncoding = contentTransferEncoding,
  171. ContentType = partContentType,
  172. CharSet = partCharSet,
  173. Text = partText,
  174. Binary = partBinary,
  175. };
  176. })
  177. .Where(p => p.ContentType != null)
  178. .ToArray();
  179. return new MhtParser()
  180. {
  181. ContentType = contentType,
  182. MimeVersion = mimeVersion,
  183. Parts = parts,
  184. };
  185. }
  186. }
  187. public class Normalizer
  188. {
  189. public static XDocument Normalize(XDocument source, XmlSchemaSet schema)
  190. {
  191. bool havePSVI = false;
  192. // validate, throw errors, add PSVI information
  193. if (schema != null)
  194. {
  195. source.Validate(schema, null, true);
  196. havePSVI = true;
  197. }
  198. return new XDocument(
  199. source.Declaration,
  200. source.Nodes().Select(n =>
  201. {
  202. // Remove comments, processing instructions, and text nodes that are
  203. // children of XDocument. Only white space text nodes are allowed as
  204. // children of a document, so we can remove all text nodes.
  205. if (n is XComment || n is XProcessingInstruction || n is XText)
  206. return null;
  207. XElement e = n as XElement;
  208. if (e != null)
  209. return NormalizeElement(e, havePSVI);
  210. return n;
  211. }
  212. )
  213. );
  214. }
  215. public static bool DeepEqualsWithNormalization(XDocument doc1, XDocument doc2,
  216. XmlSchemaSet schemaSet)
  217. {
  218. XDocument d1 = Normalize(doc1, schemaSet);
  219. XDocument d2 = Normalize(doc2, schemaSet);
  220. return XNode.DeepEquals(d1, d2);
  221. }
  222. private static IEnumerable<XAttribute> NormalizeAttributes(XElement element,
  223. bool havePSVI)
  224. {
  225. return element.Attributes()
  226. .Where(a => !a.IsNamespaceDeclaration &&
  227. a.Name != Xsi.schemaLocation &&
  228. a.Name != Xsi.noNamespaceSchemaLocation)
  229. .OrderBy(a => a.Name.NamespaceName)
  230. .ThenBy(a => a.Name.LocalName)
  231. .Select(
  232. a =>
  233. {
  234. if (havePSVI)
  235. {
  236. var dt = a.GetSchemaInfo().SchemaType.TypeCode;
  237. switch (dt)
  238. {
  239. case XmlTypeCode.Boolean:
  240. return new XAttribute(a.Name, (bool)a);
  241. case XmlTypeCode.DateTime:
  242. return new XAttribute(a.Name, (DateTime)a);
  243. case XmlTypeCode.Decimal:
  244. return new XAttribute(a.Name, (decimal)a);
  245. case XmlTypeCode.Double:
  246. return new XAttribute(a.Name, (double)a);
  247. case XmlTypeCode.Float:
  248. return new XAttribute(a.Name, (float)a);
  249. case XmlTypeCode.HexBinary:
  250. case XmlTypeCode.Language:
  251. return new XAttribute(a.Name,
  252. ((string)a).ToLower());
  253. }
  254. }
  255. return a;
  256. }
  257. );
  258. }
  259. private static XNode NormalizeNode(XNode node, bool havePSVI)
  260. {
  261. // trim comments and processing instructions from normalized tree
  262. if (node is XComment || node is XProcessingInstruction)
  263. return null;
  264. XElement e = node as XElement;
  265. if (e != null)
  266. return NormalizeElement(e, havePSVI);
  267. // Only thing left is XCData and XText, so clone them
  268. return node;
  269. }
  270. private static XElement NormalizeElement(XElement element, bool havePSVI)
  271. {
  272. if (havePSVI)
  273. {
  274. var dt = element.GetSchemaInfo();
  275. switch (dt.SchemaType.TypeCode)
  276. {
  277. case XmlTypeCode.Boolean:
  278. return new XElement(element.Name,
  279. NormalizeAttributes(element, havePSVI),
  280. (bool)element);
  281. case XmlTypeCode.DateTime:
  282. return new XElement(element.Name,
  283. NormalizeAttributes(element, havePSVI),
  284. (DateTime)element);
  285. case XmlTypeCode.Decimal:
  286. return new XElement(element.Name,
  287. NormalizeAttributes(element, havePSVI),
  288. (decimal)element);
  289. case XmlTypeCode.Double:
  290. return new XElement(element.Name,
  291. NormalizeAttributes(element, havePSVI),
  292. (double)element);
  293. case XmlTypeCode.Float:
  294. return new XElement(element.Name,
  295. NormalizeAttributes(element, havePSVI),
  296. (float)element);
  297. case XmlTypeCode.HexBinary:
  298. case XmlTypeCode.Language:
  299. return new XElement(element.Name,
  300. NormalizeAttributes(element, havePSVI),
  301. ((string)element).ToLower());
  302. default:
  303. return new XElement(element.Name,
  304. NormalizeAttributes(element, havePSVI),
  305. element.Nodes().Select(n => NormalizeNode(n, havePSVI))
  306. );
  307. }
  308. }
  309. else
  310. {
  311. return new XElement(element.Name,
  312. NormalizeAttributes(element, havePSVI),
  313. element.Nodes().Select(n => NormalizeNode(n, havePSVI))
  314. );
  315. }
  316. }
  317. }
  318. public class FileUtils
  319. {
  320. public static DirectoryInfo GetDateTimeStampedDirectoryInfo(string prefix)
  321. {
  322. DateTime now = DateTime.Now;
  323. string dirName =
  324. prefix +
  325. string.Format("-{0:00}-{1:00}-{2:00}-{3:00}{4:00}{5:00}", now.Year - 2000, now.Month, now.Day, now.Hour,
  326. now.Minute, now.Second);
  327. return new DirectoryInfo(dirName);
  328. }
  329. public static FileInfo GetDateTimeStampedFileInfo(string prefix, string suffix)
  330. {
  331. DateTime now = DateTime.Now;
  332. string fileName =
  333. prefix +
  334. string.Format("-{0:00}-{1:00}-{2:00}-{3:00}{4:00}{5:00}", now.Year - 2000, now.Month, now.Day, now.Hour,
  335. now.Minute, now.Second) +
  336. suffix;
  337. return new FileInfo(fileName);
  338. }
  339. public static void ThreadSafeCreateDirectory(DirectoryInfo dir)
  340. {
  341. while (true)
  342. {
  343. if (dir.Exists)
  344. break;
  345. try
  346. {
  347. dir.Create();
  348. break;
  349. }
  350. catch (IOException)
  351. {
  352. System.Threading.Thread.Sleep(50);
  353. }
  354. }
  355. }
  356. public static void ThreadSafeCopy(FileInfo sourceFile, FileInfo destFile)
  357. {
  358. while (true)
  359. {
  360. if (destFile.Exists)
  361. break;
  362. try
  363. {
  364. File.Copy(sourceFile.FullName, destFile.FullName);
  365. break;
  366. }
  367. catch (IOException)
  368. {
  369. System.Threading.Thread.Sleep(50);
  370. }
  371. }
  372. }
  373. public static void ThreadSafeCreateEmptyTextFileIfNotExist(FileInfo file)
  374. {
  375. while (true)
  376. {
  377. if (file.Exists)
  378. break;
  379. try
  380. {
  381. File.WriteAllText(file.FullName, "");
  382. break;
  383. }
  384. catch (IOException)
  385. {
  386. System.Threading.Thread.Sleep(50);
  387. }
  388. }
  389. }
  390. #if !NET35
  391. internal static void ThreadSafeAppendAllLines(FileInfo file, string[] strings)
  392. {
  393. while (true)
  394. {
  395. try
  396. {
  397. File.AppendAllLines(file.FullName, strings);
  398. break;
  399. }
  400. catch (IOException)
  401. {
  402. System.Threading.Thread.Sleep(50);
  403. }
  404. }
  405. }
  406. #endif
  407. public static List<string> GetFilesRecursive(DirectoryInfo dir, string searchPattern)
  408. {
  409. var fileList = new List<string>();
  410. GetFilesRecursiveInternal(dir, searchPattern, fileList);
  411. return fileList;
  412. }
  413. private static void GetFilesRecursiveInternal(DirectoryInfo dir, string searchPattern, List<string> fileList)
  414. {
  415. fileList.AddRange(dir.GetFiles(searchPattern).Select(file => file.FullName));
  416. foreach (DirectoryInfo subdir in dir.GetDirectories())
  417. GetFilesRecursiveInternal(subdir, searchPattern, fileList);
  418. }
  419. public static List<string> GetFilesRecursive(DirectoryInfo dir)
  420. {
  421. var fileList = new List<string>();
  422. GetFilesRecursiveInternal(dir, fileList);
  423. return fileList;
  424. }
  425. private static void GetFilesRecursiveInternal(DirectoryInfo dir, List<string> fileList)
  426. {
  427. fileList.AddRange(dir.GetFiles().Select(file => file.FullName));
  428. foreach (DirectoryInfo subdir in dir.GetDirectories())
  429. GetFilesRecursiveInternal(subdir, fileList);
  430. }
  431. public static void CopyStream(Stream source, Stream target)
  432. {
  433. const int bufSize = 0x4096;
  434. var buf = new byte[bufSize];
  435. int bytesRead;
  436. while ((bytesRead = source.Read(buf, 0, bufSize)) > 0)
  437. target.Write(buf, 0, bytesRead);
  438. }
  439. }
  440. public static class PtExtensions
  441. {
  442. public static XElement GetXElement(this XmlNode node)
  443. {
  444. var xDoc = new XDocument();
  445. using (XmlWriter xmlWriter = xDoc.CreateWriter())
  446. node.WriteTo(xmlWriter);
  447. return xDoc.Root;
  448. }
  449. public static XmlNode GetXmlNode(this XElement element)
  450. {
  451. var xmlDoc = new XmlDocument();
  452. using (XmlReader xmlReader = element.CreateReader())
  453. xmlDoc.Load(xmlReader);
  454. return xmlDoc;
  455. }
  456. public static XDocument GetXDocument(this XmlDocument document)
  457. {
  458. var xDoc = new XDocument();
  459. using (XmlWriter xmlWriter = xDoc.CreateWriter())
  460. document.WriteTo(xmlWriter);
  461. XmlDeclaration decl = document.ChildNodes.OfType<XmlDeclaration>().FirstOrDefault();
  462. if (decl != null)
  463. xDoc.Declaration = new XDeclaration(decl.Version, decl.Encoding, decl.Standalone);
  464. return xDoc;
  465. }
  466. public static XmlDocument GetXmlDocument(this XDocument document)
  467. {
  468. var xmlDoc = new XmlDocument();
  469. using (XmlReader xmlReader = document.CreateReader())
  470. {
  471. xmlDoc.Load(xmlReader);
  472. if (document.Declaration != null)
  473. {
  474. XmlDeclaration dec = xmlDoc.CreateXmlDeclaration(document.Declaration.Version,
  475. document.Declaration.Encoding, document.Declaration.Standalone);
  476. xmlDoc.InsertBefore(dec, xmlDoc.FirstChild);
  477. }
  478. }
  479. return xmlDoc;
  480. }
  481. public static string StringConcatenate(this IEnumerable<string> source)
  482. {
  483. return source.Aggregate(
  484. new StringBuilder(),
  485. (sb, s) => sb.Append(s),
  486. sb => sb.ToString());
  487. }
  488. public static string StringConcatenate<T>(this IEnumerable<T> source, Func<T, string> projectionFunc)
  489. {
  490. return source.Aggregate(
  491. new StringBuilder(),
  492. (sb, i) => sb.Append(projectionFunc(i)),
  493. sb => sb.ToString());
  494. }
  495. public static IEnumerable<TResult> PtZip<TFirst, TSecond, TResult>(
  496. this IEnumerable<TFirst> first,
  497. IEnumerable<TSecond> second,
  498. Func<TFirst, TSecond, TResult> func)
  499. {
  500. using (IEnumerator<TFirst> ie1 = first.GetEnumerator())
  501. using (IEnumerator<TSecond> ie2 = second.GetEnumerator())
  502. while (ie1.MoveNext() && ie2.MoveNext())
  503. yield return func(ie1.Current, ie2.Current);
  504. }
  505. public static IEnumerable<IGrouping<TKey, TSource>> GroupAdjacent<TSource, TKey>(
  506. this IEnumerable<TSource> source,
  507. Func<TSource, TKey> keySelector)
  508. {
  509. TKey last = default(TKey);
  510. var haveLast = false;
  511. var list = new List<TSource>();
  512. foreach (TSource s in source)
  513. {
  514. TKey k = keySelector(s);
  515. if (haveLast)
  516. {
  517. if (!k.Equals(last))
  518. {
  519. yield return new GroupOfAdjacent<TSource, TKey>(list, last);
  520. list = new List<TSource> { s };
  521. last = k;
  522. }
  523. else
  524. {
  525. list.Add(s);
  526. last = k;
  527. }
  528. }
  529. else
  530. {
  531. list.Add(s);
  532. last = k;
  533. haveLast = true;
  534. }
  535. }
  536. if (haveLast)
  537. yield return new GroupOfAdjacent<TSource, TKey>(list, last);
  538. }
  539. private static void InitializeSiblingsReverseDocumentOrder(XElement element)
  540. {
  541. XElement prev = null;
  542. foreach (XElement e in element.Elements())
  543. {
  544. e.AddAnnotation(new SiblingsReverseDocumentOrderInfo { PreviousSibling = prev });
  545. prev = e;
  546. }
  547. }
  548. [SuppressMessage("ReSharper", "PossibleNullReferenceException")]
  549. public static IEnumerable<XElement> SiblingsBeforeSelfReverseDocumentOrder(
  550. this XElement element)
  551. {
  552. if (element.Annotation<SiblingsReverseDocumentOrderInfo>() == null)
  553. InitializeSiblingsReverseDocumentOrder(element.Parent);
  554. XElement current = element;
  555. while (true)
  556. {
  557. XElement previousElement = current
  558. .Annotation<SiblingsReverseDocumentOrderInfo>()
  559. .PreviousSibling;
  560. if (previousElement == null)
  561. yield break;
  562. yield return previousElement;
  563. current = previousElement;
  564. }
  565. }
  566. private static void InitializeDescendantsReverseDocumentOrder(XElement element)
  567. {
  568. XElement prev = null;
  569. foreach (XElement e in element.Descendants())
  570. {
  571. e.AddAnnotation(new DescendantsReverseDocumentOrderInfo { PreviousElement = prev });
  572. prev = e;
  573. }
  574. }
  575. [SuppressMessage("ReSharper", "PossibleNullReferenceException")]
  576. public static IEnumerable<XElement> DescendantsBeforeSelfReverseDocumentOrder(
  577. this XElement element)
  578. {
  579. if (element.Annotation<DescendantsReverseDocumentOrderInfo>() == null)
  580. InitializeDescendantsReverseDocumentOrder(element.AncestorsAndSelf().Last());
  581. XElement current = element;
  582. while (true)
  583. {
  584. XElement previousElement = current
  585. .Annotation<DescendantsReverseDocumentOrderInfo>()
  586. .PreviousElement;
  587. if (previousElement == null)
  588. yield break;
  589. yield return previousElement;
  590. current = previousElement;
  591. }
  592. }
  593. private static void InitializeDescendantsTrimmedReverseDocumentOrder(XElement element, XName trimName)
  594. {
  595. XElement prev = null;
  596. foreach (XElement e in element.DescendantsTrimmed(trimName))
  597. {
  598. e.AddAnnotation(new DescendantsTrimmedReverseDocumentOrderInfo { PreviousElement = prev });
  599. prev = e;
  600. }
  601. }
  602. [SuppressMessage("ReSharper", "PossibleNullReferenceException")]
  603. public static IEnumerable<XElement> DescendantsTrimmedBeforeSelfReverseDocumentOrder(
  604. this XElement element, XName trimName)
  605. {
  606. if (element.Annotation<DescendantsTrimmedReverseDocumentOrderInfo>() == null)
  607. {
  608. XElement ances = element.AncestorsAndSelf(W.txbxContent).FirstOrDefault() ??
  609. element.AncestorsAndSelf().Last();
  610. InitializeDescendantsTrimmedReverseDocumentOrder(ances, trimName);
  611. }
  612. XElement current = element;
  613. while (true)
  614. {
  615. XElement previousElement = current
  616. .Annotation<DescendantsTrimmedReverseDocumentOrderInfo>()
  617. .PreviousElement;
  618. if (previousElement == null)
  619. yield break;
  620. yield return previousElement;
  621. current = previousElement;
  622. }
  623. }
  624. public static string ToStringNewLineOnAttributes(this XElement element)
  625. {
  626. var settings = new XmlWriterSettings
  627. {
  628. Indent = true,
  629. OmitXmlDeclaration = true,
  630. NewLineOnAttributes = true
  631. };
  632. var stringBuilder = new StringBuilder();
  633. using (var stringWriter = new StringWriter(stringBuilder))
  634. using (XmlWriter xmlWriter = XmlWriter.Create(stringWriter, settings))
  635. element.WriteTo(xmlWriter);
  636. return stringBuilder.ToString();
  637. }
  638. public static IEnumerable<XElement> DescendantsTrimmed(this XElement element,
  639. XName trimName)
  640. {
  641. return DescendantsTrimmed(element, e => e.Name == trimName);
  642. }
  643. public static IEnumerable<XElement> DescendantsTrimmed(this XElement element,
  644. Func<XElement, bool> predicate)
  645. {
  646. Stack<IEnumerator<XElement>> iteratorStack = new Stack<IEnumerator<XElement>>();
  647. iteratorStack.Push(element.Elements().GetEnumerator());
  648. while (iteratorStack.Count > 0)
  649. {
  650. while (iteratorStack.Peek().MoveNext())
  651. {
  652. XElement currentXElement = iteratorStack.Peek().Current;
  653. if (predicate(currentXElement))
  654. {
  655. yield return currentXElement;
  656. continue;
  657. }
  658. yield return currentXElement;
  659. iteratorStack.Push(currentXElement.Elements().GetEnumerator());
  660. }
  661. iteratorStack.Pop();
  662. }
  663. }
  664. public static IEnumerable<TResult> Rollup<TSource, TResult>(
  665. this IEnumerable<TSource> source,
  666. TResult seed,
  667. Func<TSource, TResult, TResult> projection)
  668. {
  669. TResult nextSeed = seed;
  670. foreach (TSource src in source)
  671. {
  672. TResult projectedValue = projection(src, nextSeed);
  673. nextSeed = projectedValue;
  674. yield return projectedValue;
  675. }
  676. }
  677. public static IEnumerable<TResult> Rollup<TSource, TResult>(
  678. this IEnumerable<TSource> source,
  679. TResult seed,
  680. Func<TSource, TResult, int, TResult> projection)
  681. {
  682. TResult nextSeed = seed;
  683. int index = 0;
  684. foreach (TSource src in source)
  685. {
  686. TResult projectedValue = projection(src, nextSeed, index++);
  687. nextSeed = projectedValue;
  688. yield return projectedValue;
  689. }
  690. }
  691. public static IEnumerable<TSource> SequenceAt<TSource>(this TSource[] source, int index)
  692. {
  693. int i = index;
  694. while (i < source.Length)
  695. yield return source[i++];
  696. }
  697. public static IEnumerable<T> SkipLast<T>(this IEnumerable<T> source, int count)
  698. {
  699. var saveList = new Queue<T>();
  700. var saved = 0;
  701. foreach (T item in source)
  702. {
  703. if (saved < count)
  704. {
  705. saveList.Enqueue(item);
  706. ++saved;
  707. continue;
  708. }
  709. saveList.Enqueue(item);
  710. yield return saveList.Dequeue();
  711. }
  712. }
  713. public static bool? ToBoolean(this XAttribute a)
  714. {
  715. if (a == null)
  716. return null;
  717. string s = ((string) a).ToLower();
  718. switch (s)
  719. {
  720. case "1":
  721. return true;
  722. case "0":
  723. return false;
  724. case "true":
  725. return true;
  726. case "false":
  727. return false;
  728. case "on":
  729. return true;
  730. case "off":
  731. return false;
  732. default:
  733. return (bool) a;
  734. }
  735. }
  736. private static string GetQName(XElement xe)
  737. {
  738. string prefix = xe.GetPrefixOfNamespace(xe.Name.Namespace);
  739. if (xe.Name.Namespace == XNamespace.None || prefix == null)
  740. return xe.Name.LocalName;
  741. return prefix + ":" + xe.Name.LocalName;
  742. }
  743. private static string GetQName(XAttribute xa)
  744. {
  745. string prefix = xa.Parent != null ? xa.Parent.GetPrefixOfNamespace(xa.Name.Namespace) : null;
  746. if (xa.Name.Namespace == XNamespace.None || prefix == null)
  747. return xa.Name.ToString();
  748. return prefix + ":" + xa.Name.LocalName;
  749. }
  750. private static string NameWithPredicate(XElement el)
  751. {
  752. if (el.Parent != null && el.Parent.Elements(el.Name).Count() != 1)
  753. return GetQName(el) + "[" +
  754. (el.ElementsBeforeSelf(el.Name).Count() + 1) + "]";
  755. else
  756. return GetQName(el);
  757. }
  758. public static string StrCat<T>(this IEnumerable<T> source,
  759. string separator)
  760. {
  761. return source.Aggregate(new StringBuilder(),
  762. (sb, i) => sb
  763. .Append(i.ToString())
  764. .Append(separator),
  765. s => s.ToString());
  766. }
  767. public static string GetXPath(this XObject xobj)
  768. {
  769. if (xobj.Parent == null)
  770. {
  771. var doc = xobj as XDocument;
  772. if (doc != null)
  773. return ".";
  774. var el = xobj as XElement;
  775. if (el != null)
  776. return "/" + NameWithPredicate(el);
  777. var xt = xobj as XText;
  778. if (xt != null)
  779. return null;
  780. //
  781. //the following doesn't work because the XPath data
  782. //model doesn't include white space text nodes that
  783. //are children of the document.
  784. //
  785. //return
  786. // "/" +
  787. // (
  788. // xt
  789. // .Document
  790. // .Nodes()
  791. // .OfType<XText>()
  792. // .Count() != 1 ?
  793. // "text()[" +
  794. // (xt
  795. // .NodesBeforeSelf()
  796. // .OfType<XText>()
  797. // .Count() + 1) + "]" :
  798. // "text()"
  799. // );
  800. //
  801. var com = xobj as XComment;
  802. if (com != null && com.Document != null)
  803. return
  804. "/" +
  805. (
  806. com
  807. .Document
  808. .Nodes()
  809. .OfType<XComment>()
  810. .Count() != 1
  811. ? "comment()[" +
  812. (com
  813. .NodesBeforeSelf()
  814. .OfType<XComment>()
  815. .Count() + 1) +
  816. "]"
  817. : "comment()"
  818. );
  819. var pi = xobj as XProcessingInstruction;
  820. if (pi != null)
  821. return
  822. "/" +
  823. (
  824. pi.Document != null && pi.Document.Nodes().OfType<XProcessingInstruction>().Count() != 1
  825. ? "processing-instruction()[" +
  826. (pi
  827. .NodesBeforeSelf()
  828. .OfType<XProcessingInstruction>()
  829. .Count() + 1) +
  830. "]"
  831. : "processing-instruction()"
  832. );
  833. return null;
  834. }
  835. else
  836. {
  837. var el = xobj as XElement;
  838. if (el != null)
  839. {
  840. return
  841. "/" +
  842. el
  843. .Ancestors()
  844. .InDocumentOrder()
  845. .Select(e => NameWithPredicate(e))
  846. .StrCat("/") +
  847. NameWithPredicate(el);
  848. }
  849. var at = xobj as XAttribute;
  850. if (at != null && at.Parent != null)
  851. return
  852. "/" +
  853. at
  854. .Parent
  855. .AncestorsAndSelf()
  856. .InDocumentOrder()
  857. .Select(e => NameWithPredicate(e))
  858. .StrCat("/") +
  859. "@" + GetQName(at);
  860. var com = xobj as XComment;
  861. if (com != null && com.Parent != null)
  862. return
  863. "/" +
  864. com
  865. .Parent
  866. .AncestorsAndSelf()
  867. .InDocumentOrder()
  868. .Select(e => NameWithPredicate(e))
  869. .StrCat("/") +
  870. (
  871. com
  872. .Parent
  873. .Nodes()
  874. .OfType<XComment>()
  875. .Count() != 1
  876. ? "comment()[" +
  877. (com
  878. .NodesBeforeSelf()
  879. .OfType<XComment>()
  880. .Count() + 1) + "]"
  881. : "comment()"
  882. );
  883. var cd = xobj as XCData;
  884. if (cd != null && cd.Parent != null)
  885. return
  886. "/" +
  887. cd
  888. .Parent
  889. .AncestorsAndSelf()
  890. .InDocumentOrder()
  891. .Select(e => NameWithPredicate(e))
  892. .StrCat("/") +
  893. (
  894. cd
  895. .Parent
  896. .Nodes()
  897. .OfType<XText>()
  898. .Count() != 1
  899. ? "text()[" +
  900. (cd
  901. .NodesBeforeSelf()
  902. .OfType<XText>()
  903. .Count() + 1) + "]"
  904. : "text()"
  905. );
  906. var tx = xobj as XText;
  907. if (tx != null && tx.Parent != null)
  908. return
  909. "/" +
  910. tx
  911. .Parent
  912. .AncestorsAndSelf()
  913. .InDocumentOrder()
  914. .Select(e => NameWithPredicate(e))
  915. .StrCat("/") +
  916. (
  917. tx
  918. .Parent
  919. .Nodes()
  920. .OfType<XText>()
  921. .Count() != 1
  922. ? "text()[" +
  923. (tx
  924. .NodesBeforeSelf()
  925. .OfType<XText>()
  926. .Count() + 1) + "]"
  927. : "text()"
  928. );
  929. var pi = xobj as XProcessingInstruction;
  930. if (pi != null && pi.Parent != null)
  931. return
  932. "/" +
  933. pi
  934. .Parent
  935. .AncestorsAndSelf()
  936. .InDocumentOrder()
  937. .Select(e => NameWithPredicate(e))
  938. .StrCat("/") +
  939. (
  940. pi
  941. .Parent
  942. .Nodes()
  943. .OfType<XProcessingInstruction>()
  944. .Count() != 1
  945. ? "processing-instruction()[" +
  946. (pi
  947. .NodesBeforeSelf()
  948. .OfType<XProcessingInstruction>()
  949. .Count() + 1) + "]"
  950. : "processing-instruction()"
  951. );
  952. return null;
  953. }
  954. }
  955. }
  956. public class ExecutableRunner
  957. {
  958. public class RunResults
  959. {
  960. public int ExitCode;
  961. public Exception RunException;
  962. public StringBuilder Output;
  963. public StringBuilder Error;
  964. }
  965. public static RunResults RunExecutable(string executablePath, string arguments, string workingDirectory)
  966. {
  967. RunResults runResults = new RunResults
  968. {
  969. Output = new StringBuilder(),
  970. Error = new StringBuilder(),
  971. RunException = null
  972. };
  973. try
  974. {
  975. if (File.Exists(executablePath))
  976. {
  977. using (Process proc = new Process())
  978. {
  979. proc.StartInfo.FileName = executablePath;
  980. proc.StartInfo.Arguments = arguments;
  981. proc.StartInfo.WorkingDirectory = workingDirectory;
  982. proc.StartInfo.UseShellExecute = false;
  983. proc.StartInfo.RedirectStandardOutput = true;
  984. proc.StartInfo.RedirectStandardError = true;
  985. proc.OutputDataReceived +=
  986. (o, e) => runResults.Output.Append(e.Data).Append(Environment.NewLine);
  987. proc.ErrorDataReceived +=
  988. (o, e) => runResults.Error.Append(e.Data).Append(Environment.NewLine);
  989. proc.Start();
  990. proc.BeginOutputReadLine();
  991. proc.BeginErrorReadLine();
  992. proc.WaitForExit();
  993. runResults.ExitCode = proc.ExitCode;
  994. }
  995. }
  996. else
  997. {
  998. throw new ArgumentException("Invalid executable path.", "executablePath");
  999. }
  1000. }
  1001. catch (Exception e)
  1002. {
  1003. runResults.RunException = e;
  1004. }
  1005. return runResults;
  1006. }
  1007. }
  1008. public class SiblingsReverseDocumentOrderInfo
  1009. {
  1010. public XElement PreviousSibling;
  1011. }
  1012. public class DescendantsReverseDocumentOrderInfo
  1013. {
  1014. public XElement PreviousElement;
  1015. }
  1016. public class DescendantsTrimmedReverseDocumentOrderInfo
  1017. {
  1018. public XElement PreviousElement;
  1019. }
  1020. public class GroupOfAdjacent<TSource, TKey> : IGrouping<TKey, TSource>
  1021. {
  1022. public GroupOfAdjacent(List<TSource> source, TKey key)
  1023. {
  1024. GroupList = source;
  1025. Key = key;
  1026. }
  1027. public TKey Key { get; set; }
  1028. private List<TSource> GroupList { get; set; }
  1029. IEnumerator IEnumerable.GetEnumerator()
  1030. {
  1031. return ((IEnumerable<TSource>) this).GetEnumerator();
  1032. }
  1033. IEnumerator<TSource> IEnumerable<TSource>.GetEnumerator()
  1034. {
  1035. return ((IEnumerable<TSource>) GroupList).GetEnumerator();
  1036. }
  1037. }
  1038. public static class PtBucketTimer
  1039. {
  1040. private class BucketInfo
  1041. {
  1042. public int Count;
  1043. public TimeSpan Time;
  1044. }
  1045. private static string LastBucket = null;
  1046. private static DateTime LastTime;
  1047. private static Dictionary<string, BucketInfo> Buckets;
  1048. public static void Bucket(string bucket)
  1049. {
  1050. DateTime now = DateTime.Now;
  1051. if (LastBucket != null)
  1052. {
  1053. TimeSpan d = now - LastTime;
  1054. if (Buckets.ContainsKey(LastBucket))
  1055. {
  1056. Buckets[LastBucket].Count = Buckets[LastBucket].Count + 1;
  1057. Buckets[LastBucket].Time += d;
  1058. }
  1059. else
  1060. {
  1061. Buckets.Add(LastBucket, new BucketInfo()
  1062. {
  1063. Count = 1,
  1064. Time = d,
  1065. });
  1066. }
  1067. }
  1068. LastBucket = bucket;
  1069. LastTime = now;
  1070. }
  1071. public static string DumpBucketsByKey()
  1072. {
  1073. StringBuilder sb = new StringBuilder();
  1074. foreach (var bucket in Buckets.OrderBy(b => b.Key))
  1075. {
  1076. string ts = bucket.Value.Time.ToString();
  1077. if (ts.Contains('.'))
  1078. ts = ts.Substring(0, ts.Length - 5);
  1079. string s = bucket.Key.PadRight(60, '-') + " " + string.Format("{0:00000000}", bucket.Value.Count) + " " + ts;
  1080. sb.Append(s + Environment.NewLine);
  1081. }
  1082. TimeSpan total = Buckets
  1083. .Aggregate(TimeSpan.Zero, (t, b) => t + b.Value.Time);
  1084. var tz = total.ToString();
  1085. sb.Append(string.Format("Total: {0}", tz.Substring(0, tz.Length - 5)));
  1086. return sb.ToString();
  1087. }
  1088. public static string DumpBucketsByTime()
  1089. {
  1090. StringBuilder sb = new StringBuilder();
  1091. foreach (var bucket in Buckets.OrderBy(b => b.Value.Time))
  1092. {
  1093. string ts = bucket.Value.Time.ToString();
  1094. if (ts.Contains('.'))
  1095. ts = ts.Substring(0, ts.Length - 5);
  1096. string s = bucket.Key.PadRight(60, '-') + " " + string.Format("{0:00000000}", bucket.Value.Count) + " " + ts;
  1097. sb.Append(s + Environment.NewLine);
  1098. }
  1099. TimeSpan total = Buckets
  1100. .Aggregate(TimeSpan.Zero, (t, b) => t + b.Value.Time);
  1101. var tz = total.ToString();
  1102. sb.Append(string.Format("Total: {0}", tz.Substring(0, tz.Length - 5)));
  1103. return sb.ToString();
  1104. }
  1105. public static void Init()
  1106. {
  1107. Buckets = new Dictionary<string, BucketInfo>();
  1108. }
  1109. }
  1110. public class XEntity : XText
  1111. {
  1112. public override void WriteTo(XmlWriter writer)
  1113. {
  1114. if (Value.Substring(0, 1) == "#")
  1115. {
  1116. string e = string.Format("&{0};", Value);
  1117. writer.WriteRaw(e);
  1118. }
  1119. else
  1120. writer.WriteEntityRef(Value);
  1121. }
  1122. public XEntity(string value) : base(value)
  1123. {
  1124. }
  1125. }
  1126. public static class Xsi
  1127. {
  1128. public static XNamespace xsi = "http://www.w3.org/2001/XMLSchema-instance";
  1129. public static XName schemaLocation = xsi + "schemaLocation";
  1130. public static XName noNamespaceSchemaLocation = xsi + "noNamespaceSchemaLocation";
  1131. }
  1132. }