Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Xml.Linq;
  6. using System.Text.RegularExpressions;
  7. using DocumentFormat.OpenXml.Drawing;
  8. using System.Security.Principal;
  9. using System.Collections;
  10. namespace Novacode
  11. {
  12. /// <summary>
  13. /// Represents a .docx paragraph.
  14. /// </summary>
  15. public class Paragraph
  16. {
  17. // A lookup for the runs in this paragraph
  18. Dictionary<int, Run> runLookup = new Dictionary<int, Run>();
  19. // The underlying XElement which this Paragraph wraps
  20. private XElement p;
  21. /// <summary>
  22. /// Wraps a XElement as a Paragraph.
  23. /// </summary>
  24. /// <param name="p">The XElement to wrap.</param>
  25. public Paragraph(XElement p)
  26. {
  27. this.p = p;
  28. BuildRunLookup(p);
  29. }
  30. private void BuildRunLookup(XElement p)
  31. {
  32. // Get the runs in this paragraph
  33. IEnumerable<XElement> runs = p.Descendants(XName.Get("r", "http://schemas.openxmlformats.org/wordprocessingml/2006/main"));
  34. int startIndex = 0;
  35. // Loop through each run in this paragraph
  36. foreach (XElement run in runs)
  37. {
  38. // Only add runs which contain text
  39. if (GetElementTextLength(run) > 0)
  40. {
  41. Run r = new Run(startIndex, run);
  42. runLookup.Add(r.EndIndex, r);
  43. startIndex = r.EndIndex;
  44. }
  45. }
  46. }
  47. /// <summary>
  48. /// Gets the value of this Novacode.DocX.Paragraph.
  49. /// </summary>
  50. public string Value
  51. {
  52. // Returns the underlying XElement's Value property.
  53. get
  54. {
  55. StringBuilder sb = new StringBuilder();
  56. // Loop through each run in this paragraph
  57. foreach (XElement r in p.Descendants(XName.Get("r", DocX.w.NamespaceName)))
  58. {
  59. // Loop through each text item in this run
  60. foreach (XElement descendant in r.Descendants())
  61. {
  62. switch (descendant.Name.LocalName)
  63. {
  64. case "tab":
  65. sb.Append("\t");
  66. break;
  67. case "br":
  68. sb.Append("\n");
  69. break;
  70. case "t":
  71. goto case "delText";
  72. case "delText":
  73. sb.Append(descendant.Value);
  74. break;
  75. default: break;
  76. }
  77. }
  78. }
  79. return sb.ToString();
  80. }
  81. }
  82. /// <summary>
  83. /// Creates an Edit either a ins or a del with the specified content and date
  84. /// </summary>
  85. /// <param name="t">The type of this edit (ins or del)</param>
  86. /// <param name="edit_time">The time stamp to use for this edit</param>
  87. /// <param name="content">The initial content of this edit</param>
  88. /// <returns></returns>
  89. private XElement CreateEdit(EditType t, DateTime edit_time, params object[] content)
  90. {
  91. if (t == EditType.del)
  92. {
  93. foreach (object o in content)
  94. {
  95. if (o is XElement)
  96. {
  97. XElement e = (o as XElement);
  98. IEnumerable<XElement> ts = e.DescendantsAndSelf(XName.Get("t", DocX.w.NamespaceName));
  99. for(int i = 0; i < ts.Count(); i ++)
  100. {
  101. XElement text = ts.ElementAt(i);
  102. text.ReplaceWith(new XElement(DocX.w + "delText", text.Attributes(), text.Value));
  103. }
  104. }
  105. }
  106. }
  107. return
  108. (
  109. new XElement(DocX.w + t.ToString(),
  110. new XAttribute(DocX.w + "id", 0),
  111. new XAttribute(DocX.w + "author", WindowsIdentity.GetCurrent().Name),
  112. new XAttribute(DocX.w + "date", edit_time),
  113. content)
  114. );
  115. }
  116. /// <summary>
  117. /// Return the first Run that will be effected by an edit at the index
  118. /// </summary>
  119. /// <param name="index">The index of this edit</param>
  120. /// <returns>The first Run that will be effected</returns>
  121. public Run GetFirstRunEffectedByEdit(int index)
  122. {
  123. foreach (int runEndIndex in runLookup.Keys)
  124. {
  125. if (runEndIndex > index)
  126. return runLookup[runEndIndex];
  127. }
  128. if (runLookup.Last().Value.EndIndex == index)
  129. return runLookup.Last().Value;
  130. throw new ArgumentOutOfRangeException();
  131. }
  132. /// <summary>
  133. /// Return the first Run that will be effected by an edit at the index
  134. /// </summary>
  135. /// <param name="index">The index of this edit</param>
  136. /// <returns>The first Run that will be effected</returns>
  137. public Run GetFirstRunEffectedByInsert(int index)
  138. {
  139. // This paragraph contains no Runs and insertion is at index 0
  140. if (runLookup.Keys.Count() == 0 && index == 0)
  141. return null;
  142. foreach (int runEndIndex in runLookup.Keys)
  143. {
  144. if (runEndIndex >= index)
  145. return runLookup[runEndIndex];
  146. }
  147. throw new ArgumentOutOfRangeException();
  148. }
  149. /// <summary>
  150. /// If the value to be inserted contains tab elements or br elements, multiple runs will be inserted.
  151. /// </summary>
  152. /// <param name="text">The text to be inserted, this text can contain the special characters \t (tab) and \n (br)</param>
  153. /// <returns></returns>
  154. private List<XElement> formatInput(string text, XElement rPr)
  155. {
  156. List<XElement> newRuns = new List<XElement>();
  157. XElement tabRun = new XElement(DocX.w + "tab");
  158. string[] runTexts = text.Split('\t');
  159. XElement firstRun;
  160. if (runTexts[0] != String.Empty)
  161. {
  162. XElement firstText = new XElement(DocX.w + "t", runTexts[0]);
  163. Text.PreserveSpace(firstText);
  164. firstRun = new XElement(DocX.w + "r", rPr, firstText);
  165. newRuns.Add(firstRun);
  166. }
  167. if (runTexts.Length > 1)
  168. {
  169. for (int k = 1; k < runTexts.Length; k++)
  170. {
  171. XElement newText = new XElement(DocX.w + "t", runTexts[k]);
  172. XElement newRun;
  173. if (runTexts[k] == String.Empty)
  174. newRun = new XElement(DocX.w + "r", tabRun);
  175. else
  176. {
  177. // Value begins or ends with a space
  178. Text.PreserveSpace(newText);
  179. newRun = new XElement(DocX.w + "r", rPr, tabRun, newText);
  180. }
  181. newRuns.Add(newRun);
  182. }
  183. }
  184. return newRuns;
  185. }
  186. /// <summary>
  187. /// Counts the text length of an element
  188. /// </summary>
  189. /// <param name="run">An element</param>
  190. /// <returns>The length of this elements text</returns>
  191. public static int GetElementTextLength(XElement run)
  192. {
  193. int count = 0;
  194. if (run == null)
  195. return count;
  196. foreach (var d in run.Descendants())
  197. {
  198. switch (d.Name.LocalName)
  199. {
  200. case "tab": goto case "br";
  201. case "br": count++; break;
  202. case "t": goto case "delText";
  203. case "delText": count += d.Value.Length; break;
  204. default: break;
  205. }
  206. }
  207. return count;
  208. }
  209. /// <summary>
  210. /// Splits an edit element at the specified run, at the specified index.
  211. /// </summary>
  212. /// <param name="edit">The edit element to split</param>
  213. /// <param name="run">The run element to split</param>
  214. /// <param name="index">The index to split at</param>
  215. /// <returns></returns>
  216. public XElement[] SplitEdit(XElement edit, int index, EditType type)
  217. {
  218. Run run;
  219. if(type == EditType.del)
  220. run = GetFirstRunEffectedByEdit(index);
  221. else
  222. run = GetFirstRunEffectedByInsert(index);
  223. XElement[] splitRun = Run.SplitRun(run, index);
  224. XElement splitLeft = new XElement(edit.Name, edit.Attributes(), run.Xml.ElementsBeforeSelf(), splitRun[0]);
  225. if (GetElementTextLength(splitLeft) == 0)
  226. splitLeft = null;
  227. XElement splitRight = new XElement(edit.Name, edit.Attributes(), splitRun[1], run.Xml.ElementsAfterSelf());
  228. if (GetElementTextLength(splitRight) == 0)
  229. splitRight = null;
  230. return
  231. (
  232. new XElement[]
  233. {
  234. splitLeft,
  235. splitRight
  236. }
  237. );
  238. }
  239. public void Insert(int index, string value, bool trackChanges)
  240. {
  241. Insert(index, value, null, trackChanges);
  242. }
  243. /// <summary>
  244. /// Inserts a specified instance of System.String into a Novacode.DocX.Paragraph at a specified index position.
  245. /// </summary>
  246. /// <example>
  247. /// <code>
  248. /// // Description: Simple string insertion
  249. ///
  250. /// // Load Example.docx
  251. /// DocX dx = DocX.Load(@"C:\Example.docx");
  252. ///
  253. /// // Iterate through the paragraphs
  254. /// foreach (Paragraph p in dx.Paragraphs)
  255. /// {
  256. /// // Insert the string "Start: " at the begining of every paragraph and flag it as a change.
  257. /// p.Insert(0, "Start: ", true);
  258. /// }
  259. ///
  260. /// // Save changes to Example.docx
  261. /// dx.Save();
  262. /// </code>
  263. /// </example>
  264. /// <example>
  265. /// <code>
  266. /// // Description: Inserting tabs using the \t switch
  267. ///
  268. /// // Load Example.docx
  269. /// DocX dx = DocX.Load(@"C:\Example.docx");
  270. ///
  271. /// // Iterate through the paragraphs
  272. /// foreach (Paragraph p in dx.Paragraphs)
  273. /// {
  274. /// // Insert the string "\tStart:\t" at the begining of every paragraph and flag it as a change.
  275. /// p.Insert(0, "\tStart:\t", true);
  276. /// }
  277. ///
  278. /// // Save changes to Example.docx
  279. /// dx.Save();
  280. /// </code>
  281. /// </example>
  282. /// <seealso cref="Paragraph.Remove"/>
  283. /// <seealso cref="Paragraph.Replace"/>
  284. /// <param name="index">The index position of the insertion.</param>
  285. /// <param name="value">The System.String to insert.</param>
  286. /// <param name="trackChanges">Flag this insert as a change</param>
  287. public void Insert(int index, string value, Formatting formatting, bool trackChanges)
  288. {
  289. // Timestamp to mark the start of insert
  290. DateTime now = DateTime.Now;
  291. DateTime insert_datetime = new DateTime(now.Year, now.Month, now.Day, now.Hour, now.Minute, 0, DateTimeKind.Utc);
  292. // Get the first run effected by this Insert
  293. Run run = GetFirstRunEffectedByInsert(index);
  294. if (run == null)
  295. {
  296. object insert;
  297. if (formatting != null)
  298. insert = formatInput(value, formatting.Xml);
  299. else
  300. insert = formatInput(value, null);
  301. if (trackChanges)
  302. insert = CreateEdit(EditType.ins, insert_datetime, insert);
  303. p.Add(insert);
  304. }
  305. else
  306. {
  307. object newRuns;
  308. if (formatting != null)
  309. newRuns = formatInput(value, formatting.Xml);
  310. else
  311. newRuns = formatInput(value, run.Xml.Element(XName.Get("rPr", DocX.w.NamespaceName)));
  312. // The parent of this Run
  313. XElement parentElement = run.Xml.Parent;
  314. switch (parentElement.Name.LocalName)
  315. {
  316. case "ins":
  317. {
  318. // The datetime that this ins was created
  319. DateTime parent_ins_date = DateTime.Parse(parentElement.Attribute(XName.Get("date", DocX.w.NamespaceName)).Value);
  320. /*
  321. * Special case: You want to track changes,
  322. * and the first Run effected by this insert
  323. * has a datetime stamp equal to now.
  324. */
  325. if (trackChanges && parent_ins_date.CompareTo(insert_datetime) == 0)
  326. {
  327. /*
  328. * Inserting into a non edit and this special case, is the same procedure.
  329. */
  330. goto default;
  331. }
  332. /*
  333. * If not the special case above,
  334. * then inserting into an ins or a del, is the same procedure.
  335. */
  336. goto case "del";
  337. }
  338. case "del":
  339. {
  340. object insert = newRuns;
  341. if (trackChanges)
  342. insert = CreateEdit(EditType.ins, insert_datetime, newRuns);
  343. // Split this Edit at the point you want to insert
  344. XElement[] splitEdit = SplitEdit(parentElement, index, EditType.ins);
  345. // Replace the origional run
  346. parentElement.ReplaceWith
  347. (
  348. splitEdit[0],
  349. insert,
  350. splitEdit[1]
  351. );
  352. break;
  353. }
  354. default:
  355. {
  356. object insert = newRuns;
  357. if (trackChanges && !parentElement.Name.LocalName.Equals("ins"))
  358. insert = CreateEdit(EditType.ins, insert_datetime, newRuns);
  359. // Split this run at the point you want to insert
  360. XElement[] splitRun = Run.SplitRun(run, index);
  361. // Replace the origional run
  362. run.Xml.ReplaceWith
  363. (
  364. splitRun[0],
  365. insert,
  366. splitRun[1]
  367. );
  368. break;
  369. }
  370. }
  371. }
  372. // Rebuild the run lookup for this paragraph
  373. runLookup.Clear();
  374. BuildRunLookup(p);
  375. DocX.RenumberIDs();
  376. }
  377. /// <summary>
  378. /// Removes characters from a Novacode.DocX.Paragraph.
  379. /// </summary>
  380. /// <example>
  381. /// <code>
  382. /// // Load Example.docx
  383. /// DocX dx = DocX.Load(@"C:\Example.docx");
  384. ///
  385. /// // Iterate through the paragraphs
  386. /// foreach (Paragraph p in dx.Paragraphs)
  387. /// {
  388. /// // Remove the first two characters from every paragraph
  389. /// p.Remove(0, 2);
  390. /// }
  391. ///
  392. /// // Save changes to Example.docx
  393. /// dx.Save();
  394. /// </code>
  395. /// </example>
  396. /// <seealso cref="Paragraph.Insert"/>
  397. /// <seealso cref="Paragraph.Replace"/>
  398. /// <param name="index">The position to begin deleting characters.</param>
  399. /// <param name="count">The number of characters to delete</param>
  400. /// <param name="trackChanges">Track changes</param>
  401. public void Remove(int index, int count, bool trackChanges)
  402. {
  403. // Timestamp to mark the start of insert
  404. DateTime now = DateTime.Now;
  405. DateTime remove_datetime = new DateTime(now.Year, now.Month, now.Day, now.Hour, now.Minute, 0, DateTimeKind.Utc);
  406. // The number of characters processed so far
  407. int processed = 0;
  408. do
  409. {
  410. // Get the first run effected by this Remove
  411. Run run = GetFirstRunEffectedByEdit(index + processed);
  412. // The parent of this Run
  413. XElement parentElement = run.Xml.Parent;
  414. switch (parentElement.Name.LocalName)
  415. {
  416. case "ins":
  417. {
  418. XElement[] splitEditBefore = SplitEdit(parentElement, index + processed, EditType.del);
  419. int min = Math.Min(count - processed, run.Xml.ElementsAfterSelf().Sum(e => GetElementTextLength(e)));
  420. XElement[] splitEditAfter = SplitEdit(parentElement, index + processed + min, EditType.del);
  421. XElement temp = SplitEdit(splitEditBefore[1], index + processed + min, EditType.del)[0];
  422. object middle = CreateEdit(EditType.del, remove_datetime, temp.Elements().ToArray());
  423. processed += GetElementTextLength(middle as XElement);
  424. if (!trackChanges)
  425. middle = null;
  426. parentElement.ReplaceWith
  427. (
  428. splitEditBefore[0],
  429. middle,
  430. splitEditAfter[1]
  431. );
  432. processed += GetElementTextLength(middle as XElement);
  433. break;
  434. }
  435. case "del":
  436. {
  437. if (trackChanges)
  438. {
  439. // You cannot delete from a deletion, advance processed to the end of this del
  440. processed += GetElementTextLength(parentElement);
  441. }
  442. else
  443. goto case "ins";
  444. break;
  445. }
  446. default:
  447. {
  448. XElement[] splitRunBefore = Run.SplitRun(run, index + processed);
  449. int min = Math.Min(index + processed + (count - processed), run.EndIndex);
  450. XElement[] splitRunAfter = Run.SplitRun(run, min);
  451. object middle = CreateEdit(EditType.del, remove_datetime, Run.SplitRun(new Run(run.StartIndex + GetElementTextLength(splitRunBefore[0]), splitRunBefore[1]), min)[0]);
  452. processed += GetElementTextLength(middle as XElement);
  453. if (!trackChanges)
  454. middle = null;
  455. run.Xml.ReplaceWith
  456. (
  457. splitRunBefore[0],
  458. middle,
  459. splitRunAfter[1]
  460. );
  461. break;
  462. }
  463. }
  464. // If after this remove the parent element is empty, remove it.
  465. if (GetElementTextLength(parentElement) == 0)
  466. parentElement.Remove();
  467. }
  468. while (processed < count);
  469. // Rebuild the run lookup
  470. runLookup.Clear();
  471. BuildRunLookup(p);
  472. DocX.RenumberIDs();
  473. }
  474. /// <summary>
  475. /// Replaces all occurrences of a specified System.String in this instance, with another specified System.String.
  476. /// </summary>
  477. /// <example>
  478. /// <code>
  479. /// // Load Example.docx
  480. /// DocX dx = DocX.Load(@"C:\Example.docx");
  481. ///
  482. /// // Iterate through the paragraphs
  483. /// foreach (Paragraph p in dx.Paragraphs)
  484. /// {
  485. /// // Replace all instances of the string "wrong" with the stirng "right"
  486. /// p.Replace("wrong", "right");
  487. /// }
  488. ///
  489. /// // Save changes to Example.docx
  490. /// dx.Save();
  491. /// </code>
  492. /// </example>
  493. /// <seealso cref="Paragraph.Remove"/>
  494. /// <seealso cref="Paragraph.Insert"/>
  495. /// <param name="newValue">A System.String to replace all occurances of oldValue.</param>
  496. /// <param name="oldValue">A System.String to be replaced.</param>
  497. /// <param name="options">A bitwise OR combination of RegexOption enumeration options.</param>
  498. /// <param name="trackChanges">Track changes</param>
  499. public void Replace(string oldValue, string newValue, bool trackChanges, RegexOptions options)
  500. {
  501. MatchCollection mc = Regex.Matches(this.Value, oldValue, options);
  502. // Loop through the matches in reverse order
  503. foreach (Match m in mc.Cast<Match>().Reverse())
  504. {
  505. Insert(m.Index + oldValue.Length, newValue, trackChanges);
  506. Remove(m.Index, m.Length, trackChanges);
  507. }
  508. }
  509. public void Replace(string oldValue, string newValue, bool trackChanges)
  510. {
  511. Replace(oldValue, newValue, trackChanges, RegexOptions.None);
  512. }
  513. }
  514. }