/*************************************************************************************
DocX – DocX is the community edition of Xceed Words for .NET
Copyright (C) 2009-2016 Xceed Software Inc.
This program is provided to you under the terms of the Microsoft Public
License (Ms-PL) as published at http://wpftoolkit.codeplex.com/license
For more features and fast professional support,
pick up Xceed Words for .NET at https://xceed.com/xceed-words-for-net/
***********************************************************************************/
using System;
using System.Linq;
using System.Xml.Linq;
using System.Collections.Generic;
namespace Xceed.Words.NET
{
///
/// Represents a List in a document.
///
public class List : InsertBeforeOrAfter
{
#region Public Members
///
/// 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.
///
public List Items
{
get; private set;
}
///
/// The numId used to reference the list settings in the numbering.xml
///
public int NumId
{
get; private set;
}
///
/// The ListItemType (bullet or numbered) of the list.
///
public ListItemType? ListType
{
get; private set;
}
#endregion
#region Constructors
internal List( DocX document, XElement xml )
: base( document, xml )
{
Items = new List();
ListType = null;
}
#endregion
#region Public Methods
///
/// Adds an item to the list.
///
///
///
/// Throws an InvalidOperationException if the item cannot be added to the list.
///
public void AddItem( Paragraph paragraph )
{
if( paragraph.IsListItem )
{
var numIdNode = paragraph.Xml.Descendants().FirstOrDefault( s => s.Name.LocalName == "numId" );
if( numIdNode == null )
return;
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 );
}
///
/// Determine if it is able to add the item to the list
///
///
///
/// Return true if AddItem(...) will succeed with the given paragraph.
///
public bool CanAddListItem( Paragraph paragraph )
{
if( paragraph.IsListItem )
{
//var lvlNode = paragraph.Xml.Descendants().First(s => s.Name.LocalName == "ilvl");
var numIdNode = paragraph.Xml.Descendants().FirstOrDefault( s => s.Name.LocalName == "numId" );
if( numIdNode == null )
return false;
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() );
}
#endregion
#region Internal Methods
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( "Xceed.Words.NET.Resources.numbering.default_bullet_abstract.xml.gz" );
break;
case ListItemType.Numbered:
listTemplate = HelperFunctions.DecompressXMLResource( "Xceed.Words.NET.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 );
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;
}
///
/// Get the abstractNum definition for the given numId
///
/// The numId on the pPr element
/// XElement representing the requested abstractNum
internal XElement GetAbstractNum( int numId )
{
return HelperFunctions.GetAbstractNum( this.Document, numId.ToString() );
}
#endregion
#region Private Methods
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 );
}
private XElement GetAbstractNumXml( int abstractNumId, int numId, int? startNumber, bool continueNumbering )
{
var start = new XElement( XName.Get( "startOverride", DocX.w.NamespaceName ), new XAttribute( DocX.w + "val", startNumber ?? 1 ) );
var level = new XElement( XName.Get( "lvlOverride", DocX.w.NamespaceName ), new XAttribute( DocX.w + "ilvl", 0 ), start );
var element = 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 ), element )
: new XElement( XName.Get( "num", DocX.w.NamespaceName ), new XAttribute( DocX.w + "numId", numId ), element, level );
}
///
/// 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.
///
///
/// 0 if there are no elements in the list already.
/// Increment the return for the next valid value of a new list element.
///
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;
}
///
/// 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.
///
///
/// -1 if there are no elements in the list already.
/// Increment the return for the next valid value of a new list element.
///
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;
}
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 );
}
#endregion
}
}