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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Xml.Linq;
  5. namespace Novacode
  6. {
  7. /// <summary>
  8. /// Represents a List in a document.
  9. /// </summary>
  10. public class List : InsertBeforeOrAfter
  11. {
  12. /// <summary>
  13. /// This is a list of paragraphs that will be added to the document
  14. /// when the list is inserted into the document.
  15. /// The paragraph needs a numPr defined to be in this items collection.
  16. /// </summary>
  17. public List<Paragraph> Items { get; private set; }
  18. /// <summary>
  19. /// The numId used to reference the list settings in the numbering.xml
  20. /// </summary>
  21. public int NumId { get; private set; }
  22. /// <summary>
  23. /// The ListItemType (bullet or numbered) of the list.
  24. /// </summary>
  25. public ListItemType? ListType { get; private set; }
  26. internal List(DocX document, XElement xml)
  27. : base(document, xml)
  28. {
  29. Items = new List<Paragraph>();
  30. ListType = null;
  31. }
  32. /// <summary>
  33. /// Adds an item to the list.
  34. /// </summary>
  35. /// <param name="paragraph"></param>
  36. /// <exception cref="InvalidOperationException">
  37. /// Throws an InvalidOperationException if the item cannot be added to the list.
  38. /// </exception>
  39. public void AddItem(Paragraph paragraph)
  40. {
  41. if (paragraph.IsListItem)
  42. {
  43. var numIdNode = paragraph.Xml.Descendants().First(s => s.Name.LocalName == "numId");
  44. var numId = Int32.Parse(numIdNode.Attribute(DocX.w + "val").Value);
  45. if (CanAddListItem(paragraph))
  46. {
  47. NumId = numId;
  48. Items.Add(paragraph);
  49. }
  50. else
  51. throw new InvalidOperationException("New list items can only be added to this list if they are have the same numId.");
  52. }
  53. }
  54. public void AddItemWithStartValue(Paragraph paragraph, int start)
  55. {
  56. //TODO: Update the numbering
  57. UpdateNumberingForLevelStartNumber(int.Parse(paragraph.IndentLevel.ToString()), start);
  58. if (ContainsLevel(start))
  59. throw new InvalidOperationException("Cannot add a paragraph with a start value if another element already exists in this list with that level.");
  60. AddItem(paragraph);
  61. }
  62. private void UpdateNumberingForLevelStartNumber(int iLevel, int start)
  63. {
  64. var abstractNum = GetAbstractNum(NumId);
  65. var level = abstractNum.Descendants().First(el => el.Name.LocalName == "lvl" && el.GetAttribute(DocX.w + "ilvl") == iLevel.ToString());
  66. level.Descendants().First(el => el.Name.LocalName == "start").SetAttributeValue(DocX.w + "val", start);
  67. }
  68. /// <summary>
  69. /// Determine if it is able to add the item to the list
  70. /// </summary>
  71. /// <param name="paragraph"></param>
  72. /// <returns>
  73. /// Return true if AddItem(...) will succeed with the given paragraph.
  74. /// </returns>
  75. public bool CanAddListItem(Paragraph paragraph)
  76. {
  77. if (paragraph.IsListItem)
  78. {
  79. //var lvlNode = paragraph.Xml.Descendants().First(s => s.Name.LocalName == "ilvl");
  80. var numIdNode = paragraph.Xml.Descendants().First(s => s.Name.LocalName == "numId");
  81. var numId = Int32.Parse(numIdNode.Attribute(DocX.w + "val").Value);
  82. //Level = Int32.Parse(lvlNode.Attribute(DocX.w + "val").Value);
  83. if (NumId == 0 || (numId == NumId && numId > 0))
  84. {
  85. return true;
  86. }
  87. }
  88. return false;
  89. }
  90. public bool ContainsLevel(int ilvl)
  91. {
  92. return Items.Any(i => i.ParagraphNumberProperties.Descendants().First(el => el.Name.LocalName == "ilvl").Value == ilvl.ToString());
  93. }
  94. internal void CreateNewNumberingNumId(int level = 0, ListItemType listType = ListItemType.Numbered, int? startNumber = null, bool continueNumbering = false)
  95. {
  96. ValidateDocXNumberingPartExists();
  97. if (Document.numbering.Root == null)
  98. {
  99. throw new InvalidOperationException("Numbering section did not instantiate properly.");
  100. }
  101. ListType = listType;
  102. var numId = GetMaxNumId() + 1;
  103. var abstractNumId = GetMaxAbstractNumId() + 1;
  104. XDocument listTemplate;
  105. switch (listType)
  106. {
  107. case ListItemType.Bulleted:
  108. listTemplate = HelperFunctions.DecompressXMLResource("Novacode.Resources.numbering.default_bullet_abstract.xml.gz");
  109. break;
  110. case ListItemType.Numbered:
  111. listTemplate = HelperFunctions.DecompressXMLResource("Novacode.Resources.numbering.default_decimal_abstract.xml.gz");
  112. break;
  113. default:
  114. throw new InvalidOperationException(string.Format("Unable to deal with ListItemType: {0}.", listType.ToString()));
  115. }
  116. var abstractNumTemplate = listTemplate.Descendants().Single(d => d.Name.LocalName == "abstractNum");
  117. abstractNumTemplate.SetAttributeValue(DocX.w + "abstractNumId", abstractNumId);
  118. //Fixing an issue where numbering would continue from previous numbered lists. Setting startOverride assures that a numbered list starts on the provided number.
  119. //The override needs only be on level 0 as this will cascade to the rest of the list.
  120. var abstractNumXml = GetAbstractNumXml(abstractNumId, numId, startNumber, continueNumbering);
  121. var abstractNumNode = Document.numbering.Root.Descendants().LastOrDefault(xElement => xElement.Name.LocalName == "abstractNum");
  122. var numXml = Document.numbering.Root.Descendants().LastOrDefault(xElement => xElement.Name.LocalName == "num");
  123. if (abstractNumNode == null || numXml == null)
  124. {
  125. Document.numbering.Root.Add(abstractNumTemplate);
  126. Document.numbering.Root.Add(abstractNumXml);
  127. }
  128. else
  129. {
  130. abstractNumNode.AddAfterSelf(abstractNumTemplate);
  131. numXml.AddAfterSelf(
  132. abstractNumXml
  133. );
  134. }
  135. NumId = numId;
  136. }
  137. private XElement GetAbstractNumXml(int abstractNumId, int numId, int? startNumber, bool continueNumbering)
  138. {
  139. //Fixing an issue where numbering would continue from previous numbered lists. Setting startOverride assures that a numbered list starts on the provided number.
  140. //The override needs only be on level 0 as this will cascade to the rest of the list.
  141. var startOverride = new XElement(XName.Get("startOverride", DocX.w.NamespaceName), new XAttribute(DocX.w + "val", startNumber ?? 1));
  142. var lvlOverride = new XElement(XName.Get("lvlOverride", DocX.w.NamespaceName), new XAttribute(DocX.w + "ilvl", 0), startOverride);
  143. var abstractNumIdElement = new XElement(XName.Get("abstractNumId", DocX.w.NamespaceName), new XAttribute(DocX.w + "val", abstractNumId));
  144. return continueNumbering
  145. ? new XElement(XName.Get("num", DocX.w.NamespaceName), new XAttribute(DocX.w + "numId", numId), abstractNumIdElement)
  146. : new XElement(XName.Get("num", DocX.w.NamespaceName), new XAttribute(DocX.w + "numId", numId), abstractNumIdElement, lvlOverride);
  147. }
  148. /// <summary>
  149. /// Method to determine the last numId for a list element.
  150. /// Also useful for determining the next numId to use for inserting a new list element into the document.
  151. /// </summary>
  152. /// <returns>
  153. /// 0 if there are no elements in the list already.
  154. /// Increment the return for the next valid value of a new list element.
  155. /// </returns>
  156. private int GetMaxNumId()
  157. {
  158. const int defaultValue = 0;
  159. if (Document.numbering == null)
  160. return defaultValue;
  161. var numlist = Document.numbering.Descendants().Where(d => d.Name.LocalName == "num").ToList();
  162. if (numlist.Any())
  163. return numlist.Attributes(DocX.w + "numId").Max(e => int.Parse(e.Value));
  164. return defaultValue;
  165. }
  166. /// <summary>
  167. /// Method to determine the last abstractNumId for a list element.
  168. /// Also useful for determining the next abstractNumId to use for inserting a new list element into the document.
  169. /// </summary>
  170. /// <returns>
  171. /// -1 if there are no elements in the list already.
  172. /// Increment the return for the next valid value of a new list element.
  173. /// </returns>
  174. private int GetMaxAbstractNumId()
  175. {
  176. const int defaultValue = -1;
  177. if (Document.numbering == null)
  178. return defaultValue;
  179. var numlist = Document.numbering.Descendants().Where(d => d.Name.LocalName == "abstractNum").ToList();
  180. if (numlist.Any())
  181. {
  182. var maxAbstractNumId = numlist.Attributes(DocX.w + "abstractNumId").Max(e => int.Parse(e.Value));
  183. return maxAbstractNumId;
  184. }
  185. return defaultValue;
  186. }
  187. /// <summary>
  188. /// Get the abstractNum definition for the given numId
  189. /// </summary>
  190. /// <param name="numId">The numId on the pPr element</param>
  191. /// <returns>XElement representing the requested abstractNum</returns>
  192. internal XElement GetAbstractNum(int numId)
  193. {
  194. var num = Document.numbering.Descendants().First(d => d.Name.LocalName == "num" && d.GetAttribute(DocX.w + "numId").Equals(numId.ToString()));
  195. var abstractNumId = num.Descendants().First(d => d.Name.LocalName == "abstractNumId");
  196. return Document.numbering.Descendants().First(d => d.Name.LocalName == "abstractNum" && d.GetAttribute("abstractNumId").Equals(abstractNumId.Value));
  197. }
  198. private void ValidateDocXNumberingPartExists()
  199. {
  200. var numberingUri = new Uri("/word/numbering.xml", UriKind.Relative);
  201. // If the internal document contains no /word/numbering.xml create one.
  202. if (!Document.package.PartExists(numberingUri))
  203. Document.numbering = HelperFunctions.AddDefaultNumberingXml(Document.package);
  204. }
  205. }
  206. }