namespace Framework.Doc { using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.Serialization; using System.Text; using System.Text.Json; using System.Text.Json.Serialization; /// /// Component serialization, deserialization mapping. /// internal enum DataEnum { None, AppDoc, MdDoc, MdPage, MdSpace, MdParagraph, MdNewLine, MdComment, MdTitle, MdBullet, MdCode, MdImage, MdBracket, MdQuotation, MdSymbol, MdSpecial, MdFont, MdLink, MdContent, SyntaxDoc, SyntaxPage, SyntaxComment, SyntaxTitle, SyntaxBullet, SyntaxCode, SyntaxFont, SyntaxLink, SyntaxImage, SyntaxCustomNote, SyntaxCustomYoutube, SyntaxCustomImage, SyntaxCustomPage, SyntaxParagraph, SyntaxNewLine, SyntaxContent, SyntaxIgnore, HtmlDoc, HtmlPage, HtmlComment, HtmlTitle, HtmlParagraph, HtmlBulletList, HtmlBulletItem, HtmlFont, HtmlLink, HtmlImage, HtmlCode, HtmlCustomNote, HtmlCustomYoutube, HtmlCustomImage, HtmlContent, } /// /// Component data. /// internal sealed class DataDoc { public Registry Registry; public int Id { get; set; } public DataEnum DataEnum { get; set; } /// /// Gets Owner. Not serialized. /// public DataDoc Owner; /// /// Gets Index. This is the index of this object in Owner.List. Not serialized. /// public int Index; /// /// Gets List. Can be null if no children. See also method ListGet(); /// public List List { get; set; } public List ListGet() { if (List == null) { return new List(); } else { return List; } } public int ListCount() { return List != null ? List.Count : 0; } public DataDoc Last(bool isOrDefault = false, int offset = 0) { DataDoc result = null; int index = ListCount() - 1 - offset; bool isOutOfRange = index < 0 || index > List.Count; if (isOrDefault == false) { result = List[index]; } else { if (!isOutOfRange) { result = List[index]; } } return result; } private Component componentCache; public Component Component() { var result = componentCache; if (result == null) { result = Registry.Deserialize(this); componentCache = result; } return result; } public Type ComponentType() { return Registry.DataEnumList[DataEnum]; } public string Text { get; set; } public bool IsCommentEnd { get; set; } public int TitleLevel { get; set; } public string TitleId { get; set; } public MdBracketEnum BracketEnum { get; set; } public bool IsBracketEnd { get; set; } public MdQuotationEnum QuotationEnum { get; set; } public MdSymbolEnum SymbolEnum { get; set; } public MdFontEnum FontEnum { get; set; } public int TokenIdBegin { get; set; } public int TokenIdEnd { get; set; } public int TokenIndexBegin { get; set; } public int TokenIndexEnd { get; set; } public int? MdDocId { get; set; } public int? SyntaxDocOneId { get; set; } public int? SyntaxDocTwoId { get; set; } public int? SyntaxDocThreeId { get; set; } public int? SyntaxDocFourId { get; set; } public int? SyntaxDocFiveId { get; set; } public int? HtmlDocId { get; set; } public int? SyntaxId { get; set; } public string Link { get; set; } public string Src { get; set; } public string Href { get; set; } public string Title { get; set; } public string Description { get; set; } public string LinkText { get; set; } public string PagePath { get; set; } public string PageTitleHtml { get; set; } public string CodeLanguage { get; set; } public string CodeText { get; set; } public bool IsCreateNew { get; set; } public bool IsEndExceptional { get; set; } } /// /// Parse steps. /// public enum ParseEnum { None = 0, /// /// Parse stage one (SyntaxToken). Build syntax token from md token. Not hierarchical like md token. /// ParseOne = 1, /// /// Parse stage two (Block). Build blocks. Detect for example the end of bold font and build a block. Content goes inside. /// ParseTwo = 2, /// /// Parse stage three (Fold). Add following syntax components inside as long as it is valid child. Then break. For example title add following content inside. /// ParseThree = 3, /// /// Parse stage four (Owner Insert). For example insert paragraph (owner) for content directly on page. /// ParseFour = 4, /// /// Parse stage five (Owner Merge). For example merge two inserted paragraphs into one. /// ParseFive = 5, /// /// Parse stage html. Convert syntax component to html component. /// ParseHtml = 6, } /// /// Component registry. /// internal class Registry { public Registry() { // Doc Add(typeof(AppDoc)); // Token Add(typeof(MdDoc)); Add(typeof(MdPage)); Add(typeof(MdSpace)); Add(typeof(MdParagraph)); Add(typeof(MdNewLine)); Add(typeof(MdComment)); Add(typeof(MdTitle)); Add(typeof(MdBullet)); Add(typeof(MdCode)); Add(typeof(MdImage)); Add(typeof(MdBracket)); Add(typeof(MdQuotation)); Add(typeof(MdSymbol)); Add(typeof(MdFont)); Add(typeof(MdLink)); Add(typeof(MdContent)); // Syntax Add(typeof(SyntaxDoc)); Add(typeof(SyntaxPage)); Add(typeof(SyntaxComment)); Add(typeof(SyntaxTitle)); Add(typeof(SyntaxCode)); Add(typeof(SyntaxBullet)); Add(typeof(SyntaxFont)); Add(typeof(SyntaxLink)); Add(typeof(SyntaxImage)); Add(typeof(SyntaxCustomNote)); Add(typeof(SyntaxCustomYoutube)); Add(typeof(SyntaxCustomImage)); Add(typeof(SyntaxCustomPage)); Add(typeof(SyntaxParagraph)); Add(typeof(SyntaxNewLine)); Add(typeof(SyntaxContent)); Add(typeof(SyntaxIgnore)); // Needs to be last Syntax // Html Add(typeof(HtmlDoc)); Add(typeof(HtmlPage)); Add(typeof(HtmlComment)); Add(typeof(HtmlTitle)); Add(typeof(HtmlParagraph)); Add(typeof(HtmlBulletList)); Add(typeof(HtmlBulletItem)); Add(typeof(HtmlFont)); Add(typeof(HtmlLink)); Add(typeof(HtmlImage)); Add(typeof(HtmlCode)); Add(typeof(HtmlCustomNote)); Add(typeof(HtmlCustomYoutube)); Add(typeof(HtmlCustomImage)); Add(typeof(HtmlContent)); } private void Add(Type type) { if (!Enum.TryParse(type.Name, out var dataEnum)) { throw new Exception(string.Format("Type not registered in enum! ({0}, {1})", nameof(DataEnum), type.Name)); } List.Add(type); TypeList.Add(type, dataEnum); DataEnumList.Add(dataEnum, type); } /// /// (Type) Keeps sequence. /// public List List = new List(); /// /// (Type, DataEnum) /// public Dictionary TypeList = new Dictionary(); /// /// (DataEnum, Type) /// public Dictionary DataEnumList = new Dictionary(); public int IdCount; /// /// (Id, Data) /// public Dictionary IdList = new Dictionary(); /// /// Gets SyntaxRegistry. Available if one SyntaxRegistry has been created out of this Registry. /// public SyntaxRegistry SyntaxRegistry; /// /// Gets or sets IsDebug. If true, for example html is rendered with additional debug information. /// public bool IsDebug; /// /// Use in getter for component reference. /// public T ReferenceGet(int? id) where T : Component { T result = null; if (id != null) { result = (T)IdList[id.Value].Component(); } return result; } /// /// Use in setter for component reference. /// public static int? ReferenceSet(Component value) { return value?.Data.Id; } /// /// Gets or sets ParseEnum. This is the current parse step. /// public ParseEnum ParseEnum { get; set; } public Component Deserialize(DataDoc data) { var type = DataEnumList[data.DataEnum]; var result = (Component)FormatterServices.GetUninitializedObject(type); result.Data = data; return result; } } /// /// Register new Component in Registry constructor. /// internal class Component { internal DataDoc Data; private Component(Component owner, Registry registry) { if (owner == null) { if (registry == null) { registry = new Registry(); } var dataEnum = registry.TypeList[GetType()]; Data = new DataDoc { Registry = registry, Id = registry.IdCount += 1, DataEnum = dataEnum }; registry.IdList.Add(Data.Id, Data); } else { UtilDoc.Assert(registry == null); registry = owner.Data.Registry; var dataEnum = registry.TypeList[GetType()]; // See also constructor Registry and enum DataEnum. Data = new DataDoc { Registry = registry, Id = registry.IdCount += 1, DataEnum = dataEnum, Owner = owner.Data }; registry.IdList.Add(Data.Id, Data); if (owner.Data.List == null) { owner.Data.List = new List(); } owner.Data.List.Add(Data); Data.Index = owner.Data.List.Count - 1; } } /// /// Constructor root. /// public Component() : this(null, null) { } /// /// Constructor. /// public Component(Component owner) : this(owner, null) { } /// /// Constructor registry, factory mode. Root with existing registry. /// public Component(Registry registry) : this(null, registry) { } /// /// Gets Id. Unique Id in component tree. /// public int Id => Data.Id; /// /// Gets Owner. This is the components owner in tree. /// public Component Owner => Data.Owner?.Component(); public T OwnerFind() where T : Component { T result = null; var dataEnum = Data.Registry.TypeList[typeof(T)]; var next = Data; do { if (next.DataEnum == dataEnum) { result = (T)next.Component(); break; } next = next.Owner; } while (next != null); return result; } public IReadOnlyList List { get { var result = new List(); if (Data.List != null) { foreach (var item in Data.List) { result.Add(item.Component()); } } return result; } } private static void ListAll(Component component, List result) { result.Add(component); foreach (var item in component.List) { ListAll(item, result); } } /// /// Returns list of all child components recursive including this. /// public IReadOnlyList ListAll() { var result = new List(); ListAll(this, result); return result; } /// /// Remove component from owner. Needs to be last component. /// public void Remove() { UtilDoc.Assert(Data.Owner != null); UtilDoc.Assert(Data.Owner.List.Last() == Data); Data.Owner.List.Remove(this.Data); Data.Owner = null; } /// /// Returns next or previous component. /// /// Can be null for no range check. /// For example 1 (next) or -1 (previous) private Component Next(Component componentBeginEnd, int offset) { Component result = null; if (Data.Owner != null) // Not root { UtilDoc.Assert(Data.Owner.List[Data.Index] == Data); // Index check if (this != componentBeginEnd) // Reached not yet begin or end { if (Data.Index + offset >= 0 && Data.Index + offset < Data.Owner.List.Count) // There is a next component { result = Data.Owner.List[Data.Index + offset].Component(); // Move next } } } return result; } public T Next(T componentEnd) where T : Component { var result = Next(componentEnd, offset: 1); return (T)result; } public T Next() where T : Component { var result = Next(null, offset: 1); return (T)result; } public Component Next() { var result = Next(null, offset: 1); return result; } /// /// Returns true, if next component. /// public static bool Next(ref T component, T componentEnd) where T : Component { var result = component?.Next(componentEnd); if (result != null) { component = result; } return result != null; } /// /// Returns previous component. /// public T Previous() where T : Component { var result = Next(null, offset: -1); return (T)result; } /// /// Returns previous component. /// public Component Previous() { return Previous(); } public void Serialize(out string json) { var option = new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault }; // Do not serialize default values. json = JsonSerializer.Serialize(Data, option); } private static void Deserialize(Registry registry, DataDoc owner, int index, DataDoc data) { data.Registry = registry; data.Owner = owner; data.Index = index; registry.IdList.Add(data.Id, data); if (data.List != null) { for (int i = 0; i < data.List.Count; i++) { Deserialize(registry, data, i, data.List[i]); } } } public static T Deserialize(string json) where T : Component { var data = JsonSerializer.Deserialize(json); var registry = new Registry(); Deserialize(registry, null, -1, data); var result = registry.Deserialize(data); return (T)result; } } /// /// Store and parse (*.md) pages. /// internal class AppDoc : Component { public AppDoc() : base() { MdDoc = new MdDoc(this); SyntaxDocOne = new SyntaxDoc(this); SyntaxDocTwo = new SyntaxDoc(this); SyntaxDocThree = new SyntaxDoc(this); SyntaxDocFour = new SyntaxDoc(this); SyntaxDocFive = new SyntaxDoc(this); HtmlDoc = new HtmlDoc(this); } /// /// Parse md pages. /// public void Parse() { // Init registries var mdRegistry = new MdRegistry(Data.Registry); var syntaxRegistry = new SyntaxRegistry(Data.Registry); // Lexer foreach (MdPage page in MdDoc.List) { // Clear token list if (page.Data.ListCount() > 0) { page.Data.List = new List(); } // Lexer mdRegistry.Parse(page); } syntaxRegistry.Parse(this); } public MdDoc MdDoc { get => Data.Registry.ReferenceGet(Data.MdDocId); set => Data.MdDocId = Registry.ReferenceSet(value); } public SyntaxDoc SyntaxDocOne { get => Data.Registry.ReferenceGet(Data.SyntaxDocOneId); set => Data.SyntaxDocOneId = Registry.ReferenceSet(value); } public SyntaxDoc SyntaxDocTwo { get => Data.Registry.ReferenceGet(Data.SyntaxDocTwoId); set => Data.SyntaxDocTwoId = Registry.ReferenceSet(value); } public SyntaxDoc SyntaxDocThree { get => Data.Registry.ReferenceGet(Data.SyntaxDocThreeId); set => Data.SyntaxDocThreeId = Registry.ReferenceSet(value); } public SyntaxDoc SyntaxDocFour { get => Data.Registry.ReferenceGet(Data.SyntaxDocFourId); set => Data.SyntaxDocFourId = Registry.ReferenceSet(value); } public SyntaxDoc SyntaxDocFive { get => Data.Registry.ReferenceGet(Data.SyntaxDocFiveId); set => Data.SyntaxDocFiveId = Registry.ReferenceSet(value); } public HtmlDoc HtmlDoc { get => Data.Registry.ReferenceGet(Data.HtmlDocId); set => Data.HtmlDocId = Registry.ReferenceSet(value); } } /// /// Span extensions. /// internal static class TextExtension { /// /// Returns char at index or null if out of range. /// public static char? Char(this ReadOnlySpan text, int index) { char? result = null; if (index >= 0 && index < text.Length) { result = text.Slice(index, 1)[0]; } return result; } public static bool StartsWith(this ReadOnlySpan text, int index, string textFind) { return text.Slice(index).StartsWith(textFind, StringComparison.Ordinal); } } /// /// Subset of Registry. /// internal class MdRegistry { public MdRegistry(Registry registry) { foreach (var type in registry.List) { if (type.IsSubclassOf(typeof(MdTokenBase))) { var token = (MdTokenBase)Activator.CreateInstance(type); List.Add(token); } } } /// /// (Token). Registry, factory mode. /// public List List = new List(); public void Parse(MdPage owner) { var text = owner.Text.AsSpan(); while (MdTokenBase.ParseMain(this, owner, text)) ; } } /// /// Md tree root. Containes (*.md) pages. /// internal class MdDoc : Component { public MdDoc(Component owner) : base(owner) { } } internal class MdPage : Component { public MdPage(MdDoc owner, string text) : base(owner) { Data.Text = text; } public string Text => Data.Text; } /// /// Base class for token. /// internal abstract class MdTokenBase : Component { /// /// Constructor registry, factory mode. /// public MdTokenBase() : base() { } /// /// Constructor instance token. /// public MdTokenBase(MdPage owner, int length) : base(owner) { UtilDoc.Assert(length > 0); // IndexBegin is IndexEnd + 1 of previous var indexBegin = 0; if (Owner.List.Count > 1) { var previous = (MdTokenBase)Owner.Data.Last(offset: 1).Component(); indexBegin = previous.IndexEnd + 1; } Data.TokenIndexBegin = indexBegin; Data.TokenIndexEnd = Data.TokenIndexBegin + length - 1; UtilDoc.Assert(Data.TokenIndexEnd <= owner.Text.Length); } public new MdPage Owner => (MdPage)base.Owner; public int IndexBegin => Data.TokenIndexBegin; public int IndexEnd => Data.TokenIndexEnd; public int Length => IndexEnd - IndexBegin + 1; internal void IndexEndSet(int index) { var isLast = Owner.Data.Last() == Data; UtilDoc.Assert(isLast, "Can only set IndexEnd of last token!"); UtilDoc.Assert(index >= 0 && index < Owner.Text.Length, "Index out of range!"); Data.TokenIndexEnd = index; } /// /// Gets Text. This is the text between IndexBegin and IndexEnd. /// public string Text { get { return Owner.Text.Substring(Data.TokenIndexBegin, Data.TokenIndexEnd - Data.TokenIndexBegin + 1); } } /// /// Main entry for parse md. /// internal static bool ParseMain(MdRegistry registry, MdPage owner, ReadOnlySpan text, bool isExcludeContent = false) { var result = false; var index = UtilParse.ParseIndex(owner); foreach (var tokenParser in registry.List) { if (isExcludeContent) { if (tokenParser.GetType() == typeof(MdContent)) { break; } } // Parse var countBefore = owner.Data.ListCount(); tokenParser.Parse(registry, owner, text, index); var countAfter = owner.Data.ListCount(); UtilDoc.Assert(countBefore <= countAfter); // A token has been created if (countBefore < countAfter) { result = true; break; } } return result; } internal virtual void Parse(MdRegistry registry, MdPage owner, ReadOnlySpan text, int index) { } } internal class MdParagraph : MdTokenBase { /// /// Constructor registry, factory mode. /// public MdParagraph() : base() { } /// /// Constructor instance token. /// public MdParagraph(MdPage owner, int length) : base(owner, length) { } internal override void Parse(MdRegistry registry, MdPage owner, ReadOnlySpan text, int index) { int next = index; int count = 0; while (MdNewLine.Parse(text, next, out int length)) { next += length; count += 1; } if (count >= 2) { new MdParagraph(owner, next - index); } } } internal class MdNewLine : MdTokenBase { /// /// Constructor registry, factory mode. /// public MdNewLine() : base() { } /// /// Constructor instance token. /// public MdNewLine(MdPage owner, int length) : base(owner, length) { } /// /// Returns true, if NewLine. /// internal static bool Parse(ReadOnlySpan text, int index, out int length) { var result = false; length = 0; var textFindList = new List { "\r\n", "\r", "\n" }; foreach (var textFind in textFindList) { if (text.StartsWith(index, textFind)) { result = true; length = textFind.Length; break; } } return result; } internal override void Parse(MdRegistry registry, MdPage owner, ReadOnlySpan text, int index) { if (Parse(text, index, out int length)) { new MdNewLine(owner, length); } } } internal class MdSpace : MdTokenBase { /// /// Constructor registry, factory mode. /// public MdSpace() : base() { } /// /// Constructor instance token. /// public MdSpace(MdPage owner, int length) : base(owner, length) { } internal override void Parse(MdRegistry registry, MdPage owner, ReadOnlySpan text, int index) { var length = 0; while (text.StartsWith(index + length, " ")) { length += 1; } if (length > 0) { new MdSpace(owner, length); } } } internal class MdComment : MdTokenBase { /// /// Constructor registry, factory mode. /// public MdComment() : base() { } /// /// Constructor instance token. /// public MdComment(MdPage owner, int length, bool isCommentEnd) : base(owner, length) { Data.IsCommentEnd = isCommentEnd; } public bool IsCommentEnd => Data.IsCommentEnd; internal override void Parse(MdRegistry registry, MdPage owner, ReadOnlySpan text, int index) { var commentBegin = ""; if (text.StartsWith(index, commentBegin)) { new MdComment(owner, commentBegin.Length, isCommentEnd: false); } if (text.StartsWith(index, commentEnd)) { new MdComment(owner, commentEnd.Length, isCommentEnd: true); } } } internal class MdTitle : MdTokenBase { /// /// Constructor registry, factory mode. /// public MdTitle() : base() { } /// /// Constructor instance token. /// public MdTitle(MdPage owner, int length, int titleLevel) : base(owner, length) { Data.TitleLevel = titleLevel; } public int TitleLevel => Data.TitleLevel; internal override void Parse(MdRegistry registry, MdPage owner, ReadOnlySpan text, int index) { if (!text.StartsWith(index, "####")) { if (text.StartsWith(index, "### ")) { new MdTitle(owner, length: 3, titleLevel: 3); } if (text.StartsWith(index, "## ")) { new MdTitle(owner, length: 2, titleLevel: 2); } if (text.StartsWith(index, "# ")) { new MdTitle(owner, length: 1, titleLevel: 1); } } } } internal class MdBullet : MdTokenBase { /// /// Constructor registry, factory mode. /// public MdBullet() { } /// /// Constructor instance token. /// public MdBullet(MdPage owner, int length) : base(owner, length) { } internal override void Parse(MdRegistry registry, MdPage owner, ReadOnlySpan text, int index) { if (text.StartsWith(index, "* ")) { new MdBullet(owner, 1); } } } internal class MdImage : MdTokenBase { /// /// Constructor registry, factory mode. /// public MdImage() : base() { } /// /// Constructor instance token. /// public MdImage(MdPage owner, int length) : base(owner, length) { } internal override void Parse(MdRegistry registry, MdPage owner, ReadOnlySpan text, int index) { if (text.StartsWith(index, "![")) { new MdImage(owner, 2); } } } internal enum MdBracketEnum { None, Round, Square, } internal class MdBracket : MdTokenBase { /// /// Constructor registry, factory mode. /// public MdBracket() : base() { } /// /// Constructor instance token. /// public MdBracket(MdPage owner, int length, MdBracketEnum bracketEnum, bool isBracketEnd) : base(owner, length) { Data.BracketEnum = bracketEnum; Data.IsBracketEnd = isBracketEnd; } public MdBracketEnum BracketEnum => Data.BracketEnum; public bool IsBracketEnd => Data.IsBracketEnd; public string TextBracket { get { switch (BracketEnum) { case MdBracketEnum.Round: if (IsBracketEnd == false) { return "("; } else { return ")"; } case MdBracketEnum.Square: if (IsBracketEnd == false) { return "["; } else { return "]"; } default: throw new Exception("Enum unknown!"); } } } internal override void Parse(MdRegistry registry, MdPage owner, ReadOnlySpan text, int index) { var textChar = text.Char(index); switch (textChar) { case '(': new MdBracket(owner, 1, MdBracketEnum.Round, isBracketEnd: false); break; case ')': new MdBracket(owner, 1, MdBracketEnum.Round, isBracketEnd: true); break; case '[': new MdBracket(owner, 1, MdBracketEnum.Square, isBracketEnd: false); break; case ']': new MdBracket(owner, 1, MdBracketEnum.Square, isBracketEnd: true); break; } } } internal enum MdQuotationEnum { None, Single, Double, } internal class MdQuotation : MdTokenBase { /// /// Constructor registry, factory mode. /// public MdQuotation() : base() { } /// /// Constructor instance token. /// public MdQuotation(MdPage owner, int length, MdQuotationEnum quotationEnum) : base(owner, length) { Data.QuotationEnum = quotationEnum; } public MdQuotationEnum QuotationEnum => Data.QuotationEnum; public string TextQuotation { get { switch (QuotationEnum) { case MdQuotationEnum.Single: return "'"; case MdQuotationEnum.Double: return "\""; default: throw new Exception("Enum unknown!"); } } } internal override void Parse(MdRegistry registry, MdPage owner, ReadOnlySpan text, int index) { var textChar = text.Char(index); switch (textChar) { case '\'': new MdQuotation(owner, 1, MdQuotationEnum.Single); break; case '"': new MdQuotation(owner, 1, MdQuotationEnum.Double); break; } } } internal enum MdSymbolEnum { None, Equal, } internal class MdSymbol : MdTokenBase { /// /// Constructor registry, factory mode. /// public MdSymbol() : base() { } /// /// Constructor instance token. /// public MdSymbol(MdPage owner, int length, MdSymbolEnum symbolEnum) : base(owner, length) { Data.SymbolEnum = symbolEnum; } public MdSymbolEnum SymbolEnum => Data.SymbolEnum; public string TextSymbol { get { switch (SymbolEnum) { case MdSymbolEnum.Equal: return "="; default: throw new Exception("Enum unknown!"); } } } internal override void Parse(MdRegistry registry, MdPage owner, ReadOnlySpan text, int index) { var textChar = text.Char(index); switch (textChar) { case '=': new MdSymbol(owner, 1, MdSymbolEnum.Equal); break; } } } internal class MdCode : MdTokenBase { /// /// Constructor registry, factory mode. /// public MdCode() { } /// /// Constructor instance token. /// public MdCode(MdPage owner, int length) : base(owner, length) { } internal override void Parse(MdRegistry registry, MdPage owner, ReadOnlySpan text, int index) { if (text.StartsWith(index, "```") && text.Char(index + 3) != '`') { new MdCode(owner, 3); } } } internal enum MdFontEnum { None, Bold, Italic, } internal class MdFont : MdTokenBase { /// /// Constructor registry, factory mode. /// public MdFont() { } /// /// Constructor instance token. /// public MdFont(MdPage owner, int length, MdFontEnum fontEnum) : base(owner, length) { Data.FontEnum = fontEnum; } public MdFontEnum FontEnum => Data.FontEnum; internal override void Parse(MdRegistry registry, MdPage owner, ReadOnlySpan text, int index) { if (text.StartsWith(index, "**") && text.Char(index + 2) != '*') { new MdFont(owner, 2, MdFontEnum.Bold); } else { if (text.StartsWith(index, "*") && text.Char(index + 1) != '*') { new MdFont(owner, 1, MdFontEnum.Italic); } } } } internal class MdLink : MdTokenBase { /// /// Constructor registry, factory mode. /// public MdLink() { textList = new List { "http://", "https://" }; } /// /// Constructor instance token. /// public MdLink(MdPage owner, int length) : base(owner, length) { } private readonly List textList; internal override void Parse(MdRegistry registry, MdPage owner, ReadOnlySpan text, int index) { foreach (var item in textList) { if (text.StartsWith(index, item)) { new MdLink(owner, item.Length); break; } } } } internal class MdContent : MdTokenBase { /// /// Constructor registry, factory mode. /// public MdContent() : base() { } /// /// Constructor instance token. /// public MdContent(MdPage owner, int length) : base(owner, length) { } internal override void Parse(MdRegistry registry, MdPage owner, ReadOnlySpan text, int index) { MdContent token = null; while (UtilParse.ParseIndex(owner) < text.Length) { if (ParseMain(registry, owner, text, isExcludeContent: true) == false) { if (token == null) { token = new MdContent(owner, 1); } else { token.IndexEndSet(token.IndexEnd + 1); } } else { break; } } } } /// /// Subset of Registry. /// internal class SyntaxRegistry { public SyntaxRegistry(Registry registry) { UtilDoc.Assert(registry.SyntaxRegistry == null); registry.SyntaxRegistry = this; // Init SchemaTypeList foreach (var type in registry.List) { SchemaTypeList[type] = new List(); } SchemaTypeIsBlockList = new Dictionary(); foreach (var type in registry.List) { if (type.IsSubclassOf(typeof(SyntaxBase))) { // Create instance for registry, factory mode. // Call new SyntaxBase(registry); var syntaxParser = (SyntaxBase)type.GetConstructor(new Type[] { typeof(Registry) }).Invoke(new object[] { registry }); // Activator List.Add(syntaxParser); TypeList.Add(type, syntaxParser); // SchemaTypeList var result = new SyntaxBase.RegistrySchemaResult(); syntaxParser.RegistrySchema(result); SchemaOwnerTypeList.Add(type, result.List.Where(item => item.IsChild == true).Select(item => item.OwnerType).ToList()); foreach (var item in result.List.Select(item => item.OwnerType).ToList()) { SchemaTypeList[item].Add(type); } SchemaTypeIsBlockList[type] = result.IsBlock; } } } /// /// (Syntax). Syntax parser list. /// public List List = new List(); /// /// (Type, Syntax). Syntax parser list. /// public Dictionary TypeList = new Dictionary(); /// /// (OwnerType, Type). Owner type, child type. Contains all possible (and not valid) owner. See also property IsChildDirect. /// public Dictionary> SchemaTypeList = new Dictionary>(); /// /// (Type, OwnerType). Child type, owner type. Contains all possible (and valid) owner. See also property IsChildDirect. /// public Dictionary> SchemaOwnerTypeList = new Dictionary>(); /// /// (Type, IsBlock). For example true for font and comment. False for title. /// public Dictionary SchemaTypeIsBlockList = new Dictionary(); public void Parse(AppDoc appDoc) { var mdDoc = appDoc.MdDoc; var syntaxDocOne = appDoc.SyntaxDocOne; var syntaxDocTwo = appDoc.SyntaxDocTwo; var syntaxDocThree = appDoc.SyntaxDocThree; var syntaxDocFour = appDoc.SyntaxDocFour; var syntaxDocFive = appDoc.SyntaxDocFive; var htmlDoc = appDoc.HtmlDoc; // ParseOne appDoc.Data.Registry.ParseEnum = ParseEnum.ParseOne; foreach (MdPage page in mdDoc.List) { if (page.Data.ListCount() > 0) { var tokenBegin = (MdTokenBase)page.Data.List[0].Component(); var tokenEnd = (MdTokenBase)page.Data.List[page.Data.List.Count - 1].Component(); var syntaxPage = new SyntaxPage(syntaxDocOne, tokenBegin, tokenEnd); SyntaxBase.ParseOneMain(syntaxPage); } } // ParseTwo appDoc.Data.Registry.ParseEnum = ParseEnum.ParseTwo; SyntaxBase.ParseMain(syntaxDocTwo, syntaxDocOne); // ParseThree appDoc.Data.Registry.ParseEnum = ParseEnum.ParseThree; SyntaxBase.ParseMain(syntaxDocThree, syntaxDocTwo); // ParseFour appDoc.Data.Registry.ParseEnum = ParseEnum.ParseFour; SyntaxBase.ParseMain(syntaxDocFour, syntaxDocThree); // ParseFive appDoc.Data.Registry.ParseEnum = ParseEnum.ParseFive; SyntaxBase.ParseMain(syntaxDocFive, syntaxDocFour); // ParseHtml appDoc.Data.Registry.ParseEnum = ParseEnum.ParseHtml; syntaxDocFive.ParseHtml(htmlDoc); } } /// /// Base class for md syntax tree. /// internal abstract class SyntaxBase : Component { /// /// Constructor registry, factory mode. /// public SyntaxBase(Registry registry) : base(registry) { } /// /// Constructor for Doc. /// public SyntaxBase(Component owner) : base(owner) { Data.TokenIdBegin = -1; Data.TokenIdEnd = -1; } /// /// Create instance of this object in registry, factory mode. /// protected internal virtual SyntaxBase Create(SyntaxBase owner, SyntaxBase syntax) { throw new Exception("Not implemented!"); } /// /// Override this method to register possible owner types. /// internal virtual void RegistrySchema(RegistrySchemaResult result) { } internal class RegistrySchemaResult { public List List = new List(); internal class RegistrySchemaResultItem { public Type OwnerType; public bool IsChild; } /// /// Gets or sets IsBlock. True, if syntax has a corresponding end syntax. For example true for font and comment. False for title. /// public bool IsBlock; /// /// Register possible owner. /// /// This syntax can be a child of owner. /// This syntax can be a direct child of owner. Otherwise first owner in list is created in between in ParseFour (Owner Insert). public void AddOwner(bool isChildDirect = true) where T : SyntaxBase { List.Add(new RegistrySchemaResultItem { OwnerType = typeof(T), IsChild = isChildDirect }); } } /// /// Constructor ParseOne. /// public SyntaxBase(SyntaxBase owner, MdTokenBase tokenBegin, MdTokenBase tokenEnd) : base(owner) { UtilDoc.Assert(owner.Data.Registry.ParseEnum == ParseEnum.ParseOne); Data.TokenIdBegin = tokenBegin.Data.Id; Data.TokenIdEnd = tokenEnd.Data.Id; Data.TokenIndexBegin = tokenBegin.Data.TokenIndexBegin; Data.TokenIndexEnd = tokenEnd.Data.TokenIndexEnd; CreateValidate(); } /// /// Constructor ParseTwo, ParseThree, ParseFour and ParseFive. /// public SyntaxBase(SyntaxBase owner, SyntaxBase syntaxBegin, SyntaxBase syntaxEnd) : base(owner) { var parseEnum = owner.Data.Registry.ParseEnum; UtilDoc.Assert(parseEnum == ParseEnum.ParseTwo || parseEnum == ParseEnum.ParseThree || parseEnum == ParseEnum.ParseFour || parseEnum == ParseEnum.ParseFive); Data.SyntaxId = syntaxBegin.Data.Id; Data.TokenIdBegin = syntaxBegin.TokenBegin.Data.Id; Data.TokenIdEnd = syntaxEnd.TokenEnd.Data.Id; Data.TokenIndexBegin = syntaxBegin.TokenBegin.Data.TokenIndexBegin; Data.TokenIndexEnd = syntaxEnd.TokenEnd.Data.TokenIndexEnd; // Enable, disable (for debug only) ParseThree validate. if (parseEnum == ParseEnum.ParseThree) { // Uncomment return to disable ParseThree validate. // No more exceptions but wrong sequence of components in output! // return; } if (owner is not SyntaxDoc) { var indexBegin = Index(syntaxBegin.Data.TokenIdBegin); var indexEnd = Index(syntaxEnd.Data.TokenIdEnd); var indexEndOwner = Index(owner.Data.TokenIdEnd); bool isShorten = indexEnd <= indexEndOwner; bool isExtend = indexBegin == indexEndOwner + 1; UtilDoc.Assert(isShorten ^ isExtend); // If exception in ParseThree, disable (for debug only) ParseThree validate above. Caused by wrong break in method ParseTwo calling method ParseTwoMainBreak. var next = owner.Data; while (next.DataEnum != DataEnum.SyntaxDoc) { UtilDoc.Assert(next.Owner.List.Last() == next); // Modify TokenIdEnd only on last item next.TokenIdEnd = syntaxEnd.Data.TokenIdEnd; // Modify owner TokenIdEnd next.TokenIndexEnd = syntaxEnd.TokenEnd.Data.TokenIndexEnd; next = next.Owner; } UtilDoc.Assert(Index(owner.Data.TokenIdBegin) <= Index(owner.Data.TokenIdEnd)); } CreateValidate(); } /// /// Constructor ParseTwo, ParseThree, ParseFour and ParseFive. /// public SyntaxBase(SyntaxBase owner, SyntaxBase syntax) : this(owner, syntax, syntax) { } /// /// Validate index ranges after create. /// private void CreateValidate() { var typeOwner = Data.Registry.DataEnumList[Data.Owner.DataEnum]; if (!UtilDoc.IsSubclassOf(typeOwner, typeof(SyntaxDoc))) { UtilDoc.Assert(Index(Data.TokenIdBegin) <= Index(Data.TokenIdEnd)); if (UtilDoc.IsSubclassOf(typeOwner, typeof(SyntaxBase))) { UtilDoc.Assert(Index(Data.Owner.TokenIdBegin) <= Index(Data.TokenIdBegin)); UtilDoc.Assert(Index(Data.Owner.TokenIdEnd) >= Index(Data.TokenIdEnd)); if (Data.Index == 0) { UtilDoc.Assert(Index(Data.Owner.TokenIdBegin) == Index(Data.TokenIdBegin)); } else { var indexPrevious = Index(Owner.Data.List[Data.Index - 1].TokenIdEnd); var index = Index(Data.TokenIdBegin); UtilDoc.Assert(indexPrevious + 1 == index); } } } } private int Index(int id) { return Data.Registry.IdList[id].Index; } public MdTokenBase TokenBegin => (MdTokenBase)Data.Registry.IdList[Data.TokenIdBegin].Component(); public MdTokenBase TokenEnd => (MdTokenBase)Data.Registry.IdList[Data.TokenIdEnd].Component(); /// /// Gets Text. This is the text between TokenBegin and TokenEnd. /// public string Text { get { var result = new StringBuilder(); if (this is not SyntaxDoc) { var tokenEnd = TokenEnd; Component component = TokenBegin; do { var token = (MdTokenBase)component; result.Append(token.Text); } while (Next(ref component, tokenEnd)); } return UtilDoc.StringNull(result.ToString()); } } /// /// Gets IsCreateNew. If true, syntax has been inserted by ParseThree. /// public bool IsCreateNew => Data.IsCreateNew; /// /// Main entry for ParseOne. /// internal static void ParseOneMain(SyntaxPage owner) { var registry = owner.Data.Registry.SyntaxRegistry; MdTokenBase token; while ((token = UtilParse.ParseOneToken(owner)) != null) { bool isFind = false; foreach (var syntaxParser in registry.List) { var countBefore = owner.Data.ListCount(); syntaxParser.ParseOne(owner, token); var countAfter = owner.Data.ListCount(); UtilDoc.Assert(countBefore <= countAfter); if (countBefore < countAfter) { isFind = true; break; } } UtilDoc.Assert(isFind, "No syntax parser found!"); } } /// /// Main entry for ParseTwo, ParseThree, ParseFour, ParseFive. /// internal static void ParseMain(SyntaxBase owner, IReadOnlyList list) { bool isFirst = owner.Data.ListCount() == 0; foreach (SyntaxBase item in list) { if (item.TokenEnd.IndexEnd <= owner.Data.List?.Last().TokenIndexEnd) { continue; } var countBefore = owner.Data.ListCount(); var indexEndBefore = owner.Data.TokenIndexEnd; var countOwnerBefore = owner.Data.Owner?.ListCount(); switch (owner.Data.Registry.ParseEnum) { case ParseEnum.ParseTwo: item.ParseTwo(owner); break; case ParseEnum.ParseThree: item.ParseThree(owner); break; case ParseEnum.ParseFour: item.ParseFour(owner); break; case ParseEnum.ParseFive: item.ParseFive(owner); break; default: throw new Exception("Enum unknown!"); } var countAfter = owner.Data.ListCount(); var indexEndAfter = owner.Data.TokenIndexEnd; var countOwnerAfter = owner.Data.Owner?.ListCount(); // After first child component has been created IndexEnd is smaller. UtilDoc.Assert((isFirst && indexEndBefore >= indexEndAfter) || (!isFirst && indexEndBefore <= indexEndAfter)); bool isFind = false; if ((isFirst && indexEndBefore > indexEndAfter) || (!isFirst && indexEndBefore < indexEndAfter)) { isFind = true; } // Component has been created and parse is completed. if (indexEndBefore == indexEndAfter && countBefore < countAfter) { isFind = true; } // Component has been created and added to owner.Owner. if (countOwnerBefore < countOwnerAfter) { isFind = true; } if (!isFind) { var registry = owner.Data.Registry.SyntaxRegistry; var ownerLocal = UtilParse.Create(registry, owner, item); ParseMain(ownerLocal, item); } isFirst = false; } } internal static void ParseMain(SyntaxBase owner, SyntaxBase syntax) { if (syntax.Data.ListCount() > 0) { ParseMain(owner, syntax.List); } } /// /// Main entry for ParseHtml. /// internal static void ParseHtmlMain(HtmlBase owner, SyntaxBase syntax) { foreach (SyntaxBase item in syntax.List) { item.ParseHtml(owner); } } /// /// Parse md token between tokenBegin and tokenEnd. /// internal virtual void ParseOne(SyntaxBase owner, MdTokenBase token) { } /// /// Transform this syntax source to owner dest. /// /// Owner dest. internal virtual void ParseTwo(SyntaxBase owner) { } /// /// Transform this syntax source to owner dest. /// /// Owner dest. internal virtual void ParseThree(SyntaxBase owner) { var registry = Data.Registry.SyntaxRegistry; var ownerLocal = UtilParse.Create(registry, owner, this); ParseMain(ownerLocal, this); if (!registry.SchemaTypeIsBlockList[GetType()]) { var next = Next(); bool isIgnore = false; while (next != null && registry.SchemaTypeList[GetType()].Contains(next.GetType())) { if (!isIgnore) { isIgnore = true; new SyntaxIgnore(ownerLocal, this); } var ownerLocalLocal = UtilParse.Create(registry, ownerLocal, next); ParseMain(ownerLocalLocal, next); next = next.Next(); } } } /// /// Transform this syntax source to owner dest. /// /// Owner dest. internal void ParseFour(SyntaxBase owner) { var registry = Data.Registry.SyntaxRegistry; SyntaxBase ownerLocal = owner; // Owner insert if owner can not be direct owner. var isOwnerInsert = false; if (!registry.SchemaOwnerTypeList[GetType()].Contains(owner.GetType())) { // Owner insert var ownerType = registry.SchemaOwnerTypeList[GetType()].First(); ownerLocal = UtilParse.Create(registry, owner, this, ownerType); ownerLocal.Data.IsCreateNew = true; isOwnerInsert = true; } // Insert for example paragraph to comment in order to merge later. if (!isOwnerInsert) { var ownerLast = owner.Data.List?.Last(); if (ownerLast?.IsCreateNew == true) { var ownerLastType = ownerLast.ComponentType(); if (registry.SchemaOwnerTypeList[GetType()].Contains(ownerLastType)) { // Owner insert ownerLocal = UtilParse.Create(registry, owner, this, ownerLastType); ownerLocal.Data.IsCreateNew = true; } } } // Syntax create var syntax = UtilParse.Create(registry, ownerLocal, this); if (Data.ListCount() > 0) { foreach (SyntaxBase item in List) { item.ParseFour(syntax); } } } internal void ParseFive(SyntaxBase owner) { var registry = Data.Registry.SyntaxRegistry; SyntaxBase ownerLocal; var previous = Previous(); if (IsCreateNew && previous?.GetType() == GetType()) { // Merge with previous ownerLocal = (SyntaxBase)owner.Data.List.Last().Component(); if (ownerLocal.Data.ListCount() == 0) { new SyntaxIgnore(ownerLocal, previous); } } else { // Syntax create ownerLocal = UtilParse.Create(registry, owner, this); ownerLocal.Data.IsCreateNew = IsCreateNew; } if (Data.ListCount() > 0) { foreach (SyntaxBase item in List) { item.ParseFive(ownerLocal); } } } /// /// Override this method to custom transform syntax tree ParseTwo into html. /// internal virtual void ParseHtml(HtmlBase owner) { ParseHtmlMain(owner, this); } } /// /// Syntax tree root. /// internal class SyntaxDoc : SyntaxBase { /// /// Constructor registry, factory mode. /// public SyntaxDoc(Registry registry) : base(registry) { } public SyntaxDoc(Component owner) : base(owner) { } } internal class SyntaxPage : SyntaxBase { /// /// Constructor registry, factory mode. /// public SyntaxPage(Registry registry) : base(registry) { } /// /// Constructor ParseOne. /// public SyntaxPage(SyntaxBase owner, MdTokenBase tokenBegin, MdTokenBase tokenEnd) : base(owner, tokenBegin, tokenEnd) { } /// /// Constructor ParseTwo, ParseThree, ParseFour and ParseFive. /// public SyntaxPage(SyntaxBase owner, SyntaxBase syntax) : base(owner, syntax) { UtilDoc.Assert(owner is SyntaxDoc); if (syntax is SyntaxCustomPage) { Data.PagePath = ((SyntaxCustomPage)syntax).PagePath; Data.PageTitleHtml = ((SyntaxCustomPage)syntax).PageTitleHtml; } else { Data.PagePath = ((SyntaxPage)syntax).PagePath; Data.PageTitleHtml = ((SyntaxPage)syntax).PageTitleHtml; } } /// /// Gets PagePath. Custom parameter of class SyntaxCustomPage. /// public string PagePath => Data.PagePath; /// /// Gets PageTitleHtml. Custom parameter of class SyntaxCustomPage. /// public string PageTitleHtml => Data.PageTitleHtml; internal override void RegistrySchema(RegistrySchemaResult result) { result.AddOwner(); } protected internal override SyntaxBase Create(SyntaxBase owner, SyntaxBase syntax) { return new SyntaxPage(owner, syntax); } internal override void ParseHtml(HtmlBase owner) { var page = new HtmlPage(owner, this); ParseHtmlMain(page, this); } } internal class SyntaxComment : SyntaxBase { /// /// Constructor registry, factory mode. /// public SyntaxComment(Registry registry) : base(registry) { } /// /// Constructor ParseOne. /// public SyntaxComment(SyntaxBase owner, MdTokenBase tokenBegin, MdTokenBase tokenEnd) : base(owner, tokenBegin, tokenEnd) { } /// /// Constructor ParseTwo, ParseThree, ParseFour and ParseFive. /// public SyntaxComment(SyntaxBase owner, SyntaxBase syntax) : base(owner, syntax) { } /// /// Constructor ParseTwo, ParseThree, ParseFour and ParseFive. /// public SyntaxComment(SyntaxBase owner, SyntaxBase syntaxBegin, SyntaxBase syntaxEnd) : base(owner, syntaxBegin, syntaxEnd) { } internal override void RegistrySchema(RegistrySchemaResult result) { result.IsBlock = true; result.AddOwner(); result.AddOwner(); result.AddOwner(); } protected internal override SyntaxBase Create(SyntaxBase owner, SyntaxBase syntax) { return new SyntaxComment(owner, syntax); } internal override void ParseOne(SyntaxBase owner, MdTokenBase token) { if (token is MdComment) { new SyntaxComment(owner, token, token); } } internal override void ParseTwo(SyntaxBase owner) { SyntaxBase next = this; do { next = next.Next(null); if (next is SyntaxComment comment && comment.Text == "-->") { new SyntaxComment(owner, this, comment); break; } } while (next != null); } internal override void ParseHtml(HtmlBase owner) { new HtmlComment(owner, this); } } internal class SyntaxTitle : SyntaxBase { /// /// Constructor registry, factory mode. /// public SyntaxTitle(Registry registry) : base(registry) { } /// /// Constructor ParseOne. /// public SyntaxTitle(SyntaxBase owner, MdTokenBase tokenBegin, MdTokenBase tokenEnd, int titleLevel) : base(owner, tokenBegin, tokenEnd) { Data.TitleLevel = titleLevel; } /// /// Constructor ParseTwo, ParseThree, ParseFour and ParseFive. /// public SyntaxTitle(SyntaxBase owner, SyntaxBase syntax) : base(owner, syntax) { UtilDoc.Assert(owner is SyntaxPage || owner is SyntaxCustomPage); Data.TitleLevel = ((SyntaxTitle)syntax).TitleLevel; Data.TitleId = ((SyntaxTitle)syntax).TitleId; if (Data.Registry.IsDebug == false) { if (TitleId == null) { var contentList = syntax.List.OfType().ToList(); if (contentList.Count > 0) { foreach (var item in contentList) { Data.TitleId += item.Text.ToLower().Replace(" ", "-").Replace("\"", ""); } // Title contains html. For example: // TODO parse html, xml tag. var index = Data.TitleId.IndexOf("<"); if (index != -1) { Data.TitleId = Data.TitleId.Substring(0, index); } while (Data.TitleId.EndsWith("-")) { Data.TitleId = Data.TitleId.Substring(0, Data.TitleId.Length - 1); } } } } } public int TitleLevel => Data.TitleLevel; /// /// Gets TitleId. Used for html named anchor. /// public string TitleId => Data.TitleId; protected internal override SyntaxBase Create(SyntaxBase owner, SyntaxBase syntax) { return new SyntaxTitle(owner, syntax); } internal override void RegistrySchema(RegistrySchemaResult result) { result.AddOwner(); } internal override void ParseOne(SyntaxBase owner, MdTokenBase token) { if (UtilParse.ParseOneIsNewLine(token, out var tokenEnd)) { new SyntaxTitle(owner, token, tokenEnd, tokenEnd.TitleLevel); if (tokenEnd.Next() is MdSpace tokenSpace) { // Trailing space new SyntaxIgnore(owner, tokenSpace); } } } internal override void ParseHtml(HtmlBase owner) { var title = new HtmlTitle(owner, this); ParseHtmlMain(title, this); } } internal class SyntaxBullet : SyntaxBase { /// /// Constructor registry, factory mode. /// public SyntaxBullet(Registry registry) : base(registry) { } /// /// Constructor ParseOne. /// public SyntaxBullet(SyntaxBase owner, MdTokenBase tokenBegin, MdTokenBase tokenEnd) : base(owner, tokenBegin, tokenEnd) { } /// /// Constructor ParseTwo, ParseThree, ParseFour and ParseFive. /// public SyntaxBullet(SyntaxBase owner, SyntaxBase syntax) : base(owner, syntax) { } protected internal override SyntaxBase Create(SyntaxBase owner, SyntaxBase syntax) { return new SyntaxBullet(owner, syntax); } internal override void RegistrySchema(RegistrySchemaResult result) { result.AddOwner(); } internal override void ParseOne(SyntaxBase owner, MdTokenBase token) { if (UtilParse.ParseOneIsNewLine(token, out var tokenEnd)) { new SyntaxBullet(owner, token, tokenEnd); if (tokenEnd.Next() is MdSpace tokenSpace) { // Trailing space new SyntaxIgnore(owner, tokenSpace); } } } internal override void ParseHtml(HtmlBase owner) { var bulletList = owner.Data.List?.Last().Component() as HtmlBulletList; if (bulletList == null) { bulletList = new HtmlBulletList(owner); } var bulletItem = new HtmlBulletItem(bulletList, this); ParseHtmlMain(bulletItem, this); } } internal class SyntaxCode : SyntaxBase { /// /// Constructor registry, factory mode. /// public SyntaxCode(Registry registry) : base(registry) { } /// /// Constructor ParseOne. /// public SyntaxCode(SyntaxBase owner, MdTokenBase tokenBegin, MdTokenBase tokenEnd, string codeLanguage) : base(owner, tokenBegin, tokenEnd) { Data.CodeLanguage = codeLanguage; } /// /// Constructor ParseTwo, ParseThree, ParseFour and ParseFive. /// public SyntaxCode(SyntaxBase owner, SyntaxBase syntax) : base(owner, syntax) { Data.CodeLanguage = ((SyntaxCode)syntax).CodeLanguage; Data.CodeText = ((SyntaxCode)syntax).CodeText; } protected internal override SyntaxBase Create(SyntaxBase owner, SyntaxBase syntax) { return new SyntaxCode(owner, syntax); } internal override void RegistrySchema(RegistrySchemaResult result) { result.IsBlock = true; result.AddOwner(); } public string CodeLanguage => Data.CodeLanguage; public string CodeText { get => Data.CodeText; set => Data.CodeText = value; } internal override void ParseOne(SyntaxBase owner, MdTokenBase token) { if (UtilParse.ParseOneIsNewLine(token, out var tokenEnd)) { var next = tokenEnd.Next(); // Code language string codeLanguage = null; if (next is MdContent content) { codeLanguage = content.Text; next = next.Next(); } List contentList = new List(); bool isFind = false; while (next != null) { if (next is MdCode) { isFind = true; break; } contentList.Add(next); next = next.Next(); } var codeText = new StringBuilder(); foreach (var item in contentList) { codeText.Append(item.Text); } if (isFind) { new SyntaxCode(owner, token, next, codeLanguage) { CodeText = codeText.ToString() }; } } } internal override void ParseHtml(HtmlBase owner) { new HtmlCode(owner, this); } } internal class SyntaxFont : SyntaxBase { /// /// Constructor registry, factory mode. /// public SyntaxFont(Registry registry) : base(registry) { } /// /// Constructor ParseOne. /// public SyntaxFont(SyntaxBase owner, MdTokenBase tokenBegin, MdTokenBase tokenEnd) : base(owner, tokenBegin, tokenEnd) { } /// /// Constructor ParseTwo, ParseThree, ParseFour and ParseFive. /// public SyntaxFont(SyntaxBase owner, SyntaxBase syntax) : base(owner, syntax) { } /// /// Constructor ParseTwo, ParseThree, ParseFour and ParseFive. /// public SyntaxFont(SyntaxBase owner, SyntaxBase syntaxBegin, SyntaxBase syntaxEnd) : base(owner, syntaxBegin, syntaxEnd) { } protected internal override SyntaxBase Create(SyntaxBase owner, SyntaxBase syntax) { return new SyntaxFont(owner, syntax); } internal override void RegistrySchema(RegistrySchemaResult result) { result.IsBlock = true; result.AddOwner(); result.AddOwner(); result.AddOwner(); result.AddOwner(false); } public MdFontEnum FontEnum => ((MdFont)TokenBegin).FontEnum; internal override void ParseOne(SyntaxBase owner, MdTokenBase token) { if (token is MdFont) { new SyntaxFont(owner, token, token); } } internal override void ParseTwo(SyntaxBase owner) { var registry = Data.Registry.SyntaxRegistry; var next = Next(); List contentList = new List(); bool isFind = false; while (next != null) { if (next is SyntaxFont fontSource && FontEnum == fontSource.FontEnum) { var fontDest = new SyntaxFont(owner, this, fontSource); new SyntaxIgnore(fontDest, this); foreach (var item in contentList) { UtilParse.Create(registry, fontDest, item); } new SyntaxIgnore(fontDest, fontSource); isFind = true; break; } if (!registry.SchemaOwnerTypeList[next.GetType()].Contains(this.GetType())) { break; } contentList.Add(next); next = next.Next(); } if (!isFind) { new SyntaxContent(owner, this); } } internal override void ParseHtml(HtmlBase owner) { var font = owner; bool isBegin = !(Owner is SyntaxFont); if (isBegin) { font = new HtmlFont(owner, this); } ParseHtmlMain(font, this); } } internal class SyntaxLink : SyntaxBase { /// /// Constructor registry, factory mode. /// public SyntaxLink(Registry registry) : base(registry) { } /// /// Constructor ParseOne. /// public SyntaxLink(SyntaxBase owner, MdTokenBase tokenBegin, MdTokenBase tokenEnd, string link, string linkText) : base(owner, tokenBegin, tokenEnd) { Data.Link = link; Data.LinkText = linkText; } /// /// Constructor ParseTwo, ParseThree, ParseFour and ParseFive. /// public SyntaxLink(SyntaxBase owner, SyntaxBase syntax) : base(owner, syntax) { Data.Link = ((SyntaxLink)syntax).Link; Data.LinkText = ((SyntaxLink)syntax).LinkText; } public string Link => Data.Link; public string LinkText => Data.LinkText; protected internal override SyntaxBase Create(SyntaxBase owner, SyntaxBase syntax) { return new SyntaxLink(owner, syntax); } internal override void RegistrySchema(RegistrySchemaResult result) { result.IsBlock = true; result.AddOwner(); // result.AddOwner(); // No link in title result.AddOwner(); result.AddOwner(); // For example bold link } internal override void ParseOne(SyntaxBase owner, MdTokenBase token) { // For example http:// if (token is MdLink) { if (UtilParse.ParseOneIsLink(token, null, out var linkEnd, out string link)) { new SyntaxLink(owner, token, linkEnd, link, link); } } // For example []() if (token is MdBracket bracketSquare && bracketSquare.TextBracket == "[") { var next = token.Next(); UtilParse.ParseOneIsLinkText(next, null, out var linkTextEnd, out string linkText); if (linkText != null) { next = linkTextEnd.Next(); } if (next is MdBracket bracketSquareEnd && bracketSquareEnd.TextBracket == "]") { next = next.Next(); if (next is MdBracket bracketRound && bracketRound.TextBracket == "(") { next = next.Next(); if (UtilParse.ParseOneIsLinkText(next, null, out var linkEnd, out string link)) { next = linkEnd.Next(); if (next is MdBracket bracketRoundEnd && bracketRoundEnd.TextBracket == ")") { if (linkText == null) { linkText = link; } new SyntaxLink(owner, token, next, link, linkText); } } } } } } internal override void ParseHtml(HtmlBase owner) { new HtmlLink(owner, this); } } internal class SyntaxImage : SyntaxBase { /// /// Constructor registry, factory mode. /// public SyntaxImage(Registry registry) : base(registry) { } /// /// Constructor ParseOne. /// public SyntaxImage(SyntaxBase owner, MdTokenBase tokenBegin, MdTokenBase tokenEnd, string link, string linkText) : base(owner, tokenBegin, tokenEnd) { Data.Link = link; Data.LinkText = linkText; } /// /// Constructor ParseTwo, ParseThree, ParseFour and ParseFive. /// public SyntaxImage(SyntaxBase owner, SyntaxBase syntax) : base(owner, syntax) { Data.Link = ((SyntaxImage)syntax).Link; Data.LinkText = ((SyntaxImage)syntax).LinkText; } protected internal override SyntaxBase Create(SyntaxBase owner, SyntaxBase syntax) { return new SyntaxImage(owner, syntax); } internal override void RegistrySchema(RegistrySchemaResult result) { result.IsBlock = true; result.AddOwner(); } public string Link => Data.Link; public string LinkText => Data.LinkText; internal override void ParseOne(SyntaxBase owner, MdTokenBase token) { // For example ![]() if (token is MdImage) { MdTokenBase next = token.Next(); UtilParse.ParseOneIsLinkText(next, token, out var linkTextEnd, out string linkText); if (linkText != null) { next = linkTextEnd.Next(); } if (next is MdBracket bracketSquareEnd && bracketSquareEnd.TextBracket == "]") { next = next.Next(); if (next is MdBracket bracketRound && bracketRound.TextBracket == "(") { next = next.Next(); if (UtilParse.ParseOneIsLink(next, token, out var linkEnd, out var link)) { if (linkEnd.Next() is MdBracket bracketRoundEnd && bracketRoundEnd.Text == ")") { new SyntaxImage(owner, token, bracketRoundEnd, link, linkText != null ? linkText : link); } } } } } } internal override void ParseHtml(HtmlBase owner) { new HtmlImage(owner, this); } } internal class SyntaxContent : SyntaxBase { /// /// Constructor registry, factory mode. /// public SyntaxContent(Registry registry) : base(registry) { } /// /// Constructor ParseOne. /// public SyntaxContent(SyntaxBase owner, MdTokenBase tokenBegin, MdTokenBase tokenEnd) : base(owner, tokenBegin, tokenEnd) { } /// /// Constructor ParseTwo, ParseThree, ParseFour and ParseFive. /// public SyntaxContent(SyntaxBase owner, SyntaxBase syntax) : base(owner, syntax) { } protected internal override SyntaxBase Create(SyntaxBase owner, SyntaxBase syntax) { return new SyntaxContent(owner, syntax); } internal override void RegistrySchema(RegistrySchemaResult result) { result.AddOwner(); result.AddOwner(); result.AddOwner(); result.AddOwner(); result.AddOwner(false); } internal override void ParseOne(SyntaxBase owner, MdTokenBase token) { new SyntaxContent(owner, token, token); } internal override void ParseHtml(HtmlBase owner) { new HtmlContent(owner, this); } } internal class SyntaxParagraph : SyntaxBase { /// /// Constructor registry, factory mode. /// public SyntaxParagraph(Registry registry) : base(registry) { } /// /// Constructor ParseOne. /// public SyntaxParagraph(SyntaxBase owner, MdTokenBase tokenBegin, MdTokenBase tokenEnd) : base(owner, tokenBegin, tokenEnd) { } /// /// Constructor ParseTwo, ParseThree, ParseFour and ParseFive. /// public SyntaxParagraph(SyntaxBase owner, SyntaxBase syntax) : base(owner, syntax) { UtilDoc.Assert(owner is SyntaxPage || owner is SyntaxCustomNote || owner is SyntaxCustomPage); } protected internal override SyntaxBase Create(SyntaxBase owner, SyntaxBase syntax) { return new SyntaxParagraph(owner, syntax); } internal override void RegistrySchema(RegistrySchemaResult result) { result.AddOwner(); result.AddOwner(); } internal override void ParseOne(SyntaxBase owner, MdTokenBase token) { if (token is MdParagraph) { new SyntaxParagraph(owner, token, token); } } internal override void ParseHtml(HtmlBase owner) { var paragraph = new HtmlParagraph(owner, this); ParseHtmlMain(paragraph, this); } } internal class SyntaxNewLine : SyntaxBase { /// /// Constructor registry, factory mode. /// public SyntaxNewLine(Registry registry) : base(registry) { } /// /// Constructor ParseOne. /// public SyntaxNewLine(SyntaxBase owner, MdTokenBase token) : base(owner, token, token) { } /// /// Constructor ParseTwo, ParseThree, ParseFour and ParseFive. /// public SyntaxNewLine(SyntaxBase owner, SyntaxBase syntax) : base(owner, syntax) { } protected internal override SyntaxBase Create(SyntaxBase owner, SyntaxBase syntax) { return new SyntaxNewLine(owner, syntax); } internal override void RegistrySchema(RegistrySchemaResult result) { result.AddOwner(); // result.AddOwner(); // New line in title is the end of title. result.AddOwner(); result.AddOwner(false); } internal override void ParseOne(SyntaxBase owner, MdTokenBase token) { if (token is MdNewLine) { new SyntaxNewLine(owner, token); } } internal override void ParseHtml(HtmlBase owner) { // No html for NewLine token. } } internal class SyntaxIgnore : SyntaxBase { /// /// Constructor registry, factory mode. /// public SyntaxIgnore(Registry registry) : base(registry) { } /// /// Constructor ParseOne. /// public SyntaxIgnore(SyntaxBase owner, MdTokenBase token) : base(owner, token, token) { } /// /// Constructor ParseTwo, ParseThree, ParseFour and ParseFive. /// public SyntaxIgnore(SyntaxBase owner, SyntaxBase syntax) : base(owner, syntax) { } protected internal override SyntaxBase Create(SyntaxBase owner, SyntaxBase syntax) { return new SyntaxIgnore(owner, syntax); } internal override void RegistrySchema(RegistrySchemaResult result) { result.AddOwner(); result.AddOwner(); result.AddOwner(); result.AddOwner(); result.AddOwner(); result.AddOwner(); result.AddOwner(); } internal override void ParseHtml(HtmlBase owner) { // No html } } /// /// Base class for custom syntax. For example (Message Type="Warning")Do not delete!(Message) /// internal abstract class SyntaxCustomBase : SyntaxBase { /// /// Constructor registry, factory mode. /// public SyntaxCustomBase(Registry registry) : base(registry) { } /// /// Constructor ParseOne. /// public SyntaxCustomBase(SyntaxBase owner, MdTokenBase tokenBegin, MdTokenBase tokenEnd) : base(owner, tokenBegin, tokenEnd) { } /// /// Constructor ParseTwo, ParseThree, ParseFour and ParseFive. /// public SyntaxCustomBase(SyntaxBase owner, SyntaxBase syntax) : base(owner, syntax) { } /// /// Constructor ParseTwo, ParseThree, ParseFour and ParseFive. /// public SyntaxCustomBase(SyntaxBase owner, SyntaxBase syntaxBegin, SyntaxBase syntaxEnd) : base(owner, syntaxBegin, syntaxEnd) { } } internal class SyntaxCustomNote : SyntaxCustomBase { /// /// Constructor registry, factory mode. /// public SyntaxCustomNote(Registry registry) : base(registry) { } /// /// Constructor ParseOne. /// public SyntaxCustomNote(SyntaxBase owner, MdTokenBase tokenBegin, MdTokenBase tokenEnd) : base(owner, tokenBegin, tokenEnd) { } /// /// Constructor ParseTwo, ParseThree, ParseFour and ParseFive. /// public SyntaxCustomNote(SyntaxBase owner, SyntaxBase syntax) : base(owner, syntax) { } /// /// Constructor ParseTwo, ParseThree, ParseFour and ParseFive. /// public SyntaxCustomNote(SyntaxBase owner, SyntaxBase syntaxBegin, SyntaxBase syntaxEnd) : base(owner, syntaxBegin, syntaxEnd) { } protected internal override SyntaxBase Create(SyntaxBase owner, SyntaxBase syntax) { return new SyntaxCustomNote(owner, syntax); } internal override void RegistrySchema(RegistrySchemaResult result) { result.IsBlock = true; result.AddOwner(); } internal override void ParseOne(SyntaxBase owner, MdTokenBase token) { if (UtilParse.ParseOneIsCustom(token, "Note", out var tokenEnd, out _)) { new SyntaxCustomNote(owner, token, tokenEnd); if (tokenEnd.Next() is MdNewLine newLine) { new SyntaxIgnore(owner, newLine); } } } internal override void ParseTwo(SyntaxBase owner) { var registry = Data.Registry.SyntaxRegistry; var next = Next(); List contentList = new List(); bool isFind = false; // Found closing (Note) bool isEndExceptional = false; // Ends without closing (Note). For example with title. while (next != null) { if (next is SyntaxCustomNote noteSource) { var noteDest = new SyntaxCustomNote(owner, this, noteSource); new SyntaxIgnore(noteDest, this); ParseMain(noteDest, contentList); if (!isEndExceptional) { new SyntaxIgnore(noteDest, noteSource); } else { noteSource.Data.IsEndExceptional = true; } isFind = true; break; } if (!registry.SchemaTypeList[GetType()].Contains(next.GetType())) { isEndExceptional = true; } if (!isEndExceptional) { contentList.Add(next); } next = next.Next(); } if (!isFind) { if (!Data.IsEndExceptional) { new SyntaxContent(owner, this); } else { new SyntaxIgnore(owner, this); } } } internal override void ParseHtml(HtmlBase owner) { var note = new HtmlCustomNote(owner, this); ParseHtmlMain(note, this); } } /// /// For example (Youtube) https://www.youtube.com/embed/bYJTl5axgUY /// internal class SyntaxCustomYoutube : SyntaxCustomBase { /// /// Constructor registry, factory mode. /// public SyntaxCustomYoutube(Registry registry) : base(registry) { } /// /// Constructor ParseOne. /// public SyntaxCustomYoutube(SyntaxBase owner, MdTokenBase tokenBegin, MdTokenBase tokenEnd, string link) : base(owner, tokenBegin, tokenEnd) { Data.Link = link; } /// /// Constructor ParseTwo, ParseThree, ParseFour and ParseFive. /// public SyntaxCustomYoutube(SyntaxBase owner, SyntaxBase syntax) : base(owner, syntax) { Data.Link = ((SyntaxCustomYoutube)syntax).Link; } public string Link => Data.Link; protected internal override SyntaxBase Create(SyntaxBase owner, SyntaxBase syntax) { return new SyntaxCustomYoutube(owner, syntax); } internal override void RegistrySchema(RegistrySchemaResult result) { result.AddOwner(); } internal override void ParseOne(SyntaxBase owner, MdTokenBase token) { if (UtilParse.ParseOneIsCustom(token, "Youtube", out var tokenEnd, out var paramList)) { if (paramList.TryGetValue("Link", out var paramLink)) { if (UtilParse.ParseOneIsLink(paramLink.TokenBegin, paramLink.TokenEnd, out var linkEnd, out string link)) { new SyntaxCustomYoutube(owner, token, tokenEnd, link); } } } } internal override void ParseHtml(HtmlBase owner) { var note = new HtmlCustomYoutube(owner, this); ParseHtmlMain(note, this); } } /// /// Custom Image. /// internal class SyntaxCustomImage : SyntaxCustomBase { /// /// Constructor registry, factory mode. /// public SyntaxCustomImage(Registry registry) : base(registry) { } /// /// Constructor ParseOne. /// public SyntaxCustomImage(SyntaxBase owner, MdTokenBase tokenBegin, MdTokenBase tokenEnd, string src, string href, string title, string description) : base(owner, tokenBegin, tokenEnd) { Data.Src = src; Data.Href = href; Data.Title = title; Data.Description = description; } /// /// Constructor ParseTwo, ParseThree, ParseFour and ParseFive. /// public SyntaxCustomImage(SyntaxBase owner, SyntaxBase syntax) : base(owner, syntax) { Data.Src = ((SyntaxCustomImage)syntax).Src; Data.Href = ((SyntaxCustomImage)syntax).Href; Data.Title = ((SyntaxCustomImage)syntax).Title; Data.Description = ((SyntaxCustomImage)syntax).Description; } public string Src => Data.Src; public string Href => Data.Href; public string Title => Data.Title; public string Description => Data.Description; protected internal override SyntaxBase Create(SyntaxBase owner, SyntaxBase syntax) { return new SyntaxCustomImage(owner, syntax); } internal override void RegistrySchema(RegistrySchemaResult result) { result.AddOwner(); } internal override void ParseOne(SyntaxBase owner, MdTokenBase token) { if (UtilParse.ParseOneIsCustom(token, "Image", out var tokenEnd, out var paramList)) { if (paramList.TryGetValue("Src", out var paramSrc)) { if (UtilParse.ParseOneIsLink(paramSrc.TokenBegin, paramSrc.TokenEnd, out _, out string src)) { string href = null; string title = null; string description = null; if (paramList.TryGetValue("Href", out var paramHref)) { if (UtilParse.ParseOneIsLink(paramHref.TokenBegin, paramHref.TokenEnd, out _, out string hrefLocal)) { href = hrefLocal; } } if (paramList.TryGetValue("Title", out var paramTitle)) { title = paramTitle.Text; } if (paramList.TryGetValue("Description", out var paramDescription)) { description = paramDescription.Text; } new SyntaxCustomImage(owner, token, tokenEnd, src, href, title, description); } } } } internal override void ParseHtml(HtmlBase owner) { var note = new HtmlCustomImage(owner, this); ParseHtmlMain(note, this); } } /// /// Custom syntax for page break. /// internal class SyntaxCustomPage : SyntaxCustomBase { /// /// Constructor registry, factory mode. /// public SyntaxCustomPage(Registry registry) : base(registry) { } /// /// Constructor ParseOne. /// public SyntaxCustomPage(SyntaxBase owner, MdTokenBase tokenBegin, MdTokenBase tokenEnd, string pagePath, string pageTitleHtml) : base(owner, tokenBegin, tokenEnd) { Data.PagePath = pagePath; Data.PageTitleHtml = pageTitleHtml; } /// /// Constructor ParseTwo, ParseThree, ParseFour and ParseFive. /// public SyntaxCustomPage(SyntaxBase owner, SyntaxBase syntax) : base(owner, syntax) { Data.PagePath = ((SyntaxCustomPage)syntax).PagePath; Data.PageTitleHtml = ((SyntaxCustomPage)syntax).PageTitleHtml; } /// /// Gets PagePath. Custom parameter of class SyntaxCustomPage. /// public string PagePath => Data.PagePath; /// /// Gets PageTitleHtml. Custom parameter of class SyntaxCustomPage. /// public string PageTitleHtml => Data.PageTitleHtml; protected internal override SyntaxBase Create(SyntaxBase owner, SyntaxBase syntax) { return new SyntaxCustomPage(owner, syntax); } internal override void RegistrySchema(RegistrySchemaResult result) { result.AddOwner(); result.IsBlock = true; } internal override void ParseOne(SyntaxBase owner, MdTokenBase token) { if (UtilParse.ParseOneIsCustom(token, "Page", out var tokenEnd, out var paramList)) { paramList.TryGetValue("Path", out var pagePath); paramList.TryGetValue("Title", out var titleHtml); new SyntaxCustomPage(owner, token, tokenEnd, pagePath?.Text, titleHtml?.Text); } } internal override void ParseTwo(SyntaxBase owner) { var next = Next(); List contentList = new List(); while (true) { if (next is SyntaxCustomPage || next == null) { var pageDest = new SyntaxCustomPage(owner, this); new SyntaxIgnore(pageDest, this); ParseMain(pageDest, contentList); break; } contentList.Add(next); next = next.Next(); } } internal override void ParseThree(SyntaxBase owner) { SyntaxPage ownerLocal; if (owner.Data.ListCount() == 0) { // (Page) is first text. Do not create, split new page but use existing. ownerLocal = (SyntaxPage)owner; } else { // Create, split new page, ownerLocal = new SyntaxPage((SyntaxBase)owner.Owner, this); } ownerLocal.Data.PagePath = PagePath; ownerLocal.Data.PageTitleHtml = PageTitleHtml; ParseMain(ownerLocal, this); } internal override void ParseHtml(HtmlBase owner) { throw new Exception(); // Should never come here! Create, split new page. } } /// /// Base class for html syntax tree. /// internal abstract class HtmlBase : Component { /// /// Constructor for Doc. /// public HtmlBase(Component owner) : base(owner) { } /// /// Constructor parse html. /// public HtmlBase(HtmlBase owner, SyntaxBase syntax) : base(owner) { UtilDoc.Assert(owner.Data.Registry.ParseEnum == ParseEnum.ParseHtml); Data.SyntaxId = Registry.ReferenceSet(syntax); } public SyntaxBase Syntax => Data.Registry.ReferenceGet(Data.SyntaxId); internal string Render() { var result = new StringBuilder(); Render(result); return UtilDoc.StringNull(result.ToString()); } internal void Render(StringBuilder result) { RenderBegin(result); RenderContent(result); RenderEnd(result); } internal virtual void RenderBegin(StringBuilder result) { } internal virtual void RenderContent(StringBuilder result) { foreach (HtmlBase item in List) { item.Render(result); } } internal virtual void RenderEnd(StringBuilder result) { } } /// /// Html tree root. /// internal class HtmlDoc : HtmlBase { public HtmlDoc(Component owner) : base(owner) { } } internal class HtmlPage : HtmlBase { /// /// Constructor parse html. /// public HtmlPage(HtmlBase owner, SyntaxBase syntax) : base(owner, syntax) { } internal override void RenderBegin(StringBuilder result) { if (Data.Registry.IsDebug) { result.Append("
(page)"); } } internal override void RenderEnd(StringBuilder result) { if (Data.Registry.IsDebug) { result.Append("(/page)
"); } } } internal class HtmlComment : HtmlBase { /// /// Constructor parse html. /// public HtmlComment(HtmlBase owner, SyntaxComment syntax) : base(owner, syntax) { } internal override void RenderContent(StringBuilder result) { result.Append(Syntax.Text); } } internal class HtmlTitle : HtmlBase { /// /// Constructor parse html. /// public HtmlTitle(HtmlBase owner, SyntaxTitle syntax) : base(owner, syntax) { } public new SyntaxTitle Syntax => (SyntaxTitle)base.Syntax; internal override void RenderBegin(StringBuilder result) { // For example

result.Append(""); if (Syntax.TitleId != null) { var anchor = string.Format("", Syntax.TitleId); result.Append(anchor); } } internal override void RenderEnd(StringBuilder result) { // For example

result.Append(""); } } internal class HtmlParagraph : HtmlBase { /// /// Constructor parse html. /// public HtmlParagraph(HtmlBase owner, SyntaxParagraph syntax) : base(owner, syntax) { } internal override void RenderBegin(StringBuilder result) { if (!Data.Registry.IsDebug && List.Count == 0) { // Do not render empty paragraph return; } result.Append("

"); if (Data.Registry.IsDebug) { result.Append("(p)"); } } internal override void RenderEnd(StringBuilder result) { if (!Data.Registry.IsDebug && List.Count == 0) { // Do not render empty paragraph return; } if (Data.Registry.IsDebug) { result.Append("(/p)"); } result.Append("

"); } } internal class HtmlBulletList : HtmlBase { /// /// Constructor parse html. /// public HtmlBulletList(HtmlBase owner) : base(owner, null) { } internal override void RenderBegin(StringBuilder result) { result.Append("
    "); } internal override void RenderEnd(StringBuilder result) { result.Append("
"); } } internal class HtmlBulletItem : HtmlBase { /// /// Constructor parse html. /// public HtmlBulletItem(HtmlBulletList owner, SyntaxBullet syntax) : base(owner, syntax) { } internal override void RenderBegin(StringBuilder result) { result.Append("
  • "); } internal override void RenderEnd(StringBuilder result) { result.Append("
  • "); } } internal class HtmlFont : HtmlBase { /// /// Constructor parse html. /// public HtmlFont(HtmlBase owner, SyntaxFont syntax) : base(owner, syntax) { } public new SyntaxFont Syntax => (SyntaxFont)base.Syntax; internal override void RenderBegin(StringBuilder result) { switch (Syntax.FontEnum) { case MdFontEnum.Bold: result.Append(""); break; case MdFontEnum.Italic: result.Append(""); break; default: throw new Exception("Enum unknown!"); } } internal override void RenderEnd(StringBuilder result) { switch (Syntax.FontEnum) { case MdFontEnum.Bold: result.Append(""); break; case MdFontEnum.Italic: result.Append(""); break; default: throw new Exception("Enum unknown!"); } } } internal class HtmlLink : HtmlBase { /// /// Constructor parse html. /// public HtmlLink(HtmlBase owner, SyntaxLink syntax) : base(owner, syntax) { } public new SyntaxLink Syntax => (SyntaxLink)base.Syntax; internal override void RenderContent(StringBuilder result) { result.Append($"{ Syntax.LinkText }"); } } internal class HtmlImage : HtmlBase { /// /// Constructor parse html. /// public HtmlImage(HtmlBase owner, SyntaxImage syntax) : base(owner, syntax) { } public new SyntaxImage Syntax => (SyntaxImage)base.Syntax; internal override void RenderContent(StringBuilder result) { if (Syntax.LinkText == null) { result.Append($"{ Syntax.LinkText }"); } else { var fileNameExtension = UtilDoc.StringNull(Path.GetExtension(Syntax.Link)); // Render html image tag only if src file name has an extension. // For example the image file name "/" would cause the session to navigate. if (fileNameExtension != null) { result.Append($"\"{"); } } } } internal class HtmlCode : HtmlBase { /// /// Constructor parse html. /// public HtmlCode(HtmlBase owner, SyntaxCode syntax) : base(owner, syntax) { } public new SyntaxCode Syntax => (SyntaxCode)base.Syntax; internal override void RenderContent(StringBuilder result) { result.Append(string.Format("
    ", "language-" + Syntax.CodeLanguage));
                var codeText = Syntax.CodeText;
                if (Data.Registry.IsDebug == false)
                {
                    if (codeText.StartsWith("\r"))
                    {
                        codeText = codeText.Substring(1);
                    }
                    if (codeText.StartsWith("\n"))
                    {
                        codeText = codeText.Substring(1);
                    }
                    if (codeText.EndsWith("\n"))
                    {
                        codeText = codeText.Substring(0, codeText.Length - 1);
                    }
                    if (codeText.EndsWith("\r"))
                    {
                        codeText = codeText.Substring(0, codeText.Length - 1);
                    }
                }
                // Escape html special chars.
                codeText = System.Security.SecurityElement.Escape(codeText);
                result.Append(codeText);
                result.Append("
    "); } } internal class HtmlCustomNote : HtmlBase { /// /// Constructor parse html. /// public HtmlCustomNote(HtmlBase owner, SyntaxBase syntax) : base(owner, syntax) { } internal override void RenderBegin(StringBuilder result) { result.Append("
    "); } internal override void RenderEnd(StringBuilder result) { result.Append("
    "); } } internal class HtmlCustomYoutube : HtmlBase { /// /// Constructor parse html. /// public HtmlCustomYoutube(HtmlBase owner, SyntaxBase syntax) : base(owner, syntax) { } internal override void RenderContent(StringBuilder result) { string link = ((SyntaxCustomYoutube)Syntax).Link; string html = $""; result.Append(html); } } internal class HtmlCustomImage : HtmlBase { /// /// Constructor parse html. /// public HtmlCustomImage(HtmlBase owner, SyntaxBase syntax) : base(owner, syntax) { } internal override void RenderContent(StringBuilder result) { var syntax = ((SyntaxCustomImage)Syntax); bool isFirst = true; // First image of a series. bool isLast = true; // Last image of a series. var previous = syntax.Previous(); if (previous is SyntaxParagraph previousParagraph) { if (previousParagraph.List.Count == 1 && previousParagraph.List.First() is SyntaxNewLine) { if (previous.Previous() is SyntaxCustomImage) { isFirst = false; } } } var next = syntax.Next(); if (next is SyntaxParagraph nextParagraph) { if (nextParagraph.List.Count == 1 && nextParagraph.List.First() is SyntaxNewLine) { if (next.Next() is SyntaxCustomImage) { isLast = false; } } } string alt = null; if (syntax.Title != null) { alt = syntax.Title; } else { if (syntax.Description != null) { alt = syntax.Description; } } string html = $"\"{"; if (syntax.Title != null) { html += $"

    { syntax.Title }

    "; } if (syntax.Description != null) { html += $"

    { syntax.Description }

    "; } if (syntax.Href != null) { html = $"{ html }"; } html = $"
    { html }
    "; // TODO new class Render if (isFirst) { html = "
    " + html; } if (isLast) { html = html + "
    "; } result.Append(html); } } internal class HtmlContent : HtmlBase { /// /// Constructor parse html. /// public HtmlContent(HtmlBase owner, SyntaxBase syntax) : base(owner, syntax) { } internal override void RenderContent(StringBuilder result) { result.Append(Syntax.Text); } } internal static class UtilParse { /// /// Returns current text parse index. /// public static int ParseIndex(MdPage owner) { var result = 0; MdTokenBase token = (MdTokenBase)owner.Data.Last(isOrDefault: true)?.Component(); if (token != null) { result = token.IndexEnd + 1; } return result; } /// /// Create syntax component of type createType and add it to owner. /// /// Tree to add new syntax component. /// Syntax component to reference (copy). /// Returns new syntax component. internal static SyntaxBase Create(SyntaxRegistry registry, SyntaxBase owner, SyntaxBase syntax, Type createType) { return registry.TypeList[createType].Create(owner, syntax); } /// /// Create new syntax component of type syntax and add it to owner. /// internal static SyntaxBase Create(SyntaxRegistry registry, SyntaxBase owner, SyntaxBase syntax) { return Create(registry, owner, syntax, syntax.GetType()); } /// /// Returns token of currently parsed syntax. /// internal static MdTokenBase ParseOneToken(SyntaxBase syntax) { var result = syntax.TokenBegin; var last = syntax.Data.Last(isOrDefault: true); if (last != null) { var syntaxLast = (SyntaxBase)syntax.Data.Registry.IdList[last.Id].Component(); result = syntaxLast.TokenEnd; result = result.Next(syntax.TokenEnd); } return result; } /// /// Returns true, if line starts with token T. Allows leading spaces. /// internal static bool ParseOneIsNewLine(MdTokenBase token, out T tokenEnd) where T : MdTokenBase { var result = false; tokenEnd = null; bool isStart; Component component = token; // Leading start or NewLine or Paragraph var previous = token.Previous(); isStart = previous == null || previous is MdNewLine || previous is MdParagraph; // Leading Space if (component is MdSpace) { component = component.Next(null); } // Token if (component is T) { tokenEnd = (T)component; } if (isStart && tokenEnd != null) { result = true; } return result; } /// /// Returns true, if tokenBegin is valid Link. For example https://workplacex.org /// /// Detected Link. internal static bool ParseOneIsLink(MdTokenBase tokenBegin, MdTokenBase tokenEnd, out MdTokenBase linkEnd, out string link) { bool result = false; linkEnd = tokenBegin; MdTokenBase next = tokenBegin; link = null; do { if (next is MdLink) { if (next != tokenBegin) { break; } } if (!(next is MdContent || next is MdLink || next is MdSymbol)) { break; } linkEnd = next; link += next.Text; result = true; } while (Component.Next(ref next, tokenEnd)); return result; } /// /// Returns true, if tokenBegin contains valid LinkText. For example link description. /// /// Detected LinkText. internal static bool ParseOneIsLinkText(MdTokenBase tokenBegin, MdTokenBase tokenEnd, out MdTokenBase linkTextEnd, out string linkText) { var result = false; linkTextEnd = tokenBegin; MdTokenBase next = tokenBegin; linkText = null; do { if (!(next is MdContent || next is MdSpace || next is MdLink)) { break; } linkTextEnd = next; linkText += next.Text; result = true; } while (Component.Next(ref next, tokenEnd)); return result; } public class ParseOneIsCustomItem { public MdTokenBase TokenBegin; public MdTokenBase TokenEnd; public string Text; } private static bool ParseOneIsCustom(MdTokenBase token, out MdTokenBase tokenEnd, out Dictionary paramList) { var result = false; tokenEnd = token; paramList = null; var next = token; while (next is MdContent) { var paramName = next.Text; next = next.Next(); if (next is MdSymbol symbol && symbol.SymbolEnum == MdSymbolEnum.Equal) { next = next.Next(); if (next is MdQuotation quotation && quotation.QuotationEnum == MdQuotationEnum.Double) { next = next.Next(); var paramTokenBegin = next; var paramTokenEnd = next; StringBuilder value = new StringBuilder(); while (next != null && !(next is MdQuotation quotationEnd && quotationEnd.QuotationEnum == MdQuotationEnum.Double)) { value.Append(next.Text); paramTokenEnd = next; next = next.Next(); } if (next is MdQuotation) { if (paramList == null) { paramList = new Dictionary(); } paramList[paramName] = new ParseOneIsCustomItem { Text = UtilDoc.StringNull(value.ToString() ), TokenBegin = paramTokenBegin, TokenEnd = paramTokenEnd }; tokenEnd = next; result = true; } } next = next?.Next(); if (next is MdSpace) { next = next.Next(); } } } return result; } internal static bool ParseOneIsCustom(MdTokenBase token, string commandName, out MdTokenBase tokenEnd, out Dictionary paramList) { var result = false; tokenEnd = null; paramList = null; if (ParseOneIsNewLine(token, out var bracket)) { if (bracket.TextBracket == "(") { if (bracket.Next() is MdContent content) { if (content.Text == commandName) { MdTokenBase next = content.Next(); bool isParamValid = true; if (next is MdSpace) { next = next.Next(); isParamValid = ParseOneIsCustom(next, out var paramEnd, out paramList); next = paramEnd.Next(); } if (isParamValid && next is MdBracket bracketEnd) { if (bracketEnd.TextBracket == ")") { tokenEnd = bracketEnd; if (paramList == null) { paramList = new Dictionary(); } result = true; } } } } } } return result; } } internal static class UtilDoc { public static void Debug() { var textMd = "(Note A=\""; // Doc var appDoc = new AppDoc(); appDoc.Data.Registry.IsDebug = true; new MdPage(appDoc.MdDoc, textMd); string exceptionText = null; try { appDoc.Parse(); } catch (Exception exception) { exceptionText = exception.Message; } // Serialize, deserialize appDoc.Serialize(out string json); Component.Deserialize(json); // Write file Debug.txt TextDebugWriteToFile(appDoc, exceptionText); } private static string TextDebug(string text) { return text?.Replace("\r", "\\r").Replace("\n", "\\n"); } private static void TextDebug(Component component, int level, StringBuilder result) { for (int i = 0; i < level; i++) { result.Append(" "); } string syntaxIdText = null; if (component is SyntaxBase syntaxId && syntaxId.Data.SyntaxId != null) { syntaxIdText = "->" + string.Format("{0:000}", syntaxId.Data.SyntaxId); } result.Append("-(" + component.GetType().Name + " " + string.Format("{0:000}", component.Data.Id) + syntaxIdText + ")"); // Token if (component is MdTokenBase token) { result.Append(" Text=\"" + TextDebug(token.Text) + "\";"); } // Syntax if (component is SyntaxBase syntax) { if (syntax is SyntaxDoc doc) { if (doc.OwnerFind().SyntaxDocOne.Data == doc.Data) { result.Append(" ParseOne (SyntaxToken)"); } if (doc.OwnerFind().SyntaxDocTwo.Data == doc.Data) { result.Append(" ParseTwo (Block)"); } if (doc.OwnerFind().SyntaxDocThree.Data == doc.Data) { result.Append(" ParseThree (Fold)"); } if (doc.OwnerFind().SyntaxDocFour.Data == doc.Data) { result.Append(" ParseFour (Owner Insert)"); } if (doc.OwnerFind().SyntaxDocFive.Data == doc.Data) { result.Append(" ParseFive (Owner Merge)"); } } else { result.Append(" Text=\"" + TextDebug(syntax.Text) + "\";"); if (syntax.IsCreateNew) { result.Append(" IsCreateNew;"); } } } // Html if (component is HtmlBase syntaxHtml) { result.Append(" Text=\"" + TextDebug(syntaxHtml.Syntax?.Text) + "\";"); } result.AppendLine(); foreach (var item in component.List) { TextDebug(item, level + 1, result); } } public static string TextDebug(Component component) { StringBuilder result = new StringBuilder(); TextDebug(component, 0, result); return StringNull(result.ToString()); } public static void TextDebugWriteToFile(AppDoc appDoc, string exceptionText = null) { string textMd = ((MdPage)appDoc.MdDoc.List.First()).Text; string result = TextDebug(appDoc); string textHtml = appDoc.HtmlDoc.Render(); if (exceptionText != null) { result += "\r\n\r\n" + exceptionText; } result += "\r\n\r\n" + "Md:\r\n"; result += textMd; result += "\r\n\r\n" + "Html:\r\n"; result += textHtml; string textMdEscape = textMd.Replace("\r", "\\r").Replace("\n", "\\n"); textMdEscape = textMdEscape.Replace("\"", "\\\""); string textHtmlEscape = textHtml?.Replace("\"", @"\"""); textHtmlEscape = textHtmlEscape?.Replace("\r", "\\r").Replace("\n", "\\n"); string textCSharp = "list.Add(new Item { TextMd = \"" + textMdEscape + "\", TextHtml = \"" + textHtmlEscape + "\" });"; result += "\r\n\r\n" + "CSharp:\r\n"; result += textCSharp; File.WriteAllText(@"C:\Temp\Debug.txt", result); // File.WriteAllText(@"C:\Temp\Debug.html", textHtml); } public static void Assert(bool isAssert, string exceptionText) { if (!isAssert) { throw new Exception(exceptionText); } } public static void Assert(bool isAssert) { Assert(isAssert, "Assert!"); } public static bool IsSubclassOf(Type type, Type typeBase) { return type == typeBase || type.IsSubclassOf(typeBase); } public static string StringNull(string text) { return text == "" ? null : text; } } }