/// <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; }