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.

Paragraph.cs 44KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104
  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 System.Security.Principal;
  8. using System.Collections;
  9. namespace Novacode
  10. {
  11. /// <summary>
  12. /// Represents a document paragraph.
  13. /// </summary>
  14. public class Paragraph
  15. {
  16. // This paragraphs text alignment
  17. private Alignment alignment;
  18. // A lookup for the runs in this paragraph
  19. Dictionary<int, Run> runLookup = new Dictionary<int, Run>();
  20. // The underlying XElement which this Paragraph wraps
  21. internal XElement xml;
  22. internal int startIndex, endIndex;
  23. // A collection of Images in this Paragraph
  24. private List<Picture> pictures;
  25. /// <summary>
  26. /// Returns a list of Pictures in this Paragraph.
  27. /// </summary>
  28. public List<Picture> Pictures { get { return pictures; } }
  29. DocX document;
  30. internal Paragraph(DocX document, int startIndex, XElement p)
  31. {
  32. this.document = document;
  33. this.startIndex = startIndex;
  34. this.endIndex = startIndex + GetElementTextLength(p);
  35. this.xml = p;
  36. BuildRunLookup(p);
  37. // Get all of the images in this document
  38. pictures = (from i in p.Descendants(XName.Get("drawing", DocX.w.NamespaceName))
  39. select new Picture(i)).ToList();
  40. }
  41. /// <summary>
  42. /// Gets or set this Paragraphs text alignment.
  43. /// </summary>
  44. public Alignment Alignment
  45. {
  46. get { return alignment; }
  47. set
  48. {
  49. alignment = value;
  50. XElement pPr = xml.Element(XName.Get("pPr", DocX.w.NamespaceName));
  51. if (alignment != Novacode.Alignment.left)
  52. {
  53. if (pPr == null)
  54. xml.Add(new XElement(XName.Get("pPr", DocX.w.NamespaceName)));
  55. pPr = xml.Element(XName.Get("pPr", DocX.w.NamespaceName));
  56. XElement jc = pPr.Element(XName.Get("jc", DocX.w.NamespaceName));
  57. if(jc == null)
  58. pPr.Add(new XElement(XName.Get("jc", DocX.w.NamespaceName), new XAttribute(XName.Get("val", DocX.w.NamespaceName), alignment.ToString())));
  59. else
  60. jc.Attribute(XName.Get("val", DocX.w.NamespaceName)).Value = alignment.ToString();
  61. }
  62. else
  63. {
  64. if (pPr != null)
  65. {
  66. XElement jc = pPr.Element(XName.Get("jc", DocX.w.NamespaceName));
  67. if (jc != null)
  68. jc.Remove();
  69. }
  70. }
  71. }
  72. }
  73. /// <summary>
  74. /// Remove this Paragraph from the document.
  75. /// </summary>
  76. /// <param name="trackChanges">Should this remove be tracked as a change?</param>
  77. /// <example>
  78. /// Remove a Paragraph from a document and track it as a change.
  79. /// <code>
  80. /// // Create a document using a relative filename.
  81. /// using (DocX document = DocX.Load(@"C:\Example\Test.docx"))
  82. /// {
  83. /// // Create and Insert a new Paragraph into this document.
  84. /// Paragraph p = document.InsertParagraph("Hello", false);
  85. ///
  86. /// // Remove the Paragraph and track this as a change.
  87. /// p.Remove(true);
  88. ///
  89. /// // Save all changes made to this document.
  90. /// document.Save();
  91. /// }// Release this document from memory.
  92. /// </code>
  93. /// </example>
  94. public void Remove(bool trackChanges)
  95. {
  96. if (trackChanges)
  97. {
  98. DateTime now = DateTime.Now.ToUniversalTime();
  99. List<XElement> elements = xml.Elements().ToList();
  100. List<XElement> temp = new List<XElement>();
  101. for (int i = 0; i < elements.Count(); i++ )
  102. {
  103. XElement e = elements[i];
  104. if (e.Name.LocalName != "del")
  105. {
  106. temp.Add(e);
  107. e.Remove();
  108. }
  109. else
  110. {
  111. if (temp.Count() > 0)
  112. {
  113. e.AddBeforeSelf(CreateEdit(EditType.del, now, temp.Elements()));
  114. temp.Clear();
  115. }
  116. }
  117. }
  118. if (temp.Count() > 0)
  119. xml.Add(CreateEdit(EditType.del, now, temp));
  120. }
  121. else
  122. {
  123. runLookup.Clear();
  124. if (xml.Parent.Name.LocalName == "tc")
  125. xml.Value = string.Empty;
  126. else
  127. {
  128. // Remove this paragraph from the document
  129. xml.Remove();
  130. xml = null;
  131. runLookup = null;
  132. }
  133. }
  134. DocX.RebuildParagraphs(document);
  135. }
  136. private void BuildRunLookup(XElement p)
  137. {
  138. // Get the runs in this paragraph
  139. IEnumerable<XElement> runs = p.Descendants(XName.Get("r", "http://schemas.openxmlformats.org/wordprocessingml/2006/main"));
  140. int startIndex = 0;
  141. // Loop through each run in this paragraph
  142. foreach (XElement run in runs)
  143. {
  144. // Only add runs which contain text
  145. if (GetElementTextLength(run) > 0)
  146. {
  147. Run r = new Run(startIndex, run);
  148. runLookup.Add(r.EndIndex, r);
  149. startIndex = r.EndIndex;
  150. }
  151. }
  152. }
  153. /// <summary>
  154. /// Gets the text value of this Paragraph.
  155. /// </summary>
  156. public string Text
  157. {
  158. // Returns the underlying XElement's Value property.
  159. get
  160. {
  161. StringBuilder sb = new StringBuilder();
  162. // Loop through each run in this paragraph
  163. foreach (XElement r in xml.Descendants(XName.Get("r", DocX.w.NamespaceName)))
  164. {
  165. // Loop through each text item in this run
  166. foreach (XElement descendant in r.Descendants())
  167. {
  168. switch (descendant.Name.LocalName)
  169. {
  170. case "tab":
  171. sb.Append("\t");
  172. break;
  173. case "br":
  174. sb.Append("\n");
  175. break;
  176. case "t":
  177. goto case "delText";
  178. case "delText":
  179. sb.Append(descendant.Value);
  180. break;
  181. default: break;
  182. }
  183. }
  184. }
  185. return sb.ToString();
  186. }
  187. }
  188. //public Picture InsertPicture(Picture picture)
  189. //{
  190. // Picture newPicture = picture;
  191. // newPicture.i = new XElement(picture.i);
  192. // xml.Add(newPicture.i);
  193. // pictures.Add(newPicture);
  194. // return newPicture;
  195. //}
  196. /// <summary>
  197. /// Insert a Picture at the end of this paragraph.
  198. /// </summary>
  199. /// <param name="description">A string to describe this Picture.</param>
  200. /// <param name="imageID">The unique id that identifies the Image this Picture represents.</param>
  201. /// <param name="name">The name of this image.</param>
  202. /// <returns>A Picture.</returns>
  203. /// <example>
  204. /// <code>
  205. /// // Create a document using a relative filename.
  206. /// using (DocX document = DocX.Create(@"Test.docx"))
  207. /// {
  208. /// // Add a new Paragraph to this document.
  209. /// Paragraph p = document.InsertParagraph("Here is Picture 1", false);
  210. ///
  211. /// // Add an Image to this document.
  212. /// Novacode.Image img = document.AddImage(@"Image.jpg");
  213. ///
  214. /// // Insert pic at the end of Paragraph p.
  215. /// Picture pic = p.InsertPicture(img.Id, "Photo 31415", "A pie I baked.");
  216. ///
  217. /// // Rotate the Picture clockwise by 30 degrees.
  218. /// pic.Rotation = 30;
  219. ///
  220. /// // Resize the Picture.
  221. /// pic.Width = 400;
  222. /// pic.Height = 300;
  223. ///
  224. /// // Set the shape of this Picture to be a cube.
  225. /// pic.SetPictureShape(BasicShapes.cube);
  226. ///
  227. /// // Flip the Picture Horizontally.
  228. /// pic.FlipHorizontal = true;
  229. ///
  230. /// // Save all changes made to this document.
  231. /// document.Save();
  232. /// }// Release this document from memory.
  233. /// </code>
  234. /// </example>
  235. public Picture InsertPicture(string imageID, string name, string description)
  236. {
  237. Picture p = new Picture(document, imageID, name, description);
  238. xml.Add(p.i);
  239. pictures.Add(p);
  240. return p;
  241. }
  242. //public Picture InsertPicture(int index, Picture picture)
  243. //{
  244. // Picture p = picture;
  245. // p.i = new XElement(picture.i);
  246. // Run run = GetFirstRunEffectedByEdit(index);
  247. // if (run == null)
  248. // xml.Add(p.i);
  249. // else
  250. // {
  251. // // Split this run at the point you want to insert
  252. // XElement[] splitRun = Run.SplitRun(run, index);
  253. // // Replace the origional run
  254. // run.xml.ReplaceWith
  255. // (
  256. // splitRun[0],
  257. // p.i,
  258. // splitRun[1]
  259. // );
  260. // }
  261. // // Rebuild the run lookup for this paragraph
  262. // runLookup.Clear();
  263. // BuildRunLookup(xml);
  264. // DocX.RenumberIDs(document);
  265. // return p;
  266. //}
  267. /// <summary>
  268. /// Insert a Picture into this Paragraph at a specified index.
  269. /// </summary>
  270. /// <param name="description">A string to describe this Picture.</param>
  271. /// <param name="imageID">The unique id that identifies the Image this Picture represents.</param>
  272. /// <param name="name">The name of this image.</param>
  273. /// <param name="index">The index to insert this Picture at.</param>
  274. /// <returns>A Picture.</returns>
  275. /// <example>
  276. /// <code>
  277. /// // Create a document using a relative filename.
  278. /// using (DocX document = DocX.Create(@"Test.docx"))
  279. /// {
  280. /// // Add a new Paragraph to this document.
  281. /// Paragraph p = document.InsertParagraph("Here is Picture 1", false);
  282. ///
  283. /// // Add an Image to this document.
  284. /// Novacode.Image img = document.AddImage(@"Image.jpg");
  285. ///
  286. /// // Insert pic at the start of Paragraph p.
  287. /// Picture pic = p.InsertPicture(0, img.Id, "Photo 31415", "A pie I baked.");
  288. ///
  289. /// // Rotate the Picture clockwise by 30 degrees.
  290. /// pic.Rotation = 30;
  291. ///
  292. /// // Resize the Picture.
  293. /// pic.Width = 400;
  294. /// pic.Height = 300;
  295. ///
  296. /// // Set the shape of this Picture to be a cube.
  297. /// pic.SetPictureShape(BasicShapes.cube);
  298. ///
  299. /// // Flip the Picture Horizontally.
  300. /// pic.FlipHorizontal = true;
  301. ///
  302. /// // Save all changes made to this document.
  303. /// document.Save();
  304. /// }// Release this document from memory.
  305. /// </code>
  306. /// </example>
  307. public Picture InsertPicture(int index, string imageID, string name, string description)
  308. {
  309. Picture picture = new Picture(document, imageID, name, description);
  310. Run run = GetFirstRunEffectedByEdit(index);
  311. if (run == null)
  312. xml.Add(picture.i);
  313. else
  314. {
  315. // Split this run at the point you want to insert
  316. XElement[] splitRun = Run.SplitRun(run, index);
  317. // Replace the origional run
  318. run.xml.ReplaceWith
  319. (
  320. splitRun[0],
  321. picture.i,
  322. splitRun[1]
  323. );
  324. }
  325. // Rebuild the run lookup for this paragraph
  326. runLookup.Clear();
  327. BuildRunLookup(xml);
  328. DocX.RenumberIDs(document);
  329. return picture;
  330. }
  331. /// <summary>
  332. /// Creates an Edit either a ins or a del with the specified content and date
  333. /// </summary>
  334. /// <param name="t">The type of this edit (ins or del)</param>
  335. /// <param name="edit_time">The time stamp to use for this edit</param>
  336. /// <param name="content">The initial content of this edit</param>
  337. /// <returns></returns>
  338. private XElement CreateEdit(EditType t, DateTime edit_time, object content)
  339. {
  340. if (t == EditType.del)
  341. {
  342. foreach (object o in (IEnumerable<XElement>)content)
  343. {
  344. if (o is XElement)
  345. {
  346. XElement e = (o as XElement);
  347. IEnumerable<XElement> ts = e.DescendantsAndSelf(XName.Get("t", DocX.w.NamespaceName));
  348. for(int i = 0; i < ts.Count(); i ++)
  349. {
  350. XElement text = ts.ElementAt(i);
  351. text.ReplaceWith(new XElement(DocX.w + "delText", text.Attributes(), text.Value));
  352. }
  353. }
  354. }
  355. }
  356. return
  357. (
  358. new XElement(DocX.w + t.ToString(),
  359. new XAttribute(DocX.w + "id", 0),
  360. new XAttribute(DocX.w + "author", WindowsIdentity.GetCurrent().Name),
  361. new XAttribute(DocX.w + "date", edit_time),
  362. content)
  363. );
  364. }
  365. internal Run GetFirstRunEffectedByEdit(int index)
  366. {
  367. foreach (int runEndIndex in runLookup.Keys)
  368. {
  369. if (runEndIndex > index)
  370. return runLookup[runEndIndex];
  371. }
  372. if (runLookup.Last().Value.EndIndex == index)
  373. return runLookup.Last().Value;
  374. throw new ArgumentOutOfRangeException();
  375. }
  376. internal Run GetFirstRunEffectedByInsert(int index)
  377. {
  378. // This paragraph contains no Runs and insertion is at index 0
  379. if (runLookup.Keys.Count() == 0 && index == 0)
  380. return null;
  381. foreach (int runEndIndex in runLookup.Keys)
  382. {
  383. if (runEndIndex >= index)
  384. return runLookup[runEndIndex];
  385. }
  386. throw new ArgumentOutOfRangeException();
  387. }
  388. private List<XElement> FormatInput(string text, XElement rPr)
  389. {
  390. // Need to support /n as non breaking space
  391. List<XElement> newRuns = new List<XElement>();
  392. XElement tabRun = new XElement(DocX.w + "tab");
  393. string[] runTexts = text.Split('\t');
  394. XElement firstRun;
  395. if (runTexts[0] != String.Empty)
  396. {
  397. XElement firstText = new XElement(DocX.w + "t", runTexts[0]);
  398. Novacode.Text.PreserveSpace(firstText);
  399. firstRun = new XElement(DocX.w + "r", rPr, firstText);
  400. newRuns.Add(firstRun);
  401. }
  402. if (runTexts.Length > 1)
  403. {
  404. for (int k = 1; k < runTexts.Length; k++)
  405. {
  406. XElement newText = new XElement(DocX.w + "t", runTexts[k]);
  407. XElement newRun;
  408. if (runTexts[k] == String.Empty)
  409. newRun = new XElement(DocX.w + "r", tabRun);
  410. else
  411. {
  412. // Value begins or ends with a space
  413. Novacode.Text.PreserveSpace(newText);
  414. newRun = new XElement(DocX.w + "r", rPr, tabRun, newText);
  415. }
  416. newRuns.Add(newRun);
  417. }
  418. }
  419. return newRuns;
  420. }
  421. static internal int GetElementTextLength(XElement run)
  422. {
  423. int count = 0;
  424. if (run == null)
  425. return count;
  426. foreach (var d in run.Descendants())
  427. {
  428. switch (d.Name.LocalName)
  429. {
  430. case "tab": goto case "br";
  431. case "br": count++; break;
  432. case "t": goto case "delText";
  433. case "delText": count += d.Value.Length; break;
  434. default: break;
  435. }
  436. }
  437. return count;
  438. }
  439. internal XElement[] SplitEdit(XElement edit, int index, EditType type)
  440. {
  441. Run run;
  442. if(type == EditType.del)
  443. run = GetFirstRunEffectedByEdit(index);
  444. else
  445. run = GetFirstRunEffectedByInsert(index);
  446. XElement[] splitRun = Run.SplitRun(run, index);
  447. XElement splitLeft = new XElement(edit.Name, edit.Attributes(), run.xml.ElementsBeforeSelf(), splitRun[0]);
  448. if (GetElementTextLength(splitLeft) == 0)
  449. splitLeft = null;
  450. XElement splitRight = new XElement(edit.Name, edit.Attributes(), splitRun[1], run.xml.ElementsAfterSelf());
  451. if (GetElementTextLength(splitRight) == 0)
  452. splitRight = null;
  453. return
  454. (
  455. new XElement[]
  456. {
  457. splitLeft,
  458. splitRight
  459. }
  460. );
  461. }
  462. /// <summary>
  463. /// Inserts a specified instance of System.String into a Novacode.DocX.Paragraph at a specified index position.
  464. /// </summary>
  465. /// <example>
  466. /// <code>
  467. /// // Create a document using a relative filename.
  468. /// using (DocX document = DocX.Load(@"C:\Example\Test.docx"))
  469. /// {
  470. /// // Iterate through the Paragraphs in this document.
  471. /// foreach (Paragraph p in document.Paragraphs)
  472. /// {
  473. /// // Insert the string "Start: " at the begining of every Paragraph and flag it as a change.
  474. /// p.InsertText(0, "Start: ", true);
  475. /// }
  476. ///
  477. /// // Save all changes made to this document.
  478. /// document.Save();
  479. /// }// Release this document from memory.
  480. /// </code>
  481. /// </example>
  482. /// <example>
  483. /// Inserting tabs using the \t switch.
  484. /// <code>
  485. /// // Create a document using a relative filename.
  486. /// using (DocX document = DocX.Load(@"C:\Example\Test.docx"))
  487. /// {
  488. /// // Iterate through the paragraphs in this document.
  489. /// foreach (Paragraph p in document.Paragraphs)
  490. /// {
  491. /// // Insert the string "\tStart:\t" at the begining of every paragraph and flag it as a change.
  492. /// p.InsertText(0, "\tStart:\t", true);
  493. /// }
  494. ///
  495. /// // Save all changes made to this document.
  496. /// document.Save();
  497. /// }// Release this document from memory.
  498. /// </code>
  499. /// </example>
  500. /// <seealso cref="Paragraph.RemoveText(int, bool)"/>
  501. /// <seealso cref="Paragraph.RemoveText(int, int, bool)"/>
  502. /// <seealso cref="Paragraph.ReplaceText(string, string, bool)"/>
  503. /// <seealso cref="Paragraph.ReplaceText(string, string, bool, RegexOptions)"/>
  504. /// <param name="index">The index position of the insertion.</param>
  505. /// <param name="value">The System.String to insert.</param>
  506. /// <param name="trackChanges">Flag this insert as a change.</param>
  507. public void InsertText(int index, string value, bool trackChanges)
  508. {
  509. InsertText(index, value, trackChanges, null);
  510. }
  511. /// <summary>
  512. /// Inserts a specified instance of System.String into a Novacode.DocX.Paragraph at a specified index position.
  513. /// </summary>
  514. /// <example>
  515. /// <code>
  516. /// // Create a document using a relative filename.
  517. /// using (DocX document = DocX.Load(@"C:\Example\Test.docx"))
  518. /// {
  519. /// // Iterate through the Paragraphs in this document.
  520. /// foreach (Paragraph p in document.Paragraphs)
  521. /// {
  522. /// // Insert the string "End: " at the end of every Paragraph and flag it as a change.
  523. /// p.InsertText("End: ", true);
  524. /// }
  525. ///
  526. /// // Save all changes made to this document.
  527. /// document.Save();
  528. /// }// Release this document from memory.
  529. /// </code>
  530. /// </example>
  531. /// <example>
  532. /// Inserting tabs using the \t switch.
  533. /// <code>
  534. /// // Create a document using a relative filename.
  535. /// using (DocX document = DocX.Load(@"C:\Example\Test.docx"))
  536. /// {
  537. /// // Iterate through the paragraphs in this document.
  538. /// foreach (Paragraph p in document.Paragraphs)
  539. /// {
  540. /// // Insert the string "\tEnd" at the end of every paragraph and flag it as a change.
  541. /// p.InsertText("\tEnd", true);
  542. /// }
  543. ///
  544. /// // Save all changes made to this document.
  545. /// document.Save();
  546. /// }// Release this document from memory.
  547. /// </code>
  548. /// </example>
  549. /// <seealso cref="Paragraph.RemoveText(int, bool)"/>
  550. /// <seealso cref="Paragraph.RemoveText(int, int, bool)"/>
  551. /// <seealso cref="Paragraph.ReplaceText(string, string, bool)"/>
  552. /// <seealso cref="Paragraph.ReplaceText(string, string, bool, RegexOptions)"/>
  553. /// <param name="value">The System.String to insert.</param>
  554. /// <param name="trackChanges">Flag this insert as a change.</param>
  555. public void InsertText(string value, bool trackChanges)
  556. {
  557. List<XElement> newRuns = FormatInput(value, null);
  558. xml.Add(newRuns);
  559. runLookup.Clear();
  560. BuildRunLookup(xml);
  561. DocX.RenumberIDs(document);
  562. }
  563. /// <summary>
  564. /// Inserts a specified instance of System.String into a Novacode.DocX.Paragraph at a specified index position.
  565. /// </summary>
  566. /// <example>
  567. /// <code>
  568. /// // Create a document using a relative filename.
  569. /// using (DocX document = DocX.Load(@"C:\Example\Test.docx"))
  570. /// {
  571. /// // Create a text formatting.
  572. /// Formatting f = new Formatting();
  573. /// f.FontColor = Color.Red;
  574. /// f.Size = 30;
  575. ///
  576. /// // Iterate through the Paragraphs in this document.
  577. /// foreach (Paragraph p in document.Paragraphs)
  578. /// {
  579. /// // Insert the string "Start: " at the begining of every Paragraph and flag it as a change.
  580. /// p.InsertText("Start: ", true, f);
  581. /// }
  582. ///
  583. /// // Save all changes made to this document.
  584. /// document.Save();
  585. /// }// Release this document from memory.
  586. /// </code>
  587. /// </example>
  588. /// <example>
  589. /// Inserting tabs using the \t switch.
  590. /// <code>
  591. /// // Create a document using a relative filename.
  592. /// using (DocX document = DocX.Load(@"C:\Example\Test.docx"))
  593. /// {
  594. /// // Create a text formatting.
  595. /// Formatting f = new Formatting();
  596. /// f.FontColor = Color.Red;
  597. /// f.Size = 30;
  598. ///
  599. /// // Iterate through the paragraphs in this document.
  600. /// foreach (Paragraph p in document.Paragraphs)
  601. /// {
  602. /// // Insert the string "\tEnd" at the end of every paragraph and flag it as a change.
  603. /// p.InsertText("\tEnd", true, f);
  604. /// }
  605. ///
  606. /// // Save all changes made to this document.
  607. /// document.Save();
  608. /// }// Release this document from memory.
  609. /// </code>
  610. /// </example>
  611. /// <seealso cref="Paragraph.RemoveText(int, bool)"/>
  612. /// <seealso cref="Paragraph.RemoveText(int, int, bool)"/>
  613. /// <seealso cref="Paragraph.ReplaceText(string, string, bool)"/>
  614. /// <seealso cref="Paragraph.ReplaceText(string, string, bool, RegexOptions)"/>
  615. /// <param name="value">The System.String to insert.</param>
  616. /// <param name="trackChanges">Flag this insert as a change.</param>
  617. /// <param name="formatting">The text formatting.</param>
  618. public void InsertText(string value, bool trackChanges, Formatting formatting)
  619. {
  620. List<XElement> newRuns = FormatInput(value, formatting.Xml);
  621. xml.Add(newRuns);
  622. runLookup.Clear();
  623. BuildRunLookup(xml);
  624. DocX.RenumberIDs(document);
  625. }
  626. /// <summary>
  627. /// Inserts a specified instance of System.String into a Novacode.DocX.Paragraph at a specified index position.
  628. /// </summary>
  629. /// <example>
  630. /// <code>
  631. /// // Create a document using a relative filename.
  632. /// using (DocX document = DocX.Load(@"C:\Example\Test.docx"))
  633. /// {
  634. /// // Create a text formatting.
  635. /// Formatting f = new Formatting();
  636. /// f.FontColor = Color.Red;
  637. /// f.Size = 30;
  638. ///
  639. /// // Iterate through the Paragraphs in this document.
  640. /// foreach (Paragraph p in document.Paragraphs)
  641. /// {
  642. /// // Insert the string "Start: " at the begining of every Paragraph and flag it as a change.
  643. /// p.InsertText(0, "Start: ", true, f);
  644. /// }
  645. ///
  646. /// // Save all changes made to this document.
  647. /// document.Save();
  648. /// }// Release this document from memory.
  649. /// </code>
  650. /// </example>
  651. /// <example>
  652. /// Inserting tabs using the \t switch.
  653. /// <code>
  654. /// // Create a document using a relative filename.
  655. /// using (DocX document = DocX.Load(@"C:\Example\Test.docx"))
  656. /// {
  657. /// // Create a text formatting.
  658. /// Formatting f = new Formatting();
  659. /// f.FontColor = Color.Red;
  660. /// f.Size = 30;
  661. ///
  662. /// // Iterate through the paragraphs in this document.
  663. /// foreach (Paragraph p in document.Paragraphs)
  664. /// {
  665. /// // Insert the string "\tStart:\t" at the begining of every paragraph and flag it as a change.
  666. /// p.InsertText(0, "\tStart:\t", true, f);
  667. /// }
  668. ///
  669. /// // Save all changes made to this document.
  670. /// document.Save();
  671. /// }// Release this document from memory.
  672. /// </code>
  673. /// </example>
  674. /// <seealso cref="Paragraph.RemoveText(int, bool)"/>
  675. /// <seealso cref="Paragraph.RemoveText(int, int, bool)"/>
  676. /// <seealso cref="Paragraph.ReplaceText(string, string, bool)"/>
  677. /// <seealso cref="Paragraph.ReplaceText(string, string, bool, RegexOptions)"/>
  678. /// <param name="index">The index position of the insertion.</param>
  679. /// <param name="value">The System.String to insert.</param>
  680. /// <param name="trackChanges">Flag this insert as a change.</param>
  681. /// <param name="formatting">The text formatting.</param>
  682. public void InsertText(int index, string value, bool trackChanges, Formatting formatting)
  683. {
  684. // Timestamp to mark the start of insert
  685. DateTime now = DateTime.Now;
  686. DateTime insert_datetime = new DateTime(now.Year, now.Month, now.Day, now.Hour, now.Minute, 0, DateTimeKind.Utc);
  687. // Get the first run effected by this Insert
  688. Run run = GetFirstRunEffectedByInsert(index);
  689. if (run == null)
  690. {
  691. object insert;
  692. if (formatting != null)
  693. insert = FormatInput(value, formatting.Xml);
  694. else
  695. insert = FormatInput(value, null);
  696. if (trackChanges)
  697. insert = CreateEdit(EditType.ins, insert_datetime, insert);
  698. xml.Add(insert);
  699. }
  700. else
  701. {
  702. object newRuns;
  703. if (formatting != null)
  704. newRuns = FormatInput(value, formatting.Xml);
  705. else
  706. newRuns = FormatInput(value, run.xml.Element(XName.Get("rPr", DocX.w.NamespaceName)));
  707. // The parent of this Run
  708. XElement parentElement = run.xml.Parent;
  709. switch (parentElement.Name.LocalName)
  710. {
  711. case "ins":
  712. {
  713. // The datetime that this ins was created
  714. DateTime parent_ins_date = DateTime.Parse(parentElement.Attribute(XName.Get("date", DocX.w.NamespaceName)).Value);
  715. /*
  716. * Special case: You want to track changes,
  717. * and the first Run effected by this insert
  718. * has a datetime stamp equal to now.
  719. */
  720. if (trackChanges && parent_ins_date.CompareTo(insert_datetime) == 0)
  721. {
  722. /*
  723. * Inserting into a non edit and this special case, is the same procedure.
  724. */
  725. goto default;
  726. }
  727. /*
  728. * If not the special case above,
  729. * then inserting into an ins or a del, is the same procedure.
  730. */
  731. goto case "del";
  732. }
  733. case "del":
  734. {
  735. object insert = newRuns;
  736. if (trackChanges)
  737. insert = CreateEdit(EditType.ins, insert_datetime, newRuns);
  738. // Split this Edit at the point you want to insert
  739. XElement[] splitEdit = SplitEdit(parentElement, index, EditType.ins);
  740. // Replace the origional run
  741. parentElement.ReplaceWith
  742. (
  743. splitEdit[0],
  744. insert,
  745. splitEdit[1]
  746. );
  747. break;
  748. }
  749. default:
  750. {
  751. object insert = newRuns;
  752. if (trackChanges && !parentElement.Name.LocalName.Equals("ins"))
  753. insert = CreateEdit(EditType.ins, insert_datetime, newRuns);
  754. // Split this run at the point you want to insert
  755. XElement[] splitRun = Run.SplitRun(run, index);
  756. // Replace the origional run
  757. run.xml.ReplaceWith
  758. (
  759. splitRun[0],
  760. insert,
  761. splitRun[1]
  762. );
  763. break;
  764. }
  765. }
  766. }
  767. // Rebuild the run lookup for this paragraph
  768. runLookup.Clear();
  769. BuildRunLookup(xml);
  770. DocX.RenumberIDs(document);
  771. }
  772. /// <summary>
  773. /// Removes characters from a Novacode.DocX.Paragraph.
  774. /// </summary>
  775. /// <example>
  776. /// <code>
  777. /// // Create a document using a relative filename.
  778. /// using (DocX document = DocX.Load(@"C:\Example\Test.docx"))
  779. /// {
  780. /// // Iterate through the paragraphs
  781. /// foreach (Paragraph p in document.Paragraphs)
  782. /// {
  783. /// // Remove the first two characters from every paragraph
  784. /// p.RemoveText(0, 2, false);
  785. /// }
  786. ///
  787. /// // Save all changes made to this document.
  788. /// document.Save();
  789. /// }// Release this document from memory.
  790. /// </code>
  791. /// </example>
  792. /// <seealso cref="Paragraph.ReplaceText(string, string, bool)"/>
  793. /// <seealso cref="Paragraph.ReplaceText(string, string, bool, RegexOptions)"/>
  794. /// <seealso cref="Paragraph.InsertText(string, bool)"/>
  795. /// <seealso cref="Paragraph.InsertText(int, string, bool)"/>
  796. /// <seealso cref="Paragraph.InsertText(int, string, bool, Formatting)"/>
  797. /// <seealso cref="Paragraph.InsertText(string, bool, Formatting)"/>
  798. /// <param name="index">The position to begin deleting characters.</param>
  799. /// <param name="count">The number of characters to delete</param>
  800. /// <param name="trackChanges">Track changes</param>
  801. public void RemoveText(int index, int count, bool trackChanges)
  802. {
  803. // Timestamp to mark the start of insert
  804. DateTime now = DateTime.Now;
  805. DateTime remove_datetime = new DateTime(now.Year, now.Month, now.Day, now.Hour, now.Minute, 0, DateTimeKind.Utc);
  806. // The number of characters processed so far
  807. int processed = 0;
  808. do
  809. {
  810. // Get the first run effected by this Remove
  811. Run run = GetFirstRunEffectedByEdit(index + processed);
  812. // The parent of this Run
  813. XElement parentElement = run.xml.Parent;
  814. switch (parentElement.Name.LocalName)
  815. {
  816. case "ins":
  817. {
  818. XElement[] splitEditBefore = SplitEdit(parentElement, index + processed, EditType.del);
  819. int min = Math.Min(count - processed, run.xml.ElementsAfterSelf().Sum(e => GetElementTextLength(e)));
  820. XElement[] splitEditAfter = SplitEdit(parentElement, index + processed + min, EditType.del);
  821. XElement temp = SplitEdit(splitEditBefore[1], index + processed + min, EditType.del)[0];
  822. object middle = CreateEdit(EditType.del, remove_datetime, temp.Elements());
  823. processed += GetElementTextLength(middle as XElement);
  824. if (!trackChanges)
  825. middle = null;
  826. parentElement.ReplaceWith
  827. (
  828. splitEditBefore[0],
  829. middle,
  830. splitEditAfter[1]
  831. );
  832. processed += GetElementTextLength(middle as XElement);
  833. break;
  834. }
  835. case "del":
  836. {
  837. if (trackChanges)
  838. {
  839. // You cannot delete from a deletion, advance processed to the end of this del
  840. processed += GetElementTextLength(parentElement);
  841. }
  842. else
  843. goto case "ins";
  844. break;
  845. }
  846. default:
  847. {
  848. XElement[] splitRunBefore = Run.SplitRun(run, index + processed);
  849. int min = Math.Min(index + processed + (count - processed), run.EndIndex);
  850. XElement[] splitRunAfter = Run.SplitRun(run, min);
  851. object middle = CreateEdit(EditType.del, remove_datetime, new List<XElement>() { Run.SplitRun(new Run(run.StartIndex + GetElementTextLength(splitRunBefore[0]), splitRunBefore[1]), min)[0] });
  852. processed += GetElementTextLength(middle as XElement);
  853. if (!trackChanges)
  854. middle = null;
  855. run.xml.ReplaceWith
  856. (
  857. splitRunBefore[0],
  858. middle,
  859. splitRunAfter[1]
  860. );
  861. break;
  862. }
  863. }
  864. // If after this remove the parent element is empty, remove it.
  865. if (GetElementTextLength(parentElement) == 0)
  866. {
  867. if (parentElement.Parent != null && parentElement.Parent.Name.LocalName != "tc")
  868. parentElement.Remove();
  869. }
  870. }
  871. while (processed < count);
  872. // Rebuild the run lookup
  873. runLookup.Clear();
  874. BuildRunLookup(xml);
  875. DocX.RenumberIDs(document);
  876. }
  877. /// <summary>
  878. /// Removes characters from a Novacode.DocX.Paragraph.
  879. /// </summary>
  880. /// <example>
  881. /// <code>
  882. /// // Create a document using a relative filename.
  883. /// using (DocX document = DocX.Load(@"C:\Example\Test.docx"))
  884. /// {
  885. /// // Iterate through the paragraphs
  886. /// foreach (Paragraph p in document.Paragraphs)
  887. /// {
  888. /// // Remove all but the first 2 characters from this Paragraph.
  889. /// p.RemoveText(2, false);
  890. /// }
  891. ///
  892. /// // Save all changes made to this document.
  893. /// document.Save();
  894. /// }// Release this document from memory.
  895. /// </code>
  896. /// </example>
  897. /// <seealso cref="Paragraph.ReplaceText(string, string, bool)"/>
  898. /// <seealso cref="Paragraph.ReplaceText(string, string, bool, RegexOptions)"/>
  899. /// <seealso cref="Paragraph.InsertText(string, bool)"/>
  900. /// <seealso cref="Paragraph.InsertText(int, string, bool)"/>
  901. /// <seealso cref="Paragraph.InsertText(int, string, bool, Formatting)"/>
  902. /// <seealso cref="Paragraph.InsertText(string, bool, Formatting)"/>
  903. /// <param name="index">The position to begin deleting characters.</param>
  904. /// <param name="trackChanges">Track changes</param>
  905. public void RemoveText(int index, bool trackChanges)
  906. {
  907. RemoveText(index, Text.Length - index, trackChanges);
  908. }
  909. /// <summary>
  910. /// Replaces all occurrences of a specified System.String in this instance, with another specified System.String.
  911. /// </summary>
  912. /// <example>
  913. /// <code>
  914. /// // Create a document using a relative filename.
  915. /// using (DocX document = DocX.Load(@"C:\Example\Test.docx"))
  916. /// {
  917. /// // Iterate through the paragraphs in this document.
  918. /// foreach (Paragraph p in document.Paragraphs)
  919. /// {
  920. /// // Replace all instances of the string "wrong" with the string "right" and ignore case.
  921. /// p.ReplaceText("wrong", "right", false, RegexOptions.IgnoreCase);
  922. /// }
  923. ///
  924. /// // Save all changes made to this document.
  925. /// document.Save();
  926. /// }// Release this document from memory.
  927. /// </code>
  928. /// </example>
  929. /// <seealso cref="Paragraph.RemoveText(int, int, bool)"/>
  930. /// <seealso cref="Paragraph.RemoveText(int, bool)"/>
  931. /// <seealso cref="Paragraph.InsertText(string, bool)"/>
  932. /// <seealso cref="Paragraph.InsertText(int, string, bool)"/>
  933. /// <seealso cref="Paragraph.InsertText(int, string, bool, Formatting)"/>
  934. /// <seealso cref="Paragraph.InsertText(string, bool, Formatting)"/>
  935. /// <param name="newValue">A System.String to replace all occurances of oldValue.</param>
  936. /// <param name="oldValue">A System.String to be replaced.</param>
  937. /// <param name="options">A bitwise OR combination of RegexOption enumeration options.</param>
  938. /// <param name="trackChanges">Track changes</param>
  939. public void ReplaceText(string oldValue, string newValue, bool trackChanges, RegexOptions options)
  940. {
  941. MatchCollection mc = Regex.Matches(this.Text, Regex.Escape(oldValue), options);
  942. // Loop through the matches in reverse order
  943. foreach (Match m in mc.Cast<Match>().Reverse())
  944. {
  945. InsertText(m.Index + oldValue.Length, newValue, trackChanges);
  946. RemoveText(m.Index, m.Length, trackChanges);
  947. }
  948. }
  949. /// <summary>
  950. /// Replaces all occurrences of a specified System.String in this instance, with another specified System.String.
  951. /// </summary>
  952. /// <example>
  953. /// <code>
  954. /// // Create a document using a relative filename.
  955. /// using (DocX document = DocX.Load(@"C:\Example\Test.docx"))
  956. /// {
  957. /// // Iterate through the paragraphs in this document.
  958. /// foreach (Paragraph p in document.Paragraphs)
  959. /// {
  960. /// // Replace all instances of the string "wrong" with the string "right".
  961. /// p.ReplaceText("wrong", "right", false);
  962. /// }
  963. ///
  964. /// // Save all changes made to this document.
  965. /// document.Save();
  966. /// }// Release this document from memory.
  967. /// </code>
  968. /// </example>
  969. /// <seealso cref="Paragraph.RemoveText(int, int, bool)"/>
  970. /// <seealso cref="Paragraph.RemoveText(int, bool)"/>
  971. /// <seealso cref="Paragraph.InsertText(string, bool)"/>
  972. /// <seealso cref="Paragraph.InsertText(int, string, bool)"/>
  973. /// <seealso cref="Paragraph.InsertText(int, string, bool, Formatting)"/>
  974. /// <seealso cref="Paragraph.InsertText(string, bool, Formatting)"/>
  975. /// <param name="newValue">A System.String to replace all occurances of oldValue.</param>
  976. /// <param name="oldValue">A System.String to be replaced.</param>
  977. /// <param name="trackChanges">Track changes</param>
  978. public void ReplaceText(string oldValue, string newValue, bool trackChanges)
  979. {
  980. ReplaceText(oldValue, newValue, trackChanges, RegexOptions.None);
  981. }
  982. }
  983. }