選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

List.cs 9.8KB

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