You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

HelperFunctions.cs 32KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Globalization;
  4. using System.IO;
  5. using System.IO.Compression;
  6. using System.IO.Packaging;
  7. using System.Linq;
  8. using System.Reflection;
  9. using System.Security.Principal;
  10. using System.Text;
  11. using System.Xml.Linq;
  12. namespace Novacode
  13. {
  14. internal static class HelperFunctions
  15. {
  16. public const string DOCUMENT_DOCUMENTTYPE = "application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml";
  17. public const string TEMPLATE_DOCUMENTTYPE = "application/vnd.openxmlformats-officedocument.wordprocessingml.template.main+xml";
  18. public const string MACRO_DOCUMENTTYPE = "application/vnd.ms-word.document.macroEnabled.main+xml";
  19. /// <summary>
  20. /// List of restricted character in xml: [#x1-#x8] | [#xB-#xC] | [#xE-#x1F] | [#x7F-#x84] | [#x86-#x9F]
  21. /// See: https://www.w3.org/TR/xml11/#sec-xml11
  22. /// </summary>
  23. public static readonly char[] RestrictedXmlChar = new char[] {
  24. '\x1','\x2','\x3','\x4','\x5','\x6','\x7','\x8','\xb','\xc','\xe','\xf',
  25. '\x10','\x11','\x12','\x13','\x14','\x15','\x16','\x17','\x18','\x19','\x1a','\x1b','\x1c','\x1e','\x1f',
  26. '\x7f','\x80','\x81','\x82','\x83','\x84','\x86','\x87','\x88','\x89','\x8a','\x8b','\x8c','\x8d','\x8e','\x8f',
  27. '\x90','\x91','\x92','\x93','\x94','\x95','\x96','\x97','\x98','\x99','\x9a','\x9b','\x9c','\x9d','\x9e','\x9f'
  28. };
  29. public static bool IsNullOrWhiteSpace(this string value)
  30. {
  31. if (value == null) return true;
  32. return string.IsNullOrEmpty(value.Trim());
  33. }
  34. /// <summary>
  35. /// Checks whether 'toCheck' has all children that 'desired' has and values of 'val' attributes are the same
  36. /// </summary>
  37. /// <param name="desired"></param>
  38. /// <param name="toCheck"></param>
  39. /// <param name="fo">Matching options whether check if desired attributes are inder a, or a has exactly and only these attributes as b has.</param>
  40. /// <returns></returns>
  41. internal static bool ContainsEveryChildOf(XElement desired, XElement toCheck, MatchFormattingOptions fo)
  42. {
  43. foreach (XElement e in desired.Elements())
  44. {
  45. // If a formatting property has the same name and 'val' attribute's value, its considered to be equivalent.
  46. if (!toCheck.Elements(e.Name).Where(bElement => bElement.GetAttribute(XName.Get("val", DocX.w.NamespaceName)) == e.GetAttribute(XName.Get("val", DocX.w.NamespaceName))).Any())
  47. return false;
  48. }
  49. // If the formatting has to be exact, no additionaly formatting must exist.
  50. if (fo == MatchFormattingOptions.ExactMatch)
  51. return desired.Elements().Count() == toCheck.Elements().Count();
  52. return true;
  53. }
  54. internal static void CreateRelsPackagePart(DocX Document, Uri uri)
  55. {
  56. PackagePart pp = Document.package.CreatePart(uri, DocX.contentTypeApplicationRelationShipXml, CompressionOption.Maximum);
  57. using (TextWriter tw = new StreamWriter(new PackagePartStream(pp.GetStream())))
  58. {
  59. XDocument d = new XDocument
  60. (
  61. new XDeclaration("1.0", "UTF-8", "yes"),
  62. new XElement(XName.Get("Relationships", DocX.rel.NamespaceName))
  63. );
  64. var root = d.Root;
  65. d.Save(tw);
  66. }
  67. }
  68. internal static int GetSize(XElement Xml)
  69. {
  70. switch (Xml.Name.LocalName)
  71. {
  72. case "tab":
  73. return 1;
  74. case "br":
  75. return 1;
  76. case "t":
  77. goto case "delText";
  78. case "delText":
  79. return Xml.Value.Length;
  80. case "tr":
  81. goto case "br";
  82. case "tc":
  83. goto case "br";
  84. default:
  85. return 0;
  86. }
  87. }
  88. internal static string GetText(XElement e)
  89. {
  90. StringBuilder sb = new StringBuilder();
  91. GetTextRecursive(e, ref sb);
  92. return sb.ToString();
  93. }
  94. internal static void GetTextRecursive(XElement Xml, ref StringBuilder sb)
  95. {
  96. sb.Append(ToText(Xml));
  97. if (Xml.HasElements)
  98. foreach (XElement e in Xml.Elements())
  99. GetTextRecursive(e, ref sb);
  100. }
  101. internal static List<FormattedText> GetFormattedText(XElement e)
  102. {
  103. List<FormattedText> alist = new List<FormattedText>();
  104. GetFormattedTextRecursive(e, ref alist);
  105. return alist;
  106. }
  107. internal static void GetFormattedTextRecursive(XElement Xml, ref List<FormattedText> alist)
  108. {
  109. FormattedText ft = ToFormattedText(Xml);
  110. FormattedText last = null;
  111. if (ft != null)
  112. {
  113. if (alist.Count() > 0)
  114. last = alist.Last();
  115. if (last != null && last.CompareTo(ft) == 0)
  116. {
  117. // Update text of last entry.
  118. last.text += ft.text;
  119. }
  120. else
  121. {
  122. if (last != null)
  123. ft.index = last.index + last.text.Length;
  124. alist.Add(ft);
  125. }
  126. }
  127. if (Xml.HasElements)
  128. foreach (XElement e in Xml.Elements())
  129. GetFormattedTextRecursive(e, ref alist);
  130. }
  131. internal static FormattedText ToFormattedText(XElement e)
  132. {
  133. // The text representation of e.
  134. String text = ToText(e);
  135. if (text == String.Empty)
  136. return null;
  137. // e is a w:t element, it must exist inside a w:r element or a w:tabs, lets climb until we find it.
  138. while (!e.Name.Equals(XName.Get("r", DocX.w.NamespaceName)) &&
  139. !e.Name.Equals(XName.Get("tabs", DocX.w.NamespaceName)))
  140. e = e.Parent;
  141. // e is a w:r element, lets find the rPr element.
  142. XElement rPr = e.Element(XName.Get("rPr", DocX.w.NamespaceName));
  143. FormattedText ft = new FormattedText();
  144. ft.text = text;
  145. ft.index = 0;
  146. ft.formatting = null;
  147. // Return text with formatting.
  148. if (rPr != null)
  149. ft.formatting = Formatting.Parse(rPr);
  150. return ft;
  151. }
  152. internal static string ToText(XElement e)
  153. {
  154. switch (e.Name.LocalName)
  155. {
  156. case "tab":
  157. return "\t";
  158. case "br":
  159. return "\n";
  160. case "t":
  161. goto case "delText";
  162. case "delText":
  163. {
  164. if (e.Parent != null && e.Parent.Name.LocalName == "r")
  165. {
  166. XElement run = e.Parent;
  167. var rPr = run.Elements().FirstOrDefault(a => a.Name.LocalName == "rPr");
  168. if (rPr != null)
  169. {
  170. var caps = rPr.Elements().FirstOrDefault(a => a.Name.LocalName == "caps");
  171. if (caps != null)
  172. return e.Value.ToUpper();
  173. }
  174. }
  175. return e.Value;
  176. }
  177. case "tr":
  178. goto case "br";
  179. case "tc":
  180. goto case "tab";
  181. default: return "";
  182. }
  183. }
  184. internal static XElement CloneElement(XElement element)
  185. {
  186. return new XElement
  187. (
  188. element.Name,
  189. element.Attributes(),
  190. element.Nodes().Select
  191. (
  192. n =>
  193. {
  194. XElement e = n as XElement;
  195. if (e != null)
  196. return CloneElement(e);
  197. return n;
  198. }
  199. )
  200. );
  201. }
  202. internal static PackagePart GetMainDocumentPart(Package package)
  203. {
  204. return package.GetParts().Single(p => p.ContentType.Equals(DOCUMENT_DOCUMENTTYPE, StringComparison.CurrentCultureIgnoreCase) ||
  205. p.ContentType.Equals(TEMPLATE_DOCUMENTTYPE, StringComparison.CurrentCultureIgnoreCase) ||
  206. p.ContentType.Equals(MACRO_DOCUMENTTYPE, StringComparison.CurrentCultureIgnoreCase));
  207. }
  208. internal static PackagePart CreateOrGetSettingsPart(Package package)
  209. {
  210. PackagePart settingsPart;
  211. Uri settingsUri = new Uri("/word/settings.xml", UriKind.Relative);
  212. if (!package.PartExists(settingsUri))
  213. {
  214. settingsPart = package.CreatePart(settingsUri, "application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml", CompressionOption.Maximum);
  215. PackagePart mainDocumentPart = GetMainDocumentPart(package);
  216. mainDocumentPart.CreateRelationship(settingsUri, TargetMode.Internal, "http://schemas.openxmlformats.org/officeDocument/2006/relationships/settings");
  217. XDocument settings = XDocument.Parse
  218. (@"<?xml version='1.0' encoding='utf-8' standalone='yes'?>
  219. <w:settings xmlns:o='urn:schemas-microsoft-com:office:office' xmlns:r='http://schemas.openxmlformats.org/officeDocument/2006/relationships' xmlns:m='http://schemas.openxmlformats.org/officeDocument/2006/math' xmlns:v='urn:schemas-microsoft-com:vml' xmlns:w10='urn:schemas-microsoft-com:office:word' xmlns:w='http://schemas.openxmlformats.org/wordprocessingml/2006/main' xmlns:sl='http://schemas.openxmlformats.org/schemaLibrary/2006/main'>
  220. <w:zoom w:percent='100' />
  221. <w:defaultTabStop w:val='720' />
  222. <w:characterSpacingControl w:val='doNotCompress' />
  223. <w:compat />
  224. <w:rsids>
  225. <w:rsidRoot w:val='00217F62' />
  226. <w:rsid w:val='001915A3' />
  227. <w:rsid w:val='00217F62' />
  228. <w:rsid w:val='00A906D8' />
  229. <w:rsid w:val='00AB5A74' />
  230. <w:rsid w:val='00F071AE' />
  231. </w:rsids>
  232. <m:mathPr>
  233. <m:mathFont m:val='Cambria Math' />
  234. <m:brkBin m:val='before' />
  235. <m:brkBinSub m:val='--' />
  236. <m:smallFrac m:val='off' />
  237. <m:dispDef />
  238. <m:lMargin m:val='0' />
  239. <m:rMargin m:val='0' />
  240. <m:defJc m:val='centerGroup' />
  241. <m:wrapIndent m:val='1440' />
  242. <m:intLim m:val='subSup' />
  243. <m:naryLim m:val='undOvr' />
  244. </m:mathPr>
  245. <w:themeFontLang w:val='en-IE' w:bidi='ar-SA' />
  246. <w:clrSchemeMapping w:bg1='light1' w:t1='dark1' w:bg2='light2' w:t2='dark2' w:accent1='accent1' w:accent2='accent2' w:accent3='accent3' w:accent4='accent4' w:accent5='accent5' w:accent6='accent6' w:hyperlink='hyperlink' w:followedHyperlink='followedHyperlink' />
  247. <w:shapeDefaults>
  248. <o:shapedefaults v:ext='edit' spidmax='2050' />
  249. <o:shapelayout v:ext='edit'>
  250. <o:idmap v:ext='edit' data='1' />
  251. </o:shapelayout>
  252. </w:shapeDefaults>
  253. <w:decimalSymbol w:val='.' />
  254. <w:listSeparator w:val=',' />
  255. </w:settings>"
  256. );
  257. XElement themeFontLang = settings.Root.Element(XName.Get("themeFontLang", DocX.w.NamespaceName));
  258. themeFontLang.SetAttributeValue(XName.Get("val", DocX.w.NamespaceName), CultureInfo.CurrentCulture);
  259. // Save the settings document.
  260. using (TextWriter tw = new StreamWriter(new PackagePartStream(settingsPart.GetStream())))
  261. settings.Save(tw);
  262. }
  263. else
  264. settingsPart = package.GetPart(settingsUri);
  265. return settingsPart;
  266. }
  267. internal static void CreateCustomPropertiesPart(DocX document)
  268. {
  269. PackagePart customPropertiesPart = document.package.CreatePart(new Uri("/docProps/custom.xml", UriKind.Relative), "application/vnd.openxmlformats-officedocument.custom-properties+xml", CompressionOption.Maximum);
  270. XDocument customPropDoc = new XDocument
  271. (
  272. new XDeclaration("1.0", "UTF-8", "yes"),
  273. new XElement
  274. (
  275. XName.Get("Properties", DocX.customPropertiesSchema.NamespaceName),
  276. new XAttribute(XNamespace.Xmlns + "vt", DocX.customVTypesSchema)
  277. )
  278. );
  279. using (TextWriter tw = new StreamWriter(new PackagePartStream(customPropertiesPart.GetStream(FileMode.Create, FileAccess.Write))))
  280. customPropDoc.Save(tw, SaveOptions.None);
  281. document.package.CreateRelationship(customPropertiesPart.Uri, TargetMode.Internal, "http://schemas.openxmlformats.org/officeDocument/2006/relationships/custom-properties");
  282. }
  283. internal static XDocument DecompressXMLResource(string manifest_resource_name)
  284. {
  285. // XDocument to load the compressed Xml resource into.
  286. XDocument document;
  287. // Get a reference to the executing assembly.
  288. Assembly assembly = Assembly.GetExecutingAssembly();
  289. // Open a Stream to the embedded resource.
  290. Stream stream = assembly.GetManifestResourceStream(manifest_resource_name);
  291. // Decompress the embedded resource.
  292. using (GZipStream zip = new GZipStream(stream, CompressionMode.Decompress))
  293. {
  294. // Load this decompressed embedded resource into an XDocument using a TextReader.
  295. using (TextReader sr = new StreamReader(zip))
  296. {
  297. document = XDocument.Load(sr);
  298. }
  299. }
  300. // Return the decompressed Xml as an XDocument.
  301. return document;
  302. }
  303. /// <summary>
  304. /// If this document does not contain a /word/numbering.xml add the default one generated by Microsoft Word
  305. /// when the default bullet, numbered and multilevel lists are added to a blank document
  306. /// </summary>
  307. /// <param name="package"></param>
  308. /// <returns></returns>
  309. internal static XDocument AddDefaultNumberingXml(Package package)
  310. {
  311. XDocument numberingDoc;
  312. // Create the main document part for this package
  313. PackagePart wordNumbering = package.CreatePart(new Uri("/word/numbering.xml", UriKind.Relative), "application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml", CompressionOption.Maximum);
  314. numberingDoc = DecompressXMLResource("Novacode.Resources.numbering.xml.gz");
  315. // Save /word/numbering.xml
  316. using (TextWriter tw = new StreamWriter(new PackagePartStream(wordNumbering.GetStream(FileMode.Create, FileAccess.Write))))
  317. numberingDoc.Save(tw, SaveOptions.None);
  318. PackagePart mainDocumentPart = GetMainDocumentPart(package);
  319. mainDocumentPart.CreateRelationship(wordNumbering.Uri, TargetMode.Internal, "http://schemas.openxmlformats.org/officeDocument/2006/relationships/numbering");
  320. return numberingDoc;
  321. }
  322. /// <summary>
  323. /// If this document does not contain a /word/styles.xml add the default one generated by Microsoft Word.
  324. /// </summary>
  325. /// <param name="package"></param>
  326. /// <returns></returns>
  327. internal static XDocument AddDefaultStylesXml(Package package)
  328. {
  329. XDocument stylesDoc;
  330. // Create the main document part for this package
  331. PackagePart word_styles = package.CreatePart(new Uri("/word/styles.xml", UriKind.Relative), "application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml", CompressionOption.Maximum);
  332. stylesDoc = HelperFunctions.DecompressXMLResource("Novacode.Resources.default_styles.xml.gz");
  333. XElement lang = stylesDoc.Root.Element(XName.Get("docDefaults", DocX.w.NamespaceName)).Element(XName.Get("rPrDefault", DocX.w.NamespaceName)).Element(XName.Get("rPr", DocX.w.NamespaceName)).Element(XName.Get("lang", DocX.w.NamespaceName));
  334. lang.SetAttributeValue(XName.Get("val", DocX.w.NamespaceName), CultureInfo.CurrentCulture);
  335. // Save /word/styles.xml
  336. using (TextWriter tw = new StreamWriter(new PackagePartStream(word_styles.GetStream(FileMode.Create, FileAccess.Write))))
  337. stylesDoc.Save(tw, SaveOptions.None);
  338. PackagePart mainDocumentPart = GetMainDocumentPart(package);
  339. mainDocumentPart.CreateRelationship(word_styles.Uri, TargetMode.Internal, "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles");
  340. return stylesDoc;
  341. }
  342. internal static XElement CreateEdit(EditType t, DateTime edit_time, object content)
  343. {
  344. if (t == EditType.del)
  345. {
  346. foreach (object o in (IEnumerable<XElement>)content)
  347. {
  348. if (o is XElement)
  349. {
  350. XElement e = (o as XElement);
  351. IEnumerable<XElement> ts = e.DescendantsAndSelf(XName.Get("t", DocX.w.NamespaceName));
  352. for (int i = 0; i < ts.Count(); i++)
  353. {
  354. XElement text = ts.ElementAt(i);
  355. text.ReplaceWith(new XElement(DocX.w + "delText", text.Attributes(), text.Value));
  356. }
  357. }
  358. }
  359. }
  360. return
  361. (
  362. new XElement(DocX.w + t.ToString(),
  363. new XAttribute(DocX.w + "id", 0),
  364. new XAttribute(DocX.w + "author", WindowsIdentity.GetCurrent().Name),
  365. new XAttribute(DocX.w + "date", edit_time),
  366. content)
  367. );
  368. }
  369. internal static XElement CreateTable(int rowCount, int columnCount)
  370. {
  371. int[] columnWidths = new int[columnCount];
  372. for (int i = 0; i < columnCount; i++)
  373. {
  374. columnWidths[i] = 2310;
  375. }
  376. return CreateTable(rowCount, columnWidths);
  377. }
  378. internal static XElement CreateTable(int rowCount, int[] columnWidths)
  379. {
  380. XElement newTable =
  381. new XElement
  382. (
  383. XName.Get("tbl", DocX.w.NamespaceName),
  384. new XElement
  385. (
  386. XName.Get("tblPr", DocX.w.NamespaceName),
  387. new XElement(XName.Get("tblStyle", DocX.w.NamespaceName), new XAttribute(XName.Get("val", DocX.w.NamespaceName), "TableGrid")),
  388. new XElement(XName.Get("tblW", DocX.w.NamespaceName), new XAttribute(XName.Get("w", DocX.w.NamespaceName), "5000"), new XAttribute(XName.Get("type", DocX.w.NamespaceName), "auto")),
  389. new XElement(XName.Get("tblLook", DocX.w.NamespaceName), new XAttribute(XName.Get("val", DocX.w.NamespaceName), "04A0"))
  390. )
  391. );
  392. /*XElement tableGrid = new XElement(XName.Get("tblGrid", DocX.w.NamespaceName));
  393. for (int i = 0; i < columnWidths.Length; i++)
  394. tableGrid.Add(new XElement(XName.Get("gridCol", DocX.w.NamespaceName), new XAttribute(XName.Get("w", DocX.w.NamespaceName), XmlConvert.ToString(columnWidths[i]))));
  395. newTable.Add(tableGrid);*/
  396. for (int i = 0; i < rowCount; i++)
  397. {
  398. XElement row = new XElement(XName.Get("tr", DocX.w.NamespaceName));
  399. for (int j = 0; j < columnWidths.Length; j++)
  400. {
  401. XElement cell = CreateTableCell();
  402. row.Add(cell);
  403. }
  404. newTable.Add(row);
  405. }
  406. return newTable;
  407. }
  408. /// <summary>
  409. /// Create and return a cell of a table
  410. /// </summary>
  411. internal static XElement CreateTableCell(double w = 2310)
  412. {
  413. return new XElement
  414. (
  415. XName.Get("tc", DocX.w.NamespaceName),
  416. new XElement(XName.Get("tcPr", DocX.w.NamespaceName),
  417. new XElement(XName.Get("tcW", DocX.w.NamespaceName),
  418. new XAttribute(XName.Get("w", DocX.w.NamespaceName), w),
  419. new XAttribute(XName.Get("type", DocX.w.NamespaceName), "dxa"))),
  420. new XElement(XName.Get("p", DocX.w.NamespaceName),
  421. new XElement(XName.Get("pPr", DocX.w.NamespaceName)))
  422. );
  423. }
  424. internal static List CreateItemInList(List list, string listText, int level = 0, ListItemType listType = ListItemType.Numbered, int? startNumber = null, bool trackChanges = false, bool continueNumbering = false)
  425. {
  426. if (list.NumId == 0)
  427. {
  428. list.CreateNewNumberingNumId(level, listType, startNumber, continueNumbering);
  429. }
  430. if (listText != null) //I see no reason why you shouldn't be able to insert an empty element. It simplifies tasks such as populating an item from html.
  431. {
  432. var newParagraphSection = new XElement
  433. (
  434. XName.Get("p", DocX.w.NamespaceName),
  435. new XElement(XName.Get("pPr", DocX.w.NamespaceName),
  436. new XElement(XName.Get("numPr", DocX.w.NamespaceName),
  437. new XElement(XName.Get("ilvl", DocX.w.NamespaceName), new XAttribute(DocX.w + "val", level)),
  438. new XElement(XName.Get("numId", DocX.w.NamespaceName), new XAttribute(DocX.w + "val", list.NumId)))),
  439. new XElement(XName.Get("r", DocX.w.NamespaceName), new XElement(XName.Get("t", DocX.w.NamespaceName), listText))
  440. );
  441. if (trackChanges)
  442. newParagraphSection = CreateEdit(EditType.ins, DateTime.Now, newParagraphSection);
  443. if (startNumber == null)
  444. {
  445. list.AddItem(new Paragraph(list.Document, newParagraphSection, 0, ContainerType.Paragraph));
  446. }
  447. else
  448. {
  449. list.AddItemWithStartValue(new Paragraph(list.Document, newParagraphSection, 0, ContainerType.Paragraph), (int)startNumber);
  450. }
  451. }
  452. return list;
  453. }
  454. internal static void RenumberIDs(DocX document)
  455. {
  456. IEnumerable<XAttribute> trackerIDs =
  457. (from d in document.mainDoc.Descendants()
  458. where d.Name.LocalName == "ins" || d.Name.LocalName == "del"
  459. select d.Attribute(XName.Get("id", "http://schemas.openxmlformats.org/wordprocessingml/2006/main")));
  460. for (int i = 0; i < trackerIDs.Count(); i++)
  461. trackerIDs.ElementAt(i).Value = i.ToString();
  462. }
  463. internal static Paragraph GetFirstParagraphEffectedByInsert(DocX document, int index)
  464. {
  465. // This document contains no Paragraphs and insertion is at index 0
  466. if (document.Paragraphs.Count() == 0 && index == 0)
  467. return null;
  468. foreach (Paragraph p in document.Paragraphs)
  469. {
  470. if (p.endIndex >= index)
  471. return p;
  472. }
  473. throw new ArgumentOutOfRangeException();
  474. }
  475. internal static List<XElement> FormatInput(string text, XElement rPr)
  476. {
  477. List<XElement> newRuns = new List<XElement>();
  478. XElement tabRun = new XElement(DocX.w + "tab");
  479. XElement breakRun = new XElement(DocX.w + "br");
  480. StringBuilder sb = new StringBuilder();
  481. if (string.IsNullOrEmpty(text))
  482. {
  483. return newRuns; //I dont wanna get an exception if text == null, so just return empy list
  484. }
  485. char lastChar = '\0';
  486. foreach (char c in text)
  487. {
  488. switch (c)
  489. {
  490. case '\t':
  491. if (sb.Length > 0)
  492. {
  493. XElement t = new XElement(DocX.w + "t", sb.ToString());
  494. Novacode.Text.PreserveSpace(t);
  495. newRuns.Add(new XElement(DocX.w + "r", rPr, t));
  496. sb = new StringBuilder();
  497. }
  498. newRuns.Add(new XElement(DocX.w + "r", rPr, tabRun));
  499. break;
  500. case '\r':
  501. if (sb.Length > 0)
  502. {
  503. XElement t = new XElement(DocX.w + "t", sb.ToString());
  504. Novacode.Text.PreserveSpace(t);
  505. newRuns.Add(new XElement(DocX.w + "r", rPr, t));
  506. sb = new StringBuilder();
  507. }
  508. newRuns.Add(new XElement(DocX.w + "r", rPr, breakRun));
  509. break;
  510. case '\n':
  511. if (lastChar == '\r') break;
  512. if (sb.Length > 0)
  513. {
  514. XElement t = new XElement(DocX.w + "t", sb.ToString());
  515. Novacode.Text.PreserveSpace(t);
  516. newRuns.Add(new XElement(DocX.w + "r", rPr, t));
  517. sb = new StringBuilder();
  518. }
  519. newRuns.Add(new XElement(DocX.w + "r", rPr, breakRun));
  520. break;
  521. default:
  522. // Check the character against restricted list:
  523. // RestrictedChar ::= [#x1-#x8] | [#xB-#xC] | [#xE-#x1F] | [#x7F-#x84] | [#x86-#x9F]
  524. // See https://www.w3.org/TR/xml11/#sec-xml11
  525. if( RestrictedXmlChar.Contains( c ) )
  526. {
  527. // skip the character
  528. }
  529. else
  530. sb.Append(c);
  531. break;
  532. }
  533. lastChar = c;
  534. }
  535. if (sb.Length > 0)
  536. {
  537. XElement t = new XElement(DocX.w + "t", sb.ToString());
  538. Novacode.Text.PreserveSpace(t);
  539. newRuns.Add(new XElement(DocX.w + "r", rPr, t));
  540. }
  541. return newRuns;
  542. }
  543. internal static XElement[] SplitParagraph(Paragraph p, int index)
  544. {
  545. // In this case edit dosent really matter, you have a choice.
  546. Run r = p.GetFirstRunEffectedByEdit(index, EditType.ins);
  547. XElement[] split;
  548. XElement before, after;
  549. if (r.Xml.Parent.Name.LocalName == "ins")
  550. {
  551. split = p.SplitEdit(r.Xml.Parent, index, EditType.ins);
  552. before = new XElement(p.Xml.Name, p.Xml.Attributes(), r.Xml.Parent.ElementsBeforeSelf(), split[0]);
  553. after = new XElement(p.Xml.Name, p.Xml.Attributes(), r.Xml.Parent.ElementsAfterSelf(), split[1]);
  554. }
  555. else if (r.Xml.Parent.Name.LocalName == "del")
  556. {
  557. split = p.SplitEdit(r.Xml.Parent, index, EditType.del);
  558. before = new XElement(p.Xml.Name, p.Xml.Attributes(), r.Xml.Parent.ElementsBeforeSelf(), split[0]);
  559. after = new XElement(p.Xml.Name, p.Xml.Attributes(), r.Xml.Parent.ElementsAfterSelf(), split[1]);
  560. }
  561. else
  562. {
  563. split = Run.SplitRun(r, index);
  564. before = new XElement(p.Xml.Name, p.Xml.Attributes(), r.Xml.ElementsBeforeSelf(), split[0]);
  565. after = new XElement(p.Xml.Name, p.Xml.Attributes(), split[1], r.Xml.ElementsAfterSelf());
  566. }
  567. if (before.Elements().Count() == 0)
  568. before = null;
  569. if (after.Elements().Count() == 0)
  570. after = null;
  571. return new XElement[] { before, after };
  572. }
  573. /// <!--
  574. /// Bug found and fixed by trnilse. To see the change,
  575. /// please compare this release to the previous release using TFS compare.
  576. /// -->
  577. internal static bool IsSameFile(Stream streamOne, Stream streamTwo)
  578. {
  579. int file1byte, file2byte;
  580. if (streamOne.Length != streamTwo.Length)
  581. {
  582. // Return false to indicate files are different
  583. return false;
  584. }
  585. // Read and compare a byte from each file until either a
  586. // non-matching set of bytes is found or until the end of
  587. // file1 is reached.
  588. do
  589. {
  590. // Read one byte from each file.
  591. file1byte = streamOne.ReadByte();
  592. file2byte = streamTwo.ReadByte();
  593. }
  594. while ((file1byte == file2byte) && (file1byte != -1));
  595. // Return the success of the comparison. "file1byte" is
  596. // equal to "file2byte" at this point only if the files are
  597. // the same.
  598. streamOne.Position = 0;
  599. streamTwo.Position = 0;
  600. return ((file1byte - file2byte) == 0);
  601. }
  602. internal static UnderlineStyle GetUnderlineStyle(string underlineStyle)
  603. {
  604. switch (underlineStyle)
  605. {
  606. case "single":
  607. return UnderlineStyle.singleLine;
  608. case "double":
  609. return UnderlineStyle.doubleLine;
  610. case "thick":
  611. return UnderlineStyle.thick;
  612. case "dotted":
  613. return UnderlineStyle.dotted;
  614. case "dottedHeavy":
  615. return UnderlineStyle.dottedHeavy;
  616. case "dash":
  617. return UnderlineStyle.dash;
  618. case "dashedHeavy":
  619. return UnderlineStyle.dashedHeavy;
  620. case "dashLong":
  621. return UnderlineStyle.dashLong;
  622. case "dashLongHeavy":
  623. return UnderlineStyle.dashLongHeavy;
  624. case "dotDash":
  625. return UnderlineStyle.dotDash;
  626. case "dashDotHeavy":
  627. return UnderlineStyle.dashDotHeavy;
  628. case "dotDotDash":
  629. return UnderlineStyle.dotDotDash;
  630. case "dashDotDotHeavy":
  631. return UnderlineStyle.dashDotDotHeavy;
  632. case "wave":
  633. return UnderlineStyle.wave;
  634. case "wavyHeavy":
  635. return UnderlineStyle.wavyHeavy;
  636. case "wavyDouble":
  637. return UnderlineStyle.wavyDouble;
  638. case "words":
  639. return UnderlineStyle.words;
  640. default:
  641. return UnderlineStyle.none;
  642. }
  643. }
  644. }
  645. }