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