PtUtil.cs 44 KB

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