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

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