public override BlockState TryOpen(BlockProcessor processor) { var slice = processor.Line; var column = processor.Column; var sourcePosition = processor.Start; if (processor.IsCodeIndent || !ExtensionsHelper.MatchStart(ref slice, ":::")) { return(BlockState.None); } ExtensionsHelper.SkipSpaces(ref slice); var extensionName = "triple-colon"; ITripleColonExtensionInfo extension; IDictionary <string, string> attributes; HtmlAttributes htmlAttributes; IDictionary <string, string> renderProperties; Action <string> logError = (string message) => _context.LogError( $"invalid-{extensionName}", $"Invalid {extensionName} on line {processor.LineIndex}. \"{slice.Text}\" is invalid. {message}", null, line: processor.LineIndex); if (!TryMatchIdentifier(ref slice, out extensionName) || !_extensions.TryGetValue(extensionName, out extension) || !extension.TryValidateAncestry(processor.CurrentContainer, logError) || !TryMatchAttributes(ref slice, out attributes, extensionName, extension.SelfClosing, logError) || !extension.TryProcessAttributes(attributes, out htmlAttributes, out renderProperties, logError)) { return(BlockState.None); } var block = new TripleColonBlock(this) { Closed = false, Column = column, Line = processor.LineIndex, Span = new SourceSpan(sourcePosition, slice.End), Extension = extension, RenderProperties = renderProperties }; if (htmlAttributes != null) { block.SetData(typeof(HtmlAttributes), htmlAttributes); } processor.NewBlocks.Push(block); if (extension.SelfClosing) { return(BlockState.BreakDiscard); } return(BlockState.ContinueDiscard); }
public override BlockState TryOpen(BlockProcessor processor) { var slice = processor.Line; var sourcePosition = processor.Start; if (processor.IsCodeIndent || !ExtensionsHelper.MatchStart(ref slice, ":::")) { return(BlockState.None); } ExtensionsHelper.SkipSpaces(ref slice); var extensionName = "triple-colon"; Action <string> logError = (string message) => _context.LogError( $"invalid-{extensionName}", $"{message}", null, line: processor.LineIndex); Action <string> logWarning = (string message) => _context.LogWarning( $"invalid-{extensionName}", $"{message}", null, line: processor.LineIndex); var block = new TripleColonBlock(this) { Closed = false, Column = processor.Column, Line = processor.LineIndex, Span = new SourceSpan(sourcePosition, slice.End), }; if (!TryMatchIdentifier(ref slice, out extensionName) || !_extensions.TryGetValue(extensionName, out var extension) || !extension.TryValidateAncestry(processor.CurrentContainer, logError) || !TryMatchAttributes(ref slice, out var attributes, extensionName, extension.SelfClosing, logError) || !extension.TryProcessAttributes(attributes, out var htmlAttributes, out var renderProperties, logError, logWarning, block)) { return(BlockState.None); } block.Extension = extension; block.Attributes = attributes; block.RenderProperties = renderProperties; if (htmlAttributes != null) { block.SetData(typeof(HtmlAttributes), htmlAttributes); } processor.NewBlocks.Push(block); if (extension.GetType() == typeof(ImageExtension) && htmlAttributes != null && ImageExtension.RequiresClosingTripleColon(attributes)) { block.EndingTripleColons = true; return(BlockState.ContinueDiscard); } if (extension.SelfClosing) { return(BlockState.BreakDiscard); } return(BlockState.ContinueDiscard); }
public bool Render(HtmlRenderer renderer, TripleColonBlock block) { return(RenderDelegate != null ? RenderDelegate(renderer, block) : false); }
public bool TryProcessAttributes(IDictionary <string, string> attributes, out HtmlAttributes htmlAttributes, out IDictionary <string, string> renderProperties, Action <string> logError, Action <string> logWarning, TripleColonBlock block) { htmlAttributes = null; renderProperties = new Dictionary <string, string>(); var src = string.Empty; var alt = string.Empty; var type = string.Empty; var loc_scope = string.Empty; foreach (var attribute in attributes) { var name = attribute.Key; var value = attribute.Value; switch (name) { case "alt-text": alt = value; break; case "type": type = value; break; case "loc-scope": loc_scope = value; break; case "source": src = value; break; case "border": break; case "lightbox": break; case "link": break; default: logError($"Image reference '{src}' is invalid per the schema. Unexpected attribute: '{name}'."); return(false); } } if (string.IsNullOrEmpty(type)) { type = "content"; } if (string.IsNullOrEmpty(src)) { logError("source is a required attribute. Please ensure you have specified a source attribute."); } if (string.IsNullOrEmpty(alt) && type != "icon") { logError("alt-text is a required attribute. Please ensure you have specified an alt-text attribute."); } if ((string.IsNullOrEmpty(alt) && type != "icon") || string.IsNullOrEmpty(src)) { return(false); } htmlAttributes = new HtmlAttributes(); htmlAttributes.AddProperty("src", _context.GetLink(src, InclusionContext.File, InclusionContext.RootFile, block)); if (type == "icon") { htmlAttributes.AddProperty("role", "presentation"); } else { htmlAttributes.AddProperty("alt", alt); } var id = GetHtmlId(block.Line, block.Column); if (type == "complex") { htmlAttributes.AddProperty("aria-describedby", id); } RenderDelegate = (renderer, obj) => { var currentType = string.Empty; var currentLightbox = string.Empty; var currentBorderStr = string.Empty; var currentBorder = true; var currentLink = string.Empty; if (!obj.Attributes.TryGetValue("type", out currentType)) { currentType = "content"; } obj.Attributes.TryGetValue("lightbox", out currentLightbox); //it's okay if this is null obj.Attributes.TryGetValue("border", out currentBorderStr); //it's okay if this is null obj.Attributes.TryGetValue("link", out currentLink); //it's okay if this is null if (!bool.TryParse(currentBorderStr, out currentBorder)) { if (currentType == "icon") { currentBorder = false; } else { currentBorder = true; } } if (!string.IsNullOrEmpty(currentLink)) { var linkHtmlAttributes = new HtmlAttributes(); currentLink = _context.GetLink(currentLink, InclusionContext.File, InclusionContext.RootFile, obj); linkHtmlAttributes.AddProperty("href", $"{currentLink}"); renderer.Write("<a").WriteAttributes(linkHtmlAttributes).WriteLine(">"); } else if (!string.IsNullOrEmpty(currentLightbox)) { var lighboxHtmlAttributes = new HtmlAttributes(); var path = _context.GetLink(currentLightbox, InclusionContext.File, InclusionContext.RootFile, obj); lighboxHtmlAttributes.AddProperty("href", $"{path}#lightbox"); lighboxHtmlAttributes.AddProperty("data-linktype", $"relative-path"); renderer.Write("<a").WriteAttributes(lighboxHtmlAttributes).WriteLine(">"); } if (currentBorder) { renderer.WriteLine("<div class=\"mx-imgBorder\"><p>"); } if (currentType != "complex") { renderer.Write("<img").WriteAttributes(obj).WriteLine(">"); } else { if (currentType == "complex" && obj.Count == 0) { logWarning("If type is \"complex\", then descriptive content is required. Please make sure you have descriptive content."); return(false); } var htmlId = GetHtmlId(obj.Line, obj.Column); renderer.Write("<img").WriteAttributes(obj).WriteLine(">"); renderer.WriteLine($"<div id=\"{htmlId}\" class=\"visually-hidden\">"); renderer.WriteChildren(obj); renderer.WriteLine("</div>"); } if (currentBorder) { renderer.WriteLine("</p></div>"); } if (!string.IsNullOrEmpty(currentLightbox) || !string.IsNullOrEmpty(currentLink)) { renderer.WriteLine($"</a>"); } return(true); }; return(true); }
public bool TryProcessAttributes(IDictionary <string, string> attributes, out HtmlAttributes htmlAttributes, out IDictionary <string, string> renderProperties, Action <string> logError, Action <string> logWarning, TripleColonBlock block) { htmlAttributes = null; renderProperties = new Dictionary <string, string>(); var src = string.Empty; var title = string.Empty; var maxWidth = string.Empty; foreach (var attribute in attributes) { var name = attribute.Key; var value = attribute.Value; switch (name) { case "title": title = value; break; case "max-width": maxWidth = value; break; case "source": src = value; break; default: logError($"Video reference '{src}' is invalid per the schema. Unexpected attribute: '{name}'."); return(false); } } if (string.IsNullOrEmpty(src)) { logError("source is a required attribute. Please ensure you have specified a source attribute."); return(false); } if (!src.Contains("channel9.msdn.com") && !src.Contains("youtube.com/embed") && !src.Contains("microsoft.com/en-us/videoplayer/embed")) { logWarning($"Video source, '{src}', should be from https://channel9.msdn.com, https://www.youtube.com/embed, or https://www.microsoft.com/en-us/videoplayer/embed"); } if (src.Contains("channel9.msdn.com") && !src.Contains("/player")) { logWarning($"Your source from channel9.msdn.com does not end in '/player'. Please make sure you are correctly linking to the Channel 9 video player. "); } htmlAttributes = new HtmlAttributes(); htmlAttributes.AddProperty("src", QuoteSectionNoteRender.FixUpLink(src)); htmlAttributes.AddProperty("allowFullScreen", "true"); htmlAttributes.AddProperty("frameBorder", "0"); if (!string.IsNullOrEmpty(title)) { htmlAttributes.AddProperty("title", title); } if (!string.IsNullOrEmpty(maxWidth)) { int number; if (!int.TryParse(maxWidth, out number)) { logError($"Video reference '{src}' is invalid. 'max-width' must be a number."); return(false); } htmlAttributes.AddProperty("style", $"max-width:{maxWidth}px;"); } RenderDelegate = (renderer, obj) => { renderer.WriteLine("<div class=\"embeddedvideo\">"); renderer.Write($"<iframe").WriteAttributes(obj).WriteLine(">"); renderer.WriteLine("</div>"); return(true); }; return(true); }
public bool TryProcessAttributes(IDictionary <string, string> attributes, out HtmlAttributes htmlAttributes, out IDictionary <string, string> renderProperties, Action <string> logError, Action <string> logWarning, TripleColonBlock block) { htmlAttributes = null; renderProperties = null; var target = string.Empty; var pivot = string.Empty; foreach (var attribute in attributes) { var name = attribute.Key; var value = attribute.Value; switch (name) { case "target": if (value != "docs" && value != "chromeless" && value != "pdf") { logError($"Unexpected target \"{value}\". Permitted targets are \"docs\", \"chromeless\" or \"pdf\"."); return(false); } target = value; break; case "pivot": if (!pivotRegex.IsMatch(value)) { logError($"Invalid pivot \"{value}\". Pivot must be a comma-delimited list of pivot names. Pivot names must be lower-case and contain only letters, numbers or dashes."); return(false); } pivot = value; break; default: logError($"Unexpected attribute \"{name}\"."); return(false); } } if (target == string.Empty && pivot == string.Empty) { logError("Either target or privot must be specified."); return(false); } if (target == "pdf" && pivot != string.Empty) { logError("Pivot not permitted on pdf target."); return(false); } htmlAttributes = new HtmlAttributes(); htmlAttributes.AddClass("zone"); if (target != string.Empty) { htmlAttributes.AddClass("has-target"); htmlAttributes.AddProperty("data-target", target); } if (pivot != string.Empty) { htmlAttributes.AddClass("has-pivot"); htmlAttributes.AddProperty("data-pivot", pivot.Trim().ReplaceRegex(pivotReplaceCommasRegex, " ")); } return(true); }
public bool Render(HtmlRenderer renderer, TripleColonBlock block) { return(false); }
public bool TryProcessAttributes(IDictionary <string, string> attributes, out HtmlAttributes htmlAttributes, out IDictionary <string, string> renderProperties, Action <string> logError, Action <string> logWarning, TripleColonBlock block) { htmlAttributes = null; renderProperties = new Dictionary <string, string>(); var source = string.Empty; var range = string.Empty; var id = string.Empty; var highlight = string.Empty; var language = string.Empty; var interactive = string.Empty; foreach (var attribute in attributes) { var name = attribute.Key; var value = attribute.Value; switch (name) { case "source": source = value; break; case "range": range = value; break; case "id": id = value; break; case "highlight": highlight = value; break; case "language": language = value; break; case "interactive": interactive = value; break; default: logError($"Unexpected attribute \"{name}\"."); return(false); } } if (string.IsNullOrEmpty(source)) { logError("source is a required attribute. Please ensure you have specified a source attribute"); return(false); } if (string.IsNullOrEmpty(language)) { language = InferLanguageFromFile(source, logError); } htmlAttributes = new HtmlAttributes(); htmlAttributes.AddProperty("class", $"lang-{language}"); if (!string.IsNullOrEmpty(interactive)) { htmlAttributes.AddProperty("data-interactive", language); htmlAttributes.AddProperty("data-interactive-mode", interactive); } if (!string.IsNullOrEmpty(highlight)) { htmlAttributes.AddProperty("highlight-lines", highlight); } RenderDelegate = (renderer, obj) => { var currentId = string.Empty; var currentRange = string.Empty; var currentSource = string.Empty; obj.Attributes.TryGetValue("id", out currentId); //it's okay if this is null obj.Attributes.TryGetValue("range", out currentRange); //it's okay if this is null obj.Attributes.TryGetValue("source", out currentSource); //source has already been checked above var(code, codePath) = _context.ReadFile(currentSource, InclusionContext.File, obj); if (string.IsNullOrEmpty(code)) { logWarning($"The code snippet \"{currentSource}\" could not be found."); return(false); } var updatedCode = GetCodeSnippet(currentRange, currentId, code, logError).TrimEnd(); if (updatedCode == string.Empty) { return(false); } renderer.WriteLine("<pre>"); renderer.Write("<code").WriteAttributes(obj).Write(">"); renderer.WriteLine(updatedCode); renderer.WriteLine("</code></pre>"); return(true); }; return(true); }
public bool TryProcessAttributes(IDictionary <string, string> attributes, out HtmlAttributes htmlAttributes, out IDictionary <string, string> renderProperties, Action <string> logError, Action <string> logWarning, TripleColonBlock block) { htmlAttributes = null; renderProperties = new Dictionary <string, string>(); var model = string.Empty; var action = string.Empty; var submitText = string.Empty; foreach (var attribute in attributes) { var name = attribute.Key; var value = attribute.Value; switch (name) { case "model": model = value; break; case "action": action = value; break; case "submittext": submitText = WebUtility.HtmlEncode(value); break; default: logError($"Unexpected attribute \"{name}\"."); return(false); } } if (action == string.Empty) { logError("Form action must be specified."); return(false); } if (submitText == string.Empty) { logError("Submit text must be specified."); return(false); } htmlAttributes = new HtmlAttributes(); if (model != string.Empty) { htmlAttributes.AddProperty("data-model", model); } htmlAttributes.AddProperty("data-action", action); htmlAttributes.AddClass("chromeless-form"); renderProperties.Add(new KeyValuePair <string, string>("submitText", submitText)); RenderDelegate = (renderer, obj) => { var buttonText = "Submit"; obj.RenderProperties.TryGetValue("submitText", out buttonText); renderer.Write("<form").WriteAttributes(obj).WriteLine(">"); renderer.WriteLine("<div></div>"); renderer.WriteLine($"<button class=\"button is-primary\" disabled=\"disabled\" type=\"submit\">{buttonText}</button>"); renderer.WriteLine("</form>"); return(true); }; return(true); }