/// <summary>
        /// Maps a single work item into the document.
        /// </summary>
        /// <param name="node">The node for the work item to map.</param>
        /// <param name="workItemLayout">The work item layout that is to be used.</param>
        /// <param name="bookmarkNamingFunction">A function to return the bookmark to use for the given work item id.</param>
        /// <param name="workItemsCustomXMLPart">The custom XML part that contains the work item data.</param>
        /// <param name="insertionPoint">The relative location where the work item is to be mapped.</param>
        /// <param name="relativeWorkItemId">If inserting relative to a work item, the id of the work item relative to which the insertion is to be done, otherwise ignored.</param>
        private void MapWorkItemIntoDocument(WorkItemTreeNode node, IWorkItemLayout workItemLayout, Func <int, string> bookmarkNamingFunction, CustomXMLPart workItemsCustomXMLPart, InsertionPoint insertionPoint, int relativeWorkItemId)
        {
            BuildingBlock buildingBlock = workItemLayout.ChooseBuildingBlock(node);
            IEnumerable <ContentControl> contentControls = null;

            switch (insertionPoint)
            {
            case InsertionPoint.CurrentLocation:
            {
                contentControls = this.wordDocument.InsertBuildingBlock(buildingBlock, bookmarkNamingFunction(node.WorkItem.Id));
                break;
            }

            case InsertionPoint.BeforeBookmark:
            {
                contentControls = this.wordDocument.InsertBuildingBlockBeforeBookmark(buildingBlock, bookmarkNamingFunction(node.WorkItem.Id), bookmarkNamingFunction(relativeWorkItemId));
                break;
            }

            case InsertionPoint.AfterBookmark:
            {
                contentControls = this.wordDocument.InsertBuildingBlockAfterBookmark(buildingBlock, bookmarkNamingFunction(node.WorkItem.Id), bookmarkNamingFunction(relativeWorkItemId));
                break;
            }
            }

            foreach (ContentControl c in contentControls)
            {
                if (Utilities.IsValidTag(c.Tag) && node.WorkItem.FieldReferenceNames.Contains(c.Tag))
                {
                    if (c.Type == WdContentControlType.wdContentControlText || c.Type == WdContentControlType.wdContentControlDate)
                    {
                        string xpath = string.Format(CultureInfo.InvariantCulture, "/wi:WorkItems/wi:WorkItem[wi:Field[@name='{0}']={1}]/wi:Field[@name='{2}']", Constants.SystemIdFieldReferenceName, node.WorkItem.Id, c.Tag);
                        this.wordDocument.MapContentControl(c, xpath, "xmlns:wi='" + Constants.WorkItemNamespace + "'", workItemsCustomXMLPart);
                    }
                    else if (c.Type == WdContentControlType.wdContentControlRichText)
                    {
                        this.wordDocument.PopulateRichTextContentControlWithHtml(c, node.WorkItem[c.Tag].ToString());
                        c.Tag = string.Concat(c.Tag, "-", node.WorkItem.Id.ToString(CultureInfo.InvariantCulture));
                    }
                }
            }
        }
 /// <summary>
 /// Adds new work items when there were none before to place the new ones relative to.
 /// </summary>
 /// <param name="afterIds">The new list of work item ids.</param>
 /// <param name="nodes">The work item tree nodes for the query being processed.</param>
 /// <param name="layout">The layout to apply to the newly added work items.</param>
 /// <param name="customXMLPart">The custom XML part that contains the work item data.</param>
 /// <param name="bookmarkNamingFunction">A function to return the bookmark to use for the given work item, parameter is work item id.</param>
 private void AddNewWorkItemsWhenNoneBefore(int[] afterIds, WorkItemTreeNode[] nodes, IWorkItemLayout layout, CustomXMLPart customXMLPart, Func <int, string> bookmarkNamingFunction)
 {
     for (int i = 0; i < afterIds.Length; i++)
     {
         this.MapWorkItemIntoDocument(nodes[i], layout, (int id) => bookmarkNamingFunction(id), customXMLPart, InsertionPoint.CurrentLocation, 0);
     }
 }
        /// <summary>
        /// Adds new work items after the last existing one.
        /// </summary>
        /// <param name="newIds">The new work items ids.</param>
        /// <param name="succeeds">The id of the last existing work item.</param>
        /// <param name="nodes">The work item tree nodes for the query being processed.</param>
        /// <param name="workItemLayout">The work item layout to apply to the newly added work items.</param>
        /// <param name="customXMLPart">The custom XML part that contains the work item data.</param>
        /// <param name="bookmarkNamingFunction">A function to return the bookmark to use for the given work item, parameter is work item id.</param>
        private void AddAllNewWorkItemsAfterLastExisting(int[] newIds, int succeeds, WorkItemTreeNode[] nodes, IWorkItemLayout workItemLayout, CustomXMLPart customXMLPart, Func <int, string> bookmarkNamingFunction)
        {
            int lastId = succeeds;

            for (int i = 0; i < newIds.Length; i++)
            {
                this.MapWorkItemIntoDocument(nodes.Where(n => n.WorkItem.Id == newIds[i]).Single(), workItemLayout, (int id) => bookmarkNamingFunction(id), customXMLPart, InsertionPoint.AfterBookmark, lastId);
                lastId = newIds[i];
            }
        }
 /// <summary>
 /// Adds new work items that come after any existing ones, stopping when the first existing one is found.
 /// </summary>
 /// <param name="newIds">The new work item ids.</param>
 /// <param name="beforeIds">The old list of work item ids.</param>
 /// <param name="afterIds">The new list of work item ids.</param>
 /// <param name="nodes">The work item tree nodes for the query being processed.</param>
 /// <param name="workItemLayout">The work item layout to apply to the newly added work items.</param>
 /// <param name="customXMLPart">The custom XML part that contains the work item data.</param>
 /// <param name="bookmarkNamingFunction">A function to return the bookmark to use for the given work item, parameter is work item id.</param>
 private void AddNewWorkItemsAfterExisting(int[] newIds, int[] beforeIds, int[] afterIds, WorkItemTreeNode[] nodes, IWorkItemLayout workItemLayout, CustomXMLPart customXMLPart, Func <int, string> bookmarkNamingFunction)
 {
     for (int i = afterIds.Length - 1; i >= 0; i--)
     {
         if (newIds.Contains(afterIds[i]))
         {
             int succeeds = FindLastExistingWorkItemId(afterIds.Take(i).ToArray(), beforeIds);
             if (succeeds >= 0)
             {
                 this.MapWorkItemIntoDocument(nodes[i], workItemLayout, (int id) => bookmarkNamingFunction(id), customXMLPart, InsertionPoint.AfterBookmark, succeeds);
             }
         }
         else
         {
             break;
         }
     }
 }
 /// <summary>
 /// Adds new work items that come before any existing ones.
 /// </summary>
 /// <param name="newIds">The new work item ids.</param>
 /// <param name="beforeIds">The old list of work item ids.</param>
 /// <param name="afterIds">The new list of work item ids.</param>
 /// <param name="nodes">The work item tree nodes for the query being processed.</param>
 /// <param name="workItemLayout">The work item layout to apply to the newly added work items.</param>
 /// <param name="customXMLPart">The custom XML part that contains the work item data.</param>
 /// <param name="bookmarkNamingFunction">A function to return the bookmark to use for the given work item, parameter is work item id.</param>
 private void AddNewWorkItemsBeforeExisting(int[] newIds, int[] beforeIds, int[] afterIds, WorkItemTreeNode[] nodes, IWorkItemLayout workItemLayout, CustomXMLPart customXMLPart, Func <int, string> bookmarkNamingFunction)
 {
     for (int i = 0; i < afterIds.Length; i++)
     {
         if (newIds.Contains(afterIds[i]))
         {
             int precedes = FindFirstExistingWorkItemId(afterIds.Skip(i + 1).ToArray(), beforeIds);
             if (precedes >= 0)
             {
                 this.MapWorkItemIntoDocument(nodes[i], workItemLayout, (int id) => bookmarkNamingFunction(id), customXMLPart, InsertionPoint.BeforeBookmark, precedes);
             }
         }
     }
 }
 /// <summary>
 /// Adds the new work items that were not in the last run of a query.
 /// </summary>
 /// <remarks>
 /// If the current work items have been manually sorted then new items are just added to the end, otherwise they are added in the new sort order.
 /// </remarks>
 /// <param name="queryWorkItemsBefore">The query to work item association data from before the refresh.</param>
 /// <param name="queryWorkItemsAfter">The query to work item association data as a result of the refresh.</param>
 /// <param name="workItemLayout">The work item layout associated with the query.</param>
 /// <param name="customXMLPart">The custom XML part that contains the work item data.</param>
 /// <param name="bookmarkNamingFunction">A function to return the bookmark to use for the given work item, given the work item id.</param>
 /// <param name="isManuallySorted">Indicates if the current work items are manually sorted.</param>
 /// <param name="cancellationToken">Used to cancel the refresh.</param>
 private void AddNewWorkItems(QueryWorkItems queryWorkItemsBefore, QueryWorkItems queryWorkItemsAfter, IWorkItemLayout workItemLayout, CustomXMLPart customXMLPart, Func <int, string> bookmarkNamingFunction, bool isManuallySorted, CancellationToken cancellationToken)
 {
     this.logger.Log(TraceEventType.Verbose, "Adding new work items");
     cancellationToken.ThrowIfCancellationRequested();
     int[] newIds = queryWorkItemsAfter.WorkItemIds.Except(queryWorkItemsBefore.WorkItemIds).ToArray();
     if (newIds.Length > 0)
     {
         // We have some new work items that were not present last time the query was executed.
         int[] beforeIds          = queryWorkItemsBefore.WorkItemIds.ToArray();
         int[] afterIds           = queryWorkItemsAfter.WorkItemIds.ToArray();
         WorkItemTreeNode[] nodes = queryWorkItemsAfter.WorkItemTreeNodes.ToArray();
         Debug.Assert(afterIds.Length == nodes.Length, "Number of after nodes must match number of after ids.");
         if (isManuallySorted)
         {
             this.AddAllNewWorkItemsAfterLastExisting(newIds, this.GetLastPhysicalWorkItem(beforeIds, bookmarkNamingFunction), nodes, workItemLayout, customXMLPart, bookmarkNamingFunction);
         }
         else if (afterIds.Intersect(beforeIds).Count() > 0)
         {
             this.AddNewWorkItemsBeforeExisting(newIds, beforeIds, afterIds, nodes, workItemLayout, customXMLPart, (int id) => bookmarkNamingFunction(id));
             this.AddNewWorkItemsAfterExisting(newIds, beforeIds, afterIds, nodes, workItemLayout, customXMLPart, (int id) => bookmarkNamingFunction(id));
         }
         else
         {
             this.AddNewWorkItemsWhenNoneBefore(afterIds, nodes, workItemLayout, customXMLPart, (int id) => bookmarkNamingFunction(id));
         }
     }
 }