//=====================================================================

        /// <summary>
        /// Handle elements with generic bold styling
        /// </summary>
        /// <param name="props">The element properties</param>
        private static void BoldElement(ElementProperties props)
        {
            props.Converter.AddInlineToContainer(new Bold());
        }
 /// <summary>
 /// Handle elements with generic italic styling
 /// </summary>
 /// <param name="props">The element properties</param>
 private static void ItalicElement(ElementProperties props)
 {
     props.Converter.AddInlineToContainer(new Italic());
 }
        //=====================================================================

        /// <summary>
        /// Handle table elements
        /// </summary>
        /// <param name="props">The element properties</param>
        private static void TableElement(ElementProperties props)
        {
            // If there is a title element, add a title using its content right before the table.  The title
            // element handler will ignore it since it will have this table as its parent.
            XElement title = props.Element.Element(ddue + "title");

            if(title != null)
            {
                Paragraph p = new Paragraph(new Run(reCondenseWhitespace.Replace(title.Value.Trim(), " ")));
                props.Converter.AddNonParentToBlockContainer(p);

                p.SetResourceReference(Paragraph.StyleProperty, NamedStyle.TableTitle);
            }

            props.Converter.AddToBlockContainer(new Table());
        }
        /// <summary>
        /// Handle table row elements
        /// </summary>
        /// <param name="props">The element properties</param>
        private static void RowElement(ElementProperties props)
        {
            // If not already in a table row group, add the group first
            if(!(props.Converter.CurrentBlockElement is TableRowGroup))
            {
                var group = new TableRowGroup();

                // We'll use this group as the parent for all child elements but we don't want to
                // push it on the stack.  The current element should be a table.
                ((Table)props.Converter.CurrentBlockElement).RowGroups.Add(group);
                props.Converter.CurrentBlockElement = group;
            }

            props.Converter.AddToBlockContainer(new TableRow());
        }
 /// <summary>
 /// Handle list item elements
 /// </summary>
 /// <param name="props">The element properties</param>
 private static void ListItemElement(ElementProperties props)
 {
     props.Converter.AddToBlockContainer(new ListItem());
 }
        //=====================================================================

        /// <summary>
        /// Handle media link elements
        /// </summary>
        /// <param name="props">The element properties</param>
        private static void MediaLinkElement(ElementProperties props)
        {
            BlockUIContainer imageBlock = new BlockUIContainer();
            XElement imageElement = props.Element.Descendants(ddue + "image").FirstOrDefault(),
                captionElement = props.Element.Descendants(ddue + "caption").FirstOrDefault();
            XAttribute attribute;
            HorizontalAlignment alignment = HorizontalAlignment.Left;
            KeyValuePair<string, string> imageInfo = new KeyValuePair<string, string>(null, null);
            Image image = new Image();
            string id = "???", caption = null, leadIn = null;
            bool captionAfter = false;

            if(imageElement != null)
            {
                attribute = imageElement.Attribute(xlink + "href");

                if(attribute != null)
                {
                    id = attribute.Value;

                    if(!props.Converter.MediaFiles.TryGetValue(id, out imageInfo))
                        imageInfo = new KeyValuePair<string, string>(null, null);
                }

                attribute = imageElement.Attribute("placement");

                if(attribute != null)
                    switch(attribute.Value)
                    {
                        case "center":
                            alignment = HorizontalAlignment.Center;
                            break;

                        case "far":
                            alignment = HorizontalAlignment.Right;
                            break;
                    }
            }

            if(captionElement != null)
            {
                caption = reCondenseWhitespace.Replace(captionElement.Value.Trim(), " ");

                attribute = captionElement.Attribute("placement");

                if(attribute != null && attribute.Value == "after")
                    captionAfter = true;

                attribute = captionElement.Attribute("lead");

                if(attribute != null)
                    leadIn = attribute.Value;
            }

            if(!String.IsNullOrEmpty(imageInfo.Key) && File.Exists(imageInfo.Key))
            {
                var bm = new BitmapImage();

                // Cache on load to prevent it locking the image
                bm.BeginInit();
                bm.CacheOption = BitmapCacheOption.OnLoad;
                bm.UriSource = new Uri(imageInfo.Key);
                bm.EndInit();

                // Use the actual image size to mimic the HTML layout
                image.Height = bm.Height;
                image.Width = bm.Width;
                image.ToolTip = !String.IsNullOrEmpty(imageInfo.Value) ? imageInfo.Value :
                    "ID: " + id + "\nFilename: " + imageInfo.Key;
                image.Margin = new Thickness(10);
                image.HorizontalAlignment = alignment;

                image.Source = bm;

                if(String.IsNullOrEmpty(caption))
                    imageBlock.Child = image;
                else
                {
                    StackPanel sp = new StackPanel();
                    sp.HorizontalAlignment = alignment;

                    if(captionAfter)
                        sp.Children.Add(image);

                    StackPanel spChild = new StackPanel { Orientation = Orientation.Horizontal };
                    spChild.HorizontalAlignment = alignment;

                    if(!String.IsNullOrEmpty(leadIn))
                        spChild.Children.Add(new TextBlock
                        {
                            Text = leadIn + ": ",
                            FontWeight = FontWeights.Bold,
                            Foreground = new SolidColorBrush(Color.FromRgb(0, 0x33, 0x99))
                        });

                    spChild.Children.Add(new TextBlock
                    {
                        Text = caption,
                        Foreground = new SolidColorBrush(Color.FromRgb(0, 0x33, 0x99))
                    });

                    sp.Children.Add(spChild);

                    if(!captionAfter)
                        sp.Children.Add(image);

                    imageBlock.Child = sp;
                }
            }
            else
                imageBlock.Child = new TextBlock
                {
                    Text = String.Format(CultureInfo.InvariantCulture, "[INVALID IMAGE ID: {0}]", id),
                    Background = new SolidColorBrush(Colors.Red),
                    Foreground = new SolidColorBrush(Colors.White),
                    HorizontalAlignment = alignment
                };

            props.Converter.AddToBlockContainer(imageBlock);
            props.ParseChildren = false;
        }
        /// <summary>
        /// Handle markup elements
        /// </summary>
        /// <param name="props">The element properties</param>
        /// <remarks>Markup elements can contain anything so no attempt is made to parse the content.
        /// Its is added as an inline or block element depending on the current context.</remarks>
        private static void MarkupElement(ElementProperties props)
        {
            // Get the content with all formatting preserved
            string content = reRemoveNamespace.Replace(
                props.Element.Nodes().Aggregate("", (c, node) => c += node.ToString()), String.Empty);

            // This can be a block or inline element depending on the parent element
            Span span = props.Converter.CurrentSpanElement as Span;

            if(span != null)
            {
                Run r = new Run
                {
                    Text = reCondenseWhitespace.Replace(content, " "),
                    Background = new SolidColorBrush(Color.FromRgb(0xEF, 0xEF, 0xF7))
                };

                span.Inlines.Add(r);
                props.ReturnToParent = false;
            }
            else
            {
                Paragraph p = props.Converter.CurrentBlockElement as Paragraph;

                if(p != null)
                {
                    Run r = new Run
                    {
                        Text = reCondenseWhitespace.Replace(content, " "),
                        Background = new SolidColorBrush(Color.FromRgb(0xEF, 0xEF, 0xF7))
                    };

                    p.Inlines.Add(r);
                    props.ReturnToParent = false;
                }
                else
                {
                    Section s = new Section();
                    props.Converter.AddToBlockContainer(s);

                    p = new Paragraph();
                    s.Blocks.Add(p);
                    p.Inlines.Add(new Run(StripLeadingWhitespace(content, 4)));

                    s.SetResourceReference(Section.StyleProperty, NamedStyle.CodeBlock);
                }
            }

            props.ParseChildren = false;
        }
 /// <summary>
 /// Handle definition elements
 /// </summary>
 /// <param name="props">The element properties</param>
 private static void DefinitionElement(ElementProperties props)
 {
     Section s = new Section();
     props.Converter.AddToBlockContainer(s);
     s.SetResourceReference(Section.StyleProperty, NamedStyle.Definition);
 }
 /// <summary>
 /// Handle ignored elements
 /// </summary>
 /// <param name="props">The element properties</param>
 /// <remarks>For ignored elements, we don't return to the parent element after parsing
 /// this one's children as there may be other sibiling elements after it.</remarks>
 private static void IgnoredElement(ElementProperties props)
 {
     props.ReturnToParent = false;
 }
 /// <summary>
 /// Handle ignored elements with ignored child elements
 /// </summary>
 /// <param name="props">The element properties</param>
 /// <remarks>This ignores all child elements too.  For ignored elements, we don't return to the parent
 /// element as there may be other sibiling elements after it.</remarks>
 private static void IgnoredElementWithChildren(ElementProperties props)
 {
     props.ParseChildren = props.ReturnToParent = false;
 }
        //=====================================================================

        /// <summary>
        /// Handle auto-outline elements
        /// </summary>
        /// <param name="props">The element properties</param>
        private static void AutoOutlineElement(ElementProperties props)
        {
            XElement parent;
            XAttribute attribute;
            List list;
            string leadText = String.Empty;
            bool excludeRelatedTopics, isInIntro = (props.Element.Parent.Name.LocalName == "introduction");
            int maxDepth;

            props.ParseChildren = props.ReturnToParent = false;

            attribute = props.Element.Attribute("lead");

            if(attribute != null)
            {
                leadText = attribute.Value.Trim();

                if(leadText == "none")
                    leadText = null;
            }

            if(leadText != null && leadText.Length == 0)
                if(isInIntro)
                    leadText = "This topic contains the following sections.";
                else
                    leadText = "This section contains the following subsections.";

            if(isInIntro)
            {
                parent = props.Element.Parent.Parent;
                attribute = props.Element.Attribute("excludeRelatedTopics");

                if(attribute == null || !Boolean.TryParse(attribute.Value, out excludeRelatedTopics))
                    excludeRelatedTopics = false;

                if(!excludeRelatedTopics)
                {
                    var relatedTopics = parent.Element(ddue + "relatedTopics");

                    if(relatedTopics == null || !relatedTopics.HasElements)
                        excludeRelatedTopics = true;
                }
            }
            else
            {
                parent = props.Element.Parent.Parent.Elements(ddue + "sections").FirstOrDefault();
                excludeRelatedTopics = true;
            }

            if(String.IsNullOrEmpty(props.Element.Value) || !Int32.TryParse(props.Element.Value, out maxDepth))
                maxDepth = 0;

            Section s = new Section();
            props.Converter.AddNonParentToBlockContainer(s);

            if(leadText != null)
            {
                Paragraph p = new Paragraph();
                p.Inlines.Add(new Run(leadText));
                s.Blocks.Add(p);
            }

            if(parent != null)
            {
                list = InsertAutoOutline(parent, 0, maxDepth);

                if(list != null)
                {
                    // If not exluding related topics, add a link to the See Also section
                    if(!excludeRelatedTopics)
                        list.ListItems.Add(new ListItem(new Paragraph(new Hyperlink(new Run("See Also"))
                        {
                            NavigateUri = new Uri("link://#seeAlsoSection")
                        })));

                    s.Blocks.Add(list);
                }
            }
        }
        /// <summary>
        /// Handle link elements
        /// </summary>
        /// <param name="props">The element properties</param>
        private static void LinkElement(ElementProperties props)
        {
            XAttribute href;
            string id, title;
            int pos;

            href = props.Element.Attribute(xlink + "href");

            if(href != null)
                id = href.Value;
            else
                id = "MISSING_LINK_HREF";

            props.Converter.AddInlineToContainer(new Hyperlink { NavigateUri = new Uri("link://" + id) });

            // Add the topic title as the link text if there is no inner text
            if(!props.ParseChildren)
            {
                pos = id.IndexOf('#');

                if(pos != -1)
                    id = id.Substring(0, pos);

                if(!props.Converter.TopicTitles.TryGetValue(id, out title))
                    title = "[UNKNOWN TOPIC ID: " + id + "]";

                props.Converter.AddInlineToContainer(new Run(title));
            }
            else
            {
                props.Converter.AddInlineToContainer(new Run(reCondenseWhitespace.Replace(
                    props.Element.Value.Trim(), " ")));
                props.ParseChildren = false;
            }
        }
        /// <summary>
        /// Handle external link elements
        /// </summary>
        /// <param name="props">The element properties</param>
        private static void ExternalLinkElement(ElementProperties props)
        {
            XElement linkTextElement = props.Element.Descendants(ddue + "linkText").FirstOrDefault(),
                linkAltTextElement = props.Element.Descendants(ddue + "linkAlternateText").FirstOrDefault(),
                linkUriElement = props.Element.Descendants(ddue + "linkUri").FirstOrDefault(),
                linkTargetElement = props.Element.Descendants(ddue + "linkTarget").FirstOrDefault();
            Hyperlink l;
            string linkText, linkUri;

            if(linkUriElement != null)
                linkUri = linkUriElement.Value;
            else
                linkUri = "none://MISSING_LINKURI_ELEMENT";

            if(linkTextElement != null)
                linkText = reCondenseWhitespace.Replace(linkTextElement.Value.Trim(), " ");
            else
                linkText = linkUri;

            try
            {
                l = new Hyperlink { NavigateUri = new Uri(linkUri, UriKind.RelativeOrAbsolute) };
            }
            catch(UriFormatException )
            {
                l = new Hyperlink { NavigateUri = new Uri("none://UNABLE_TO_CONVERT_URL_TO_URI",
                    UriKind.RelativeOrAbsolute) };
            }

            if(linkAltTextElement != null)
                l.ToolTip = linkAltTextElement.Value;

            if(linkTargetElement != null)
                l.TargetName = linkTargetElement.Value;

            l.Inlines.Add(new Run(linkText));

            props.Converter.AddInlineToContainer(l);

            props.ParseChildren = false;
        }
        //=====================================================================

        /// <summary>
        /// Handle code entity reference elements
        /// </summary>
        /// <param name="props">The element properties</param>
        private static void CodeEntityReferenceElement(ElementProperties props)
        {
            XAttribute hint;
            string linkText, memberId = (props.Element.Value ?? String.Empty).Trim();
            string[] parts;
            bool qualifyHint;
            char prefix = 'N';
            int pos;

            linkText = (string)props.Element.Attribute("linkText");

            if(String.IsNullOrWhiteSpace(linkText))
            {
                hint = props.Element.Attribute("qualifyHint");

                if(hint == null || !Boolean.TryParse(hint.Value, out qualifyHint))
                    qualifyHint = false;

                // Remove parameters from the ID
                pos = memberId.IndexOf('(');

                if(pos != -1)
                    memberId = memberId.Substring(0, pos);

                // Remove the type prefix
                if(memberId.Length > 2 && memberId[1] == ':')
                {
                    prefix = memberId[0];
                    memberId = memberId.Substring(2);
                }

                parts = memberId.Split(new[] { '.' }, StringSplitOptions.RemoveEmptyEntries);

                // If qualified, add the appropriate parts
                if(qualifyHint)
                {
                    if(prefix != 'N' && prefix != 'T')
                        memberId = String.Join(".", parts, parts.Length - 2, 2);
                }
                else
                    memberId = parts[parts.Length - 1];

                if(parts.Length > 2)
                    memberId = memberId.Replace("#ctor", parts[parts.Length - 2]).Replace("#cctor",
                        parts[parts.Length - 2]);
            }
            else
                memberId = linkText;

            props.Converter.AddInlineToContainer(new Bold(new Run(memberId)));
            props.ParseChildren = false;
        }
        /// <summary>
        /// Handle token elements
        /// </summary>
        /// <param name="props">The element properties</param>
        private static void TokenElement(ElementProperties props)
        {
            XElement token;

            if(props.Converter.Tokens.TryGetValue(props.Element.Value.Trim(), out token))
            {
                props.Converter.ParseChildren(token.Nodes());

                // Add elements after the token to the current parent element
                props.ReturnToParent = false;
            }
            else
            {
                props.Converter.AddInlineToContainer(new Bold(new Run(String.Format(CultureInfo.InvariantCulture, 
                    "[MISSING TOKEN: {0}]", props.Element.Value.Trim()))));

                // We just added a bold span so it will need to go back to the last parent
                // span if any on return.
            }

            props.ParseChildren = false;
        }
        //=====================================================================

        /// <summary>
        /// Handle defined term elements
        /// </summary>
        /// <param name="props">The element properties</param>
        private static void DefinedTermElement(ElementProperties props)
        {
            Paragraph p = new Paragraph();
            props.Converter.AddToBlockContainer(p);
            p.SetResourceReference(Paragraph.StyleProperty, NamedStyle.DefinedTerm);
        }
        //=====================================================================

        /// <summary>
        /// Handle all aspects of glossary element and sub-element formatting
        /// </summary>
        /// <param name="props">The element properties</param>
        private static void GlossaryElement(ElementProperties props)
        {
            Section glossary = new Section();
            Paragraph p = null;
            XElement titleElement;
            XAttribute addressAttr;
            List<XElement> divisions;
            Dictionary<XElement, Tuple<string, string>> divisionIds = new Dictionary<XElement, Tuple<string, string>>();
            List<GlossaryEntry> entries = new List<GlossaryEntry>();
            string address, id, title;
            bool isFirst = true;
            int autoId = 1;

            // All elements are handled here
            props.ParseChildren = false;
            props.Converter.AddToBlockContainer(glossary);

            // If there is a title element, add a title using its content
            titleElement = props.Element.Element(ddue + "title");

            if(titleElement != null)
            {
                p = new Paragraph(new Run(reCondenseWhitespace.Replace(titleElement.Value.Trim(), " ")));
                glossary.Blocks.Add(p);

                p.SetResourceReference(Paragraph.StyleProperty, NamedStyle.Title);
            }

            // See if there are divisions.  If so, add one section for each division.  If not, lump all entries
            // into one untitled division.
            divisions = props.Element.Descendants(ddue + "glossaryDiv").ToList();

            // If there are multiple divisions, add a link to each one provided we have an address and a title
            if(divisions.Count == 0)
            {
                divisions = new List<XElement>() { props.Element };
                divisionIds.Add(props.Element, Tuple.Create<string, string>(null, null));
            }
            else
            {
                foreach(var d in divisions)
                {
                    addressAttr = d.Attribute("address");
                    titleElement = d.Element(ddue + "title");

                    if(addressAttr != null)
                    {
                        address = addressAttr.Value;
                        id = ToElementName(address);
                    }
                    else
                        address = id = null;

                    if(titleElement != null)
                        title = reCondenseWhitespace.Replace(titleElement.Value.Trim(), " ");
                    else
                        title = null;

                    divisionIds.Add(d, Tuple.Create(id, title));

                    if(!String.IsNullOrEmpty(title))
                    {
                        if(isFirst)
                        {
                            p = new Paragraph();
                            glossary.Blocks.Add(p);
                            isFirst = false;
                        }
                        else
                            p.Inlines.Add(new Run(" | "));

                        if(!String.IsNullOrEmpty(address))
                            p.Inlines.Add(new Hyperlink(new Run(title)) { NavigateUri = new Uri("link://#" + address) });
                        else
                            p.Inlines.Add(new Bold(new Run(title)));
                    }
                }

                p.Inlines.Add(new LineBreak());
            }

            // Extract all glossary entries for use in creating the divisions.  Entries may refer to related
            // entries in other divisions so we need to get them all up front.
            entries.AddRange(props.Element.Descendants(
                MamlToFlowDocumentConverter.ddue + "glossaryEntry").Select(g => new GlossaryEntry(g)));

            // Render each division
            foreach(var d in divisions)
            {
                var titleAndId = divisionIds[d];

                // Add a title if there is one
                if(!String.IsNullOrEmpty(titleAndId.Item2))
                {
                    id = titleAndId.Item1;
                    p = new Paragraph(new Run(titleAndId.Item2)) { Name = id };
                    glossary.Blocks.Add(p);
                    p.SetResourceReference(Paragraph.StyleProperty, NamedStyle.GlossaryDivisionTitle);
                }
                else
                {
                    id = "__GlossaryDiv" + autoId.ToString(CultureInfo.InvariantCulture);
                    autoId++;
                }

                RenderGlossaryDivision(d, id, entries, props.Converter);

                glossary.Blocks.Add(new Paragraph());
            }
        }
        /// <summary>
        /// Handle list elements
        /// </summary>
        /// <param name="props">The element properties</param>
        private static void ListElement(ElementProperties props)
        {
            XAttribute listClass = props.Element.Attribute("class");
            TextMarkerStyle markerStyle = TextMarkerStyle.Disc;

            if(listClass != null)
                switch(listClass.Value)
                {
                    case "ordered":
                        markerStyle = TextMarkerStyle.Decimal;
                        break;

                    case "nobullet":
                        markerStyle = TextMarkerStyle.None;
                        break;

                    default:
                        break;
                }

            props.Converter.AddToBlockContainer(new List { MarkerStyle = markerStyle });
        }
        //=====================================================================

        /// <summary>
        /// Handle alert elements
        /// </summary>
        /// <param name="props">The element properties</param>
        private static void AlertElement(ElementProperties props)
        {
            XAttribute attribute;
            string title = null, icon = null;

            // Map the class name to a title
            attribute = props.Element.Attribute("class");

            if(attribute == null || !AlertTitles.TryGetValue(attribute.Value, out title))
                title = "Note";

            if(attribute == null || !AlertIcons.TryGetValue(attribute.Value, out icon))
                icon = "AlertNote";

            Section alert = new Section();
            props.Converter.AddToBlockContainer(alert);

            Paragraph p = new Paragraph();
            alert.Blocks.Add(p);

            // Set the icon based on the alert type
            var iconImage = (ImageSource)props.Converter.Document.Resources[icon];

            p.Inlines.Add(new InlineUIContainer(new Image
            {
                Source = iconImage,
                Height = iconImage.Height / 2,
                Width = iconImage.Width / 2,
                ToolTip = title,
                Margin = new Thickness(0, 0, 5, 0),
                VerticalAlignment = VerticalAlignment.Center
            }));

            p.Inlines.Add(new Run(title));
            p.SetResourceReference(Paragraph.StyleProperty, NamedStyle.AlertTitle);

            Section alertBody = new Section();
            alert.Blocks.Add(alertBody);

            // We want the body section to be the current parent here rather than the containing section
            props.Converter.CurrentBlockElement = alertBody;

            alertBody.SetResourceReference(Section.StyleProperty, NamedStyle.AlertBody);
        }
        /// <summary>
        /// Handle related topics elements
        /// </summary>
        /// <param name="props">The element properties</param>
        /// <remarks>If empty, the related topics section is omitted</remarks>
        private static void RelatedTopicsElement(ElementProperties props)
        {
            List<XElement> tasks = new List<XElement>(), reference = new List<XElement>(),
                concepts = new List<XElement>(), otherResources = new List<XElement>(),
                tokenContent = new List<XElement>();
            XElement token;
            XAttribute attribute;
            Guid topicId, href;
            string linkType;

            if(!props.ParseChildren || !props.Element.HasElements)
            {
                props.ParseChildren = props.ReturnToParent = false;
                return;
            }

            // All links are handled here
            props.ParseChildren = false;

            // A name is added here for use by autoOutline elements
            Section s = new Section { Name = ToElementName("seeAlsoSection") };
            props.Converter.AddToBlockContainer(s);

            // Add the section title
            Paragraph p = new Paragraph(new Run(NamedSectionTitles[props.Element.Name.LocalName]));
            s.Blocks.Add(p);
            p.SetResourceReference(Paragraph.StyleProperty, NamedStyle.Title);

            // Expand tokens first
            foreach(var link in props.Element.Nodes().OfType<XElement>().Where(n => n.Name.LocalName == "token"))
                if(props.Converter.Tokens.TryGetValue(link.Value.Trim(), out token))
                    tokenContent.AddRange(token.Nodes().OfType<XElement>());

            // Group the elements by type or topic ID
            foreach(var link in props.Element.Nodes().OfType<XElement>().Concat(tokenContent))
            {
                linkType = link.Name.LocalName;
                attribute = link.Attribute("topicType_id");

                if(attribute == null || !Guid.TryParse(attribute.Value, out topicId))
                    topicId = Guid.Empty;

                attribute = link.Attribute(xlink + "href");

                if(attribute == null || !Guid.TryParse(attribute.Value, out href))
                    href = Guid.Empty;

                if(href != Guid.Empty && (linkType == "link" || linkType == "legacyLink") && (
                  topicId == HowToId || topicId == WalkthroughId || topicId == SampleId ||
                  topicId == TroubleshootingId))
                {
                    tasks.Add(link);
                }
                else
                    if(linkType == "codeEntityReference" || ((linkType == "link" || linkType == "legacyLink") && (
                      href == Guid.Empty || topicId == ReferenceWithoutSyntaxId ||
                      topicId == ReferenceWithSyntaxId || topicId == XmlReferenceId ||
                      topicId == ErrorMessageId || topicId == UIReferenceId)))
                    {
                        reference.Add(link);
                    }
                    else
                        if(href != Guid.Empty && (linkType == "link" || linkType == "legacyLink") && (
                          topicId == ConceptualId || topicId == SdkTechnologyOverviewArchitectureId ||
                          topicId == SdkTechnologyOverviewCodeDirectoryId ||
                          topicId == SdkTechnologyOverviewScenariosId ||
                          topicId == SdkTechnologyOverviewTechnologySummaryId))
                            concepts.Add(link);
                        else
                            if(linkType == "externalLink" || ((linkType == "link" || linkType == "legacyLink") &&
                              href != Guid.Empty && (topicId == Guid.Empty || topicId == OrientationId ||
                              topicId == WhitePaperId || topicId == CodeEntityId || topicId == GlossaryId ||
                              topicId == SDKTechnologyOverviewOrientationId)))
                                otherResources.Add(link);
            }

            // Add each set
            AddRelatedTopicLinks(props.Converter, s, tasks, "Tasks");
            AddRelatedTopicLinks(props.Converter, s, reference, "Reference");
            AddRelatedTopicLinks(props.Converter, s, concepts, "Concepts");
            AddRelatedTopicLinks(props.Converter, s, otherResources, "Other Resources");
        }
        /// <summary>
        /// Handle code elements
        /// </summary>
        /// <param name="props">The element properties</param>
        private static void CodeElement(ElementProperties props)
        {
            XAttribute attribute;
            Section code = new Section();
            props.Converter.AddToBlockContainer(code);
            string language = "none", title, sourceFile, region;
            bool removeRegionMarkers, numberLines;

            if(props.Element.Name.LocalName != "codeReference")
            {
                attribute = props.Element.Attribute("lang");

                if(attribute == null)
                    attribute = props.Element.Attribute("language");

                if(attribute != null)
                    language = attribute.Value;

                // If there is a title attribute, use that for the title.  If not, map the language ID to
                // a display title.
                attribute = props.Element.Attribute("title");

                // For a blank title, a single space is required.  If empty, use the language title.
                if(attribute != null && attribute.Value.Length != 0)
                    title = attribute.Value;
                else
                    if(CodeColorizer == null || !CodeColorizer.FriendlyNames.TryGetValue(language, out title))
                        title = String.Empty;

                // If there are nested code blocks, import the code and replace them with their content
                foreach(var nestedBlock in props.Element.Descendants(ddue + "code").ToList())
                {
                    attribute = nestedBlock.Attribute("source");

                    if(attribute != null)
                    {
                        sourceFile = attribute.Value;
                        attribute = nestedBlock.Attribute("region");

                        if(attribute != null)
                            region = attribute.Value;
                        else
                            region = null;

                        attribute = nestedBlock.Attribute("removeRegionMarkers");

                        if(attribute == null || !Boolean.TryParse(attribute.Value, out removeRegionMarkers))
                            removeRegionMarkers = false;

                        nestedBlock.Value = LoadCodeBlock(sourceFile, region, removeRegionMarkers);
                    }
                }
            }
            else
                title = "Code Reference";

            // Create the title and Copy link elements.  These will reside in a grid in a block UI container.
            Grid g = new Grid();

            g.ColumnDefinitions.Add(new ColumnDefinition());
            g.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });

            TextBlock tb = new TextBlock
            {
                TextAlignment = TextAlignment.Left,
                FontWeight = FontWeights.Bold,
                Text = title
            };
            
            Grid.SetColumn(tb, 0);
            g.Children.Add(tb);

            Hyperlink l = new Hyperlink(new Run("Copy"))
            {
                // The URI signals the generic handler to copy code rather than launch the URL
                NavigateUri = new Uri("copy://code", UriKind.RelativeOrAbsolute),
                ToolTip = "Copy Code",
                FontSize = 10
            };

            tb = new TextBlock(l) { TextAlignment = TextAlignment.Right };
            Grid.SetColumn(tb, 1);
            g.Children.Add(tb);

            BlockUIContainer buic = new BlockUIContainer(g) { Margin = new Thickness(0, 3, 0, 3) };
            code.Blocks.Add(buic);

            // Create the section that will hold the code block
            Section codeBlock = new Section();
            code.Blocks.Add(codeBlock);
            codeBlock.SetResourceReference(Section.StyleProperty, NamedStyle.CodeBlock);

            Paragraph p = new Paragraph();
            codeBlock.Blocks.Add(p);

            // See if lines are to be numbered
            attribute = props.Element.Attribute("numberLines");

            if(attribute == null || !Boolean.TryParse(attribute.Value, out numberLines))
                numberLines = false;

            // If importing from an external file, include that info in the content
            attribute = props.Element.Attribute("source");

            if(attribute != null)
            {
                sourceFile = attribute.Value;
                attribute = props.Element.Attribute("region");

                if(attribute != null)
                    region = attribute.Value;
                else
                    region = null;

                attribute = props.Element.Attribute("removeRegionMarkers");

                if(attribute == null || !Boolean.TryParse(attribute.Value, out removeRegionMarkers))
                    removeRegionMarkers = false;

                if(CodeColorizer != null)
                    ColorizeCode(LoadCodeBlock(sourceFile, region, removeRegionMarkers), language, numberLines, p);
                else
                    p.Inlines.Add(new Run(LoadCodeBlock(sourceFile, region, removeRegionMarkers)));
            }
            else
                if(props.Element.Name.LocalName != "codeReference" && CodeColorizer != null)
                    ColorizeCode(props.Element.Value, language, numberLines, p);
                else
                    p.Inlines.Add(new Run(StripLeadingWhitespace(props.Element.Value, 4)));

            props.ParseChildren = false;
        }
        /// <summary>
        /// Handle media link inline elements
        /// </summary>
        /// <param name="props">The element properties</param>
        private static void MediaLinkInlineElement(ElementProperties props)
        {
            InlineUIContainer inlineImage = new InlineUIContainer();
            XElement imageElement = props.Element.Descendants(ddue + "image").FirstOrDefault();
            XAttribute href;
            Image image = new Image();
            KeyValuePair<string, string> imageInfo = new KeyValuePair<string, string>(null, null);
            string id = "???";

            if(imageElement != null)
            {
                href = imageElement.Attribute(xlink + "href");

                if(href != null)
                {
                    id = href.Value;

                    if(!props.Converter.MediaFiles.TryGetValue(id, out imageInfo))
                        imageInfo = new KeyValuePair<string, string>(null, null);
                }
            }

            if(!String.IsNullOrEmpty(imageInfo.Key) && File.Exists(imageInfo.Key))
            {
                var bm = new BitmapImage();

                // Cache on load to prevent it locking the image
                bm.BeginInit();
                bm.CacheOption = BitmapCacheOption.OnLoad;
                bm.UriSource = new Uri(imageInfo.Key);
                bm.EndInit();

                // Use the actual image size to mimic the HTML layout
                image.Height = bm.Height;
                image.Width = bm.Width;
                image.ToolTip = !String.IsNullOrEmpty(imageInfo.Value) ? imageInfo.Value :
                    "ID: " + id + "\nFilename: " + imageInfo.Key;
                image.Margin = new Thickness(5, 0, 5, 0);

                image.Source = bm;
                inlineImage.Child = image;
            }
            else
                inlineImage.Child = new TextBlock
                {
                    Text = String.Format(CultureInfo.InvariantCulture, "[INVALID IMAGE ID: {0}]", id),
                    Background = new SolidColorBrush(Colors.Red),
                    Foreground = new SolidColorBrush(Colors.White)
                };

            props.Converter.AddInlineToContainer(inlineImage);

            props.ReturnToParent = props.ParseChildren = false;
        }
 /// <summary>
 /// Handle general paragraph type elements
 /// </summary>
 /// <param name="props">The element properties</param>
 private static void ParagraphElement(ElementProperties props)
 {
     // If empty, ignore it to match the Sandcastle's behavior
     if(props.ParseChildren && props.Element.FirstNode != null && !String.IsNullOrEmpty(props.Element.Value))
         props.Converter.AddToBlockContainer(new Paragraph());
     else
         props.ReturnToParent = false;
 }
 /// <summary>
 /// Handle table header elements
 /// </summary>
 /// <param name="props">The element properties</param>
 private static void TableHeaderElement(ElementProperties props)
 {
     var g = new TableRowGroup();
     props.Converter.AddToBlockContainer(g);
     g.SetResourceReference(TableRowGroup.StyleProperty, NamedStyle.TableHeaderRow);
 }
        /// <summary>
        /// Handle general and named section elements
        /// </summary>
        /// <param name="props">The element properties</param>
        /// <remarks>If the section is empty, it will be omitted</remarks>
        private static void SectionElement(ElementProperties props)
        {
            if(props.ParseChildren && props.Element.HasElements)
            {
                Section s = new Section();
                string title;

                // If this is a named section, add the standard title
                if(NamedSectionTitles.TryGetValue(props.Element.Name.LocalName, out title))
                {
                    Paragraph p = new Paragraph(new Run(title));
                    s.Blocks.Add(p);
                    p.SetResourceReference(Paragraph.StyleProperty, NamedStyle.Title);
                }

                props.Converter.AddToBlockContainer(s);
            }
            else
                props.ParseChildren = props.ReturnToParent = false;
        }
 /// <summary>
 /// Handle table cell entry elements
 /// </summary>
 /// <param name="props">The element properties</param>
 private static void EntryElement(ElementProperties props)
 {
     props.Converter.AddToBlockContainer(new TableCell());
 }
        /// <summary>
        /// Handle summary elements
        /// </summary>
        /// <param name="props">The element properties</param>
        /// <remarks>If the abstract attribute is set to true, this element is skipped</remarks>
        private static void SummaryElement(ElementProperties props)
        {
            XAttribute abstractAttr = props.Element.Attribute("abstract");
            bool excluded;

            if(abstractAttr == null || !Boolean.TryParse(abstractAttr.Value, out excluded))
                excluded = false;

            if(!excluded)
                props.Converter.AddToBlockContainer(new Section());
            else
                props.ParseChildren = props.ReturnToParent = false;
        }
 /// <summary>
 /// Handle elements with inline code styling
 /// </summary>
 /// <param name="props">The element properties</param>
 private static void InlineCodeElement(ElementProperties props)
 {
     Span s = new Span();
     props.Converter.AddInlineToContainer(s);
     s.SetResourceReference(Run.StyleProperty, NamedStyle.CodeInline);
 }
        /// <summary>
        /// Handle title elements
        /// </summary>
        /// <param name="props">The element properties</param>
        /// <remarks>If the title element is inside a table, it is skipped as the table will already have
        /// added it outside of itself.</remarks>
        private static void TitleElement(ElementProperties props)
        {
            if(props.Converter.CurrentBlockElement is Table)
                props.ReturnToParent = false;
            else
            {
                Paragraph p = new Paragraph(new Run(reCondenseWhitespace.Replace(
                    props.Element.Value.Trim(), " ")));
                props.Converter.AddToBlockContainer(p);
                p.SetResourceReference(Paragraph.StyleProperty, NamedStyle.Title);
            }

            props.ParseChildren = false;
        }
        /// <summary>
        /// Recursively parse the document elements to create the flow document
        /// </summary>
        /// <param name="nodes">The nodes in the document</param>
        private void ParseChildren(IEnumerable <XNode> nodes)
        {
            ElementProperties          elementProps = new ElementProperties(this);
            Action <ElementProperties> handler;
            XText  text;
            string innerText;

            foreach (XNode n in nodes)
            {
                elementProps.Element = n as XElement;

                if (elementProps.Element != null)
                {
                    elementProps.ReturnToParent = true;
                    elementProps.ParseChildren  = !elementProps.Element.IsEmpty;

                    // If it's an element we recognize, handle it
                    if (elementHandlers.TryGetValue(elementProps.Element.Name.LocalName, out handler))
                    {
                        handler(elementProps);
                    }
                    else
                    {
                        System.Diagnostics.Debug.WriteLine("Unhandled element type: " +
                                                           elementProps.Element.Name.LocalName);

                        // Take a guess at the type (block or inline).  For blocks, we'll just
                        // create a new section to act as a parent to any child elements.
                        if (currentSpan == null && !(currentBlock is Paragraph))
                        {
                            if (elementProps.ParseChildren)
                            {
                                SectionElement(elementProps);
                            }
                            else
                            {
                                elementProps.ReturnToParent = false;
                            }
                        }
                        else
                        {
                            // If the inline element has no children, insert its name so that it shows up
                            if (!elementProps.ParseChildren)
                            {
                                this.AddInlineToContainer(new Bold(new Run(String.Format(CultureInfo.InvariantCulture,
                                                                                         "[{0}]", elementProps.Element.Name.LocalName))));

                                // We just added a bold run so we'll need to return to the parent
                                elementProps.ReturnToParent = true;
                            }
                            else
                            {
                                elementProps.ReturnToParent = false;
                            }
                        }
                    }

                    // Store address values in the current document element's name for linking.  The value
                    // is converted to an internal ID to make sure it is valid for use by the element.
                    if (elementProps.Element.Attribute("address") != null)
                    {
                        if (currentSpan != null)
                        {
                            currentSpan.Name = ToElementName(elementProps.Element.Attribute("address").Value);
                        }
                        else
                        if (currentBlock != null)
                        {
                            currentBlock.Name = ToElementName(elementProps.Element.Attribute("address").Value);
                        }
                    }

                    // Parse the child elements
                    if (elementProps.ParseChildren)
                    {
                        this.ParseChildren(elementProps.Element.Nodes());
                    }

                    // Return to the parent element after parsing the child elements?
                    if (elementProps.ReturnToParent)
                    {
                        if (currentSpan != null)
                        {
                            if (parentSpans.Count != 0)
                            {
                                currentSpan = parentSpans.Pop();
                            }
                            else
                            {
                                currentSpan = null;
                            }
                        }
                        else
                        if (parentBlocks.Count != 0)
                        {
                            currentBlock = parentBlocks.Pop();
                        }
                        else
                        {
                            currentBlock = null;
                        }
                    }
                }
                else
                {
                    text = n as XText;

                    // Whitespace is condensed into a single space to mimic the behavior of text in
                    // HTML documents.  Since we are building the document in memory using the objects,
                    // they preserve whitespace in the text runs which we don't want.
                    if (text != null)
                    {
                        innerText = reCondenseWhitespace.Replace(text.Value, " ");

                        // If this is the first child, trim the leading whitespace.  This prevents an extra
                        // space showing up at the start of paragraph inner text when it starts on a new line.
                        if (text.Parent.FirstNode == text)
                        {
                            innerText = innerText.TrimStart();
                        }

                        this.AddInlineToContainer(new Run(innerText));
                    }

                    // Ignore everything else (comments, processing instructions, etc.)
                }
            }
        }
 /// <summary>
 /// Handle elements with generic underline styling
 /// </summary>
 /// <param name="props">The element properties</param>
 private static void UnderlineElement(ElementProperties props)
 {
     props.Converter.AddInlineToContainer(new Underline());
 }