/// <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 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 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>
        /// This is used to render a glossary division
        /// </summary>
        /// <param name="d">The root glossary division element</param>
        /// <param name="divisionId">The division ID used as a prefix for each letter section in the division</param>
        /// <param name="entries">An enumerable list of all glossary entries</param>
        /// <param name="converter">The converter to which the glossary elements will be added</param>
        /// <returns>An enumerable list of blocks that define the glossary division in the document</returns>
        private static void RenderGlossaryDivision(XElement d, string divisionId,
          IEnumerable<GlossaryEntry> entries, MamlToFlowDocumentConverter converter)
        {
            Section s;
            Paragraph p = new Paragraph();
            bool isFirst = true;

            // Group all entries in this division by the first letter of the first term
            var groupLetters = entries.Where(g => g.Parent == d).GroupBy(
                g => Char.ToUpperInvariant(g.Terms.First().Key[0])).OrderBy(g => g.Key);

            // Generate the letter bar for the division
            for(char ch = 'A'; ch <= 'Z'; ch++)
            {
                if(isFirst)
                    isFirst = false;
                else
                    p.Inlines.Add(new Run(" | "));

                if(!groupLetters.Any(g => g.Key == ch))
                    p.Inlines.Add(new Bold(new Run(ch.ToString())));
                else
                    p.Inlines.Add(new Hyperlink(new Run(ch.ToString()))
                    {
                        NavigateUri = new Uri("link://#" + divisionId + "_" + ch.ToString())
                    });
            }

            converter.AddNonParentToBlockContainer(p);
            p.SetResourceReference(Paragraph.StyleProperty, NamedStyle.GlossaryLetterBar);

            foreach(var g in groupLetters)
            {
                p = new Paragraph(new Run(g.Key.ToString())
                {
                    Name = ToElementName(divisionId + "_" + g.Key.ToString())
                });

                converter.AddNonParentToBlockContainer(p);
                p.SetResourceReference(Paragraph.StyleProperty, NamedStyle.GlossaryLetterTitle);

                foreach(var entry in g)
                {
                    s = new Section();

                    converter.AddNonParentToBlockContainer(s);
                    s.SetResourceReference(Section.StyleProperty, NamedStyle.GlossaryDefinition);

                    p = new Paragraph();
                    isFirst = true;

                    foreach(var t in entry.Terms)
                    {
                        if(isFirst)
                            isFirst = false;
                        else
                            p.Inlines.Add(new Run(", "));

                        p.Inlines.Add(new Bold(new Run(t.Key))
                        {
                            Name = (t.Value == null) ? String.Empty : ToElementName(t.Value)
                        });
                    }

                    s.Blocks.Add(p);
                    p.SetResourceReference(Paragraph.StyleProperty, NamedStyle.NoMargin);

                    converter.ParseChildren(s, entry.Definition.Nodes());

                    if(entry.RelatedEntries.Count != 0)
                    {
                        p = new Paragraph();
                        p.Inlines.Add(new Run("See also: "));
                        isFirst = true;

                        foreach(var r in entry.RelatedEntries)
                        {
                            if(isFirst)
                                isFirst = false;
                            else
                                p.Inlines.Add(new Run(", "));

                            var related = entries.SelectMany(e => e.Terms).FirstOrDefault(t => t.Value == r);

                            if(related.Key != null)
                                p.Inlines.Add(new Hyperlink(new Run(related.Key))
                                {
                                    NavigateUri = new Uri("link://#" + r)
                                });
                        }

                        p.Inlines.Add(new LineBreak());
                        s.Blocks.Add(p);
                    }
                }
            }
        }
        /// <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>
        /// Convert the specified MAML file to a flow document
        /// </summary>
        /// <param name="filename">The name of the MAML file to convert</param>
        /// <param name="content">The content to convert or null or an empty string to load it from the
        /// named file</param>
        /// <returns>The converted flow document</returns>
        public FlowDocument ToFlowDocument(string filename, string content)
        {
            XDocument mamlDoc;

            try
            {
                document = CreateFlowDocument();

                parentBlocks.Clear();
                parentSpans.Clear();
                currentBlock = null;
                currentSpan = null;

                if((!String.IsNullOrEmpty(filename) && File.Exists(filename)) || !String.IsNullOrEmpty(content))
                {
                    if(String.IsNullOrEmpty(content))
                        mamlDoc = XDocument.Load(filename, LoadOptions.SetLineInfo);
                    else
                        mamlDoc = XDocument.Parse(content, LoadOptions.SetLineInfo);

                    if(IsMamlDocument(mamlDoc))
                    {
                        this.ParseChildren(mamlDoc.Nodes());

                        // Have a go at auto-sizing the table columns
                        foreach(var t in document.Blocks.SelectMany(b => b.Flatten().OfType<Table>()))
                            t.AutoSizeTableColumns();
                    }
                    else
                        document.Blocks.Add(new Section(new Paragraph(new Run("Not a MAML document: " +
                            filename))));
                }
                else
                    document.Blocks.Add(new Section(new Paragraph(new Run("Empty container node."))));
            }
            catch(Exception ex)
            {
                // It should exist, but just in case...
                if(document == null)
                    document = new FlowDocument();

                var rootSection = new Section();

                if(document.Blocks.Count == 0)
                    document.Blocks.Add(rootSection);
                else
                    document.Blocks.InsertBefore(document.Blocks.FirstBlock, rootSection);

                Style errorStyle = new Style(typeof(Section));
                errorStyle.Setters.Add(new Setter(Section.FontFamilyProperty,
                    new FontFamily("Segoe UI, Lucida Grande, Verdana")));
                errorStyle.Setters.Add(new Setter(Section.FontSizeProperty, 12.0));
                errorStyle.Setters.Add(new Setter(Section.PaddingProperty, new Thickness(3)));
                errorStyle.Setters.Add(new Setter(Section.BorderThicknessProperty, new Thickness(1)));
                errorStyle.Setters.Add(new Setter(Section.BorderBrushProperty,
                    new SolidColorBrush(Colors.Red)));

                rootSection.Resources.Add(typeof(Section), errorStyle);
                rootSection.Resources.Add(typeof(Paragraph), document.Resources[typeof(Paragraph)]);
                rootSection.Resources.Add(NamedStyle.Definition, document.Resources[NamedStyle.Definition]);
                rootSection.Resources.Add(NamedStyle.NoTopMargin, document.Resources[NamedStyle.NoTopMargin]);

                var parentSection = new Section();

                parentSection.Blocks.Add(new Paragraph(new Run("Filename: " + filename)));
                parentSection.Blocks.Add(new Paragraph(new Run("Unable to convert topic file.  Reason(s):")));
                rootSection.Blocks.Add(parentSection);

                var section = new Section();

                parentSection.Blocks.Add(section);
                section.SetResourceReference(Section.StyleProperty, NamedStyle.Definition);

                section.Blocks.Add(new Paragraph(new Run(ex.Message)));

                while(ex.InnerException != null)
                {
                    ex = ex.InnerException;
                    section.Blocks.Add(new Paragraph(new Run(ex.Message)));
                }

                parentSection.Blocks.Add(new Paragraph(new Run("Parsed content up to the point of " +
                    "failure follows.")));
            }

            return document;
        }