Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
MarkdownDocument.cs
1using SkiaSharp;
2using System;
5using System.IO;
6using System.Reflection;
7using System.Text;
8using System.Text.RegularExpressions;
9using System.Threading.Tasks;
10using System.Xml;
20using Waher.Events;
25using Waher.Script;
30
32{
39 public delegate bool MarkdownElementHandler(MarkdownElement Element, object State);
40
45 public delegate Task AsyncMarkdownProcessing(object State);
46
51 public class MarkdownDocument : IFileNameResource, IEnumerable<MarkdownElement>, IJsonEncodingHint
52 {
56 public const string MarkdownSettingsVariableName = " MarkdownSettings ";
57
58 internal static readonly Regex endOfHeader = new Regex(@"\n\s*\n", RegexOptions.Multiline | RegexOptions.Compiled);
59 internal static readonly Regex scriptHeader = new Regex(@"^(?'Tag'(([Ss][Cc][Rr][Ii][Pp][Tt])|([Ii][Nn][Ii][Tt]))):\s*(?'ScriptFile'[^\r\n]*)", RegexOptions.Multiline | RegexOptions.Compiled);
60
62 private readonly Dictionary<string, Multimedia> references = new Dictionary<string, Multimedia>();
63 private readonly Dictionary<string, KeyValuePair<string, bool>[]> metaData = new Dictionary<string, KeyValuePair<string, bool>[]>();
64 private Dictionary<string, int> footnoteNumberByKey = null;
65 private Dictionary<string, Footnote> footnotes = null;
66 private SortedDictionary<int, char> toInsert = null;
67 private readonly Type[] transparentExceptionTypes;
68 private ChunkedList<string> footnoteOrder = null;
69 private ChunkedList<MarkdownElement> elements;
70 private readonly ChunkedList<Header> headers = new ChunkedList<Header>();
71 private readonly IEmojiSource emojiSource;
72 private string markdownText;
73 private string fileName = string.Empty;
74 private string resourceName = string.Empty;
75 private string url = string.Empty;
76 private MarkdownDocument master = null;
77 private MarkdownDocument detail = null;
78 private readonly MarkdownSettings settings;
79 private int lastFootnote = 0;
80 private bool syntaxHighlighting = false;
81 private bool includesTableOfContents = false;
82 private bool isDynamic = false;
83 private bool? allowScriptTag = null;
84 private object tag = null;
85
92 public static Task<MarkdownDocument> CreateAsync(string MarkdownText, params Type[] TransparentExceptionTypes)
93 {
94 return CreateAsync(MarkdownText, new MarkdownSettings(), string.Empty, string.Empty, string.Empty, TransparentExceptionTypes);
95 }
96
104 public static Task<MarkdownDocument> CreateAsync(string MarkdownText, MarkdownSettings Settings, params Type[] TransparentExceptionTypes)
105 {
106 return CreateAsync(MarkdownText, Settings, string.Empty, string.Empty, string.Empty, TransparentExceptionTypes);
107 }
108
120 public static async Task<MarkdownDocument> CreateAsync(string MarkdownText, MarkdownSettings Settings, string FileName, string ResourceName, string URL,
121 params Type[] TransparentExceptionTypes)
122 {
123 bool IsDynamic = false;
124
125 if (!(Settings.Variables is null))
126 {
127 KeyValuePair<string, bool> P = await Preprocess(MarkdownText, Settings, FileName, TransparentExceptionTypes);
128 MarkdownText = P.Key;
129 IsDynamic = P.Value;
130 }
131
133
134 ICodecProgress Progress = Settings?.Progress;
135 ChunkedList<Block> Blocks = ParseTextToBlocks(Result.markdownText);
137 Block Block;
138 KeyValuePair<string, bool>[] Prev;
139 bool HasProgress = !(Progress is null);
140 string s, s2;
141 string Key = null;
142 int Start = 0;
143 int End = Blocks.Count - 1;
144 int i, j;
145
146 if (Settings.ParseMetaData && Blocks.Count > 0)
147 {
148 Block = Blocks[0];
149 for (i = Block.Start; i <= Block.End; i++)
150 {
151 s = Block.Rows[i];
152
153 j = s.IndexOf(':');
154 if (j < 0)
155 {
156 if (string.IsNullOrEmpty(Key))
157 break;
158
159 Values.Add(new KeyValuePair<string, bool>(s.Trim(), s.EndsWith(" ")));
160 }
161 else
162 {
163 s2 = s.Substring(0, j).TrimEnd().ToUpper();
164
165 if (string.IsNullOrEmpty(Key))
166 {
167 foreach (char ch in s2)
168 {
169 if (!char.IsLetter(ch) && !char.IsWhiteSpace(ch))
170 {
171 s2 = null;
172 break;
173 }
174 }
175
176 if (s2 is null)
177 break;
178 }
179 else
180 {
181 if (HasProgress)
182 await CheckEarlyHints(Result.settings.Progress, Key, Values);
183
184 if (Result.metaData.TryGetValue(Key, out Prev))
185 Values.AddRangeFirst(Prev);
186 else if (Key == "LOGIN")
187 Result.isDynamic = true;
188
189 Result.metaData[Key] = Values.ToArray();
190 }
191
192 Values.Clear();
193 Key = s2;
194 Values.Add(new KeyValuePair<string, bool>(s.Substring(j + 1).Trim(), s.EndsWith(" ")));
195 }
196 }
197
198 if (!string.IsNullOrEmpty(Key))
199 {
200 if (HasProgress)
201 await CheckEarlyHints(Result.settings.Progress, Key, Values);
202
203 if (Result.metaData.TryGetValue(Key, out Prev))
204 Values.AddRangeFirst(Prev);
205 else if (Key == "LOGIN")
206 Result.isDynamic = true;
207
208 Result.metaData[Key] = Values.ToArray();
209 Start++;
210 }
211 }
212
213 if (HasProgress)
214 await Progress.HeaderProcessed();
215
216 Result.elements = await Result.ParseBlocks(Blocks, Start, End);
217
218 if (HasProgress)
219 await Progress.BodyProcessed();
220
221 if (!(Result.toInsert is null))
222 {
223 StringBuilder sb = new StringBuilder();
224 int Last = 0;
225
226 foreach (KeyValuePair<int, char> P in Result.toInsert)
227 {
228 if (P.Key > Last)
229 sb.Append(Result.markdownText.Substring(Last, P.Key - Last));
230
231 sb.Append(P.Value);
232 Last = P.Key;
233 }
234
235 Result.markdownText = sb.ToString();
236 }
237
238 return Result;
239 }
240
241 private MarkdownDocument(string MarkdownText, bool IsDynamic, MarkdownSettings Settings, string FileName, string ResourceName, string URL,
242 params Type[] TransparentExceptionTypes)
243 {
244 this.markdownText = MarkdownText?.Replace("\r\n", "\n").Replace('\r', '\n') ?? string.Empty;
245 this.isDynamic = IsDynamic;
246 this.emojiSource = Settings.EmojiSource;
247 this.settings = Settings;
248 this.fileName = FileName;
249 this.resourceName = ResourceName;
250 this.url = URL;
251 this.transparentExceptionTypes = TransparentExceptionTypes;
252 }
253
254 private static async Task CheckEarlyHints(ICodecProgress Progress, string Key,
255 IEnumerable<KeyValuePair<string, bool>> Values)
256 {
257 switch (Key)
258 {
259 case "JAVASCRIPT":
260 foreach (KeyValuePair<string, bool> P in Values)
261 {
262 await Progress.EarlyHint(P.Key, "preload",
263 new KeyValuePair<string, string>("as", "script"));
264 }
265 break;
266
267 case "CSS":
268 foreach (KeyValuePair<string, bool> P in Values)
269 {
270 await Progress.EarlyHint(P.Key, "preload",
271 new KeyValuePair<string, string>("as", "style"));
272 }
273 break;
274 }
275 }
276
280 [Obsolete("Use GenerateMarkdown() instead.")]
281 public string MarkdownText
282 {
283 get
284 {
285 return this.GenerateMarkdown(false).Result;
286 }
287 }
288
293 public Type[] TransparentExceptionTypes => this.transparentExceptionTypes;
294
300 public static int? HeaderEndPosition(string Markdown)
301 {
302 Match M = endOfHeader.Match(Markdown);
303 if (!M.Success)
304 return null;
305
306 string Header = Markdown.Substring(0, M.Index);
307 string[] Rows = Header.Split(CommonTypes.CRLF, StringSplitOptions.RemoveEmptyEntries);
308 string s;
309
310 foreach (string Row in Rows)
311 {
312 s = Row.Trim();
313 if (string.IsNullOrEmpty(s))
314 continue;
315
316 if (s.IndexOf(':') < 0)
317 return null;
318 }
319
320 return M.Index;
321 }
322
331 public static async Task<string> Preprocess(string Markdown, MarkdownSettings Settings, params Type[] TransparentExceptionTypes)
332 {
333 KeyValuePair<string, bool> P = await Preprocess(Markdown, Settings, string.Empty, TransparentExceptionTypes);
334 return P.Key;
335 }
336
346 public static async Task<KeyValuePair<string, bool>> Preprocess(string Markdown, MarkdownSettings Settings, string FileName, params Type[] TransparentExceptionTypes)
347 {
348 if (Settings.Variables is null)
349 Settings.Variables = new Variables();
350
352 Expression Exp;
353 string Script, s2;
354 int i, j;
355 bool IsDynamic = false;
356
357 if (!string.IsNullOrEmpty(FileName))
358 {
359 Match M = endOfHeader.Match(Markdown);
360 if (M.Success)
361 {
362 s2 = Markdown.Substring(0, M.Index);
363
364 foreach (Match M2 in scriptHeader.Matches(s2))
365 {
366 if (M.Success)
367 {
368 string Tag = M2.Groups["Tag"].Value.ToUpper();
369 string FileName2 = M2.Groups["ScriptFile"].Value;
370
371 FileName2 = Settings.GetFileName(FileName, FileName2);
372
373 if (Tag == "INIT" && !await InitScriptFile.NeedsExecution(FileName2))
374 continue;
375
376 try
377 {
378 Script = await Files.ReadAllTextAsync(FileName2);
379
380 if (!IsDynamic)
381 {
382 IsDynamic = true;
384 }
385
386 Exp = new Expression(Script, FileName2, Settings.ScriptContext);
387
388 if (!(Settings.AuthorizeExpression is null))
389 {
390 ScriptNode Prohibited = await Settings.AuthorizeExpression(Exp);
391 if (!(Prohibited is null))
392 throw new UnauthorizedAccessException("Expression not permitted: " + Prohibited.SubExpression);
393 }
394
395 await Exp.EvaluateAsync(Variables);
396 }
397 catch (Exception ex)
398 {
399 Log.Exception(ex, FileName2);
400 }
401 }
402 }
403 }
404 }
405
406 i = Markdown.IndexOf("{{");
407 if (i < 0)
408 return new KeyValuePair<string, bool>(Markdown, IsDynamic);
409
410 StringBuilder Transformed = new StringBuilder();
411 int From = 0;
412 bool UsesImplicitPrint = false;
413 bool HasImplicitPrint = false;
414 object Result;
415
416 while (i >= 0)
417 {
418 j = Markdown.IndexOf("}}", i + 2);
419 if (j < 0)
420 {
421 if (From == 0)
422 return new KeyValuePair<string, bool>(Markdown, IsDynamic);
423 else
424 break;
425 }
426
427 if (i > From)
428 Transformed.Append(Markdown.Substring(From, i - From));
429
430 From = j + 2;
431 Script = Markdown.Substring(i + 2, j - i - 2);
432
433 try
434 {
436
437 if (!(Settings.AuthorizeExpression is null))
438 {
439 ScriptNode Prohibited = await Settings.AuthorizeExpression(Exp);
440 if (!(Prohibited is null))
441 throw new UnauthorizedAccessException("Expression not permitted: " + Prohibited.SubExpression);
442 }
443
444 if (!IsDynamic)
445 {
446 IsDynamic = true;
448 }
449
450 HasImplicitPrint = Exp.ContainsImplicitPrint;
451 if (!HasImplicitPrint && UsesImplicitPrint && Exp.ReferencesImplicitPrint(Variables))
452 HasImplicitPrint = true;
453
454 if (HasImplicitPrint)
455 {
456 UsesImplicitPrint = true;
457
458 ValuePrinter PrinterBak = Variables.Printer;
459 TextWriter Bak = Variables.ConsoleOut;
460 StringBuilder sb = new StringBuilder();
461
462 Variables.ConsoleOut = new StringWriter(sb);
463 Variables.Printer = PrintMarkdown;
464 try
465 {
466 await Exp.EvaluateAsync(Variables);
467 }
468 finally
469 {
470 Variables.ConsoleOut?.Flush();
471 Variables.ConsoleOut = Bak;
472 Variables.Printer = PrinterBak;
473 }
474
475 Result = sb.ToString();
476 }
477 else
478 Result = await Exp.EvaluateAsync(Variables);
479 }
480 catch (Exception ex)
481 {
482 ex = Log.UnnestException(ex);
483
484 Transformed.AppendLine("<font class=\"error\">");
485
486 if (ex is AggregateException ex2)
487 {
488 foreach (Exception ex3 in ex2.InnerExceptions)
489 {
490 CheckException(ex3, TransparentExceptionTypes);
491
492 Log.Exception(ex3, FileName);
493
494 Transformed.Append("<p>");
495 Transformed.Append(XML.HtmlValueEncode(ex3.Message));
496 Transformed.AppendLine("</p>");
497 }
498 }
499 else
500 {
501 CheckException(ex, TransparentExceptionTypes);
502
504
505 Transformed.AppendLine(XML.HtmlValueEncode(ex.Message));
506 }
507
508 Transformed.AppendLine("</font>");
509
510 Result = null;
511 }
512
513 if (!(Result is null))
514 {
515 if (!(Result is string s3))
516 s3 = await PrintMarkdown(Result, Variables);
517
518 Transformed.Append(s3);
519 }
520
521 i = Markdown.IndexOf("{{", From);
522 }
523
524 if (From < Markdown.Length)
525 Transformed.Append(Markdown.Substring(From));
526
527 return new KeyValuePair<string, bool>(Transformed.ToString(), IsDynamic);
528 }
529
530 private static async Task<string> PrintMarkdown(object Value, Variables Variables)
531 {
532 if (Expression.IsNullOrVoid(Value))
533 return string.Empty;
534
535 if (Value.GetType().IsValueType || Value is string)
536 return Value.ToString();
537
538 if (Value is XmlDocument ||
539 Value is IToMatrix ||
540 Value is Graph ||
541 Value is PixelInformation ||
542 Value is SKImage ||
543 Value is MarkdownDocument ||
544 Value is MarkdownContent ||
545 Value is Exception ||
546 Value is IMatrix ||
547 Value is Array)
548 {
550 {
551 await Renderer.RenderObject(Value, false, Variables);
552 return Renderer.ToString();
553 }
554 }
555 else
556 return Value.ToString();
557 }
558
559 internal void CheckException(Exception ex)
560 {
561 CheckException(ex, this.transparentExceptionTypes);
562 }
563
564 internal static void CheckException(Exception ex, Type[] TransparentExceptionTypes)
565 {
566 TypeInfo ExceptionType = ex.GetType().GetTypeInfo();
567
568 foreach (Type T in TransparentExceptionTypes)
569 {
570 if (T.IsAssignableFrom(ExceptionType))
571 System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(ex).Throw();
572 }
573 }
574
575 private Task<ChunkedList<MarkdownElement>> ParseBlocks(ChunkedList<Block> Blocks)
576 {
577 return this.ParseBlocks(Blocks, 0, Blocks.Count - 1);
578 }
579
580 private async Task<ChunkedList<MarkdownElement>> ParseBlocks(ChunkedList<Block> Blocks, int StartBlock, int EndBlock)
581 {
584 ChunkedList<Block> AlignedBlocks;
585 Block NextBlock;
586 Block Block;
587 string[] Rows;
588 string s, s2;
589 string InitialSectionSeparator = null;
590 int BlockIndex;
591 int i, j, c, d;
592 int Index;
593 int SectionNr = 0;
594 int InitialNrColumns = 1;
595 bool LastHtmlIndent = false;
596 bool HasSections = false;
597
598 for (BlockIndex = StartBlock; BlockIndex <= EndBlock; BlockIndex++)
599 {
600 Block = Blocks[BlockIndex];
601
602 if (Block.Indent > 0)
603 {
604 if (LastHtmlIndent || Block.Rows[Block.Start].StartsWith("<")) // HTML allowed to indent.
605 {
606 LastHtmlIndent = true;
607 Block.Indent = 0;
608 Content = await this.ParseBlock(Block);
609 Elements.AddRange(Content);
610 continue;
611 }
612
613 c = Block.Indent;
614 i = BlockIndex + 1;
615 while (i <= EndBlock && (j = Blocks[i].Indent) > 0)
616 {
617 i++;
618 if (j < c)
619 c = j;
620 }
621
622 if (i == BlockIndex + 1)
623 Elements.Add(new CodeBlock(this, Block.Rows, Block.Start, Block.End, c - 1));
624 else
625 {
627
628 while (BlockIndex < i)
629 {
630 if (CodeBlock.Count > 0)
631 CodeBlock.Add(string.Empty);
632
633 Block = Blocks[BlockIndex++];
634
635 if (Block.Indent == c)
636 {
637 for (j = Block.Start; j <= Block.End; j++)
638 CodeBlock.Add(Block.Rows[j]);
639 }
640 else
641 {
642 s = JSON.IndentString(Block.Indent - c);
643 for (j = Block.Start; j <= Block.End; j++)
644 CodeBlock.Add(s + Block.Rows[j]);
645 }
646 }
647
648 Elements.Add(new CodeBlock(this, CodeBlock.ToArray(), 0, CodeBlock.Count - 1, c - 1));
649 BlockIndex--;
650 }
651 continue;
652 }
653 else
654 {
655 LastHtmlIndent = false;
656
657 if (Block.IsPrefixedBy("```", false))
658 {
659 s = Block.Rows[Block.Start];
660 i = 0;
661 foreach (char ch in s)
662 {
663 if (ch == '`')
664 i++;
665 else
666 break;
667 }
668
669 s = s.Substring(0, i);
670
671 i = BlockIndex;
672 while (i <= EndBlock &&
673 (!(Block = Blocks[i]).Rows[Block.End].StartsWith(s) ||
674 (i == BlockIndex && Block.Start == Block.End)))
675 {
676 i++;
677 }
678
680 bool Complete = true;
681
682 if (i > EndBlock)
683 {
684 i = EndBlock;
685 Complete = false;
686 }
687
688 for (j = BlockIndex; j <= i; j++)
689 {
690 Block = Blocks[j];
691 if (j == BlockIndex)
692 Index = Block.Start + 1;
693 else
694 {
695 Code.Add(string.Empty);
696 Index = Block.Start;
697 }
698
699 if (j == i && Complete)
700 c = Block.End - 1;
701 else
702 c = Block.End;
703
704 while (Index <= c)
705 {
706 Code.Add(Block.Rows[Index]);
707 Index++;
708 }
709 }
710
711 Block = Blocks[BlockIndex];
712 s = Block.Rows[Block.Start].Substring(3).Trim('`', ' ', '\t');
713
715
716 if (s.StartsWith("base64", StringComparison.CurrentCultureIgnoreCase))
717 {
718 try
719 {
720 byte[] Bin = Convert.FromBase64String(Code.Concatenate());
721 s2 = Encoding.UTF8.GetString(Bin);
722
723 Rows = s2.Replace("\r\n", "\n").Replace("\r", "\n").Split('\n');
724
725 CodeBlock = new CodeBlock(this, Rows, 0, Rows.Length - 1, 0, s.Substring(6));
726 }
727 catch (Exception)
728 {
729 CodeBlock = new CodeBlock(this, Code.ToArray(), 0, Code.Count - 1, 0, s);
730 }
731 }
732 else
733 CodeBlock = new CodeBlock(this, Code.ToArray(), 0, Code.Count - 1, 0, s);
734
736
737 if (!this.syntaxHighlighting && !string.IsNullOrEmpty(CodeBlock.Language))
738 {
740 if (HtmlRenderer is null)
741 this.syntaxHighlighting = true;
742 }
743
744 BlockIndex = i;
745 continue;
746 }
747 }
748
749 if (Block.IsPrefixedBy(">", false))
750 {
751 if (Block.IsSuffixedBy("<<") && Block.IsPrefixedBy(">>", false))
752 {
753 AlignedBlocks = Block.RemovePrefixAndSuffix(">>", 2, "<<");
754
755 while (BlockIndex < EndBlock &&
756 (NextBlock = Blocks[BlockIndex + 1]).IsPrefixedBy(">>", false) &&
757 NextBlock.IsSuffixedBy("<<"))
758 {
759 BlockIndex++;
760 AlignedBlocks.AddRange(NextBlock.RemovePrefixAndSuffix(">>", 2, "<<"));
761 }
762
763 Content = await this.ParseBlocks(AlignedBlocks);
764
766 CenterAligned.AddChildren(Content);
767 else
768 Elements.Add(new CenterAligned(this, Content));
769 }
770 else if (Block.IsSuffixedBy(">>"))
771 {
772 AlignedBlocks = Block.RemoveSuffix(">>");
773
774 while (BlockIndex < EndBlock &&
775 (NextBlock = Blocks[BlockIndex + 1]).IsSuffixedBy(">>"))
776 {
777 BlockIndex++;
778 AlignedBlocks.AddRange(NextBlock.RemoveSuffix(">>"));
779 }
780
781 Content = await this.ParseBlocks(AlignedBlocks);
782
784 RightAligned.AddChildren(Content);
785 else
786 Elements.Add(new RightAligned(this, Content));
787 }
788 else
789 {
790 Content = await this.ParseBlocks(Block.RemovePrefix(">", 2));
791
793 BlockQuote.AddChildren(Content);
794 else
795 Elements.Add(new BlockQuote(this, Content));
796 }
797
798 continue;
799 }
800 else if (Block.IsPrefixedBy("<<", false))
801 {
802 if (Block.IsSuffixedBy(">>"))
803 {
804 AlignedBlocks = Block.RemovePrefixAndSuffix("<<", 2, ">>");
805
806 while (BlockIndex < EndBlock &&
807 (NextBlock = Blocks[BlockIndex + 1]).IsPrefixedBy("<<", false) &&
808 NextBlock.IsSuffixedBy(">>"))
809 {
810 BlockIndex++;
811 AlignedBlocks.AddRange(NextBlock.RemovePrefixAndSuffix("<<", 2, ">>"));
812 }
813
814 Content = await this.ParseBlocks(AlignedBlocks);
815
817 MarginAligned.AddChildren(Content);
818 else
819 Elements.Add(new MarginAligned(this, Content));
820 }
821 else
822 {
823 AlignedBlocks = Block.RemovePrefix("<<", 2);
824
825 while (BlockIndex < EndBlock &&
826 (NextBlock = Blocks[BlockIndex + 1]).IsPrefixedBy("<<", false))
827 {
828 BlockIndex++;
829 AlignedBlocks.AddRange(NextBlock.RemovePrefix("<<", 2));
830 }
831
832 Content = await this.ParseBlocks(AlignedBlocks);
833
835 LeftAligned.AddChildren(Content);
836 else
837 Elements.Add(new LeftAligned(this, Content));
838 }
839
840 continue;
841 }
842 else if (Block.IsSuffixedBy(">>"))
843 {
844 Content = await this.ParseBlocks(Block.RemoveSuffix(">>"));
845
847 RightAligned.AddChildren(Content);
848 else
849 Elements.Add(new RightAligned(this, Content));
850
851 continue;
852 }
853 else if (Block.IsPrefixedBy("+>", false))
854 {
855 Content = await this.ParseBlocks(Block.RemovePrefix("+>", 3));
856
858 InsertBlocks.AddChildren(Content);
859 else
860 Elements.Add(new InsertBlocks(this, Content));
861
862 continue;
863 }
864 else if (Block.IsPrefixedBy("->", false))
865 {
866 Content = await this.ParseBlocks(Block.RemovePrefix("->", 3));
867
869 DeleteBlocks.AddChildren(Content);
870 else
871 Elements.Add(new DeleteBlocks(this, Content));
872
873 continue;
874 }
875 else if (Block.IsPrefixedBy("//", false))
876 {
877 string[] Comment = new string[Block.End - Block.Start + 1];
878
879 for (i = Block.Start; i <= Block.End; i++)
880 Comment[i] = Block.Rows[i].Substring(2);
881
882 Elements.Add(new CommentBlock(this, Comment));
883 continue;
884 }
885 else if (Block.End == Block.Start && (IsUnderline(Block.Rows[0], '-', true, true) || IsUnderline(Block.Rows[0], '*', true, true)))
886 {
887 Elements.Add(new HorizontalRule(this, Block.Rows[0]));
888 continue;
889 }
890 else if (Block.End == Block.Start && IsUnderline(Block.Rows[0], '=', true, false))
891 {
892 int NrColumns = Block.Rows[0].Split(whiteSpace, StringSplitOptions.RemoveEmptyEntries).Length;
893 HasSections = true;
894
896 {
897 InitialNrColumns = NrColumns;
898 InitialSectionSeparator = Block.Rows[0];
899 }
900 else
901 Elements.Add(new SectionSeparator(this, ++SectionNr, NrColumns, Block.Rows[0]));
902
903 continue;
904 }
905 else if (Block.End == Block.Start && IsUnderline(Block.Rows[0], '~', false, false))
906 {
907 Elements.Add(new InvisibleBreak(this, Block.Rows[0]));
908 continue;
909 }
910 else if (Block.IsPrefixedBy(s2 = "*", true) ||
911 Block.IsPrefixedBy(s2 = "+", true) ||
912 Block.IsPrefixedBy(s2 = "-", true))
913 {
914 ChunkedList<Block> Segments = null;
915 i = 0;
916 c = Block.End;
917
918 for (d = Block.Start + 1; d <= c; d++)
919 {
920 s = Block.Rows[d];
921 if (IsPrefixedBy(s, s2, true))
922 {
923 if (Segments is null)
924 Segments = new ChunkedList<Block>();
925
926 Segments.Add(new Block(Block.Rows, Block.Positions, 0, i, d - 1));
927 i = d;
928 }
929 }
930
931 Segments?.Add(new Block(Block.Rows, Block.Positions, 0, i, c));
932
934 UnnumberedItem LastItem;
935
936 if (Segments is null)
937 {
938 ChunkedList<Block> SubBlocks = Block.RemovePrefix(s2, 4);
939
940 while (BlockIndex < EndBlock && (Block = Blocks[BlockIndex + 1]).Indent > 1)
941 {
942 BlockIndex++;
943 Block.Indent--;
944 SubBlocks.Add(Block);
945 }
946
947 Items = await this.ParseBlocks(SubBlocks);
948 LastItem = new UnnumberedItem(this, s2, new NestedBlock(this, Items));
949
951 BulletList.AddChild(LastItem);
952 else
953 Elements.Add(new BulletList(this, LastItem));
954 }
955 else
956 {
957 Items = await this.ParseUnnumberedItems(Segments, s2);
958
960 BulletList.AddChildren(Items);
961 else
962 Elements.Add(new BulletList(this, Items));
963
964 if (Items.HasLastItem)
965 LastItem = Items.LastItem as UnnumberedItem;
966 else
967 LastItem = null;
968 }
969
970 if (!(LastItem is null))
971 {
972 i = BlockIndex;
973 while (BlockIndex < EndBlock && (Block = Blocks[BlockIndex + 1]).Indent > 0)
974 {
975 BlockIndex++;
976 Block.Indent--;
977 }
978
979 if (BlockIndex > i)
980 {
981 Items = await this.ParseBlocks(Blocks, i + 1, BlockIndex);
982
983 if (LastItem.Child is NestedBlock LastItemChildren)
984 {
985 if (LastItemChildren.IsBlockElement)
986 LastItemChildren.AddChildren(Items);
987 else
988 {
989 Items.AddFirstItem(new Paragraph(this, LastItemChildren.Children, true));
990 LastItem.Child = new NestedBlock(this, Items);
991 }
992 }
993 else
994 {
995 if (LastItem.Child.IsBlockElement)
996 Items.AddFirstItem(LastItem.Child);
997 else
998 Items.AddFirstItem(new Paragraph(this, new ChunkedList<MarkdownElement>(LastItem.Child), true));
999
1000 LastItem.Child = new NestedBlock(this, Items);
1001 }
1002 }
1003 }
1004
1005 continue;
1006 }
1007 else if (Block.IsPrefixedBy("#.", true))
1008 {
1009 ChunkedList<Tuple<int, bool, Block>> Segments = null;
1010 i = 0;
1011 c = Block.End;
1012 int Index2 = 1;
1013 bool Explicit = false;
1014
1015 for (d = Block.Start + 1; d <= c; d++)
1016 {
1017 s = Block.Rows[d];
1018 if (IsPrefixedByNumber(s, out j))
1019 {
1020 if (Segments is null)
1021 Segments = new ChunkedList<Tuple<int, bool, Block>>();
1022
1023 Segments.Add(new Tuple<int, bool, Block>(Index2, Explicit, new Block(Block.Rows, Block.Positions, 0, i, d - 1)));
1024 i = d;
1025 Index2 = j;
1026 Explicit = true;
1027 }
1028 else if (IsPrefixedBy(s, "#.", true))
1029 {
1030 if (Segments is null)
1031 Segments = new ChunkedList<Tuple<int, bool, Block>>();
1032
1033 Segments.Add(new Tuple<int, bool, Block>(Index2, Explicit, new Block(Block.Rows, Block.Positions, 0, i, d - 1)));
1034 i = d;
1035 Index2++;
1036 Explicit = false;
1037 }
1038 }
1039
1040 Segments?.Add(new Tuple<int, bool, Block>(Index2, Explicit, new Block(Block.Rows, Block.Positions, 0, i, c)));
1041
1043 NumberedItem LastItem;
1044
1045 if (Segments is null)
1046 {
1047 ChunkedList<Block> SubBlocks = Block.RemovePrefix("#.", 4);
1048
1049 while (BlockIndex < EndBlock && (Block = Blocks[BlockIndex + 1]).Indent > 1)
1050 {
1051 BlockIndex++;
1052 Block.Indent--;
1053 SubBlocks.Add(Block);
1054 }
1055
1056 Items = await this.ParseBlocks(SubBlocks);
1057 LastItem = new NumberedItem(this, Index2, Explicit, new NestedBlock(this, Items));
1058
1060 NumberedList.AddChild(LastItem);
1061 else
1062 Elements.Add(new NumberedList(this, LastItem));
1063 }
1064 else
1065 {
1066 Items = await this.ParseNumberedItems(Segments);
1067
1070 else
1071 Elements.Add(new NumberedList(this, Items));
1072
1073 if (Items.HasLastItem)
1074 LastItem = Items.LastItem as NumberedItem;
1075 else
1076 LastItem = null;
1077 }
1078
1079 if (!(LastItem is null))
1080 {
1081 i = BlockIndex;
1082 while (BlockIndex < EndBlock && (Block = Blocks[BlockIndex + 1]).Indent > 0)
1083 {
1084 BlockIndex++;
1085 Block.Indent--;
1086 }
1087
1088 if (BlockIndex > i)
1089 {
1090 Items = await this.ParseBlocks(Blocks, i + 1, BlockIndex);
1091
1092 if (LastItem.Child is NestedBlock LastItemChildren)
1093 {
1094 if (LastItemChildren.IsBlockElement)
1095 LastItemChildren.AddChildren(Items);
1096 else
1097 {
1098 Items.AddFirstItem(new Paragraph(this, LastItemChildren.Children, true));
1099 LastItem.Child = new NestedBlock(this, Items);
1100 }
1101 }
1102 else
1103 {
1104 if (LastItem.Child.IsBlockElement)
1105 Items.AddFirstItem(LastItem.Child);
1106 else
1107 Items.AddFirstItem(new Paragraph(this, new ChunkedList<MarkdownElement>(LastItem.Child), true));
1108
1109 LastItem.Child = new NestedBlock(this, Items);
1110 }
1111 }
1112 }
1113
1114 continue;
1115 }
1116 else if (Block.IsPrefixedBy(s2 = "[ ]", true) ||
1117 Block.IsPrefixedBy(s2 = "[x]", true) ||
1118 Block.IsPrefixedBy(s2 = "[X]", true))
1119 {
1121 int CheckPosition = Block.Positions[0] + 1;
1122 string s3;
1123 i = 0;
1124 c = Block.End;
1125
1126 for (d = Block.Start + 1; d <= c; d++)
1127 {
1128 s = Block.Rows[d];
1129 if (IsPrefixedBy(s, s3 = "[ ]", true) ||
1130 IsPrefixedBy(s, s3 = "[x]", true) ||
1131 IsPrefixedBy(s, s3 = "[X]", true))
1132 {
1133 if (Segments is null)
1134 Segments = new ChunkedList<Tuple<Block, string, int>>();
1135
1136 Segments.Add(new Tuple<Block, string, int>(new Block(Block.Rows, Block.Positions, 0, i, d - 1), s2, CheckPosition));
1137 s2 = s3;
1138 i = d;
1139 CheckPosition = Block.Positions[d] + 1;
1140 }
1141 }
1142
1143 Segments?.Add(new Tuple<Block, string, int>(new Block(Block.Rows, Block.Positions, 0, i, c), s2, CheckPosition));
1144
1146 TaskItem LastItem;
1147
1148 if (Segments is null)
1149 {
1150 ChunkedList<Block> SubBlocks = Block.RemovePrefix(s2, 4);
1151
1152 while (BlockIndex < EndBlock && (Block = Blocks[BlockIndex + 1]).Indent > 1)
1153 {
1154 BlockIndex++;
1155 Block.Indent--;
1156 SubBlocks.Add(Block);
1157 }
1158
1159 Items = await this.ParseBlocks(SubBlocks);
1160 LastItem = new TaskItem(this, s2 != "[ ]", CheckPosition, new NestedBlock(this, Items));
1161
1163 TaskList.AddChild(LastItem);
1164 else
1165 Elements.Add(new TaskList(this, LastItem));
1166 }
1167 else
1168 {
1169 Items = await this.ParseTaskItems(Segments);
1170
1172 TaskList.AddChildren(Items);
1173 else
1174 Elements.Add(new TaskList(this, Items));
1175
1176 if (Items.HasLastItem)
1177 LastItem = Items.LastItem as TaskItem;
1178 else
1179 LastItem = null;
1180 }
1181
1182 if (!(LastItem is null))
1183 {
1184 i = BlockIndex;
1185 while (BlockIndex < EndBlock && (Block = Blocks[BlockIndex + 1]).Indent > 0)
1186 {
1187 BlockIndex++;
1188 Block.Indent--;
1189 }
1190
1191 if (BlockIndex > i)
1192 {
1193 Items = await this.ParseBlocks(Blocks, i + 1, BlockIndex);
1194
1195 if (LastItem.Child is NestedBlock LastItemChildren)
1196 {
1197 if (LastItemChildren.IsBlockElement)
1198 LastItemChildren.AddChildren(Items);
1199 else
1200 {
1201 Items.AddFirstItem(new Paragraph(this, LastItemChildren.Children, true));
1202 LastItem.Child = new NestedBlock(this, Items);
1203 }
1204 }
1205 else
1206 {
1207 if (LastItem.Child.IsBlockElement)
1208 Items.AddFirstItem(LastItem.Child);
1209 else
1210 Items.AddFirstItem(new Paragraph(this, new ChunkedList<MarkdownElement>(LastItem.Child), true));
1211
1212 LastItem.Child = new NestedBlock(this, Items);
1213 }
1214 }
1215 }
1216
1217 continue;
1218 }
1219 else if (Block.IsPrefixedByNumber(out Index))
1220 {
1221 ChunkedList<Tuple<int, bool, Block>> Segments = null;
1222 i = 0;
1223 c = Block.End;
1224 bool Explicit = true;
1225
1226 for (d = Block.Start + 1; d <= c; d++)
1227 {
1228 s = Block.Rows[d];
1229 if (IsPrefixedByNumber(s, out j))
1230 {
1231 if (Segments is null)
1232 Segments = new ChunkedList<Tuple<int, bool, Block>>();
1233
1234 Segments.Add(new Tuple<int, bool, Block>(Index, Explicit, new Block(Block.Rows, Block.Positions, 0, i, d - 1)));
1235 i = d;
1236 Index = j;
1237 Explicit = true;
1238 }
1239 else if (IsPrefixedBy(s, "#.", true))
1240 {
1241 if (Segments is null)
1242 Segments = new ChunkedList<Tuple<int, bool, Block>>();
1243
1244 Segments.Add(new Tuple<int, bool, Block>(Index, Explicit, new Block(Block.Rows, Block.Positions, 0, i, d - 1)));
1245 i = d;
1246 Index++;
1247 Explicit = false;
1248 }
1249 }
1250
1251 Segments?.Add(new Tuple<int, bool, Block>(Index, Explicit, new Block(Block.Rows, Block.Positions, 0, i, c)));
1252
1254 NumberedItem LastItem;
1255
1256 if (Segments is null)
1257 {
1258 s = Index.ToString();
1259 ChunkedList<Block> SubBlocks = Block.RemovePrefix(s + ".", Math.Max(4, s.Length + 2));
1260
1261 while (BlockIndex < EndBlock && (Block = Blocks[BlockIndex + 1]).Indent > 1)
1262 {
1263 BlockIndex++;
1264 Block.Indent--;
1265 SubBlocks.Add(Block);
1266 }
1267
1268 Items = await this.ParseBlocks(SubBlocks);
1269 LastItem = new NumberedItem(this, Index, Explicit, new NestedBlock(this, Items));
1270
1272 NumberedList.AddChild(LastItem);
1273 else
1274 Elements.Add(new NumberedList(this, LastItem));
1275 }
1276 else
1277 {
1278 Items = await this.ParseNumberedItems(Segments);
1279
1282 else
1283 Elements.Add(new NumberedList(this, Items));
1284
1285 if (Items.HasLastItem)
1286 LastItem = Items.LastItem as NumberedItem;
1287 else
1288 LastItem = null;
1289 }
1290
1291 if (!(LastItem is null))
1292 {
1293 i = BlockIndex;
1294 while (BlockIndex < EndBlock && (Block = Blocks[BlockIndex + 1]).Indent > 0)
1295 {
1296 BlockIndex++;
1297 Block.Indent--;
1298 }
1299
1300 if (BlockIndex > i)
1301 {
1302 Items = await this.ParseBlocks(Blocks, i + 1, BlockIndex);
1303
1304 if (LastItem.Child is NestedBlock LastItemChildren)
1305 {
1306 if (LastItemChildren.IsBlockElement)
1307 LastItemChildren.AddChildren(Items);
1308 else
1309 {
1310 Items.AddFirstItem(new Paragraph(this, LastItemChildren.Children, true));
1311 LastItem.Child = new NestedBlock(this, Items);
1312 }
1313 }
1314 else
1315 {
1316 if (LastItem.Child.IsBlockElement)
1317 Items.AddFirstItem(LastItem.Child);
1318 else
1319 Items.AddFirstItem(new Paragraph(this, new ChunkedList<MarkdownElement>(LastItem.Child), true));
1320
1321 LastItem.Child = new NestedBlock(this, Items);
1322 }
1323 }
1324 }
1325
1326 continue;
1327 }
1328 else if (Block.IsTable(out TableInformation TableInformation))
1329 {
1330 MarkdownElement[][] Headers = new MarkdownElement[TableInformation.NrHeaderRows][];
1331 MarkdownElement[][] DataRows = new MarkdownElement[TableInformation.NrDataRows][];
1332 TextAlignment?[][] HeaderCellAlignments = new TextAlignment?[TableInformation.NrHeaderRows][];
1333 TextAlignment?[][] DataCellAlignments = new TextAlignment?[TableInformation.NrDataRows][];
1334 ChunkedList<MarkdownElement> CellElements;
1335 string[] Row;
1336 int[] Positions;
1337
1338 c = TableInformation.Columns;
1339
1340 for (j = 0; j < TableInformation.NrHeaderRows; j++)
1341 {
1342 Row = TableInformation.Headers[j];
1343 Positions = TableInformation.HeaderPositions[j];
1344
1345 Headers[j] = new MarkdownElement[c];
1346 HeaderCellAlignments[j] = new TextAlignment?[c];
1347
1348 for (i = 0; i < c; i++)
1349 {
1350 s = Row[i];
1351 if (s is null)
1352 {
1353 Headers[j][i] = null;
1354 HeaderCellAlignments[j][i] = null;
1355 }
1356 else
1357 {
1358 CellElements = await this.ParseCell(Row[i], Positions[i], out HeaderCellAlignments[j][i]);
1359
1360 if (CellElements.Count == 1)
1361 {
1362 if (CellElements.FirstItem is FootnoteReference FRef)
1363 {
1364 FRef.AutoExpand = true;
1365
1366 if (this.footnotes.TryGetValue(FRef.Key, out Footnote Note))
1367 Note.TableCellContents = true;
1368
1369 if (this.footnoteNumberByKey.TryGetValue(FRef.Key, out int Nr) &&
1370 Nr == this.lastFootnote)
1371 {
1372 this.footnoteNumberByKey.Remove(FRef.Key);
1373 this.lastFootnote--;
1374 }
1375 }
1376
1377 Headers[j][i] = CellElements.FirstItem;
1378 }
1379 else
1380 Headers[j][i] = new NestedBlock(this, CellElements);
1381 }
1382 }
1383 }
1384
1385 for (j = 0; j < TableInformation.NrDataRows; j++)
1386 {
1387 Row = TableInformation.Rows[j];
1388 Positions = TableInformation.RowPositions[j];
1389
1390 DataRows[j] = new MarkdownElement[c];
1391 DataCellAlignments[j] = new TextAlignment?[c];
1392
1393 for (i = 0; i < c; i++)
1394 {
1395 s = Row[i];
1396 if (s is null)
1397 {
1398 DataRows[j][i] = null;
1399 DataCellAlignments[j][i] = null;
1400 }
1401 else
1402 {
1403 CellElements = await this.ParseCell(Row[i], Positions[i], out DataCellAlignments[j][i]);
1404
1405 if (CellElements.Count == 1)
1406 {
1407 if (CellElements.FirstItem is FootnoteReference FRef)
1408 {
1409 FRef.AutoExpand = true;
1410
1411 if (this.footnotes.TryGetValue(FRef.Key, out Footnote Note))
1412 Note.TableCellContents = true;
1413
1414 if (this.footnoteNumberByKey.TryGetValue(FRef.Key, out int Nr) &&
1415 Nr == this.lastFootnote)
1416 {
1417 this.footnoteNumberByKey.Remove(FRef.Key);
1418 this.lastFootnote--;
1419 }
1420 }
1421
1422 DataRows[j][i] = CellElements.FirstItem;
1423 }
1424 else
1425 DataRows[j][i] = new NestedBlock(this, CellElements);
1426 }
1427 }
1428 }
1429
1430 Elements.Add(new Table(this, c, Headers, DataRows, TableInformation.Alignments, TableInformation.AlignmentDefinitions,
1431 HeaderCellAlignments, DataCellAlignments, TableInformation.Caption, TableInformation.Id));
1432
1433 continue;
1434 }
1435 else if (Block.IsPrefixedBy(":", true) && Elements.HasLastItem)
1436 {
1437 ChunkedList<MarkdownElement> Description = await this.ParseBlocks(Block.RemovePrefix(":", 4));
1439
1440 i = BlockIndex;
1441 while (BlockIndex < EndBlock && (Block = Blocks[BlockIndex + 1]).Indent > 0)
1442 {
1443 BlockIndex++;
1444 Block.Indent--;
1445 }
1446
1447 if (BlockIndex > i)
1448 Description.AddRange(await this.ParseBlocks(Blocks, i + 1, BlockIndex));
1449
1450 if (!Description.HasFirstItem)
1451 continue;
1452
1453 if (Description.Count == 1)
1455 else
1457
1458 if (Elements.HasLastItem && Elements.LastItem is DefinitionDescriptions DefinitionDescriptions2)
1459 DefinitionDescriptions2.AddChildren(DefinitionDescriptions.Children);
1461 Elements.LastItem = new DefinitionList(this, DefinitionTerms, DefinitionDescriptions);
1464 else
1466
1467 continue;
1468 }
1469 else if (BlockIndex < EndBlock && Blocks[BlockIndex + 1].IsPrefixedBy(":", true))
1470 {
1473
1474 Rows = Block.Rows;
1475 c = Block.End;
1476 for (i = Block.Start; i <= c; i++)
1477 {
1478 Term = await this.ParseBlock(Rows, Block.Positions, i, i);
1479 if (!Term.HasFirstItem)
1480 continue;
1481
1482 if (Term.Count == 1)
1483 Terms.Add(Term.FirstItem);
1484 else
1485 Terms.Add(new NestedBlock(this, Term));
1486 }
1487
1489 DefinitionList.AddChild(new DefinitionTerms(this, Terms));
1490 else
1491 Elements.Add(new DefinitionTerms(this, Terms));
1492
1493 continue;
1494 }
1495 else if (Block.IsFootnote(out s, out int WhiteSparePrefix))
1496 {
1497 Footnote Footnote = new Footnote(this, s,
1498 await this.ParseBlocks(Block.RemovePrefix(string.Empty, WhiteSparePrefix)));
1499
1500 i = BlockIndex;
1501 while (BlockIndex < EndBlock && (Block = Blocks[BlockIndex + 1]).Indent > 0)
1502 {
1503 BlockIndex++;
1504 Block.Indent--;
1505 }
1506
1507 if (BlockIndex > i)
1508 Footnote.AddChildren(await this.ParseBlocks(Blocks, i + 1, BlockIndex));
1509
1510 if (this.footnoteNumberByKey is null)
1511 {
1512 this.footnoteNumberByKey = new Dictionary<string, int>();
1513 this.footnoteOrder = new ChunkedList<string>();
1514 this.footnotes = new Dictionary<string, Footnote>();
1515 }
1516
1517 this.footnotes[Footnote.Key] = Footnote;
1518
1519 continue;
1520 }
1521
1522 Rows = Block.Rows;
1523 c = Block.End;
1524
1525 if (c >= 1)
1526 {
1527 s = Rows[c];
1528
1529 if (IsUnderline(s, '=', false, false))
1530 {
1531 Header Header = new Header(this, 1, false, s, this.PrepareHeader(await this.ParseBlock(Rows, Block.Positions, 0, c - 1)));
1533 this.headers.Add(Header);
1534 continue;
1535 }
1536 else if (IsUnderline(s, '-', false, false))
1537 {
1538 Header Header = new Header(this, 2, false, s, this.PrepareHeader(await this.ParseBlock(Rows, Block.Positions, 0, c - 1)));
1540 this.headers.Add(Header);
1541 continue;
1542 }
1543 }
1544
1545 s = Rows[Block.Start];
1546 if (IsPrefixedBy(s, '#', out d, true) && d < s.Length)
1547 {
1548 string Prefix = s.Substring(0, d);
1549 Rows[Block.Start] = s.Substring(d).Trim();
1550
1551 s = Rows[c];
1552 i = s.Length - 1;
1553 while (i >= 0 && s[i] == '#')
1554 i--;
1555
1556 if (++i < s.Length)
1557 Rows[c] = s.Substring(0, i).TrimEnd();
1558
1559 Header Header = new Header(this, d, true, Prefix, this.PrepareHeader(await this.ParseBlock(Rows, Block.Positions, Block.Start, c)));
1561 this.headers.Add(Header);
1562 continue;
1563 }
1564
1565 KeyValuePair<ChunkedList<MarkdownElement>, int> P = await this.ParseBlock(Block, Blocks, BlockIndex, EndBlock);
1566 Content = P.Key;
1567 BlockIndex = P.Value;
1568
1569 if (Content.HasFirstItem)
1570 {
1571 if (Content.HasFirstItem &&
1572 Content.HasLastItem &&
1573 Content.FirstItem is InlineHTML &&
1574 Content.LastItem is InlineHTML &&
1575 this.settings.AllowHtml)
1576 {
1577 Elements.Add(new HtmlBlock(this, Content));
1578 }
1579 else if (Content.Count == 1 && Content.FirstItem.OutsideParagraph)
1580 {
1581 if (Content.HasFirstItem &&
1582 Content.HasLastItem &&
1584 MarkdownElementChildren.JoinOverParagraphs &&
1585 Elements.LastItem is MarkdownElementChildren MarkdownElementChildrenLast)
1586 {
1587 MarkdownElementChildrenLast.AddChildren(MarkdownElementChildren.Children);
1588 }
1589 else
1590 Elements.Add(Content.FirstItem);
1591 }
1592 else
1593 Elements.Add(new Paragraph(this, Content));
1594 }
1595 }
1596
1597 if (HasSections)
1598 {
1600 Sections.Add(new Sections(this, InitialNrColumns, InitialSectionSeparator, Elements));
1601 return Sections;
1602 }
1603 else
1604 return Elements;
1605 }
1606
1607 private async Task<ChunkedList<MarkdownElement>> ParseUnnumberedItems(
1608 ChunkedList<Block> Segments, string Prefix)
1609 {
1611 ChunkNode<Block> Loop = Segments.FirstChunk;
1612 ChunkNode<Block> Loop2;
1613
1614 while (!(Loop is null))
1615 {
1616 for (int i = Loop.Start, c = Loop.Pos; i < c; i++)
1617 {
1618 Loop2 = Loop[i].RemovePrefix(Prefix, 4).FirstChunk;
1619
1620 while (!(Loop2 is null))
1621 {
1622 for (int j = Loop2.Start, d = Loop2.Pos; j < d; j++)
1623 {
1624 Items.Add(new UnnumberedItem(this, Prefix, new NestedBlock(this,
1625 await this.ParseBlock(Loop2[j]))));
1626 }
1627
1628 Loop2 = Loop2.Next;
1629 }
1630 }
1631
1632 Loop = Loop.Next;
1633 }
1634
1635 return Items;
1636 }
1637
1638 private async Task<ChunkedList<MarkdownElement>> ParseNumberedItems(
1639 ChunkedList<Tuple<int, bool, Block>> Segments)
1640 {
1643 ChunkNode<Block> Loop2;
1644 string s;
1645
1646 while (!(Loop is null))
1647 {
1648 for (int i = Loop.Start, c = Loop.Pos; i < c; i++)
1649 {
1650 Tuple<int, bool, Block> Segment = Loop[i];
1651
1652 s = Segment.Item2 ? Segment.Item1.ToString() + "." : "#.";
1653 Loop2 = Segment.Item3.RemovePrefix(s, Math.Max(4, s.Length + 2)).FirstChunk;
1654
1655 while (!(Loop2 is null))
1656 {
1657 for (int j = Loop2.Start, d = Loop2.Pos; j < d; j++)
1658 {
1659 Items.Add(new NumberedItem(this, Segment.Item1, Segment.Item2,
1660 new NestedBlock(this, await this.ParseBlock(Loop2[j]))));
1661 }
1662
1663 Loop2 = Loop2.Next;
1664 }
1665 }
1666
1667 Loop = Loop.Next;
1668 }
1669
1670 return Items;
1671 }
1672
1673 private async Task<ChunkedList<MarkdownElement>> ParseTaskItems(ChunkedList<Tuple<Block, string, int>> Segments)
1674 {
1677 ChunkNode<Block> Loop2;
1678
1679 while (!(Loop is null))
1680 {
1681 for (int i = Loop.Start, c = Loop.Pos; i < c; i++)
1682 {
1683 Tuple<Block, string, int> Segment = Loop[i];
1684
1685 Loop2 = Segment.Item1.RemovePrefix(Segment.Item2, 4).FirstChunk;
1686
1687 while (!(Loop2 is null))
1688 {
1689 for (int j = Loop2.Start, d = Loop2.Pos; j < d; j++)
1690 {
1691 Items.Add(new TaskItem(this, Segment.Item2 != "[ ]", Segment.Item3,
1692 new NestedBlock(this, await this.ParseBlock(Loop2[j]))));
1693 }
1694
1695 Loop2 = Loop2.Next;
1696 }
1697 }
1698
1699 Loop = Loop.Next;
1700 }
1701
1702 return Items;
1703 }
1704
1705 private ChunkedList<MarkdownElement> PrepareHeader(ChunkedList<MarkdownElement> Content)
1706 {
1707 if (Content?.FirstItem is NumberedList NumberedList &&
1708 Content.Count == 1 &&
1711 Item.NumberExplicit)
1712 {
1714 {
1715 new InlineText(this, Item.Number.ToString() + ". ")
1716 };
1717
1718 if (Item.Child is NestedBlock B)
1719 NewContent.AddRange(B.Children);
1720 else
1721 NewContent.Add(Item.Child);
1722
1723 return NewContent;
1724 }
1725 else
1726 return Content;
1727 }
1728
1729 private Task<ChunkedList<MarkdownElement>> ParseCell(string Cell, int Position, out TextAlignment? Alignment)
1730 {
1731 if (Cell.StartsWith("<<"))
1732 {
1733 Position += 2;
1734
1735 if (Cell.EndsWith(">>"))
1736 {
1737 Alignment = TextAlignment.Center;
1738 Cell = Cell.Substring(2, Cell.Length - 4);
1739 }
1740 else
1741 {
1742 Alignment = TextAlignment.Left;
1743 Cell = Cell.Substring(2);
1744 }
1745 }
1746 else if (Cell.StartsWith(">>") && Cell.EndsWith("<<"))
1747 {
1748 Alignment = TextAlignment.Center;
1749 Cell = Cell.Substring(2, Cell.Length - 4);
1750 Position += 2;
1751 }
1752 else if (Cell.EndsWith(">>"))
1753 {
1754 Alignment = TextAlignment.Right;
1755 Cell = Cell.Substring(0, Cell.Length - 2);
1756 }
1757 else
1758 Alignment = null;
1759
1760 return this.ParseBlock(new string[] { Cell }, new int[] { Position });
1761 }
1762
1763 private async Task<ChunkedList<MarkdownElement>> ParseBlock(string[] Rows, int[] Positions)
1764 {
1765 return (await this.ParseBlock(Rows, Positions, 0, Rows.Length - 1, null, 0, 0)).Key;
1766 }
1767
1768 private async Task<ChunkedList<MarkdownElement>> ParseBlock(Block Block)
1769 {
1770 return (await this.ParseBlock(Block.Rows, Block.Positions, Block.Start, Block.End, null, 0, 0)).Key;
1771 }
1772
1773 private Task<KeyValuePair<ChunkedList<MarkdownElement>, int>> ParseBlock(Block Block, ChunkedList<Block> Blocks, int BlockIndex, int EndBlock)
1774 {
1775 return this.ParseBlock(Block.Rows, Block.Positions, Block.Start, Block.End, Blocks, BlockIndex, EndBlock);
1776 }
1777
1778 private async Task<ChunkedList<MarkdownElement>> ParseBlock(string[] Rows, int[] Positions, int StartRow, int EndRow)
1779 {
1780 return (await this.ParseBlock(Rows, Positions, StartRow, EndRow, null, 0, 0)).Key;
1781 }
1782
1783 private async Task<KeyValuePair<ChunkedList<MarkdownElement>, int>> ParseBlock(string[] Rows, int[] Positions, int StartRow, int EndRow, ChunkedList<Block> Blocks,
1784 int BlockIndex, int EndBlock)
1785 {
1787 bool PreserveCrLf = Rows[StartRow].StartsWith("<") && Rows[EndRow].EndsWith(">");
1788 BlockParseState State = new BlockParseState(Rows, Positions, StartRow, EndRow, PreserveCrLf, Blocks, BlockIndex, EndBlock);
1789
1790 await this.ParseBlock(State, (char)0, 1, Elements, true);
1791
1792 return new KeyValuePair<ChunkedList<MarkdownElement>, int>(Elements, State.BlockIndex);
1793 }
1794
1795 private async Task<bool> ParseBlock(BlockParseState State, char TerminationCharacter, int TerminationCharacterCount,
1796 ChunkedList<MarkdownElement> Elements, bool AcceptIncomplete)
1797 {
1798 ChunkedList<MarkdownElement> ChildElements;
1799 StringBuilder Text = new StringBuilder();
1800 string Url, Title;
1801 char ch, ch2, ch3;
1802 char PrevChar = ' ';
1803 int? Width;
1804 int? Height;
1805 bool FirstCharOnLine;
1806
1807 while ((ch = State.NextChar()) != (char)0)
1808 {
1809 if (ch == TerminationCharacter)
1810 {
1811 if (TerminationCharacterCount == 1 ||
1812 State.CheckRestOfTermination(TerminationCharacter, TerminationCharacterCount - 1))
1813 {
1814 break;
1815 }
1816 }
1817
1818 switch (ch)
1819 {
1820 case '\n':
1821 this.AppendAnyText(Elements, Text);
1822 Elements.Add(new LineBreak(this));
1823 break;
1824
1825 case '\r':
1826 Text.AppendLine();
1827 break;
1828
1829 case '*':
1830 if (((ch2 = State.PeekNextCharSameRow()) <= ' ' && ch2 > 0) || ch2 == 160)
1831 {
1832 if (State.IsFirstCharOnLine)
1833 {
1834 this.AppendAnyText(Elements, Text);
1835
1836 while (((ch2 = State.PeekNextCharSameRow()) <= ' ' && ch2 > 0) || ch2 == 160)
1837 State.NextCharSameRow();
1838
1839 UnnumberedItem Item;
1841 ChunkedList<int> Positions = new ChunkedList<int>()
1842 {
1843 State.CurrentPosition
1844 };
1845
1846 Rows.Add(State.RestOfRow());
1847
1848 while (!State.EOF)
1849 {
1850 if ((ch2 = State.PeekNextCharSameRow()) == '*' || ch2 == '+' || ch2 == '-')
1851 {
1852 Item = new UnnumberedItem(this, new string(ch, 1), new NestedBlock(this, await this.ParseBlock(Rows.ToArray(), Positions.ToArray())));
1853
1855 BulletList.AddChild(Item);
1856 else
1857 Elements.Add(new BulletList(this, Item));
1858
1859 State.NextCharSameRow();
1860 State.SkipWhitespaceSameRow(3);
1861
1862 Rows.Clear();
1863 Positions.Clear();
1864
1865 Positions.Add(State.CurrentPosition);
1866 Rows.Add(State.RestOfRow());
1867 }
1868 else
1869 {
1870 State.SkipWhitespaceSameRow(4);
1871
1872 Positions.Add(State.CurrentPosition);
1873 Rows.Add(State.RestOfRow());
1874 }
1875 }
1876
1877 if (Rows.Count > 0)
1878 {
1879 Item = new UnnumberedItem(this, new string(ch, 1), new NestedBlock(this, await this.ParseBlock(Rows.ToArray(), Positions.ToArray())));
1880
1882 BulletList.AddChild(Item);
1883 else
1884 Elements.Add(new BulletList(this, Item));
1885 }
1886 }
1887 else
1888 Text.Append('*');
1889
1890 break;
1891 }
1892
1893 this.AppendAnyText(Elements, Text);
1894 ChildElements = new ChunkedList<MarkdownElement>();
1895 ch2 = State.PeekNextCharSameRow();
1896 if (ch2 == '*')
1897 {
1898 State.NextCharSameRow();
1899
1900 await this.ParseBlock(State, '*', 2, ChildElements, true);
1901 Elements.Add(new Strong(this, ChildElements));
1902 }
1903 else
1904 {
1905 if (this.emojiSource is null)
1906 ch2 = (char)0;
1907
1908 switch (ch2)
1909 {
1910 case ')':
1911 State.NextCharSameRow();
1913 break;
1914
1915 case '-':
1916 State.BackupState();
1917 State.NextCharSameRow();
1918
1919 if (State.PeekNextCharSameRow() == ')')
1920 {
1921 State.DiscardBackup();
1922 State.NextCharSameRow();
1924 }
1925 else
1926 {
1927 State.RestoreState();
1928
1929 if (await this.ParseBlock(State, '*', 1, ChildElements, TerminationCharacter != '*'))
1930 Elements.Add(new Emphasize(this, ChildElements));
1931 else
1932 this.FixSyntaxError(Elements, "*", ChildElements);
1933 }
1934 break;
1935
1936 case '\\':
1937 State.BackupState();
1938 State.NextCharSameRow();
1939 if ((ch3 = State.NextCharSameRow()) == '0' || ch3 == 'O')
1940 {
1941 if (State.NextCharSameRow() == '/')
1942 {
1943 if (State.NextCharSameRow() == '*')
1944 {
1945 State.DiscardBackup();
1946 this.AppendAnyText(Elements, Text);
1948 break;
1949 }
1950 }
1951 }
1952
1953 State.RestoreState();
1954 if (await this.ParseBlock(State, '*', 1, ChildElements, TerminationCharacter != '*'))
1955 Elements.Add(new Emphasize(this, ChildElements));
1956 else
1957 this.FixSyntaxError(Elements, "*", ChildElements);
1958
1959 break;
1960
1961 default:
1962 if (await this.ParseBlock(State, '*', 1, ChildElements, TerminationCharacter != '*'))
1963 Elements.Add(new Emphasize(this, ChildElements));
1964 else
1965 this.FixSyntaxError(Elements, "*", ChildElements);
1966 break;
1967 }
1968 }
1969 break;
1970
1971 case '_':
1972 if ((ch2 = State.PeekNextCharSameRow()) <= ' ' || ch2 == 160)
1973 {
1974 Text.Append('_');
1975 break;
1976 }
1977
1978 this.AppendAnyText(Elements, Text);
1979 ChildElements = new ChunkedList<MarkdownElement>();
1980 ch2 = State.PeekNextCharSameRow();
1981 if (ch2 == '_')
1982 {
1983 State.NextCharSameRow();
1984
1985 await this.ParseBlock(State, '_', 2, ChildElements, true);
1986 Elements.Add(new Insert(this, ChildElements));
1987 }
1988 else
1989 {
1990 if (await this.ParseBlock(State, '_', 1, ChildElements, TerminationCharacter != '_'))
1991 Elements.Add(new Underline(this, ChildElements));
1992 else
1993 this.FixSyntaxError(Elements, "_", ChildElements);
1994 }
1995 break;
1996
1997 case '~':
1998 if ((ch2 = State.PeekNextCharSameRow()) <= ' ' || ch2 == 160)
1999 {
2000 Text.Append('~');
2001 break;
2002 }
2003
2004 this.AppendAnyText(Elements, Text);
2005 ChildElements = new ChunkedList<MarkdownElement>();
2006 ch2 = State.PeekNextCharSameRow();
2007 if (ch2 == '~')
2008 {
2009 State.NextCharSameRow();
2010
2011 await this.ParseBlock(State, '~', 2, ChildElements, true);
2012 Elements.Add(new Delete(this, ChildElements));
2013 }
2014 else
2015 {
2016 if (await this.ParseBlock(State, '~', 1, ChildElements, TerminationCharacter != '~'))
2017 Elements.Add(new StrikeThrough(this, ChildElements));
2018 else
2019 this.FixSyntaxError(Elements, "~", ChildElements);
2020 }
2021 break;
2022
2023 case '`':
2024 this.AppendAnyText(Elements, Text);
2025 ch2 = State.PeekNextCharSameRow();
2026 if (ch2 == '`')
2027 {
2028 State.NextCharSameRow();
2029
2030 while ((ch2 = State.NextChar()) != 0)
2031 {
2032 if (ch2 == '`' && State.PeekNextCharSameRow() == '`')
2033 {
2034 State.NextCharSameRow();
2035 break;
2036 }
2037
2038 Text.Append(ch2);
2039 }
2040 }
2041 else
2042 {
2043 while ((ch2 = State.NextChar()) != '`' && ch2 != 0)
2044 Text.Append(ch2);
2045 }
2046
2047 Elements.Add(new InlineCode(this, Text.ToString()));
2048 Text.Clear();
2049 break;
2050
2051 case '[':
2052 case '!':
2053 FirstCharOnLine = State.IsFirstCharOnLine;
2054
2055 if (ch == '!')
2056 {
2057 ch2 = State.PeekNextCharSameRow();
2058 if (ch2 != '[')
2059 {
2060 Text.Append('!');
2061 break;
2062 }
2063
2064 State.NextCharSameRow();
2065 }
2066 else
2067 {
2068 ch2 = State.PeekNextCharSameRow();
2069 if (ch2 == '[')
2070 {
2071 State.NextCharSameRow();
2072 this.AppendAnyText(Elements, Text);
2073 Elements.Add(new HtmlEntity(this, "LeftDoubleBracket"));
2074 break;
2075 }
2076 else if (ch2 == '%')
2077 {
2078 State.NextCharSameRow();
2079 this.AppendAnyText(Elements, Text);
2080
2081 while ((ch3 = State.NextCharSameRow()) != ']' && ch3 != 0)
2082 Text.Append(ch3);
2083
2084 if (ch3 == ']')
2085 {
2086 Url = Text.ToString();
2087 if (string.Compare(Url, "Details", true) == 0)
2088 Elements.Add(new DetailsReference(this, Url));
2089 else
2090 Elements.Add(new MetaReference(this, Url));
2091 Text.Clear();
2092 }
2093 else
2094 Text.Insert(0, "[%");
2095
2096 break;
2097 }
2098 else if (ch2 == '^')
2099 {
2100 State.NextCharSameRow();
2101 this.AppendAnyText(Elements, Text);
2102
2103 while ((ch3 = State.NextChar()) != ']' && ch3 != 0)
2104 Text.Append(ch3);
2105
2106 if (ch3 == ']')
2107 {
2108 Url = Text.ToString();
2109 Text.Clear();
2110
2111 if (this.footnoteNumberByKey is null)
2112 {
2113 this.footnoteNumberByKey = new Dictionary<string, int>();
2114 this.footnoteOrder = new ChunkedList<string>();
2115 this.footnotes = new Dictionary<string, Footnote>();
2116 }
2117
2118 try
2119 {
2120 Title = Url.ToLower();
2121 Elements.Add(new FootnoteReference(this, XmlConvert.VerifyNCName(Title)));
2122 if (!this.footnoteNumberByKey.ContainsKey(Title))
2123 {
2124 this.footnoteNumberByKey[Title] = ++this.lastFootnote;
2125 this.footnoteOrder.Add(Title);
2126 }
2127 }
2128 catch
2129 {
2130 Title = Guid.NewGuid().ToString();
2131
2132 Elements.Add(new FootnoteReference(this, Title));
2133 this.footnoteNumberByKey[Title] = ++this.lastFootnote;
2134 this.footnoteOrder.Add(Title);
2135 this.footnotes[Title] = new Footnote(this, Title, new Paragraph(this, await this.ParseBlock(new string[] { Url }, new int[] { State.CurrentPosition - 1 - Url.Length })));
2136 }
2137 }
2138 else
2139 Text.Insert(0, "[^");
2140
2141 break;
2142 }
2143 }
2144
2145 char[] chs;
2146
2147 if (FirstCharOnLine && (((chs = State.PeekNextChars(3))[0] == ' ' || chs[0] == 'x' || chs[0] == 'X') && chs[1] == ']' && ((chs[2] <= ' ' && chs[2] > 0) || chs[2] == 160)))
2148 {
2149 int CheckPosition = State.CurrentPosition;
2150
2151 State.NextChar();
2152 State.NextChar();
2153 State.NextChar();
2154
2155 this.AppendAnyText(Elements, Text);
2156
2157 while (((ch2 = State.PeekNextCharSameRow()) <= ' ' && ch2 > 0) || ch2 == 160)
2158 State.NextCharSameRow();
2159
2160 TaskItem Item;
2162 {
2163 State.RestOfRow()
2164 };
2165 ChunkedList<int> Positions = new ChunkedList<int>()
2166 {
2167 State.CurrentPosition
2168 };
2169 bool Checked = (chs[0] != ' ');
2170
2171 while (!State.EOF)
2172 {
2173 if ((chs = State.PeekNextChars(4))[0] == '[' &&
2174 (chs[1] == ' ' || chs[1] == 'x' || chs[1] == 'X') &&
2175 chs[2] == ']' && ((chs[3] <= ' ' && chs[3] > 0) || chs[3] == 160))
2176 {
2177 Item = new TaskItem(this, Checked, CheckPosition,
2178 new NestedBlock(this, await this.ParseBlock(Rows.ToArray(), Positions.ToArray())));
2179
2181 TaskList.AddChild(Item);
2182 else
2183 Elements.Add(new TaskList(this, Item));
2184
2185 Rows.Clear();
2186 Positions.Clear();
2187
2188 State.NextChar();
2189
2190 CheckPosition = State.CurrentPosition;
2191
2192 State.NextChar();
2193 State.NextChar();
2194 State.NextChar();
2195
2196 while (((ch2 = State.PeekNextCharSameRow()) <= ' ' && ch2 > 0) || ch2 == 160)
2197 State.NextCharSameRow();
2198
2199 Positions.Add(State.CurrentPosition);
2200 Rows.Add(State.RestOfRow());
2201
2202 Checked = (chs[1] != ' ');
2203 }
2204 else
2205 {
2206 State.SkipWhitespaceSameRow(4);
2207 Positions.Add(State.CurrentPosition);
2208 Rows.Add(State.RestOfRow());
2209 }
2210 }
2211
2212 if (Rows.Count > 0)
2213 {
2214 Item = new TaskItem(this, Checked, CheckPosition,
2215 new NestedBlock(this, await this.ParseBlock(Rows.ToArray(), Positions.ToArray())));
2216
2218 TaskList.AddChild(Item);
2219 else
2220 Elements.Add(new TaskList(this, Item));
2221 }
2222
2223 break;
2224 }
2225
2226 ChildElements = new ChunkedList<MarkdownElement>();
2227 this.AppendAnyText(Elements, Text);
2228
2229 if (await this.ParseBlock(State, ']', 1, ChildElements, false))
2230 {
2231 ch2 = State.PeekNextNonWhitespaceChar(false);
2232 if (ch2 == '(')
2233 {
2234 State.NextNonWhitespaceChar();
2235 Title = string.Empty;
2236
2237 while ((ch2 = State.PeekNextCharSameRow()) != 0 && ch2 > ' ' && ch2 != ')' && ch2 != 160)
2238 {
2239 State.NextChar();
2240 Text.Append(ch2);
2241 }
2242
2243 Url = Text.ToString();
2244 if (Url.StartsWith("abbr:", StringComparison.CurrentCultureIgnoreCase))
2245 {
2246 if (ch2 == ')')
2247 State.NextChar();
2248 else if (ch2 != 0)
2249 {
2250 while ((ch2 = State.NextCharSameRow()) != 0 && ch2 != ')')
2251 Text.Append(ch2);
2252 }
2253
2254 Url = Text.ToString();
2255 Text.Clear();
2256
2257 Elements.Add(new Abbreviation(this, ChildElements, Url.Substring(5).Trim()));
2258 }
2259 else
2260 {
2261 Text.Clear();
2262
2263 if (Url.StartsWith("<") && Url.EndsWith(">"))
2264 Url = Url.Substring(1, Url.Length - 2);
2265
2266 if (ch2 <= ' ' || ch2 == 160)
2267 {
2268 ch2 = State.PeekNextNonWhitespaceChar(true);
2269
2270 if (ch2 == '"' || ch2 == '\'')
2271 {
2272 State.NextNonWhitespaceChar();
2273 while ((ch3 = State.NextCharSameRow()) != 0 && ch3 != ch2)
2274 Text.Append(ch3);
2275
2276 Title = Text.ToString();
2277 Text.Clear();
2278
2279 ch2 = State.PeekNextNonWhitespaceChar(true);
2280 }
2281 else
2282 Title = string.Empty;
2283 }
2284
2285 if (ch == '!' && ch2 != ')')
2286 {
2287 ParseWidthHeight(State, out Width, out Height);
2288 ch2 = State.PeekNextCharSameRow();
2289 }
2290 else
2291 Width = Height = null;
2292
2293 while (ch2 != 0 && ch2 != ')')
2294 {
2295 State.NextCharSameRow();
2296 ch2 = State.PeekNextCharSameRow();
2297 }
2298
2299 if (ch2 == ')')
2300 State.NextChar();
2301
2302 if (ch == '!')
2303 {
2305 {
2306 new MultimediaItem(this, Url, Title, Width, Height)
2307 };
2308
2309 if (!this.includesTableOfContents && string.Compare(Url, "ToC", true) == 0)
2310 this.includesTableOfContents = true;
2311
2312 State.BackupState();
2313 ch2 = State.PeekNextNonWhitespaceChar(false);
2314
2315 while (ch2 == '(')
2316 {
2317 State.NextNonWhitespaceChar();
2318 Title = string.Empty;
2319
2320 while ((ch2 = State.PeekNextCharSameRow()) != 0 && ch2 > ' ' && ch2 != ')' && ch2 != 160)
2321 {
2322 State.NextChar();
2323 Text.Append(ch2);
2324 }
2325
2326 Url = Text.ToString();
2327
2328 Text.Clear();
2329
2330 if (Url.StartsWith("<") && Url.EndsWith(">"))
2331 Url = Url.Substring(1, Url.Length - 2);
2332
2333 if (ch2 <= ' ' || ch2 == 160)
2334 {
2335 ch2 = State.PeekNextNonWhitespaceChar(true);
2336
2337 if (ch2 == '"' || ch2 == '\'')
2338 {
2339 State.NextNonWhitespaceChar();
2340 while ((ch3 = State.NextCharSameRow()) != 0 && ch3 != ch2)
2341 Text.Append(ch3);
2342
2343 Title = Text.ToString();
2344 Text.Clear();
2345
2346 ch2 = State.PeekNextNonWhitespaceChar(true);
2347 }
2348 else
2349 Title = string.Empty;
2350 }
2351
2352 if (ch2 != ')')
2353 {
2354 ParseWidthHeight(State, out Width, out Height);
2355
2356 ch2 = State.PeekNextCharSameRow();
2357
2358 while (ch2 != 0 && ch2 != ')')
2359 {
2360 State.NextCharSameRow();
2361 ch2 = State.PeekNextCharSameRow();
2362 }
2363 }
2364
2365 Items.Add(new MultimediaItem(this, Url, Title, Width, Height));
2366
2367 if (ch2 == ')')
2368 {
2369 State.NextChar();
2370 ch2 = State.PeekNextNonWhitespaceChar(true);
2371 }
2372
2373 State.DiscardBackup();
2374 State.BackupState();
2375 }
2376
2377 State.RestoreState();
2378
2379 Multimedia Multimedia = new Multimedia(this, ChildElements,
2380 !Elements.HasFirstItem && State.PeekNextChar() == 0,
2381 Items.ToArray());
2382
2384
2385 if (!(this.settings?.Progress is null))
2386 {
2388 if (!(Renderer is null))
2389 await Renderer.Preload(this.settings.Progress, Multimedia.Items);
2390 }
2391 }
2392 else
2393 Elements.Add(new Link(this, ChildElements, Url, Title));
2394 }
2395 }
2396 else if (ch2 == ':' && FirstCharOnLine)
2397 {
2398 State.NextNonWhitespaceChar();
2399 ch2 = State.NextChar();
2400 while ((ch2 != 0 && ch2 <= ' ') || ch2 == 160)
2401 ch2 = State.NextChar();
2402
2403 if (ch2 > ' ' && ch2 != 160)
2404 {
2406
2407 Text.Append(ch2);
2408
2409 while (ch2 > ' ' && ch2 != 160 && ch2 != '[')
2410 {
2411 ch2 = State.NextNonWhitespaceChar();
2412 while (ch2 != 0 && ch2 > ' ' && ch2 != 160)
2413 {
2414 Text.Append(ch2);
2415 ch2 = State.NextCharSameRow();
2416 }
2417
2418 Url = Text.ToString();
2419 Text.Clear();
2420
2421 if (Url.StartsWith("<") && Url.EndsWith(">"))
2422 Url = Url.Substring(1, Url.Length - 2);
2423
2424 ch2 = State.PeekNextNonWhitespaceChar(true);
2425
2426 if (ch2 == '"' || ch2 == '\'' || ch2 == '(')
2427 {
2428 State.NextNonWhitespaceChar();
2429 if (ch2 == '(')
2430 ch2 = ')';
2431
2432 while ((ch3 = State.NextCharSameRow()) != 0 && ch3 != ch2)
2433 Text.Append(ch3);
2434
2435 Title = Text.ToString();
2436 Text.Clear();
2437 }
2438 else
2439 Title = string.Empty;
2440
2441 ParseWidthHeight(State, out Width, out Height);
2442
2443 Items.Add(new MultimediaItem(this, Url, Title, Width, Height));
2444 if (!this.includesTableOfContents && string.Compare(Url, "ToC", true) == 0)
2445 this.includesTableOfContents = true;
2446
2447 ch2 = State.PeekNextNonWhitespaceChar(true);
2448 }
2449
2450 using (TextRenderer Renderer = new TextRenderer(Text))
2451 {
2452 await Renderer.Render(ChildElements);
2453 }
2454
2455 Multimedia Multimedia = new Multimedia(this, null,
2456 !Elements.HasFirstItem && State.PeekNextChar() == 0, Items.ToArray());
2457
2458 this.references[Text.ToString().ToLower()] = Multimedia;
2459
2460 if (!(this.settings?.Progress is null))
2461 {
2463 if (!(Renderer is null))
2464 await Renderer.Preload(this.settings.Progress, Multimedia.Items);
2465 }
2466
2467 Text.Clear();
2468 }
2469 }
2470 else if (ch2 == '[')
2471 {
2472 State.NextNonWhitespaceChar();
2473 while ((ch2 = State.NextCharSameRow()) != 0 && ch2 != ']')
2474 Text.Append(ch2);
2475
2476 Title = Text.ToString();
2477 Text.Clear();
2478
2479 if (string.IsNullOrEmpty(Title))
2480 {
2481 using (TextRenderer Renderer = new TextRenderer(Text))
2482 {
2483 await Renderer.Render(ChildElements);
2484 }
2485
2486 Title = Text.ToString();
2487 Text.Clear();
2488 }
2489
2490 if (ch == '!')
2491 {
2492 Elements.Add(new MultimediaReference(this, ChildElements, Title,
2493 !Elements.HasFirstItem && State.PeekNextChar() == 0));
2494 }
2495 else
2496 Elements.Add(new LinkReference(this, ChildElements, Title));
2497 }
2498 else if (ch != '!')
2499 Elements.Add(new SubScript(this, ChildElements));
2500 else
2501 {
2502 this.FixSyntaxError(Elements, "![", ChildElements);
2503
2504 if (ch2 == (char)0)
2505 Elements.Add(new InlineText(this, "]"));
2506 else
2507 Elements.Add(new InlineText(this, "]" + ch2));
2508 }
2509 }
2510 else
2511 this.FixSyntaxError(Elements, ch == '!' ? "![" : "[", ChildElements);
2512 break;
2513
2514 case ']':
2515 ch2 = State.PeekNextCharSameRow();
2516 if (ch2 == ']')
2517 {
2518 State.NextCharSameRow();
2519 this.AppendAnyText(Elements, Text);
2520 Elements.Add(new HtmlEntity(this, "RightDoubleBracket"));
2521 }
2522 else
2523 Text.Append(']');
2524 break;
2525
2526 case '<':
2527 ch2 = State.PeekNextCharSameRow();
2528 if (ch2 == '<')
2529 {
2530 State.NextCharSameRow();
2531 this.AppendAnyText(Elements, Text);
2532
2533 ch3 = State.PeekNextCharSameRow();
2534 if (ch3 == '<')
2535 {
2536 State.NextCharSameRow();
2537 Elements.Add(new HtmlEntity(this, "Ll"));
2538 }
2539 else
2540 Elements.Add(new HtmlEntity(this, "laquo"));
2541 break;
2542 }
2543 else if (ch2 == '-')
2544 {
2545 State.NextCharSameRow();
2546 ch3 = State.PeekNextCharSameRow();
2547
2548 if (ch3 == '-')
2549 {
2550 State.NextCharSameRow();
2551 this.AppendAnyText(Elements, Text);
2552
2553 ch3 = State.PeekNextCharSameRow();
2554 if (ch3 == '>')
2555 {
2556 State.NextCharSameRow();
2557 Elements.Add(new HtmlEntity(this, "harr"));
2558 }
2559 else
2560 Elements.Add(new HtmlEntity(this, "larr"));
2561 }
2562 else
2563 Text.Append("<-");
2564 break;
2565 }
2566 else if (ch2 == '=')
2567 {
2568 State.NextCharSameRow();
2569 this.AppendAnyText(Elements, Text);
2570 ch3 = State.PeekNextCharSameRow();
2571
2572 if (ch3 == '=')
2573 {
2574 State.NextCharSameRow();
2575
2576 ch3 = State.PeekNextCharSameRow();
2577 if (ch3 == '>')
2578 {
2579 State.NextCharSameRow();
2580 Elements.Add(new HtmlEntity(this, "hArr"));
2581 }
2582 else
2583 Elements.Add(new HtmlEntity(this, "lArr"));
2584 }
2585 else
2586 Elements.Add(new HtmlEntity(this, "leq"));
2587 break;
2588 }
2589 else if (ch2 == '>')
2590 {
2591 State.NextCharSameRow();
2592 this.AppendAnyText(Elements, Text);
2593 Elements.Add(new HtmlEntity(this, "ne"));
2594 break;
2595 }
2596 else if (ch2 == '3' && !(this.emojiSource is null))
2597 {
2598 State.NextCharSameRow();
2599 this.AppendAnyText(Elements, Text);
2601 break;
2602 }
2603 else if (ch2 == '/')
2604 {
2605 State.NextCharSameRow();
2606 if (!(this.emojiSource is null) && State.PeekNextCharSameRow() == '3')
2607 {
2608 State.NextCharSameRow();
2609 this.AppendAnyText(Elements, Text);
2611 break;
2612 }
2613 }
2614
2615 if ((!char.IsLetter(ch2) && ch2 != '/') || !this.settings.AllowHtml)
2616 {
2617 Text.Append(ch);
2618 break;
2619 }
2620
2621 this.AppendAnyText(Elements, Text);
2622 Text.Append(ch);
2623
2624 if (ch2 == '/')
2625 Text.Append(ch2);
2626
2627 while ((ch2 = State.NextChar()) != 0 && ch2 != '>')
2628 {
2629 if (ch2 == '\r')
2630 Text.AppendLine();
2631 else
2632 Text.Append(ch2);
2633 }
2634
2635 if (ch2 == 0)
2636 break;
2637
2638 Text.Append(ch2);
2639 Url = Text.ToString();
2640
2641 if (Url.StartsWith("</"))
2642 {
2643 if (Url.StartsWith("</script", StringComparison.CurrentCultureIgnoreCase))
2644 Elements.Add(new InlineCode(this, Url));
2645 else
2646 Elements.Add(new InlineHTML(this, Url));
2647 }
2648 else if (Url.StartsWith("<script", StringComparison.CurrentCultureIgnoreCase))
2649 {
2650 if (this.AllowScriptTag && this.settings.AllowScriptTag)
2651 {
2652 Text.Append(State.UntilToken("</SCRIPT>"));
2653 Text.Append("</");
2654 Text.Append(Url.Substring(1, 6));
2655 Text.Append('>');
2656
2657 Elements.Add(new InlineHTML(this, Text.ToString()));
2658 }
2659 else
2660 Elements.Add(new InlineCode(this, Url));
2661 }
2662 else if (Url.StartsWith("<textarea", StringComparison.CurrentCultureIgnoreCase))
2663 {
2664 Elements.Add(new InlineHTML(this, Url));
2665
2666 string s = State.UntilToken("</TEXTAREA>");
2667
2668 if (!string.IsNullOrEmpty(s))
2669 Elements.Add(new InlineText(this, s));
2670
2671 Elements.Add(new InlineHTML(this, "</" + Url.Substring(1, 8) + ">"));
2672 }
2673 else
2674 {
2675 int i = Url.IndexOf(' ');
2676
2677 if ((i < 0 && Url.IndexOf(':') >= 0) || (i > 0 && Url.LastIndexOf(':', i) >= 0))
2678 Elements.Add(new AutomaticLinkUrl(this, Url.Substring(1, Url.Length - 2)));
2679 else if ((i < 0 && Url.IndexOf('@') >= 0) || (i > 0 && Url.LastIndexOf('@', i) >= 0))
2680 Elements.Add(new AutomaticLinkMail(this, Url.Substring(1, Url.Length - 2)));
2681 else
2682 {
2683 Elements.Add(new InlineHTML(this, Url));
2684
2685 if (Url.StartsWith("<textarea", StringComparison.CurrentCultureIgnoreCase))
2686 {
2687 string s = State.UntilToken("</TEXTAREA>");
2688
2689 if (!string.IsNullOrEmpty(s))
2690 Elements.Add(new InlineText(this, s));
2691
2692 Elements.Add(new InlineHTML(this, "</" + Url.Substring(1, 8) + ">"));
2693 }
2694 }
2695 }
2696
2697 Text.Clear();
2698 break;
2699
2700 case '>':
2701 switch (State.PeekNextCharSameRow())
2702 {
2703 case '>':
2704 State.NextCharSameRow();
2705 this.AppendAnyText(Elements, Text);
2706
2707 ch3 = State.PeekNextCharSameRow();
2708 if (ch3 == '>')
2709 {
2710 State.NextCharSameRow();
2711 Elements.Add(new HtmlEntity(this, "Gg"));
2712 }
2713 else
2714 Elements.Add(new HtmlEntity(this, "raquo"));
2715 break;
2716
2717 case '=':
2718 this.AppendAnyText(Elements, Text);
2719 State.NextCharSameRow();
2720
2721 if (!(this.emojiSource is null) && State.PeekNextCharSameRow() == ')')
2722 {
2723 State.NextCharSameRow();
2725 }
2726 else
2727 Elements.Add(new HtmlEntity(this, "geq"));
2728 break;
2729
2730 case ':':
2731 if (!(this.emojiSource is null))
2732 {
2733 State.NextCharSameRow();
2734 switch (State.PeekNextCharSameRow())
2735 {
2736 case ')':
2737 State.NextCharSameRow();
2738 this.AppendAnyText(Elements, Text);
2740 break;
2741
2742 case '(':
2743 State.NextCharSameRow();
2744 this.AppendAnyText(Elements, Text);
2746 break;
2747
2748 case '[':
2749 State.NextCharSameRow();
2750 this.AppendAnyText(Elements, Text);
2752 break;
2753
2754 case 'O':
2755 State.NextCharSameRow();
2756 this.AppendAnyText(Elements, Text);
2758 break;
2759
2760 case 'P':
2761 case 'p':
2762 case 'b':
2763 case 'Þ':
2764 case 'þ':
2765 State.NextCharSameRow();
2766 this.AppendAnyText(Elements, Text);
2768 break;
2769
2770 case '/':
2771 case '\\':
2772 case 'L':
2773 State.NextCharSameRow();
2774 this.AppendAnyText(Elements, Text);
2776 break;
2777
2778 case 'X':
2779 case 'x':
2780 case '#':
2781 State.NextCharSameRow();
2782 this.AppendAnyText(Elements, Text);
2784 break;
2785
2786 case '-':
2787 State.NextCharSameRow();
2788 switch (State.PeekNextCharSameRow())
2789 {
2790 case ')':
2791 State.NextCharSameRow();
2792 this.AppendAnyText(Elements, Text);
2794 break;
2795
2796 case '(':
2797 State.NextCharSameRow();
2798 this.AppendAnyText(Elements, Text);
2800 break;
2801
2802 default:
2803 Text.Append(">:-");
2804 break;
2805 }
2806 break;
2807
2808 default:
2809 Text.Append(">:");
2810 break;
2811 }
2812 }
2813 else
2814 Text.Append('>');
2815 break;
2816
2817 case ';':
2818 if (!(this.emojiSource is null))
2819 {
2820 State.NextCharSameRow();
2821 if (State.PeekNextCharSameRow() == ')')
2822 {
2823 State.NextCharSameRow();
2824 this.AppendAnyText(Elements, Text);
2826 }
2827 else
2828 Text.Append(">;");
2829 }
2830 else
2831 Text.Append('>');
2832 break;
2833
2834 case '.':
2835 if (!(this.emojiSource is null))
2836 {
2837 State.NextCharSameRow();
2838 if (State.PeekNextCharSameRow() == '<')
2839 {
2840 State.NextCharSameRow();
2841 this.AppendAnyText(Elements, Text);
2843 }
2844 else
2845 Text.Append(">.");
2846 }
2847 else
2848 Text.Append('>');
2849 break;
2850
2851 default:
2852 Text.Append('>');
2853 break;
2854 }
2855 break;
2856
2857 case '{':
2858 if (!this.settings.AllowInlineScript || this.settings.Variables is null)
2859 {
2860 int Pos = State.CurrentPosition - 1;
2861 if (Pos < this.markdownText.Length && this.markdownText[Pos] == '{')
2862 {
2863 if (this.toInsert is null)
2864 this.toInsert = new SortedDictionary<int, char>();
2865
2866 this.toInsert[Pos] = '\\';
2867 if (Pos > 0 && ((ch2 = this.markdownText[Pos - 1]) == ':' || ch2 == '*' || ch2 == '='))
2868 this.toInsert[Pos - 1] = '\\'; // To avoid creating a smiley.
2869 }
2870 Text.Append(ch);
2871 break;
2872 }
2873
2874 this.AppendAnyText(Elements, Text);
2875 State.BackupState();
2876
2877 int StartPosition = State.CurrentPosition - 1;
2878
2879 while ((ch2 = State.NextChar()) != '}' && ch2 != 0)
2880 Text.Append(ch2);
2881
2882 int EndPosition = State.CurrentPosition;
2883
2884 if (ch2 == 0)
2885 {
2886 State.RestoreState();
2887 Text.Clear();
2888 Text.Append(ch);
2889 break;
2890 }
2891
2892 try
2893 {
2894 State.DiscardBackup();
2895 Expression Exp = new Expression(Text.ToString(), this.fileName,
2896 this.settings.ScriptContext);
2897
2898 Elements.Add(new InlineScript(this, Exp, this.settings.Variables,
2899 !Elements.HasFirstItem && State.PeekNextChar() == 0, StartPosition, EndPosition));
2900 Text.Clear();
2901 this.isDynamic = true;
2902 }
2903 catch (Exception ex)
2904 {
2905 ex = Log.UnnestException(ex);
2906
2908 new InlineHTML(this, "<font class=\"error\">"))));
2909
2910 if (ex is AggregateException ex2)
2911 {
2912 foreach (Exception ex3 in ex2.InnerExceptions)
2913 {
2914 this.CheckException(ex3);
2915
2916 Log.Exception(ex3, this.fileName);
2917
2919 new InlineText(this, ex3.Message))));
2920 }
2921 }
2922 else
2923 {
2924 this.CheckException(ex);
2925
2926 Log.Exception(ex, this.fileName);
2927
2929 new InlineText(this, ex.Message))));
2930 }
2931
2933 new InlineHTML(this, "</font>"))));
2934
2935 this.CheckException(ex);
2936 }
2937
2938 break;
2939
2940 case '-':
2941 ch2 = State.PeekNextCharSameRow();
2942 if (ch2 == '-')
2943 {
2944 State.NextCharSameRow();
2945 this.AppendAnyText(Elements, Text);
2946
2947 ch3 = State.PeekNextCharSameRow();
2948
2949 if (ch3 == '>')
2950 {
2951 State.NextCharSameRow();
2952 Elements.Add(new HtmlEntity(this, "rarr"));
2953 }
2954 else if (ch3 == '-')
2955 {
2956 State.NextCharSameRow();
2957 Elements.Add(new HtmlEntity(this, "mdash"));
2958 }
2959 else
2960 Elements.Add(new HtmlEntity(this, "ndash"));
2961 }
2962 else if (ch2 == '+')
2963 {
2964 State.NextCharSameRow();
2965 this.AppendAnyText(Elements, Text);
2966 Elements.Add(new HtmlEntity(this, "MinusPlus"));
2967 }
2968 else if (ch2 == '_')
2969 {
2970 if (!(this.emojiSource is null))
2971 {
2972 State.BackupState();
2973 while ((ch2 = State.NextCharSameRow()) == '_')
2974 ;
2975
2976 if (ch2 == '-')
2977 {
2978 State.DiscardBackup();
2979 State.NextCharSameRow();
2980 this.AppendAnyText(Elements, Text);
2982 }
2983 else
2984 {
2985 State.RestoreState();
2986 Text.Append(ch);
2987 }
2988 }
2989 else
2990 Text.Append(ch);
2991 }
2992 else if ((ch2 <= ' ' && ch2 > 0) || ch2 == 160)
2993 {
2994 if (State.IsFirstCharOnLine)
2995 {
2996 this.AppendAnyText(Elements, Text);
2997
2998 while (((ch2 = State.PeekNextCharSameRow()) <= ' ' && ch2 > 0) || ch2 == 160)
2999 State.NextCharSameRow();
3000
3001 UnnumberedItem Item;
3003 ChunkedList<int> Positions = new ChunkedList<int>()
3004 {
3005 State.CurrentPosition
3006 };
3007
3008 Rows.Add(State.RestOfRow());
3009
3010 while (!State.EOF)
3011 {
3012 if ((ch2 = State.PeekNextCharSameRow()) == '*' || ch2 == '+' || ch2 == '-')
3013 {
3014 Item = new UnnumberedItem(this, new string(ch, 1), new NestedBlock(this, await this.ParseBlock(Rows.ToArray(), Positions.ToArray())));
3015
3017 BulletList.AddChild(Item);
3018 else
3019 Elements.Add(new BulletList(this, Item));
3020
3021 State.NextCharSameRow();
3022 State.SkipWhitespaceSameRow(3);
3023
3024 Rows.Clear();
3025 Positions.Clear();
3026
3027 Positions.Add(State.CurrentPosition);
3028 Rows.Add(State.RestOfRow());
3029 }
3030 else
3031 {
3032 State.SkipWhitespaceSameRow(4);
3033
3034 Positions.Add(State.CurrentPosition);
3035 Rows.Add(State.RestOfRow());
3036 }
3037 }
3038
3039 if (Rows.Count > 0)
3040 {
3041 Item = new UnnumberedItem(this, new string(ch, 1), new NestedBlock(this, await this.ParseBlock(Rows.ToArray(), Positions.ToArray())));
3042
3044 BulletList.AddChild(Item);
3045 else
3046 Elements.Add(new BulletList(this, Item));
3047 }
3048 }
3049 else
3050 Text.Append('-');
3051 }
3052 else
3053 Text.Append('-');
3054 break;
3055
3056 case '+':
3057 ch2 = State.PeekNextCharSameRow();
3058 if (ch2 == '-')
3059 {
3060 State.NextCharSameRow();
3061 this.AppendAnyText(Elements, Text);
3062 Elements.Add(new HtmlEntity(this, "PlusMinus"));
3063 }
3064 else if ((ch2 <= ' ' && ch2 > 0) || ch2 == 160)
3065 {
3066 if (State.IsFirstCharOnLine)
3067 {
3068 this.AppendAnyText(Elements, Text);
3069
3070 while (((ch2 = State.PeekNextCharSameRow()) <= ' ' && ch2 > 0) || ch2 == 160)
3071 State.NextCharSameRow();
3072
3073 UnnumberedItem Item;
3075 ChunkedList<int> Positions = new ChunkedList<int>()
3076 {
3077 State.CurrentPosition
3078 };
3079
3080 Rows.Add(State.RestOfRow());
3081
3082 while (!State.EOF)
3083 {
3084 if ((ch2 = State.PeekNextCharSameRow()) == '*' || ch2 == '+' || ch2 == '-')
3085 {
3086 Item = new UnnumberedItem(this, new string(ch, 1), new NestedBlock(this, await this.ParseBlock(Rows.ToArray(), Positions.ToArray())));
3087
3089 BulletList.AddChild(Item);
3090 else
3091 Elements.Add(new BulletList(this, Item));
3092
3093 State.NextCharSameRow();
3094 State.SkipWhitespaceSameRow(3);
3095
3096 Rows.Clear();
3097 Positions.Clear();
3098
3099 Positions.Add(State.CurrentPosition);
3100 Rows.Add(State.RestOfRow());
3101 }
3102 else
3103 {
3104 State.SkipWhitespaceSameRow(4);
3105
3106 Positions.Add(State.CurrentPosition);
3107 Rows.Add(State.RestOfRow());
3108 }
3109 }
3110
3111 if (Rows.Count > 0)
3112 {
3113 Item = new UnnumberedItem(this, new string(ch, 1), new NestedBlock(this, await this.ParseBlock(Rows.ToArray(), Positions.ToArray())));
3114
3116 BulletList.AddChild(Item);
3117 else
3118 Elements.Add(new BulletList(this, Item));
3119 }
3120 }
3121 else
3122 Text.Append('+');
3123 }
3124 else
3125 Text.Append('+');
3126 break;
3127
3128 case '#':
3129 ch2 = State.PeekNextCharSameRow();
3130 if (char.IsLetterOrDigit(ch2))
3131 {
3132 this.AppendAnyText(Elements, Text);
3133
3134 Text.Append(ch2);
3135 State.NextCharSameRow();
3136
3137 while (char.IsLetterOrDigit(ch2 = State.PeekNextCharSameRow()))
3138 {
3139 Text.Append(ch2);
3140 State.NextCharSameRow();
3141 }
3142
3143 Elements.Add(new HashTag(this, Text.ToString()));
3144 Text.Clear();
3145 }
3146 else if (State.IsFirstCharOnLine && ch2 == '.')
3147 {
3148 State.NextCharSameRow();
3149
3150 if (((ch2 = State.PeekNextCharSameRow()) <= ' ' && ch2 > 0) || ch2 == 160)
3151 {
3152 this.AppendAnyText(Elements, Text);
3153
3154 while (((ch2 = State.PeekNextCharSameRow()) <= ' ' && ch2 > 0) || ch2 == 160)
3155 State.NextCharSameRow();
3156
3157 NumberedItem Item;
3159 ChunkedList<int> Positions = new ChunkedList<int>()
3160 {
3161 State.CurrentPosition
3162 };
3163
3164 Rows.Add(State.RestOfRow());
3165
3166 while (!State.EOF)
3167 {
3168 if (State.PeekNextCharSameRow() == '#')
3169 {
3170 State.NextCharSameRow();
3171 if (State.PeekNextCharSameRow() == '.')
3172 {
3173 Item = new NumberedItem(this, 1, false, new NestedBlock(this, await this.ParseBlock(Rows.ToArray(), Positions.ToArray())));
3174
3176 NumberedList.AddChild(Item);
3177 else
3178 Elements.Add(new NumberedList(this, Item));
3179
3180 State.NextCharSameRow();
3181 State.SkipWhitespaceSameRow(3);
3182
3183 Rows.Clear();
3184 Positions.Clear();
3185
3186 Positions.Add(State.CurrentPosition);
3187 Rows.Add(State.RestOfRow());
3188 }
3189 else
3190 {
3191 State.SkipWhitespaceSameRow(4);
3192
3193 Positions.Add(State.CurrentPosition - 1);
3194 Rows.Add("#" + State.RestOfRow());
3195 }
3196 }
3197 else
3198 {
3199 State.SkipWhitespaceSameRow(4);
3200
3201 Positions.Add(State.CurrentPosition);
3202 Rows.Add(State.RestOfRow());
3203 }
3204 }
3205
3206 if (Rows.Count > 0)
3207 {
3208 Item = new NumberedItem(this, 1, false, new NestedBlock(this, await this.ParseBlock(Rows.ToArray(), Positions.ToArray())));
3209
3211 NumberedList.AddChild(Item);
3212 else
3213 {
3214 if (!Item.NumberExplicit &&
3216 Elements.LastItem is NumberedItem PrevItem)
3217 {
3218 Item.Number = PrevItem.Number + 1;
3219 }
3220
3221 Elements.Add(new NumberedList(this, Item));
3222 }
3223 }
3224 }
3225 else
3226 Text.Append("#.");
3227 }
3228 else if (!(this.emojiSource is null))
3229 {
3230 switch (State.PeekNextCharSameRow())
3231 {
3232 case '-':
3233 State.BackupState();
3234 State.NextCharSameRow();
3235 switch (State.PeekNextCharSameRow())
3236 {
3237 case ')':
3238 State.DiscardBackup();
3239 State.NextCharSameRow();
3240 this.AppendAnyText(Elements, Text);
3242 break;
3243
3244 default:
3245 State.RestoreState();
3246 Text.Append(ch);
3247 break;
3248 }
3249 break;
3250
3251 case ')':
3252 State.NextCharSameRow();
3253 this.AppendAnyText(Elements, Text);
3255 break;
3256
3257 default:
3258 Text.Append('#');
3259 break;
3260 }
3261 }
3262 else
3263 Text.Append('#');
3264 break;
3265
3266 case '0':
3267 case '1':
3268 case '2':
3269 case '3':
3270 case '4':
3271 case '5':
3272 case '6':
3273 case '7':
3274 case '8':
3275 case '9':
3276 if (!(this.emojiSource is null) && (ch == '8' || ch == '0') && (char.IsPunctuation(PrevChar) || char.IsWhiteSpace(PrevChar)))
3277 {
3278 if (ch == '0')
3279 {
3280 switch (ch2 = State.PeekNextCharSameRow())
3281 {
3282 case ':':
3283 State.BackupState();
3284 State.NextCharSameRow();
3285 switch (State.PeekNextCharSameRow())
3286 {
3287 case '3':
3288 case ')':
3289 State.DiscardBackup();
3290 State.NextCharSameRow();
3291 this.AppendAnyText(Elements, Text);
3293 ch2 = (char)0xffff;
3294 break;
3295
3296 case '-':
3297 State.NextCharSameRow();
3298 switch (State.PeekNextCharSameRow())
3299 {
3300 case '3':
3301 case ')':
3302 State.DiscardBackup();
3303 State.NextCharSameRow();
3304 this.AppendAnyText(Elements, Text);
3306 ch2 = (char)0xffff;
3307 break;
3308
3309 default:
3310 State.RestoreState();
3311 break;
3312 }
3313 break;
3314
3315 default:
3316 State.RestoreState();
3317 break;
3318 }
3319 break;
3320
3321 case ';':
3322 State.BackupState();
3323 State.NextCharSameRow();
3324 switch (State.PeekNextCharSameRow())
3325 {
3326 case '-':
3327 case '^':
3328 State.NextCharSameRow();
3329 switch (State.PeekNextCharSameRow())
3330 {
3331 case ')':
3332 State.DiscardBackup();
3333 State.NextCharSameRow();
3334 this.AppendAnyText(Elements, Text);
3336 ch2 = (char)0xffff;
3337 break;
3338
3339 default:
3340 State.RestoreState();
3341 break;
3342 }
3343 break;
3344
3345 default:
3346 State.RestoreState();
3347 break;
3348 }
3349 break;
3350
3351 default:
3352 break;
3353 }
3354 }
3355 else
3356 {
3357 switch (ch2 = State.PeekNextCharSameRow())
3358 {
3359 case '-':
3360 State.BackupState();
3361 State.NextCharSameRow();
3362 switch (State.PeekNextCharSameRow())
3363 {
3364 case ')':
3365 case 'D':
3366 State.DiscardBackup();
3367 State.NextCharSameRow();
3368 this.AppendAnyText(Elements, Text);
3370 ch2 = (char)0xffff;
3371 break;
3372
3373 default:
3374 State.RestoreState();
3375 break;
3376 }
3377 break;
3378
3379 case ')':
3380 State.NextCharSameRow();
3381 this.AppendAnyText(Elements, Text);
3383 ch2 = (char)0xffff;
3384 break;
3385
3386 default:
3387 break;
3388 }
3389 }
3390
3391 if (ch2 == (char)0xffff)
3392 break;
3393 }
3394
3395 if (State.IsFirstCharOnLine)
3396 {
3397 StringBuilder sb = new StringBuilder();
3398 sb.Append(ch);
3399
3400 while ((ch2 = State.PeekNextCharSameRow()) >= '0' && ch2 <= '9')
3401 {
3402 State.NextCharSameRow();
3403 sb.Append(ch2);
3404 }
3405
3406 if (ch2 == '.')
3407 {
3408 State.NextCharSameRow();
3409 if ((((ch2 = State.PeekNextCharSameRow()) <= ' ' && ch2 > 0) || ch2 == 160) &&
3410 int.TryParse(sb.ToString(), out int Index))
3411 {
3412 this.AppendAnyText(Elements, Text);
3413
3414 while (((ch2 = State.PeekNextCharSameRow()) <= ' ' && ch2 > 0) || ch2 == 160)
3415 State.NextCharSameRow();
3416
3417 NumberedItem Item;
3419 ChunkedList<int> Positions = new ChunkedList<int>()
3420 {
3421 State.CurrentPosition
3422 };
3423
3424 Rows.Add(State.RestOfRow());
3425
3426 while (!State.EOF)
3427 {
3428 if ((ch2 = State.PeekNextCharSameRow()) >= '0' && ch2 <= '9')
3429 {
3430 sb.Clear();
3431 while ((ch2 = State.NextCharSameRow()) >= '0' && ch2 <= '9')
3432 sb.Append(ch2);
3433
3434 if (ch2 == '.' && int.TryParse(sb.ToString(), out int Index2))
3435 {
3436 Item = new NumberedItem(this, Index, true, new NestedBlock(this, await this.ParseBlock(Rows.ToArray(), Positions.ToArray())));
3437
3439 NumberedList.AddChild(Item);
3440 else
3441 Elements.Add(new NumberedList(this, Item));
3442
3443 State.NextCharSameRow();
3444 State.SkipWhitespaceSameRow(3);
3445
3446 Rows.Clear();
3447 Positions.Clear();
3448
3449 Positions.Add(State.CurrentPosition);
3450 Rows.Add(State.RestOfRow());
3451 Index = Index2;
3452 }
3453 else
3454 {
3455 State.SkipWhitespaceSameRow(4);
3456
3457 string s = sb.ToString();
3458 Positions.Add(State.CurrentPosition - 1 - s.Length);
3459 Rows.Add(s + ch2 + State.RestOfRow());
3460 }
3461 }
3462 else
3463 {
3464 State.SkipWhitespaceSameRow(4);
3465
3466 Positions.Add(State.CurrentPosition);
3467 Rows.Add(State.RestOfRow());
3468 }
3469 }
3470
3471 if (Rows.Count > 0)
3472 {
3473 Item = new NumberedItem(this, Index, true, new NestedBlock(this, await this.ParseBlock(Rows.ToArray(), Positions.ToArray())));
3474
3476 NumberedList.AddChild(Item);
3477 else
3478 Elements.Add(new NumberedList(this, Item));
3479 }
3480 }
3481 else
3482 {
3483 Text.Append(sb.ToString());
3484 Text.Append('.');
3485 }
3486 }
3487 else
3488 Text.Append(sb.ToString());
3489 }
3490 else
3491 Text.Append(ch);
3492 break;
3493
3494 case '=':
3495 ch2 = State.PeekNextCharSameRow();
3496 if (this.emojiSource is null && ch2 != '=')
3497 ch2 = (char)0;
3498
3499 switch (ch2)
3500 {
3501 case '=':
3502 State.NextCharSameRow();
3503 this.AppendAnyText(Elements, Text);
3504
3505 ch3 = State.PeekNextCharSameRow();
3506 if (ch3 == '>')
3507 {
3508 State.NextCharSameRow();
3509 Elements.Add(new HtmlEntity(this, "rArr"));
3510 }
3511 else
3512 Elements.Add(new HtmlEntity(this, "equiv"));
3513 break;
3514
3515 case 'D':
3516 State.NextCharSameRow();
3517 this.AppendAnyText(Elements, Text);
3519 break;
3520
3521 case ')':
3522 case ']':
3523 State.NextCharSameRow();
3524 this.AppendAnyText(Elements, Text);
3526 break;
3527
3528 case '*':
3529 State.NextCharSameRow();
3530 this.AppendAnyText(Elements, Text);
3532 break;
3533
3534 case '(':
3535 case '[':
3536 State.NextCharSameRow();
3537 this.AppendAnyText(Elements, Text);
3539 break;
3540
3541 case '$':
3542 State.NextCharSameRow();
3543 this.AppendAnyText(Elements, Text);
3545 break;
3546
3547 case '/':
3548 case '\\':
3549 case 'L':
3550 State.NextCharSameRow();
3551 this.AppendAnyText(Elements, Text);
3553 break;
3554
3555 case 'P':
3556 case 'p':
3557 case 'b':
3558 case 'Þ':
3559 case 'þ':
3560 State.NextCharSameRow();
3561 this.AppendAnyText(Elements, Text);
3563 break;
3564
3565 case 'X':
3566 case 'x':
3567 case '#':
3568 State.NextCharSameRow();
3569 this.AppendAnyText(Elements, Text);
3571 break;
3572
3573 default:
3574 Text.Append('=');
3575 break;
3576 }
3577 break;
3578
3579 case '&':
3580 if (char.IsLetter(ch2 = State.PeekNextCharSameRow()))
3581 {
3582 this.AppendAnyText(Elements, Text);
3583
3584 Text.Append('&');
3585 while (char.IsLetter(ch2 = State.NextCharSameRow()))
3586 Text.Append(ch2);
3587
3588 if (ch2 != 0)
3589 Text.Append(ch2);
3590
3591 if (ch2 != ';')
3592 break;
3593
3594 Url = Text.ToString();
3595 Text.Clear();
3596
3597 Elements.Add(new HtmlEntity(this, Url.Substring(1, Url.Length - 2)));
3598 }
3599 else if (ch2 == '#')
3600 {
3601 int Code;
3602
3603 this.AppendAnyText(Elements, Text);
3604 State.NextCharSameRow();
3605
3606 Text.Append("&#");
3607
3608 if ((ch3 = State.PeekNextCharSameRow()) == 'x' || ch3 == 'X')
3609 {
3610 Text.Append(ch3);
3611 State.NextCharSameRow();
3612
3613 while (((ch3 = char.ToUpper(State.PeekNextCharSameRow())) >= '0' && ch3 <= '9') || (ch3 >= 'A' && ch3 <= 'F'))
3614 {
3615 State.NextCharSameRow();
3616 Text.Append(ch3);
3617 }
3618
3619 if (ch3 == ';' && int.TryParse(Text.ToString().Substring(3), System.Globalization.NumberStyles.HexNumber, null, out Code))
3620 {
3621 State.NextCharSameRow();
3622 Text.Clear();
3623
3624 Elements.Add(new HtmlEntityUnicode(this, Code));
3625 }
3626 }
3627 else if (char.IsDigit(ch3))
3628 {
3629 while (char.IsDigit(ch3 = State.PeekNextCharSameRow()))
3630 {
3631 State.NextCharSameRow();
3632 Text.Append(ch3);
3633 }
3634
3635 if (ch3 == ';' && int.TryParse(Text.ToString().Substring(2), out Code))
3636 {
3637 State.NextCharSameRow();
3638 Text.Clear();
3639
3640 Elements.Add(new HtmlEntityUnicode(this, Code));
3641 }
3642 }
3643 }
3644 else
3645 Text.Append(ch);
3646
3647 break;
3648
3649 case '"':
3650 this.AppendAnyText(Elements, Text);
3651 if (IsLeftQuote(PrevChar, State.PeekNextCharSameRow()))
3652 Elements.Add(new HtmlEntity(this, "ldquo"));
3653 else
3654 Elements.Add(new HtmlEntity(this, "rdquo"));
3655 break;
3656
3657 case '\'':
3658 this.AppendAnyText(Elements, Text);
3659
3660 if (!(this.emojiSource is null))
3661 ch2 = State.PeekNextCharSameRow();
3662 else
3663 ch2 = (char)0;
3664
3665 switch (ch2)
3666 {
3667 case ':':
3668 State.NextCharSameRow();
3669 switch (State.PeekNextCharSameRow())
3670 {
3671 case ')':
3672 case 'D':
3673 State.NextCharSameRow();
3675 break;
3676
3677 case '(':
3678 State.NextCharSameRow();
3680 break;
3681
3682 case '-':
3683 State.NextCharSameRow();
3684 switch (State.PeekNextCharSameRow())
3685 {
3686 case ')':
3687 case 'D':
3688 State.NextCharSameRow();
3690 break;
3691
3692 case '(':
3693 State.NextCharSameRow();
3695 break;
3696
3697 default:
3698 if (IsLeftQuote(PrevChar, State.PeekNextCharSameRow()))
3699 Elements.Add(new HtmlEntity(this, "lsquo"));
3700 else
3701 Elements.Add(new HtmlEntity(this, "rsquo"));
3702
3703 Text.Append(":-");
3704 break;
3705 }
3706 break;
3707
3708 default:
3709 if (IsLeftQuote(PrevChar, State.PeekNextCharSameRow()))
3710 Elements.Add(new HtmlEntity(this, "lsquo"));
3711 else
3712 Elements.Add(new HtmlEntity(this, "rsquo"));
3713
3714 Text.Append(':');
3715 break;
3716 }
3717 break;
3718
3719 case '=':
3720 State.NextCharSameRow();
3721 switch (State.PeekNextCharSameRow())
3722 {
3723 case ')':
3724 case 'D':
3725 State.NextCharSameRow();
3727 break;
3728
3729 case '(':
3730 State.NextCharSameRow();
3732 break;
3733
3734 default:
3735 if (IsLeftQuote(PrevChar, State.PeekNextCharSameRow()))
3736 Elements.Add(new HtmlEntity(this, "lsquo"));
3737 else
3738 Elements.Add(new HtmlEntity(this, "rsquo"));
3739
3740 Text.Append('=');
3741 break;
3742 }
3743 break;
3744
3745 default:
3746 if (IsLeftQuote(PrevChar, State.PeekNextCharSameRow()))
3747 Elements.Add(new HtmlEntity(this, "lsquo"));
3748 else
3749 Elements.Add(new HtmlEntity(this, "rsquo"));
3750 break;
3751 }
3752 break;
3753
3754 case '.':
3755 if (State.PeekNextCharSameRow() == '.')
3756 {
3757 State.NextCharSameRow();
3758 if (State.PeekNextCharSameRow() == '.')
3759 {
3760 State.NextCharSameRow();
3761 this.AppendAnyText(Elements, Text);
3762
3763 Elements.Add(new HtmlEntity(this, "hellip"));
3764 }
3765 else
3766 Text.Append("..");
3767 }
3768 else
3769 Text.Append('.');
3770 break;
3771
3772 case '(':
3773 ch2 = State.PeekNextCharSameRow();
3774 ch3 = char.ToLower(ch2);
3775 if (ch3 == 'c' || ch3 == 'r' || ch3 == 'p' || ch3 == 's')
3776 {
3777 State.NextCharSameRow();
3778 if (State.PeekNextCharSameRow() == ')')
3779 {
3780 State.NextCharSameRow();
3781
3782 this.AppendAnyText(Elements, Text);
3783 switch (ch2)
3784 {
3785 case 'c':
3786 Url = "copy";
3787 break;
3788
3789 case 'C':
3790 Url = "COPY";
3791 break;
3792
3793 case 'r':
3794 Url = "reg";
3795 break;
3796
3797 case 'R':
3798 Url = "REG";
3799 break;
3800
3801 case 'p':
3802 Url = "copysr";
3803 break;
3804
3805 case 'P':
3806 Url = "copysr";
3807 break;
3808
3809 case 's':
3810 Url = "oS";
3811 break;
3812
3813 case 'S':
3814 Url = "circledS";
3815 break;
3816
3817 default:
3818 Url = null;
3819 break;
3820 }
3821
3822 Elements.Add(new HtmlEntity(this, Url));
3823 }
3824 else
3825 {
3826 Text.Append('(');
3827 Text.Append(ch2);
3828 }
3829 }
3830 else
3831 Text.Append('(');
3832 break;
3833
3834 case '%':
3835 switch (State.PeekNextCharSameRow())
3836 {
3837 case '0':
3838 State.NextCharSameRow();
3839 this.AppendAnyText(Elements, Text);
3840
3841 ch3 = State.PeekNextCharSameRow();
3842 if (ch3 == '0')
3843 {
3844 State.NextCharSameRow();
3845 Elements.Add(new HtmlEntity(this, "pertenk"));
3846 }
3847 else
3848 Elements.Add(new HtmlEntity(this, "permil"));
3849 break;
3850
3851 case '-':
3852 if (!(this.emojiSource is null))
3853 {
3854 State.BackupState();
3855 State.NextCharSameRow();
3856 switch (State.PeekNextCharSameRow())
3857 {
3858 case ')':
3859 State.DiscardBackup();
3860 State.NextCharSameRow();
3861 this.AppendAnyText(Elements, Text);
3863 break;
3864
3865 default:
3866 State.RestoreState();
3867 Text.Append(ch);
3868 break;
3869 }
3870 }
3871 else
3872 Text.Append('%');
3873 break;
3874
3875 case ')':
3876 if (!(this.emojiSource is null))
3877 {
3878 State.NextCharSameRow();
3879 this.AppendAnyText(Elements, Text);
3881 }
3882 else
3883 Text.Append('%');
3884 break;
3885
3886 default:
3887 Text.Append('%');
3888 break;
3889 }
3890 break;
3891
3892 case '^':
3893 ch2 = State.PeekNextCharSameRow();
3894 switch (ch2)
3895 {
3896 case 'a':
3897 State.NextCharSameRow();
3898 this.AppendAnyText(Elements, Text);
3899 Elements.Add(new HtmlEntity(this, "ordf"));
3900 break;
3901
3902 case 'o':
3903 State.NextCharSameRow();
3904 this.AppendAnyText(Elements, Text);
3905 Elements.Add(new HtmlEntity(this, "ordm"));
3906 break;
3907
3908 case '0':
3909 State.NextCharSameRow();
3910 this.AppendAnyText(Elements, Text);
3911 Elements.Add(new HtmlEntity(this, "deg"));
3912 break;
3913
3914 case '1':
3915 State.NextCharSameRow();
3916 this.AppendAnyText(Elements, Text);
3917 Elements.Add(new HtmlEntityUnicode(this, 185));
3918 break;
3919
3920 case '2':
3921 State.NextCharSameRow();
3922 this.AppendAnyText(Elements, Text);
3923 Elements.Add(new HtmlEntityUnicode(this, 178));
3924 break;
3925
3926 case '3':
3927 State.NextCharSameRow();
3928 this.AppendAnyText(Elements, Text);
3929 Elements.Add(new HtmlEntityUnicode(this, 179));
3930 break;
3931
3932 case '4':
3933 case '5':
3934 case '6':
3935 case '7':
3936 case '8':
3937 case '9':
3938 case 'b':
3939 case 'c':
3940 case 'd':
3941 case 'e':
3942 case 'f':
3943 case 'g':
3944 case 'h':
3945 case 'i':
3946 case 'j':
3947 case 'k':
3948 case 'l':
3949 case 'm':
3950 case 'p':
3951 case 'q':
3952 case 'u':
3953 case 'v':
3954 case 'w':
3955 case 'x':
3956 case 'y':
3957 case 'z':
3958 case 'A':
3959 case 'B':
3960 case 'C':
3961 case 'D':
3962 case 'E':
3963 case 'F':
3964 case 'G':
3965 case 'H':
3966 case 'I':
3967 case 'J':
3968 case 'K':
3969 case 'L':
3970 case 'M':
3971 case 'N':
3972 case 'O':
3973 case 'P':
3974 case 'Q':
3975 case 'R':
3976 case 'S':
3977 case 'U':
3978 case 'V':
3979 case 'W':
3980 case 'X':
3981 case 'Y':
3982 case 'Z':
3983 State.NextCharSameRow();
3984 this.AppendAnyText(Elements, Text);
3985 Elements.Add(new SuperScript(this, new string(ch2, 1)));
3986 break;
3987
3988 case 'T':
3989 State.NextCharSameRow();
3990 this.AppendAnyText(Elements, Text);
3991
3992 if (State.PeekNextCharSameRow() == 'M')
3993 {
3994 State.NextCharSameRow();
3995 Elements.Add(new HtmlEntity(this, "trade"));
3996 }
3997 else
3998 Elements.Add(new SuperScript(this, "T"));
3999 break;
4000
4001 case 's':
4002 State.NextCharSameRow();
4003 this.AppendAnyText(Elements, Text);
4004
4005 if (State.PeekNextCharSameRow() == 't')
4006 {
4007 State.NextCharSameRow();
4008 Elements.Add(new SuperScript(this, "st"));
4009 }
4010 else
4011 Elements.Add(new SuperScript(this, "s"));
4012 break;
4013
4014 case 'n':
4015 State.NextCharSameRow();
4016 this.AppendAnyText(Elements, Text);
4017
4018 if (State.PeekNextCharSameRow() == 'd')
4019 {
4020 State.NextCharSameRow();
4021 Elements.Add(new SuperScript(this, "nd"));
4022 }
4023 else
4024 Elements.Add(new SuperScript(this, "n"));
4025 break;
4026
4027 case 'r':
4028 State.NextCharSameRow();
4029 this.AppendAnyText(Elements, Text);
4030
4031 if (State.PeekNextCharSameRow() == 'd')
4032 {
4033 State.NextCharSameRow();
4034 Elements.Add(new SuperScript(this, "rd"));
4035 }
4036 else
4037 Elements.Add(new SuperScript(this, "r"));
4038 break;
4039
4040 case 't':
4041 State.NextCharSameRow();
4042 this.AppendAnyText(Elements, Text);
4043
4044 if (State.PeekNextCharSameRow() == 'h')
4045 {
4046 State.NextCharSameRow();
4047 Elements.Add(new SuperScript(this, "th"));
4048 }
4049 else
4050 Elements.Add(new SuperScript(this, "t"));
4051 break;
4052
4053 case '(':
4054 State.NextCharSameRow();
4055 this.AppendAnyText(Elements, Text);
4056
4057 ChildElements = new ChunkedList<MarkdownElement>();
4058
4059 await this.ParseBlock(State, ')', 1, ChildElements, true);
4060 Elements.Add(new SuperScript(this, ChildElements));
4061 break;
4062
4063 case '[':
4064 State.NextCharSameRow();
4065 this.AppendAnyText(Elements, Text);
4066
4067 ChildElements = new ChunkedList<MarkdownElement>();
4068
4069 await this.ParseBlock(State, ']', 1, ChildElements, true);
4070 Elements.Add(new SuperScript(this, ChildElements));
4071 break;
4072
4073 default:
4074 Text.Append('^');
4075 break;
4076 }
4077 break;
4078
4079 case ':':
4080 if ((ch2 = State.PeekNextCharSameRow()) <= ' ' || ch2 == 160)
4081 {
4082 if (State.IsFirstCharOnLine && ch2 > 0)
4083 {
4084 ChunkedList<MarkdownElement> TotItem = null;
4087 int i;
4088
4089 for (i = State.Start; i < State.Current; i++)
4090 {
4091 Item = await this.ParseBlock(State.Rows, State.Positions, i, i);
4092 if (!Item.HasFirstItem)
4093 continue;
4094
4095 if (TotItem is null)
4096 {
4097 if (Item.Count == 1)
4098 TotItem = Item;
4099 else
4100 TotItem.Add(Item.FirstItem);
4101 }
4102 else
4103 {
4104 if (TotItem is null)
4105 TotItem = new ChunkedList<MarkdownElement>();
4106
4107 TotItem.Add(new NestedBlock(this, Item));
4108 }
4109 }
4110
4111 if (TotItem is null)
4112 Text.Append(ch);
4113 else
4114 {
4115 DefinitionList.AddChild(new DefinitionTerms(this, TotItem));
4116
4117 Text.Clear();
4118 Elements.Clear();
4120
4121 while (((ch2 = State.PeekNextCharSameRow()) <= ' ' && ch2 > 0) || ch2 == 160)
4122 State.NextCharSameRow();
4123
4125 ChunkedList<int> Positions = new ChunkedList<int>()
4126 {
4127 State.CurrentPosition
4128 };
4129
4130 Rows.Add(State.RestOfRow());
4131
4132 while (!State.EOF)
4133 {
4134 if (State.PeekNextCharSameRow() == ':')
4135 {
4136 DefinitionList.AddChild(new DefinitionDescriptions(this, new NestedBlock(this, await this.ParseBlock(Rows.ToArray(), Positions.ToArray()))));
4137
4138 State.NextCharSameRow();
4139 State.SkipWhitespaceSameRow(3);
4140
4141 Rows.Clear();
4142 Positions.Clear();
4143
4144 Positions.Add(State.CurrentPosition);
4145 Rows.Add(State.RestOfRow());
4146 }
4147 else
4148 {
4149 State.SkipWhitespaceSameRow(4);
4150
4151 Positions.Add(State.CurrentPosition);
4152 Rows.Add(State.RestOfRow());
4153 }
4154 }
4155
4156 if (Rows.Count > 0)
4157 DefinitionList.AddChild(new DefinitionDescriptions(this, new NestedBlock(this, await this.ParseBlock(Rows.ToArray(), Positions.ToArray()))));
4158 }
4159 }
4160 else
4161 Text.Append(ch);
4162 }
4163 else if (!(this.emojiSource is null))
4164 {
4165 int LeftLevel = 1;
4166 while (ch2 == ':')
4167 {
4168 LeftLevel++;
4169 State.NextCharSameRow();
4170 ch2 = State.PeekNextCharSameRow();
4171 }
4172
4173 if (char.IsLetter(ch2) || char.IsDigit(ch2) || ch2 == '+')
4174 {
4175 this.AppendAnyText(Elements, Text);
4176 State.NextCharSameRow();
4177
4178 ch3 = State.PeekNextCharSameRow();
4179 if (char.IsLetter(ch3) || char.IsDigit(ch3) || ch3 == '_' || ch3 == '-' || ch3 == ':')
4180 Text.Append(ch2);
4181 else
4182 {
4183 switch (ch2)
4184 {
4185 case 'D':
4186 State.NextCharSameRow();
4187 if (LeftLevel > 1)
4188 Text.Append(new string(':', LeftLevel - 1));
4189
4190 this.AppendAnyText(Elements, Text);
4192 break;
4193
4194 case 'L':
4195 State.NextCharSameRow();
4196 if (LeftLevel > 1)
4197 Text.Append(new string(':', LeftLevel - 1));
4198
4199 this.AppendAnyText(Elements, Text);
4201 break;
4202
4203 case 'P':
4204 case 'p':
4205 case 'b':
4206 case 'Þ':
4207 case 'þ':
4208 State.NextCharSameRow();
4209 if (LeftLevel > 1)
4210 Text.Append(new string(':', LeftLevel - 1));
4211
4212 this.AppendAnyText(Elements, Text);
4214 break;
4215
4216 case 'O':
4217 case 'o':
4218 State.NextCharSameRow();
4219 if (LeftLevel > 1)
4220 Text.Append(new string(':', LeftLevel - 1));
4221
4222 this.AppendAnyText(Elements, Text);
4224 break;
4225
4226 case 'X':
4227 case 'x':
4228 State.NextCharSameRow();
4229 if (LeftLevel > 1)
4230 Text.Append(new string(':', LeftLevel - 1));
4231
4232 this.AppendAnyText(Elements, Text);
4234 break;
4235
4236 default:
4237 Text.Append(ch2);
4238 ch2 = (char)0;
4239 break;
4240 }
4241
4242 if (ch2 != 0)
4243 break;
4244 }
4245
4246 while (char.IsLetter(ch3 = State.PeekNextCharSameRow()) || char.IsDigit(ch3) || ch3 == '_' || ch3 == '-')
4247 {
4248 State.NextCharSameRow();
4249 Text.Append(ch3);
4250 }
4251
4252 if (ch3 == ':')
4253 {
4254 int RightLevel = 0;
4255
4256 while (ch3 == ':' && RightLevel < LeftLevel)
4257 {
4258 RightLevel++;
4259 State.NextCharSameRow();
4260 ch3 = State.PeekNextCharSameRow();
4261 }
4262
4263 Title = Text.ToString().ToLower();
4264
4265 if (EmojiUtilities.TryGetEmoji(Title, out EmojiInfo Emoji))
4266 {
4267 if (LeftLevel > RightLevel)
4268 Elements.Add(new InlineText(this, new string(':', LeftLevel - RightLevel)));
4269
4270 Elements.Add(new EmojiReference(this, Emoji, RightLevel));
4271 Text.Clear();
4272 }
4273 else
4274 {
4275 Text.Insert(0, new string(':', LeftLevel));
4276 Text.Append(new string(':', RightLevel));
4277 }
4278 }
4279 else
4280 Text.Insert(0, new string(':', LeftLevel));
4281 }
4282 else
4283 {
4284 if (LeftLevel > 1)
4285 Text.Append(new string(':', LeftLevel - 1));
4286
4287 switch (ch2)
4288 {
4289 case '\'':
4290 State.NextCharSameRow();
4291
4292 switch (State.PeekNextCharSameRow())
4293 {
4294 case ')':
4295 State.NextCharSameRow();
4296 this.AppendAnyText(Elements, Text);
4298 break;
4299
4300 case '(':
4301 State.NextCharSameRow();
4302 this.AppendAnyText(Elements, Text);
4304 break;
4305
4306 case '-':
4307 State.NextCharSameRow();
4308 if ((ch3 = State.PeekNextCharSameRow()) == ')')
4309 {
4310 State.NextCharSameRow();
4311 this.AppendAnyText(Elements, Text);
4313 }
4314 else if (ch3 == '(')
4315 {
4316 State.NextCharSameRow();
4317 this.AppendAnyText(Elements, Text);
4319 }
4320 else
4321 Text.Append(":'-");
4322 break;
4323
4324 default:
4325 Text.Append(":'");
4326 break;
4327 }
4328 break;
4329
4330 case '-':
4331 State.NextCharSameRow();
4332
4333 switch (State.PeekNextCharSameRow())
4334 {
4335 case ')':
4336 case ']':
4337 State.NextCharSameRow();
4338 this.AppendAnyText(Elements, Text);
4340 break;
4341
4342 case '(':
4343 case '[':
4344 State.NextCharSameRow();
4345 this.AppendAnyText(Elements, Text);
4347 break;
4348
4349 case 'D':
4350 State.NextCharSameRow();
4351 this.AppendAnyText(Elements, Text);
4353 break;
4354
4355 case '*':
4356 State.NextCharSameRow();
4357 this.AppendAnyText(Elements, Text);
4359 break;
4360
4361 case '/':
4362 case '.':
4363 case '\\':
4364 case 'L':
4365 State.NextCharSameRow();
4366 this.AppendAnyText(Elements, Text);
4368 break;
4369
4370 case 'P':
4371 case 'p':
4372 case 'b':
4373 case 'Þ':
4374 case 'þ':
4375 State.NextCharSameRow();
4376 this.AppendAnyText(Elements, Text);
4378 break;
4379
4380 case 'O':
4381 case 'o':
4382 State.NextCharSameRow();
4383 this.AppendAnyText(Elements, Text);
4385 break;
4386
4387 case 'X':
4388 case 'x':
4389 case '#':
4390 State.NextCharSameRow();
4391 this.AppendAnyText(Elements, Text);
4393 break;
4394
4395 case '-':
4396 State.NextCharSameRow();
4397 if ((ch3 = State.PeekNextCharSameRow()) == ')')
4398 {
4399 State.NextCharSameRow();
4400 this.AppendAnyText(Elements, Text);
4402 }
4403 else if (ch3 == '(')
4404 {
4405 State.NextCharSameRow();
4406 this.AppendAnyText(Elements, Text);
4408 }
4409 else
4410 Text.Append(":--");
4411 break;
4412
4413 case '1':
4414 State.NextCharSameRow();
4415 if (State.PeekNextCharSameRow() == ':')
4416 {
4417 State.NextCharSameRow();
4418 this.AppendAnyText(Elements, Text);
4420 }
4421 else
4422 Text.Append(":-1");
4423 break;
4424
4425 default:
4426 Text.Append(":-");
4427 break;
4428 }
4429 break;
4430
4431 case ')':
4432 case ']':
4433 State.NextCharSameRow();
4434 this.AppendAnyText(Elements, Text);
4436 break;
4437
4438 case '(':
4439 case '[':
4440 State.NextCharSameRow();
4441 this.AppendAnyText(Elements, Text);
4443 break;
4444
4445 case '*':
4446 State.NextCharSameRow();
4447 this.AppendAnyText(Elements, Text);
4449 break;
4450
4451 case '/':
4452 case '\\':
4453 State.NextCharSameRow();
4454 this.AppendAnyText(Elements, Text);
4456 break;
4457
4458 case '#':
4459 State.NextCharSameRow();
4460 this.AppendAnyText(Elements, Text);
4462 break;
4463
4464 case '@':
4465 State.NextCharSameRow();
4466 this.AppendAnyText(Elements, Text);
4468 break;
4469
4470 case '$':
4471 State.NextCharSameRow();
4472 this.AppendAnyText(Elements, Text);
4474 break;
4475
4476 case '^':
4477 State.NextCharSameRow();
4478 if (State.PeekNextCharSameRow() == '*')
4479 {
4480 State.NextCharSameRow();
4481 this.AppendAnyText(Elements, Text);
4483 }
4484 else
4485 Text.Append(":^");
4486 break;
4487
4488 default:
4489 Text.Append(ch);
4490 break;
4491 }
4492 }
4493 }
4494 else
4495 Text.Append(ch);
4496 break;
4497
4498 case ';':
4499 if (!(this.emojiSource is null))
4500 {
4501 switch (State.PeekNextCharSameRow())
4502 {
4503 case ')':
4504 case ']':
4505 case 'D':
4506 State.NextCharSameRow();
4507 this.AppendAnyText(Elements, Text);
4509 break;
4510
4511 case '(':
4512 case '[':
4513 State.NextCharSameRow();
4514 this.AppendAnyText(Elements, Text);
4516 break;
4517
4518 case '-':
4519 State.NextCharSameRow();
4520 switch (State.PeekNextCharSameRow())
4521 {
4522 case ')':
4523 case ']':
4524 State.NextCharSameRow();
4525 this.AppendAnyText(Elements, Text);
4527 break;
4528
4529 case '(':
4530 case '[':
4531 State.NextCharSameRow();
4532 this.AppendAnyText(Elements, Text);
4534 break;
4535
4536 default:
4537 Text.Append(";-");
4538 break;
4539 }
4540 break;
4541
4542 case '^':
4543 State.NextCharSameRow();
4544 if (State.PeekNextCharSameRow() == ')')
4545 {
4546 State.NextCharSameRow();
4547 this.AppendAnyText(Elements, Text);
4549 }
4550 else
4551 Text.Append(";^");
4552 break;
4553
4554 default:
4555 Text.Append(';');
4556 break;
4557 }
4558 }
4559 else
4560 Text.Append(ch);
4561 break;
4562
4563 case 'X':
4564 case 'x':
4565 if (!(this.emojiSource is null) && (char.IsPunctuation(PrevChar) || char.IsWhiteSpace(PrevChar)))
4566 {
4567 switch (State.PeekNextCharSameRow())
4568 {
4569 case '-':
4570 State.BackupState();
4571 State.NextCharSameRow();
4572 switch (State.PeekNextCharSameRow())
4573 {
4574 case 'P':
4575 case 'p':
4576 case 'b':
4577 case 'Þ':
4578 case 'þ':
4579 State.DiscardBackup();
4580 State.NextCharSameRow();
4581 this.AppendAnyText(Elements, Text);
4583 break;
4584
4585 case ')':
4586 State.DiscardBackup();
4587 State.NextCharSameRow();
4588 this.AppendAnyText(Elements, Text);
4590 break;
4591
4592 default:
4593 State.RestoreState();
4594 Text.Append(ch);
4595 break;
4596 }
4597 break;
4598
4599 case ')':
4600 State.NextCharSameRow();
4601 this.AppendAnyText(Elements, Text);
4603 break;
4604
4605 default:
4606 Text.Append(ch);
4607 break;
4608 }
4609 }
4610 else
4611 Text.Append(ch);
4612 break;
4613
4614 case 'B':
4615 if (!(this.emojiSource is null) && (char.IsPunctuation(PrevChar) || char.IsWhiteSpace(PrevChar)))
4616 {
4617 switch (State.PeekNextCharSameRow())
4618 {
4619 case '-':
4620 State.BackupState();
4621 State.NextCharSameRow();
4622 switch (State.PeekNextCharSameRow())
4623 {
4624 case ')':
4625 case 'D':
4626 State.DiscardBackup();
4627 State.NextCharSameRow();
4628 this.AppendAnyText(Elements, Text);
4630 break;
4631
4632 default:
4633 State.RestoreState();
4634 Text.Append(ch);
4635 break;
4636 }
4637 break;
4638
4639 case ')':
4640 State.NextCharSameRow();
4641 this.AppendAnyText(Elements, Text);
4643 break;
4644
4645 default:
4646 Text.Append(ch);
4647 break;
4648 }
4649 }
4650 else
4651 Text.Append(ch);
4652 break;
4653
4654 case 'd':
4655 if (!(this.emojiSource is null) && (char.IsPunctuation(PrevChar) || char.IsWhiteSpace(PrevChar)) && State.PeekNextCharSameRow() == ':')
4656 {
4657 State.NextCharSameRow();
4658 this.AppendAnyText(Elements, Text);
4660 }
4661 else
4662 Text.Append(ch);
4663 break;
4664
4665 case 'O':
4666 if (!(this.emojiSource is null) && (char.IsPunctuation(PrevChar) || char.IsWhiteSpace(PrevChar)))
4667 {
4668 switch (State.PeekNextCharSameRow())
4669 {
4670 case ':':
4671 State.BackupState();
4672 State.NextCharSameRow();
4673 switch (State.NextCharSameRow())
4674 {
4675 case ')':
4676 case '3':
4677 State.DiscardBackup();
4678 this.AppendAnyText(Elements, Text);
4680 break;
4681
4682 case '-':
4683 if ((ch3 = State.NextCharSameRow()) == ')' || ch3 == '3')
4684 {
4685 State.DiscardBackup();
4686 this.AppendAnyText(Elements, Text);
4688 }
4689 else
4690 {
4691 State.RestoreState();
4692 Text.Append(ch);
4693 }
4694 break;
4695
4696 default:
4697 State.RestoreState();
4698 Text.Append(ch);
4699 break;
4700 }
4701 break;
4702
4703 case ';':
4704 State.BackupState();
4705 State.NextCharSameRow();
4706 if (State.NextCharSameRow() == '-')
4707 {
4708 if (State.NextCharSameRow() == ')')
4709 {
4710 State.DiscardBackup();
4711 this.AppendAnyText(Elements, Text);
4713 break;
4714 }
4715 }
4716
4717 State.RestoreState();
4718 Text.Append(ch);
4719 break;
4720
4721 case '=':
4722 State.BackupState();
4723 State.NextCharSameRow();
4724 if (State.PeekNextCharSameRow() == ')')
4725 {
4726 State.NextCharSameRow();
4727 State.DiscardBackup();
4728 this.AppendAnyText(Elements, Text);
4730 }
4731 else
4732 {
4733 State.RestoreState();
4734 Text.Append(ch);
4735 }
4736 break;
4737
4738 case '_':
4739 State.BackupState();
4740 State.NextCharSameRow();
4741 if (State.PeekNextCharSameRow() == 'O')
4742 {
4743 State.NextCharSameRow();
4744 State.DiscardBackup();
4745 this.AppendAnyText(Elements, Text);
4747 }
4748 else
4749 {
4750 State.RestoreState();
4751 Text.Append(ch);
4752 }
4753 break;
4754
4755 default:
4756 Text.Append(ch);
4757 break;
4758 }
4759 }
4760 else
4761 Text.Append(ch);
4762 break;
4763
4764 case 'h':
4765 if (char.IsWhiteSpace(PrevChar) && State.PeekNextCharSameRow() == 't')
4766 {
4767 FirstCharOnLine = State.IsFirstCharOnLine;
4768
4769 chs = State.PeekNextChars(7);
4770 if (chs[1] == 't' && chs[2] == 'p' &&
4771 ((chs[3] == ':' && chs[4] == '/' && chs[5] == '/') ||
4772 (chs[3] == 's' && chs[4] == ':' && chs[5] == '/' && chs[6] == '/')))
4773 {
4774 this.AppendAnyText(Elements, Text);
4775
4776 Text.Clear();
4777 Text.Append('h');
4778
4779 if (chs[3] == ':')
4780 ch2 = (char)6;
4781 else
4782 ch2 = (char)7;
4783
4784 while (ch2 > 0)
4785 {
4786 Text.Append(State.NextChar());
4787 ch2--;
4788 }
4789
4790 while ((ch2 = State.PeekNextCharSameRow()) > ' ' && ch2 != 160)
4791 {
4792 Text.Append(ch2);
4793 State.NextChar();
4794 }
4795
4796 Url = Text.ToString();
4797 Text.Clear();
4798
4799 if (FirstCharOnLine && State.PeekNextNonWhitespaceCharSameRow(false) == 0)
4800 {
4801 IMultimediaContent Handler = Multimedia.GetMultimediaHandler<IMultimediaHtmlRenderer>(Url);
4802 if (!(Handler is null) && Handler.EmbedInlineLink(Url))
4803 {
4804 ChildElements = new ChunkedList<MarkdownElement>
4805 {
4806 new InlineText(this, Url)
4807 };
4808
4809 Multimedia Multimedia = new Multimedia(this, ChildElements, true,
4810 new MultimediaItem(this, Url, string.Empty, null, null));
4811
4813
4814 if (!(this.settings?.Progress is null))
4815 {
4817 if (!(Renderer is null))
4818 await Renderer.Preload(this.settings.Progress, Multimedia.Items);
4819 }
4820
4821 break;
4822 }
4823 }
4824
4825 Elements.Add(new AutomaticLinkUrl(this, Url));
4826 }
4827 else
4828 Text.Append(ch);
4829 }
4830 else
4831 Text.Append(ch);
4832 break;
4833
4834 case '\\':
4835 switch (ch2 = State.PeekNextCharSameRow())
4836 {
4837 case '*':
4838 case '_':
4839 case '~':
4840 case '\\':
4841 case '`':
4842 case '{':
4843 case '}':
4844 case '[':
4845 case ']':
4846 case '(':
4847 case ')':
4848 case '<':
4849 case '>':
4850 case '#':
4851 case '+':
4852 case '-':
4853 case '.':
4854 case '!':
4855 case '\'':
4856 case '"':
4857 case '^':
4858 case '%':
4859 case '&':
4860 case '=':
4861 case ':':
4862 case '|':
4863 case 'h':
4864 Text.Append(ch2);
4865 State.NextCharSameRow();
4866 break;
4867
4868 case '0':
4869 case 'O':
4870 if (!(this.emojiSource is null))
4871 {
4872 State.BackupState();
4873 State.NextCharSameRow();
4874 if (State.PeekNextCharSameRow() == '/')
4875 {
4876 State.DiscardBackup();
4877 State.NextCharSameRow();
4878 this.AppendAnyText(Elements, Text);
4880 }
4881 else
4882 {
4883 State.RestoreState();
4884 Text.Append('\\');
4885 }
4886 }
4887 else
4888 Text.Append('\\');
4889 break;
4890
4891 default:
4892 Text.Append('\\');
4893 break;
4894 }
4895 break;
4896
4897 default:
4898 Text.Append(ch);
4899 break;
4900 }
4901
4902 PrevChar = State.LastCharacter;
4903 }
4904
4905 this.AppendAnyText(Elements, Text);
4906
4907 return (ch == TerminationCharacter) || AcceptIncomplete;
4908 }
4909
4910 private static bool IsLeftQuote(char PrevChar, char NextChar)
4911 {
4912 bool Left = (PrevChar <= ' ' || PrevChar == 160 || char.IsPunctuation(PrevChar) || char.IsSeparator(PrevChar));
4913 bool Right = (NextChar <= ' ' || NextChar == 160 || char.IsPunctuation(NextChar) || char.IsSeparator(NextChar));
4914
4915 if (Left && Right)
4916 {
4917 if (char.IsSeparator(PrevChar))
4918 return true;
4919 else if (char.IsSeparator(NextChar))
4920 return false;
4921 else if (PrevChar == ')' || PrevChar == ']' || PrevChar == '}')
4922 return false;
4923 else if (NextChar == '(' || NextChar == '[' || NextChar == '[')
4924 return true;
4925 else
4926 return false;
4927 }
4928 else
4929 return Left;
4930 }
4931
4932 private static void ParseWidthHeight(BlockParseState State, out int? Width, out int? Height)
4933 {
4934 Width = null;
4935 Height = null;
4936
4937 char ch = State.PeekNextNonWhitespaceCharSameRow(true);
4938 if (ch >= '0' && ch <= '9')
4939 {
4940 StringBuilder Text = new StringBuilder();
4941
4942 Text.Append(ch);
4943 State.NextNonWhitespaceCharSameRow();
4944
4945 ch = State.PeekNextCharSameRow();
4946 while (ch >= '0' && ch <= '9')
4947 {
4948 Text.Append(ch);
4949 State.NextCharSameRow();
4950 ch = State.PeekNextCharSameRow();
4951 }
4952
4953 if (int.TryParse(Text.ToString(), out int i))
4954 {
4955 Width = i;
4956 Text.Clear();
4957
4958 ch = State.PeekNextNonWhitespaceCharSameRow(true);
4959 if (ch >= '0' && ch <= '9')
4960 {
4961 Text.Append(ch);
4962 State.NextNonWhitespaceCharSameRow();
4963
4964 ch = State.PeekNextCharSameRow();
4965 while (ch >= '0' && ch <= '9')
4966 {
4967 Text.Append(ch);
4968 State.NextCharSameRow();
4969 ch = State.PeekNextCharSameRow();
4970 }
4971
4972 if (int.TryParse(Text.ToString(), out i))
4973 Height = i;
4974 }
4975 }
4976 }
4977 }
4978
4979 private void AppendAnyText(ChunkedList<MarkdownElement> Elements, StringBuilder Text)
4980 {
4981 if (Text.Length > 0)
4982 {
4983 string s = Text.ToString();
4984 Text.Clear();
4985
4986 if (Elements.HasFirstItem || !string.IsNullOrEmpty(s.Trim()))
4987 Elements.Add(new InlineText(this, s));
4988 }
4989 }
4990
4991 private void FixSyntaxError(ChunkedList<MarkdownElement> Elements, string Prefix, ChunkedList<MarkdownElement> ChildElements)
4992 {
4993 Elements.Add(new InlineText(this, Prefix));
4994 Elements.AddRange(ChildElements);
4995 }
4996
4997 internal static bool IsPrefixedByNumber(string s, out int Numeral)
4998 {
4999 int i, c = s.Length;
5000 char ch;
5001
5002 i = 0;
5003 while (i < c && char.IsDigit(s[i]))
5004 i++;
5005
5006 if (i == 0)
5007 {
5008 Numeral = 0;
5009 return false;
5010 }
5011
5012 if (!int.TryParse(s.Substring(0, i), out Numeral) || i == c || s[i] != '.')
5013 return false;
5014
5015 i++;
5016 if (i < c && (ch = s[i]) > ' ' && ch != 160)
5017 return false;
5018
5019 return true;
5020 }
5021
5022 internal static bool IsPrefixedBy(string s, string Prefix, bool MustHaveWhiteSpaceAfter)
5023 {
5024 int i;
5025 char ch;
5026
5027 if (!s.StartsWith(Prefix))
5028 return false;
5029
5030 if (MustHaveWhiteSpaceAfter)
5031 {
5032 if (s.Length == (i = Prefix.Length))
5033 return false;
5034
5035 return (ch = s[i]) <= ' ' || ch == 160;
5036 }
5037 else
5038 return true;
5039 }
5040
5041 private static bool IsPrefixedBy(string s, char ch, out int Count, bool MustHaveWhiteSpaceAfter)
5042 {
5043 int c = s.Length;
5044
5045 Count = 0;
5046 while (Count < c && s[Count] == ch)
5047 Count++;
5048
5049 if (Count == 0)
5050 return false;
5051
5052 if (MustHaveWhiteSpaceAfter)
5053 {
5054 if (s.Length == Count)
5055 return false;
5056
5057 return (ch = s[Count]) <= ' ' || ch == 160;
5058 }
5059 else
5060 return true;
5061 }
5062
5063 internal static bool IsSuffixedBy(string s, string Suffix)
5064 {
5065 return s.EndsWith(Suffix);
5066 }
5067
5068 /*private static bool IsSuffixedBy(string s, char ch, out int Count)
5069 {
5070 int c = s.Length;
5071
5072 Count = 0;
5073 while (Count < c && s[c - Count - 1] == ch)
5074 Count++;
5075
5076 if (Count == 0)
5077 return false;
5078
5079 return true;
5080 }*/
5081
5082 private static bool IsUnderline(string s, char ch, bool AllowSpaces, bool OnlyOneSpace)
5083 {
5084 int i, c = s.Length;
5085 bool LastSpace = true;
5086 int Count = 0;
5087 char ch2;
5088
5089 for (i = 0; i < c; i++)
5090 {
5091 ch2 = s[i];
5092 if (ch2 == ch)
5093 {
5094 Count++;
5095 LastSpace = false;
5096 }
5097 else if (ch2 == ' ' || ch2 == 160)
5098 {
5099 if (OnlyOneSpace && (!AllowSpaces || LastSpace))
5100 return false;
5101
5102 LastSpace = true;
5103 }
5104 else
5105 return false;
5106 }
5107
5108 return Count >= 3;
5109 }
5110
5111 private static ChunkedList<Block> ParseTextToBlocks(string MarkdownText)
5112 {
5115 ChunkedList<int> Positions = new ChunkedList<int>();
5116 int FirstLineIndent = 0;
5117 int LineIndent = 0;
5118 int RowStart = 0;
5119 int RowEnd = 0;
5120 int Pos, Len;
5121 char ch;
5122 bool InBlock = false;
5123 bool InRow = false;
5124 bool NonWhitespaceInRow = false;
5125 bool StartsWithHashSigns = false;
5126 bool IsHeader = false;
5127 bool HasRows = false;
5128
5129 Len = MarkdownText.Length;
5130
5131 for (Pos = 0; Pos < Len; Pos++)
5132 {
5133 ch = MarkdownText[Pos];
5134
5135 if (ch == '\n')
5136 {
5137 if (InBlock)
5138 {
5139 if (InRow && NonWhitespaceInRow)
5140 {
5141 if (HasRows &&
5142 LineIndent < FirstLineIndent &&
5143 IsListPrefix(MarkdownText, RowStart))
5144 {
5145 Blocks.Add(new Block(Rows.ToArray(), Positions.ToArray(), FirstLineIndent / 4));
5146 Rows.Clear();
5147 Positions.Clear();
5148 FirstLineIndent = LineIndent;
5149 }
5150
5151 Positions.Add(RowStart);
5152 Rows.Add(MarkdownText.Substring(RowStart, RowEnd - RowStart + 1));
5153 InRow = false;
5154 HasRows = true;
5155
5156 if (IsHeader && Rows.Count == 1)
5157 {
5158 Blocks.Add(new Block(Rows.ToArray(), Positions.ToArray(), FirstLineIndent / 4));
5159 Rows.Clear();
5160 Positions.Clear();
5161 InBlock = false;
5162 HasRows = false;
5163 FirstLineIndent = 0;
5164 }
5165 }
5166 else
5167 {
5168 Blocks.Add(new Block(Rows.ToArray(), Positions.ToArray(), FirstLineIndent / 4));
5169 Rows.Clear();
5170 Positions.Clear();
5171 InBlock = false;
5172 InRow = false;
5173 HasRows = false;
5174 FirstLineIndent = 0;
5175 }
5176 }
5177 else
5178 FirstLineIndent = 0;
5179
5180 LineIndent = 0;
5181 NonWhitespaceInRow = false;
5182 StartsWithHashSigns = false;
5183 IsHeader = false;
5184 }
5185 else if (ch <= ' ' || ch == 160)
5186 {
5187 if (InBlock)
5188 {
5189 if (InRow)
5190 {
5191 RowEnd = Pos;
5192
5193 if (StartsWithHashSigns)
5194 IsHeader = true;
5195 }
5196 else
5197 {
5198 if (LineIndent >= FirstLineIndent)
5199 {
5200 InRow = true;
5201 RowStart = RowEnd = Pos;
5202 }
5203
5204 if (ch == '\t')
5205 LineIndent += 4;
5206 else if (ch == ' ' || ch == 160)
5207 LineIndent++;
5208 }
5209 }
5210 else if (ch == '\t')
5211 FirstLineIndent += 4;
5212 else if (ch == ' ' || ch == 160)
5213 FirstLineIndent++;
5214
5215 StartsWithHashSigns = false;
5216 }
5217 else
5218 {
5219 if (!InRow)
5220 {
5221 InRow = true;
5222 InBlock = true;
5223 RowStart = Pos;
5224
5225 if (ch == '#')
5226 StartsWithHashSigns = true;
5227 }
5228 else if (ch != '#')
5229 StartsWithHashSigns = false;
5230
5231 RowEnd = Pos;
5232 NonWhitespaceInRow = true;
5233 }
5234 }
5235
5236 if (InBlock)
5237 {
5238 if (InRow && NonWhitespaceInRow)
5239 {
5240 Positions.Add(RowStart);
5241 Rows.Add(MarkdownText.Substring(RowStart, RowEnd - RowStart + 1));
5242 //HasRows = true;
5243 }
5244
5245 Blocks.Add(new Block(Rows.ToArray(), Positions.ToArray(), FirstLineIndent / 4));
5246 }
5247
5248 return Blocks;
5249 }
5250
5251 private static bool IsListPrefix(string MarkdownText, int Pos)
5252 {
5253 int c = MarkdownText.Length;
5254 char ch = MarkdownText[Pos++];
5255 bool ExpectPeriod;
5256
5257 if (ch == '*' || ch == '+' || ch == '-')
5258 ExpectPeriod = false;
5259 else if (ch == '#')
5260 ExpectPeriod = true;
5261 else if (ch == '[')
5262 {
5263 ExpectPeriod = false;
5264 if (Pos >= c)
5265 return false;
5266
5267 ch = MarkdownText[Pos++];
5268 if (ch != ' ' && ch != 'x' && ch != 'X')
5269 return false;
5270
5271 if (Pos >= c)
5272 return false;
5273
5274 ch = MarkdownText[Pos++];
5275 if (ch != ']')
5276 return false;
5277 }
5278 else if (ch >= '0' && ch <= '9')
5279 {
5280 ExpectPeriod = true;
5281
5282 while (Pos < c && (ch = MarkdownText[Pos]) >= '0' && ch <= '9')
5283 Pos++;
5284 }
5285 else
5286 return false;
5287
5288 if (ExpectPeriod)
5289 {
5290 if (Pos >= c)
5291 return false;
5292
5293 ch = MarkdownText[Pos++];
5294 if (ch != '.')
5295 return false;
5296 }
5297
5298 if (Pos >= c)
5299 return false;
5300
5301 ch = MarkdownText[Pos++];
5302
5303 return ch <= ' ' || ch == 160;
5304 }
5305
5310 public async Task RenderDocument(IRenderer Output)
5311 {
5312 if (this.metaData.TryGetValue("MASTER", out KeyValuePair<string, bool>[] Master) && Master.Length == 1)
5313 {
5314 await this.LoadMasterIfNotLoaded(Master[0].Key);
5315 this.master.ClearFootnoteReferences();
5316 await Output.RenderDocument(this.master, false);
5317 }
5318 else
5319 {
5320 this.ClearFootnoteReferences();
5321 await Output.RenderDocument(this, false);
5322 }
5323
5324 this.ProcessAsyncTasks();
5325 }
5326
5330 private void ClearFootnoteReferences()
5331 {
5332 if (!(this.footnotes is null))
5333 {
5334 foreach (Footnote Footnote in this.footnotes.Values)
5335 Footnote.Referenced = false;
5336 }
5337 }
5338
5342 public IEnumerable<string> FootnoteOrder => this.footnoteOrder;
5343
5344 private async Task LoadMasterIfNotLoaded(string MasterMetaValue)
5345 {
5346 if (this.master is null)
5347 {
5348 string FileName;
5349
5350 if (!string.IsNullOrEmpty(this.url) &&
5351 Uri.TryCreate(this.url, UriKind.Absolute, out Uri ParsedUri) &&
5352 Uri.TryCreate(ParsedUri, MasterMetaValue, out Uri MasterUri) &&
5353 !(this.settings.ResourceMap is null) &&
5354 this.settings.ResourceMap.TryGetFileName(MasterUri.AbsoluteUri, false, out string s))
5355 {
5356 FileName = s;
5357 }
5358 else if (!string.IsNullOrEmpty(this.fileName) &&
5359 File.Exists(s = this.settings.GetFileName(this.fileName, MasterMetaValue)))
5360 {
5361 FileName = s;
5362 }
5363 else if (!string.IsNullOrEmpty(this.resourceName))
5364 {
5365 FileName = Path.Combine(this.resourceName.Replace('/', Path.DirectorySeparatorChar), MasterMetaValue);
5366 if (!(this.settings.ResourceMap is null) && this.settings.ResourceMap.TryGetFileName(FileName, false, out s))
5367 FileName = s;
5368 }
5369 else
5370 FileName = MasterMetaValue;
5371
5373 this.settings?.Progress?.DependencyTimestamp(File.GetLastWriteTimeUtc(FileName));
5374
5375 this.master = await CreateAsync(MarkdownText, this.settings);
5376 this.master.fileName = FileName;
5377 this.master.syntaxHighlighting |= this.syntaxHighlighting;
5378
5379 if (this.master.metaData.ContainsKey("MASTER"))
5380 {
5381 throw new GenericException("Master documents are not allowed to be embedded in other master documents.",
5382 EventType.Error, FileName, this.fileName);
5383 }
5384
5385 CopyMetaDataTags(this, this.master, true);
5386
5387 this.master.detail = this;
5388 }
5389 }
5390
5391 internal static void CopyMetaDataTags(MarkdownDocument Details, MarkdownDocument Master, bool UpdateMasterPaths)
5392 {
5393 if (UpdateMasterPaths && !string.IsNullOrEmpty(Details.fileName) && !string.IsNullOrEmpty(Master.fileName))
5394 {
5395 string DetailsFileName = Path.GetFullPath(Details.fileName);
5396 string MasterFileName = Path.GetFullPath(Master.fileName);
5397 string DetailsFolder = Path.GetDirectoryName(DetailsFileName);
5398 string MasterFolder = Path.GetDirectoryName(MasterFileName);
5399
5400 if (DetailsFolder != MasterFolder)
5401 {
5402 string Prefix = null;
5403
5404 foreach (KeyValuePair<string, KeyValuePair<string, bool>[]> Meta in Master.metaData)
5405 {
5406 switch (Meta.Key)
5407 {
5408 case "ALTERNATE":
5409 case "COPYRIGHT":
5410 case "CSS":
5411 case "ICON":
5412 case "HELP":
5413 case "IMAGE":
5414 case "INIT":
5415 case "JAVASCRIPT":
5416 case "LOGIN":
5417 case "NEXT":
5418 case "PREV":
5419 case "PREVIOUS":
5420 case "SCRIPT":
5421 case "WEB":
5422 int i, j, k, l, c, d;
5423
5424 for (k = 0, l = Meta.Value.Length; k < l; k++)
5425 {
5426 string s = Meta.Value[k].Key;
5427 if (string.IsNullOrEmpty(s))
5428 continue;
5429
5430 if (s[0] == Path.DirectorySeparatorChar || s[0] == '/')
5431 continue;
5432
5433 if (Prefix is null)
5434 {
5435 string[] DetailsParts = DetailsFolder.Split(Path.DirectorySeparatorChar);
5436 string[] MasterParts = MasterFolder.Split(Path.DirectorySeparatorChar);
5437
5438 i = 0;
5439 c = DetailsParts.Length;
5440 d = MasterParts.Length;
5441
5442 while (i < c && i < d && string.Compare(DetailsParts[i], MasterParts[i], true) == 0)
5443 i++;
5444
5445 StringBuilder sb = new StringBuilder();
5446
5447 j = c - i;
5448
5449 while (j-- > 0)
5450 sb.Append("../");
5451
5452 while (i < d)
5453 {
5454 sb.Append(MasterParts[i++]);
5455 sb.Append('/');
5456 }
5457
5458 Prefix = sb.ToString();
5459 }
5460
5461 Meta.Value[k] = new KeyValuePair<string, bool>(Prefix + s, Meta.Value[k].Value);
5462 }
5463 break;
5464 }
5465 }
5466 }
5467 }
5468
5469 foreach (KeyValuePair<string, KeyValuePair<string, bool>[]> Meta in Details.metaData)
5470 {
5471 if (Master.metaData.TryGetValue(Meta.Key, out KeyValuePair<string, bool>[] Meta0))
5472 Master.metaData[Meta.Key] = Meta0.Join(Meta.Value);
5473 else
5474 Master.metaData[Meta.Key] = Meta.Value;
5475 }
5476 }
5477
5481 internal bool NeedsToDisplayFootnotes
5482 {
5483 get
5484 {
5485 if (this.footnotes is null)
5486 return false;
5487
5488 foreach (Footnote Footnote in this.footnotes.Values)
5489 {
5490 if (Footnote.Referenced)
5491 return true;
5492 }
5493
5494 return false;
5495 }
5496 }
5497
5505 public string CheckURL(string Url, string URL)
5506 {
5507 bool IsRelative = Url.IndexOf(':') < 0;
5508
5509 if (Url.StartsWith("httpx:", StringComparison.OrdinalIgnoreCase) && !string.IsNullOrEmpty(this.settings.HttpxProxy))
5510 {
5511 if (!string.IsNullOrEmpty(this.settings.LocalHttpxResourcePath) &&
5512 Url.StartsWith(this.settings.LocalHttpxResourcePath, StringComparison.OrdinalIgnoreCase))
5513 {
5514 Url = Url.Substring(this.settings.LocalHttpxResourcePath.Length);
5515 if (!Url.StartsWith("/") && this.settings.LocalHttpxResourcePath.EndsWith("/"))
5516 Url = "/" + Url;
5517
5518 IsRelative = true;
5519 }
5520 else
5521 {
5522 Url = this.settings.HttpxProxy.Replace("%URL%", Url);
5523 IsRelative = this.settings.HttpxProxy.IndexOf(':') < 0;
5524 }
5525 }
5526
5527 if (IsRelative && !string.IsNullOrEmpty(URL))
5528 {
5529 if (Uri.TryCreate(new Uri(URL), Url, out Uri AbsoluteUri))
5530 Url = AbsoluteUri.ToString();
5531 }
5532
5533 return Url;
5534 }
5535
5540 public Task<string> GenerateMarkdown()
5541 {
5542 return this.GenerateMarkdown(true);
5543 }
5544
5551 public async Task<string> GenerateMarkdown(bool PortableSyntax)
5552 {
5553 StringBuilder Output = new StringBuilder();
5554 await this.GenerateMarkdown(Output, PortableSyntax);
5555 return Output.ToString();
5556 }
5557
5562 public Task GenerateMarkdown(StringBuilder Output)
5563 {
5564 return this.GenerateMarkdown(Output, true);
5565 }
5566
5573 public async Task GenerateMarkdown(StringBuilder Output, bool PortableSyntax)
5574 {
5575 using (MarkdownRenderer Renderer = new MarkdownRenderer(Output)
5576 {
5577 PortableSyntax = PortableSyntax
5578 })
5579 {
5580 await this.RenderDocument(Renderer);
5581 }
5582 }
5583
5588 public async Task<string> GenerateHTML()
5589 {
5590 StringBuilder Output = new StringBuilder();
5591 await this.GenerateHTML(Output);
5592 return Output.ToString();
5593 }
5594
5599 public Task GenerateHTML(StringBuilder Output)
5600 {
5601 return this.GenerateHTML(Output, new HtmlSettings());
5602 }
5603
5609 public async Task<string> GenerateHTML(HtmlSettings HtmlSettings)
5610 {
5611 StringBuilder Output = new StringBuilder();
5612 await this.GenerateHTML(Output, HtmlSettings);
5613 return Output.ToString();
5614 }
5615
5621 public async Task GenerateHTML(StringBuilder Output, HtmlSettings HtmlSettings)
5622 {
5623 using (HtmlRenderer Renderer = new HtmlRenderer(Output, HtmlSettings))
5624 {
5625 await this.RenderDocument(Renderer);
5626 }
5627 }
5628
5633 public async Task<string> GeneratePlainText()
5634 {
5635 StringBuilder Output = new StringBuilder();
5636 await this.GeneratePlainText(Output);
5637 return Output.ToString();
5638 }
5639
5644 public async Task GeneratePlainText(StringBuilder Output)
5645 {
5646 using (TextRenderer Renderer = new TextRenderer(Output))
5647 {
5648 await this.RenderDocument(Renderer);
5649 }
5650 }
5651
5657 public Multimedia GetReference(string Label)
5658 {
5659 if (this.references.TryGetValue(Label.ToLower(), out Multimedia Result))
5660 return Result;
5661 else
5662 return null;
5663 }
5664
5665 private static readonly char[] whiteSpace = new char[]
5666 {
5667 (char)1, (char)2, (char)3, (char)4, (char)5, (char)6, (char)7, (char)8, (char)9,(char)10,
5668 (char)11, (char)12, (char)13, (char)14, (char)15, (char)16, (char)17, (char)18, (char)19,(char)20,
5669 (char)21, (char)22, (char)23, (char)24, (char)25, (char)26, (char)27, (char)28, (char)29,(char)30,
5670 (char)31, (char)32
5671 };
5672
5676 public Header[] Headers => this.headers.ToArray();
5677
5684 public bool TryGetMetaData(string Key, out KeyValuePair<string, bool>[] Value)
5685 {
5686 return this.metaData.TryGetValue(Key.ToUpper(), out Value);
5687 }
5688
5694 public void AddMetaData(string Key, string Value)
5695 {
5696 if (this.metaData.TryGetValue(Key, out KeyValuePair<string, bool>[] Records))
5697 {
5699 Values.AddRange(Records);
5700 Values.Add(new KeyValuePair<string, bool>(Value.Trim(), Value.EndsWith(" ")));
5701 }
5702 else
5703 this.metaData[Key] = new KeyValuePair<string, bool>[] { new KeyValuePair<string, bool>(Value.Trim(), Value.EndsWith(" ")) };
5704 }
5705
5711 public string[] GetMetaData(string Key)
5712 {
5713 if (!this.metaData.TryGetValue(Key.ToUpper(), out KeyValuePair<string, bool>[] Value))
5714 return Array.Empty<string>();
5715
5716 int i, c = Value.Length;
5717 string[] Result = new string[c];
5718
5719 for (i = 0; i < c; i++)
5720 Result[i] = Value[i].Key;
5721
5722 return Result;
5723 }
5724
5728 public string[] MetaDataKeys
5729 {
5730 get
5731 {
5732 string[] Keys = new string[this.metaData.Count];
5733 this.metaData.Keys.CopyTo(Keys, 0);
5734 return Keys;
5735 }
5736 }
5737
5741 public IEnumerable<KeyValuePair<string, KeyValuePair<string, bool>[]>> MetaData => this.metaData;
5742
5746 public IEnumerable<KeyValuePair<string, Multimedia>> References => this.references;
5747
5751 public string[] Author => this.GetMetaData("Author");
5752
5756 public string[] Copyright => this.GetMetaData("Copyright");
5757
5761 public string[] Previous => this.Merge(this.GetMetaData("Previous"), this.GetMetaData("Prev"));
5762
5763 private string[] Merge(string[] L1, string[] L2)
5764 {
5765 int c1, c2;
5766
5767 if ((c1 = L1.Length) == 0)
5768 return L2;
5769 else if ((c2 = L2.Length) == 0)
5770 return L1;
5771
5772 string[] L = new string[c1 + c2];
5773
5774 Array.Copy(L1, 0, L, 0, c1);
5775 Array.Copy(L2, 0, L, c1, c2);
5776
5777 return L;
5778 }
5779
5783 public string[] Next => this.GetMetaData("Next");
5784
5788 public string[] CSS => this.GetMetaData("CSS");
5789
5793 public string[] JavaScript => this.GetMetaData("JAVASCRIPT");
5794
5798 public string[] Script => this.GetMetaData("SCRIPT");
5799
5805 public string[] InitializationScript => this.GetMetaData("INIT");
5806
5810 public string[] Parameters => this.GetMetaData("PARAMETER");
5811
5815 public string[] Date => this.GetMetaData("Date");
5816
5820 public string[] Description => this.GetMetaData("Description");
5821
5825 public string[] Image => this.GetMetaData("Image");
5826
5830 public string[] Keywords => this.GetMetaData("Keywords");
5831
5835 public string[] Subtitle => this.GetMetaData("Subtitle");
5836
5840 public string[] Title => this.GetMetaData("Title");
5841
5845 public string[] Web => this.GetMetaData("Web");
5846
5850 public string[] Refresh => this.GetMetaData("Refresh");
5851
5855 public string[] UserVariable => this.GetMetaData("UserVariable");
5856
5860 public string[] Login => this.GetMetaData("Login");
5861
5865 public string[] Privileges => this.GetMetaData("Privileges");
5866
5873 public bool TryGetFootnoteNumber(string Key, out int Number)
5874 {
5875 if (this.footnoteNumberByKey is null)
5876 {
5877 Number = 0;
5878 return false;
5879 }
5880 else
5881 return this.footnoteNumberByKey.TryGetValue(Key, out Number);
5882 }
5883
5890 public bool TryGetFootnote(string Key, out Footnote Footnote)
5891 {
5892 if (this.footnotes is null)
5893 {
5894 Footnote = null;
5895 return false;
5896 }
5897 else
5898 return this.footnotes.TryGetValue(Key, out Footnote);
5899 }
5900
5905 public string[] Footnotes
5906 {
5907 get
5908 {
5909 if (this.footnoteOrder is null)
5910 return Array.Empty<string>();
5911 else
5912 return this.footnoteOrder.ToArray();
5913 }
5914 }
5915
5919 public IEmojiSource EmojiSource => this.emojiSource;
5920
5926 public static string Encode(string s)
5927 {
5928 return Functions.MarkdownEncode.EscapeText(s);
5929 }
5930
5934 public bool SyntaxHighlighting => this.syntaxHighlighting;
5935
5939 public string FileName
5940 {
5941 get => this.fileName;
5942 set => this.fileName = value;
5943 }
5944
5949 public string ResourceName
5950 {
5951 get => this.resourceName;
5952 set => this.resourceName = value;
5953 }
5954
5958 public string URL
5959 {
5960 get => this.url;
5961 set => this.url = value;
5962 }
5963
5968 {
5969 get => this.master;
5970 set => this.master = value;
5971 }
5972
5977 {
5978 get
5979 {
5980 if (!(this.detail is null))
5981 return this.detail;
5982
5983 if (this.master is null)
5984 return null;
5985
5986 MarkdownDocument Doc = this.master.Detail;
5987 if (Doc != this)
5988 return Doc;
5989
5990 return null;
5991 }
5992 set => this.detail = value;
5993 }
5994
5998 public MarkdownSettings Settings => this.settings;
5999
6003 public bool IncludesTableOfContents => this.includesTableOfContents;
6004
6008 public bool IsDynamic => this.isDynamic || (this.master?.isDynamic ?? false);
6009
6013 public object Tag
6014 {
6015 get => this.tag;
6016 set => this.tag = value;
6017 }
6018
6022 public bool AllowScriptTag
6023 {
6024 get
6025 {
6026 if (!this.allowScriptTag.HasValue)
6027 {
6028 this.allowScriptTag = this.metaData.TryGetValue("ALLOWSCRIPTTAG", out KeyValuePair<string, bool>[] Value) &&
6029 Value.Length > 0 &&
6030 CommonTypes.TryParse(Value[0].Key, out bool b) &&
6031 b;
6032 }
6033
6034 return this.allowScriptTag.Value;
6035 }
6036 }
6037
6044 public bool ForEach(MarkdownElementHandler Callback, object State)
6045 {
6046 ChunkNode<MarkdownElement> Loop = this.elements?.FirstChunk;
6047 int i, c;
6048
6049 while (!(Loop is null))
6050 {
6051 for (i = Loop.Start, c = Loop.Pos; i < c; i++)
6052 {
6053 if (!Loop[i].ForEach(Callback, State))
6054 return false;
6055 }
6056
6057 Loop = Loop.Next;
6058 }
6059
6060 if (!(this.references is null))
6061 {
6062 foreach (Multimedia E in this.references.Values)
6063 {
6064 if (!E.ForEach(Callback, State))
6065 return false;
6066 }
6067 }
6068
6069 if (!(this.footnotes is null))
6070 {
6071 foreach (Footnote E in this.footnotes.Values)
6072 {
6073 if (!E.ForEach(Callback, State))
6074 return false;
6075 }
6076 }
6077
6078 return true;
6079 }
6080
6085 public string[] FindLinks()
6086 {
6087 return this.FindLinks(true, true, true);
6088 }
6089
6097 public string[] FindLinks(bool IncludeAutomaticLinks, bool IncludeLinks, bool IncludeMultimedia)
6098 {
6099 Dictionary<string, bool> Links = new Dictionary<string, bool>();
6100
6101 this.ForEach((E, Obj) =>
6102 {
6104 {
6105 if (IncludeAutomaticLinks)
6106 Links[AutomaticLinkUrl.URL] = true;
6107 }
6108 else if (E is Link Link)
6109 {
6110 if (IncludeLinks)
6111 Links[Link.Url] = true;
6112 }
6113 else if (E is Multimedia Multimedia)
6114 {
6115 if (IncludeMultimedia)
6116 {
6117 foreach (MultimediaItem Item in Multimedia.Items)
6118 Links[Item.Url] = true;
6119 }
6120 }
6121
6122 return true;
6123 }, null);
6124
6125 string[] Result = new string[Links.Count];
6126 Links.Keys.CopyTo(Result, 0);
6127 return Result;
6128 }
6129
6134 public string[] FindHashTags()
6135 {
6136 SortedDictionary<string, bool> Tags = new SortedDictionary<string, bool>();
6137
6138 this.ForEach((E, Obj) =>
6139 {
6140 if (E is HashTag Tag)
6141 Tags[Tag.Tag] = true;
6142
6143 return true;
6144 }, null);
6145
6146 string[] Result = new string[Tags.Count];
6147 Tags.Keys.CopyTo(Result, 0);
6148 return Result;
6149 }
6150
6155
6159 public IEnumerator<MarkdownElement> GetEnumerator()
6160 {
6161 return this.elements.GetEnumerator();
6162 }
6163
6164 IEnumerator IEnumerable.GetEnumerator()
6165 {
6166 return this.elements.GetEnumerator();
6167 }
6168
6176 public static Task<MarkdownDocument> Compare(MarkdownDocument Old, MarkdownDocument New, bool KeepUnchanged)
6177 {
6178 return New.Compare(Old, KeepUnchanged);
6179 }
6180
6191 public static async Task<string> Compare(string Old, string New, MarkdownSettings Settings, bool KeepUnchanged,
6192 params Type[] TransparentExceptionTypes)
6193 {
6196 MarkdownDocument DiffDoc = await Compare(OldDoc, NewDoc, KeepUnchanged);
6197
6198 return await DiffDoc.GenerateMarkdown(false);
6199 }
6200
6207 public async Task<MarkdownDocument> Compare(MarkdownDocument Previous, bool KeepUnchanged)
6208 {
6209 // TODO: Meta-data
6210
6211 MarkdownDocument Result = await CreateAsync(string.Empty, this.settings, this.transparentExceptionTypes);
6212
6213 Result.elements.AddRange(Compare(Previous.elements, this.elements, KeepUnchanged, Result));
6214
6215 // TODO: Footnotes
6216
6217 Result.markdownText = null; // Triggers export, if needed.
6218 return Result;
6219 }
6220
6221 private static ChunkedList<MarkdownElement> Atomize(ChunkedList<MarkdownElement> Elements, out bool Reassemble)
6222 {
6223 if (ContainsEditableText(Elements))
6224 {
6225 Reassemble = true;
6226 return Atomize(Elements);
6227 }
6228 else
6229 {
6230 Reassemble = false;
6231 return Elements;
6232 }
6233 }
6234
6236 {
6240 int i, c;
6241
6242 while (!(Loop is null))
6243 {
6244 for (i = Loop.Start, c = Loop.Pos; i < c; i++)
6245 {
6246 E = Loop[i];
6247
6248 if (E is IEditableText EditableText)
6249 Result.AddRange(EditableText.Atomize());
6250 else
6251 Result.Add(E);
6252 }
6253
6254 Loop = Loop.Next;
6255 }
6256
6257 return Result;
6258 }
6259
6260 private static bool ContainsEditableText(ChunkedList<MarkdownElement> Elements)
6261 {
6263 int i, c;
6264
6265 while (!(Loop is null))
6266 {
6267 for (i = Loop.Start, c = Loop.Pos; i < c; i++)
6268 {
6269 if (Loop[i] is IEditableText)
6270 return true;
6271 }
6272
6273 Loop = Loop.Next;
6274 }
6275
6276 return false;
6277 }
6278
6280 ChunkedList<MarkdownElement> Elements2, bool KeepUnchanged, MarkdownDocument Document)
6281 {
6283 MarkdownElement[] S1 = Atomize(Elements1, out bool Reassemble1).ToArray();
6284 MarkdownElement[] S2 = Atomize(Elements2, out bool Reassemble2).ToArray();
6287 int i, c = Script.Steps.Length;
6288
6289 if (Reassemble1 || Reassemble2)
6290 {
6292 StringBuilder sb = new StringBuilder();
6293
6294 for (i = 0; i < c; i++)
6295 {
6296 Step = Script.Steps[i];
6297
6298 switch (Step.Operation)
6299 {
6300 case EditOperation.Keep:
6301 case EditOperation.Delete:
6302 if (!Reassemble1)
6303 continue;
6304 break;
6305
6306 case EditOperation.Insert:
6307 if (!Reassemble2)
6308 continue;
6309 break;
6310
6311 default:
6312 continue;
6313 }
6314
6315 Type LastAtomType = null;
6316 Atom LastAtom = null;
6317 Type AtomType;
6319 int j, d;
6320
6321 for (j = 0, d = Step.Symbols.Length; j < d; j++)
6322 {
6323 E = Step.Symbols[j];
6324
6325 if (E is Atom Atom)
6326 {
6327 AtomType = Atom.GetType();
6328 if (AtomType != LastAtomType)
6329 {
6330 if (!(LastAtom is null))
6331 {
6332 Reassembled.Add(LastAtom.Source.Assemble(Document, sb.ToString()));
6333 sb.Clear();
6334 }
6335
6336 LastAtom = Atom;
6337 LastAtomType = AtomType;
6338 }
6339
6340 sb.Append(Atom.Charater);
6341 }
6342 else
6343 {
6344 if (!(LastAtom is null))
6345 {
6346 Reassembled.Add(LastAtom.Source.Assemble(Document, sb.ToString()));
6347 sb.Clear();
6348 LastAtom = null;
6349 LastAtomType = null;
6350 }
6351
6352 Reassembled.Add(E);
6353 }
6354 }
6355
6356 if (!(LastAtom is null))
6357 {
6358 Reassembled.Add(LastAtom.Source.Assemble(Document, sb.ToString()));
6359 sb.Clear();
6360 }
6361
6362 Step.Symbols = Reassembled.ToArray();
6363 Reassembled.Clear();
6364 }
6365 }
6366
6367 for (i = 0; i < c; i++)
6368 {
6369 Step = Script.Steps[i];
6370
6371 if (Step.Operation == EditOperation.Keep)
6372 {
6373 if (!KeepUnchanged)
6374 continue;
6375
6376 Result.AddRange(Step.Symbols);
6377 }
6378 else
6379 {
6380 if (i + 1 < c &&
6381 (Step2 = Script.Steps[i + 1]).Operation != EditOperation.Keep &&
6382 Step2.Operation != Step.Operation &&
6383 SameBlockTypes(Step.Symbols, Step2.Symbols))
6384 {
6385 MarkdownElement E1, E2;
6386 int j, d = Step.Symbols.Length;
6387
6388 for (j = 0; j < d; j++)
6389 {
6390 if (Step.Operation == EditOperation.Insert)
6391 {
6392 E2 = Step.Symbols[j];
6393 E1 = Step2.Symbols[j];
6394 }
6395 else
6396 {
6397 E1 = Step.Symbols[j];
6398 E2 = Step2.Symbols[j];
6399 }
6400
6401 if (E1 is MarkdownElementChildren Children1 &&
6402 E2 is MarkdownElementChildren Children2)
6403 {
6404 ChunkedList<MarkdownElement> Diff = Compare(Children1.Children, Children2.Children,
6405 KeepUnchanged || d > 1, Document);
6406
6407 Result.Add(Children1.Create(Diff, Document));
6408 }
6409 else if (E1 is MarkdownElementSingleChild Child1 &&
6410 E2 is MarkdownElementSingleChild Child2 &&
6411 Child1.Child.SameMetaData(Child2.Child) &&
6412 Child1.Child is MarkdownElementChildren GrandChildren1 &&
6413 Child2.Child is MarkdownElementChildren GrandChildren2)
6414 {
6415 ChunkedList<MarkdownElement> Diff = Compare(GrandChildren1.Children, GrandChildren2.Children,
6416 KeepUnchanged || d > 1, Document);
6417
6418 Result.Add(Child1.Create(GrandChildren1.Create(Diff, Document), Document));
6419 }
6420 else
6421 {
6422 Result.Add(GetElement(Step.Operation, Document, E1));
6423 Result.Add(GetElement(Step2.Operation, Document, E2));
6424 }
6425 }
6426
6427 i++;
6428 }
6429 else
6430 Result.Add(GetElement(Step.Operation, Document, Step.Symbols));
6431 }
6432 }
6433
6434 return Result;
6435 }
6436
6437 private static MarkdownElement GetElement(EditOperation Operation, MarkdownDocument Document, params MarkdownElement[] Symbols)
6438 {
6439 if (Symbols[0].IsBlockElement)
6440 {
6441 switch (Operation)
6442 {
6443 case EditOperation.Insert:
6444 return new InsertBlocks(Document, new ChunkedList<MarkdownElement>(Symbols));
6445
6446 case EditOperation.Delete:
6447 return new DeleteBlocks(Document, new ChunkedList<MarkdownElement>(Symbols));
6448 }
6449 }
6450 else
6451 {
6452 switch (Operation)
6453 {
6454 case EditOperation.Insert:
6455 return new Insert(Document, new ChunkedList<MarkdownElement>(Symbols));
6456
6457 case EditOperation.Delete:
6458 return new Delete(Document, new ChunkedList<MarkdownElement>(Symbols));
6459 }
6460 }
6461
6462 return new InvisibleBreak(Document, string.Empty);
6463 }
6464
6465 private static bool SameBlockTypes(MarkdownElement[] E1, MarkdownElement[] E2)
6466 {
6467 int i, c = E1.Length;
6468 if (E2.Length != c)
6469 return false;
6470
6472
6473 for (i = 0; i < c; i++)
6474 {
6475 if (!(e = E1[i]).IsBlockElement)
6476 return false;
6477
6478 if (!e.SameMetaData(E2[i]))
6479 return false;
6480 }
6481
6482 return true;
6483 }
6484
6491 public void QueueAsyncTask(AsyncMarkdownProcessing Callback, object State)
6492 {
6493 lock (this.asyncTasks)
6494 {
6495 this.asyncTasks.Add(new KeyValuePair<AsyncMarkdownProcessing, object>(Callback, State));
6496 }
6497 }
6498
6502 public IEnumerable<KeyValuePair<AsyncMarkdownProcessing, object>> AsyncTasks
6503 {
6504 get
6505 {
6506 lock (this.asyncTasks)
6507 {
6508 return this.asyncTasks.ToArray();
6509 }
6510 }
6511 }
6512
6516 public void ProcessAsyncTasks()
6517 {
6518 KeyValuePair<AsyncMarkdownProcessing, object>[] Tasks;
6519
6520 lock (this.asyncTasks)
6521 {
6522 if (this.asyncTasks.Count == 0)
6523 return;
6524
6525 Tasks = this.asyncTasks.ToArray();
6526 this.asyncTasks.Clear();
6527 }
6528
6529 Task.Run(async () =>
6530 {
6531 foreach (KeyValuePair<AsyncMarkdownProcessing, object> P in Tasks)
6532 {
6533 try
6534 {
6535 await P.Key(P.Value);
6536 }
6537 catch (Exception ex)
6538 {
6539 Log.Exception(ex);
6540 }
6541 }
6542 });
6543 }
6544
6551 public static async Task<object> TransformXml(XmlDocument Xml, Variables Variables)
6552 {
6553 try
6554 {
6556 if (Visualizer is null)
6557 return Xml;
6558
6559 return (await Visualizer.TransformXml(Xml, Variables)) ?? Xml;
6560 }
6561 catch (Exception ex)
6562 {
6563 return ex;
6564 }
6565 }
6566
6572 {
6574
6575 this.ForEach((Element, _) =>
6576 {
6577 Result.NrElements++;
6578 Element.IncrementStatistics(Result);
6579 return true;
6580 }, null);
6581
6582 Result.MailHyperlinks = Result.IntMailHyperlinks?.ToArray();
6583 Result.UrlHyperlinks = Result.IntUrlHyperlinks?.ToArray();
6584
6585 this.GenerateStatDictionary(Result.IntMultimediaPerContentCategory,
6586 out Dictionary<string, string[]> AsArrays, out Dictionary<string, int> AsCounts);
6587
6588 Result.MultimediaPerContentCategory = AsArrays;
6589 Result.NrMultimediaPerContentCategory = AsCounts;
6590
6591 this.GenerateStatDictionary(Result.IntMultimediaPerContentType, out AsArrays, out AsCounts);
6592 Result.MultimediaPerContentType = AsArrays;
6593 Result.NrMultimediaPerContentType = AsCounts;
6594
6595 this.GenerateStatDictionary(Result.IntMultimediaPerExtension, out AsArrays, out AsCounts);
6596 Result.MultimediaPerExtension = AsArrays;
6597 Result.NrMultimediaPerExtension = AsCounts;
6598
6599 return Result;
6600 }
6601
6602 private void GenerateStatDictionary(Dictionary<string, ChunkedList<string>> Temp, out Dictionary<string, string[]> AsArrays, out Dictionary<string, int> AsCounts)
6603 {
6604 if (Temp is null)
6605 {
6606 AsArrays = null;
6607 AsCounts = null;
6608 return;
6609 }
6610
6611 AsArrays = new Dictionary<string, string[]>();
6612 AsCounts = new Dictionary<string, int>();
6613
6614 foreach (KeyValuePair<string, ChunkedList<string>> P in Temp)
6615 {
6616 AsArrays[P.Key] = P.Value.ToArray();
6617 AsCounts[P.Key] = P.Value.Count;
6618 }
6619 }
6620
6626 public static string AppendRows(string[] Rows)
6627 {
6628 return AppendRows(Rows, false);
6629 }
6630
6637 public static string AppendRows(string[] Rows, bool SingleRow)
6638 {
6639 if (SingleRow && Rows.Length == 1)
6640 return Rows[0].Trim();
6641
6642 StringBuilder sb = new StringBuilder();
6643
6644 foreach (string Row in Rows)
6645 {
6646 if (SingleRow)
6647 sb.Append(Row.Trim());
6648 else
6649 sb.AppendLine(Row);
6650 }
6651
6652 return sb.ToString();
6653 }
6654
6658 public Grade CanEncodeJson => Grade.NotAtAll; // Document reference from child nodes create a stack overflow.
6659
6660 // TODO: Footnotes in included markdown files.
6661 }
6662}
6663
Helps with parsing of commong data types.
Definition: CommonTypes.cs:15
static readonly char[] CRLF
Contains the CR LF character sequence.
Definition: CommonTypes.cs:19
static bool TryParse(string s, out double Value)
Tries to decode a string encoded double.
Definition: CommonTypes.cs:48
Contains information about an emoji.
Static class that provide methods for managing emojis.
static readonly EmojiInfo Emoji_sweat
:sweat: 😓 face with cold sweat
static readonly EmojiInfo Emoji_joy
:joy: 😂 face with tears of joy
static readonly EmojiInfo Emoji_no_mouth
:no_mouth: 😶 face without mouth
static readonly EmojiInfo Emoji_broken_heart
:broken_heart: 💔 broken heart
static readonly EmojiInfo Emoji_kissing_heart
:kissing_heart: 😘 face throwing a kiss
static readonly EmojiInfo Emoji_wink
:wink: 😉 winking face
static readonly EmojiInfo Emoji_stuck_out_tongue_winking_eye
:stuck_out_tongue_winking_eye: 😜 face with stuck-out tongue and winking eye
static readonly EmojiInfo Emoji_heart
:heart: ❤ heavy black heart
static readonly EmojiInfo Emoji_cry
:cry: 😢 crying face
static bool TryGetEmoji(string ShortName, out EmojiInfo Emoji)
Tries to get information about an emoji, given its short name.
static readonly EmojiInfo Emoji_angry
:angry: 😠 angry face
static readonly EmojiInfo Emoji_expressionless
:expressionless: 😑 expressionless face
static readonly EmojiInfo Emoji_disappointed
:disappointed: 😞 disappointed face
static readonly EmojiInfo Emoji_ok_woman
:ok_woman: 🙆 face with ok gesture
static readonly EmojiInfo Emoji_sweat_smile
:sweat_smile: 😅 smiling face with open mouth and cold sweat
static readonly EmojiInfo Emoji_stuck_out_tongue
:stuck_out_tongue: 😛 face with stuck-out tongue
static readonly EmojiInfo Emoji_smile
:smile: 😄 smiling face with open mouth and smiling eyes
static readonly EmojiInfo Emoji_innocent
:innocent: 😇 smiling face with halo
static readonly EmojiInfo Emoji_persevere
:persevere: 😣 persevering face
static readonly EmojiInfo Emoji__1
:-1: 👎 thumbs down sign
static readonly EmojiInfo Emoji_flushed
:flushed: 😳 flushed face
static readonly EmojiInfo Emoji_open_mouth
:open_mouth: 😮 face with open mouth
static readonly EmojiInfo Emoji_smiley
:smiley: 😃 smiling face with open mouth
static readonly EmojiInfo Emoji_dizzy_face
:dizzy_face: 😵 dizzy face
static readonly EmojiInfo Emoji_sunglasses
:sunglasses: 😎 smiling face with sunglasses
static readonly EmojiInfo Emoji_laughing
:laughing: 😆 smiling face with open mouth and tightly-closed eyes
static readonly EmojiInfo Emoji_confused
:confused: 😕 confused face
Helps with common JSON-related tasks.
Definition: JSON.cs:16
static string IndentString(int NrTabs)
Gets an indentation string consisting of a number of tab characters. Strings are caches and reused to...
Definition: JSON.cs:780
Executes script from a file, if not executed before, or if file timestamp has changed....
static async Task< bool > NeedsExecution(string FileName)
Checks if an init-file needs to be executed.
Class that can be used to encapsulate Markdown to be returned from a Web Service, bypassing any encod...
Contains a markdown document. This markdown document class supports original markdown,...
string[] Script
Links to server-side script files that should be included before processing the page.
static async Task< object > TransformXml(XmlDocument Xml, Variables Variables)
Transforms XML to an object that is easier to visualize.
static string AppendRows(string[] Rows)
Appends a set of rows into a single string with newlines between rows.
string[] InitializationScript
Links to server-side script files that should be executed before before processing the page....
MarkdownSettings Settings
Markdown settings.
IEnumerable< KeyValuePair< AsyncMarkdownProcessing, object > > AsyncTasks
Enumerable set of asynchronous tasks that have been registered.
async Task< MarkdownDocument > Compare(MarkdownDocument Previous, bool KeepUnchanged)
Calculates the difference of the current Markdown document, and a previous version of the Markdown do...
bool IncludesTableOfContents
If the document contains a Table of Contents.
Task GenerateHTML(StringBuilder Output)
Generates HTML from the markdown text.
string CheckURL(string Url, string URL)
Checks the URL if it needs redirection to a proxy.
string[] FindLinks(bool IncludeAutomaticLinks, bool IncludeLinks, bool IncludeMultimedia)
Finds all links in the document.
bool ForEach(MarkdownElementHandler Callback, object State)
Loops through all elements in the document.
static string Encode(string s)
Encodes all special characters in a string so that it can be included in a markdown document without ...
bool TryGetFootnoteNumber(string Key, out int Number)
Tries to get the number of a footnote, given its key.
string[] Author
Author(s) of document.
string MarkdownText
Markdown text. This text might differ slightly from the original text passed to the document.
async Task RenderDocument(IRenderer Output)
Renders the document using provided output format.
Task< string > GenerateMarkdown()
Generates Markdown from the markdown text.
bool TryGetFootnote(string Key, out Footnote Footnote)
Tries to get a footnote, given its key.
string[] Next
Link to next document, in a paginated set of documents.
IEnumerator< MarkdownElement > GetEnumerator()
Gets an enumerator of root markdown elements in the document.
string[] Subtitle
Subtitle of document.
async Task GenerateHTML(StringBuilder Output, HtmlSettings HtmlSettings)
Generates HTML from the markdown text.
static Task< MarkdownDocument > Compare(MarkdownDocument Old, MarkdownDocument New, bool KeepUnchanged)
Calculates the difference of two Markdown documents.
Grade CanEncodeJson
To what extent the object supports JSON encoding.
bool SyntaxHighlighting
If syntax highlighting is used in the document.
IEnumerable< KeyValuePair< string, KeyValuePair< string, bool >[]> > MetaData
Meta-data
string[] FindHashTags()
Finds hashtags in the document.
string[] Image
Link to image for page.
bool AllowScriptTag
If client-side script tags are allowed in the document.
void QueueAsyncTask(AsyncMarkdownProcessing Callback, object State)
Queues an asynchronous task to be executed. Asynchronous tasks will be executed after the main docume...
bool TryGetMetaData(string Key, out KeyValuePair< string, bool >[] Value)
Tries to get a meta-data value given its key.
string[] Privileges
Requered user privileges to display page.
void AddMetaData(string Key, string Value)
Adds meta-data to the document.
async Task GenerateMarkdown(StringBuilder Output, bool PortableSyntax)
Generates Markdown from the markdown text.
static async Task< KeyValuePair< string, bool > > Preprocess(string Markdown, MarkdownSettings Settings, string FileName, params Type[] TransparentExceptionTypes)
Preprocesses markdown text.
MarkdownDocument Master
Master document responsible for the current document.
MarkdownStatistics GetStatistics()
Returns some basic statistics about the contents of the Markdown object.
string[] Copyright
Link to copyright statement.
IEnumerable< string > FootnoteOrder
Order of footnotes.
bool IsDynamic
If the contents of the document is dynamic (i.e. includes script), or not (i.e. is static).
string[] Parameters
Name of a query parameter recognized by the page.
Task GenerateMarkdown(StringBuilder Output)
Generates Markdown from the markdown text.
string[] Previous
Link to previous document, in a paginated set of documents.
Type[] TransparentExceptionTypes
If an exception is thrown when processing script in markdown, and the exception is of any of these ty...
IEmojiSource EmojiSource
Source for emojis in the document.
string[] FindLinks()
Finds all links in the document.
static ? int HeaderEndPosition(string Markdown)
Gets the end position of the header, if one is found, null otherwise.
void ProcessAsyncTasks()
Processes any registered asynchronous tasks. This method is normally only called from renderers of do...
string[] GetMetaData(string Key)
Gets the meta-data values given a meta-data key. If meta-data is not found, an empty array is returne...
const string MarkdownSettingsVariableName
Variable name used for storing Markdown settings.
async Task GeneratePlainText(StringBuilder Output)
Generates Plain Text from the markdown text.
string[] Refresh
Tells the browser to refresh the page after a given number of seconds.
string[] MetaDataKeys
Meta-data keys availale in document.
static async Task< string > Compare(string Old, string New, MarkdownSettings Settings, bool KeepUnchanged, params Type[] TransparentExceptionTypes)
Calculates the difference of two Markdown documents.
string[] Footnotes
Gets the keys of the footnotes in the order that they are referenced in the document....
Header[] Headers
Headers in document.
string[] CSS
Link(s) to Cascading Style Sheet(s) that should be used for visual formatting of the generated HTML p...
static Task< MarkdownDocument > CreateAsync(string MarkdownText, MarkdownSettings Settings, params Type[] TransparentExceptionTypes)
Contains a markdown document. This markdown document class supports original markdown,...
string[] JavaScript
Link(s) to JavaScript files(s) that should be includedin the generated HTML page.
string[] UserVariable
Name of the variable that will hold a reference to the IUser interface for the currently logged in us...
string ResourceName
Local resource name of Markdown document, if referenced through a web server. Master documents use th...
object Tag
Property can be used to tag document with client-specific information.
string FileName
Filename of Markdown document. Markdown inclusion will be made relative to this filename.
string[] Description
Description of document.
string URL
Absolute URL of Markdown document, if referenced through a web server.
async Task< string > GenerateMarkdown(bool PortableSyntax)
Generates Markdown from the markdown text.
MarkdownDocument Detail
Detail document of a master document.
static string AppendRows(string[] Rows, bool SingleRow)
Appends a set of rows into a single string with newlines between rows.
IEnumerable< KeyValuePair< string, Multimedia > > References
Multimedia references
async Task< string > GenerateHTML(HtmlSettings HtmlSettings)
Generates HTML from the markdown text.
static async Task< string > Preprocess(string Markdown, MarkdownSettings Settings, params Type[] TransparentExceptionTypes)
Preprocesses markdown text.
static async Task< MarkdownDocument > CreateAsync(string MarkdownText, MarkdownSettings Settings, string FileName, string ResourceName, string URL, params Type[] TransparentExceptionTypes)
Contains a markdown document. This markdown document class supports original markdown,...
Multimedia GetReference(string Label)
Gets the multimedia information referenced by a label.
string[] Date
(Publication) date of document.
async Task< string > GeneratePlainText()
Generates Plain Text from the markdown text.
async Task< string > GenerateHTML()
Generates HTML from the markdown text.
static Task< MarkdownDocument > CreateAsync(string MarkdownText, params Type[] TransparentExceptionTypes)
Contains a markdown document. This markdown document class supports original markdown,...
string[] Login
Link to a login page. This page will be shown if the user variable does not contain a user.
ChunkedList< MarkdownElement > Elements
Markdown elements making up the document.
Contains settings that the Markdown parser uses to customize its behavior.
string GetFileName(string DocumentFileName, string FileNameReference)
Evaluates a file name from a file reference.
Variables Variables
Collection of variables. Providing such a collection enables script execution inside markdown documen...
IEmojiSource EmojiSource
Optional Emoji source. Emojis and smileys are only available if an emoji source is provided.
object ScriptContext
Context object to be passed to script expressions. This can be used to ensure context-specific functi...
AuthorizeExpression AuthorizeExpression
Optional method to call to authorize execution of script expressions.
ICodecProgress Progress
Optional progress reporting of encoding/decoding. Can be null.
bool ParseMetaData
If meta-data should be parsed or not.
Contains some basic statistical information about a Markdown document.
int NrElements
Number of elements in Markdown document (total).
Represents an atom of editable text (i.e. typed character).
Definition: Atom.cs:11
IEditableText Source
Source
Definition: Atom.cs:33
Represents a block quote in a markdown document.
Definition: BlockQuote.cs:11
Represents a bullet list in a markdown document.
Definition: BulletList.cs:11
Represents a center-aligned set of blocks in a markdown document.
Represents a code block in a markdown document.
Definition: CodeBlock.cs:17
static IXmlVisualizer GetXmlVisualizerHandler(XmlDocument Xml)
Gets the best XML Visualizer for a given XML document.
Definition: CodeBlock.cs:216
Represents a comment block in a markdown document.
Definition: CommentBlock.cs:10
Represents a definition list in a markdown document.
override void AddChild(MarkdownElement NewChild)
Adds a child to the element.
Represents inserted blocks in a markdown document.
Definition: DeleteBlocks.cs:11
bool Referenced
If the Footnote has been referenced during rendering, and therefore needs to be shown at the end of t...
Definition: Footnote.cs:51
Represents a header in a markdown document.
Definition: Header.cs:15
Represents a block of HTML in a markdown document.
Definition: HtmlBlock.cs:11
Represents inserted blocks in a markdown document.
Definition: InsertBlocks.cs:11
Represents a left-aligned set of blocks in a markdown document.
Definition: LeftAligned.cs:11
Represents a margin-aligned set of blocks in a markdown document.
Represents a nested block with no special formatting rules in a markdown document.
Definition: NestedBlock.cs:12
Represents a numbered item in an ordered list.
Definition: NumberedItem.cs:10
bool NumberExplicit
If number is explicitly provided (true) or inferred (false).
Definition: NumberedItem.cs:40
Represents a numbered list in a markdown document.
Definition: NumberedList.cs:11
override void AddChild(MarkdownElement NewChild)
Adds a child to the element.
Definition: NumberedList.cs:69
override void AddChildren(ChunkedList< MarkdownElement > NewChildren)
Adds children to the element.
Definition: NumberedList.cs:86
Represents a paragraph in a markdown document.
Definition: Paragraph.cs:11
Represents a right-aligned set of blocks in a markdown document.
Definition: RightAligned.cs:11
Represents a sequence of sections.
Definition: Sections.cs:11
Represents a table in a markdown document.
Definition: Table.cs:11
Represents a task item in a task list.
Definition: TaskItem.cs:10
Represents a task list in a markdown document.
Definition: TaskList.cs:11
Represents an unnumbered item in an ordered list.
Abstract base class for all markdown elements with a variable number of child elements.
virtual void AddChildren(ChunkedList< MarkdownElement > NewChildren)
Adds children to the element.
override ChunkedList< MarkdownElement > Children
Any children of the element.
virtual void AddChild(MarkdownElement NewChild)
Adds a child to the element.
override bool ForEach(MarkdownElementHandler Callback, object State)
Loops through all child-elements for the element.
bool HasOneChild
If the element has only one child.
MarkdownElement FirstChild
First child, or null if none.
Abstract base class for all markdown elements.
virtual bool SameMetaData(MarkdownElement E)
If the current object has same meta-data as E (but not necessarily same content).
virtual bool IsBlockElement
If the element is a block element.
Abstract base class for all markdown elements with one child element.
Represents an HTML entity in Unicode format.
MultimediaItem[] Items
Multimedia items.
Definition: Multimedia.cs:41
Renders HTML from a Markdown document.
Definition: HtmlRenderer.cs:25
Contains settings that the HTML export uses to customize HTML output.
Definition: HtmlSettings.cs:7
Renders portable Markdown from a Markdown document.
Abstract base class for Markdown renderers.
Definition: Renderer.cs:15
async Task< bool > Render(ChunkedList< MarkdownElement > Elements)
Renders a collection of elements.
Definition: Renderer.cs:191
void Clear()
Clears the underlying StringBuilder.
Definition: Renderer.cs:138
override string ToString()
Returns the renderer output.
Definition: Renderer.cs:130
Renders plain text from a Markdown document.
Definition: TextRenderer.cs:18
Helps with common XML-related tasks.
Definition: XML.cs:21
static string HtmlValueEncode(string s)
Differs from Encode(String), in that it does not encode the aposotrophe or the quote.
Definition: XML.cs:211
Generic exception, with meta-data for logging.
Static class managing the application event log. Applications and services log events on this static ...
Definition: Log.cs:14
static void Exception(Exception Exception, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, params KeyValuePair< string, object >[] Tags)
Logs an exception. Event type will be determined by the severity of the exception.
Definition: Log.cs:1657
static Exception UnnestException(Exception Exception)
Unnests an exception, to extract the relevant inner exception.
Definition: Log.cs:828
Node referencing a chunk in a ChunkedList<T>
Definition: ChunkNode.cs:11
override string ToString()
String representation of chunk.
Definition: ChunkNode.cs:62
ChunkNode< T > Next
Next chunk
Definition: ChunkNode.cs:26
int Pos
Index after the last element in chunk.
Definition: ChunkNode.cs:51
int Start
Index of first element in chunk.
Definition: ChunkNode.cs:46
A chunked list is a linked list of chunks of objects of type T .
Definition: ChunkedList.cs:54
void Clear()
Clears the collection.
Definition: ChunkedList.cs:306
IEnumerator< T > GetEnumerator()
Returns an enumerator for the collection.
Definition: ChunkedList.cs:418
void AddFirstItem(T Value)
Adds a new item first in the collection.
Definition: ChunkedList.cs:832
T LastItem
Last item in the collection.
Definition: ChunkedList.cs:732
void AddRange(IEnumerable< T > Collection)
Adds a range of elements (last) to the list.
ChunkNode< T > FirstChunk
First chunk
Definition: ChunkedList.cs:259
bool HasFirstItem
If there is a first item in the collection
Definition: ChunkedList.cs:778
bool HasLastItem
If there is a last item in the collection
Definition: ChunkedList.cs:726
int IndexOf(T Item)
Determines the index of a specific item
T FirstItem
First item in the collection.
Definition: ChunkedList.cs:784
int Count
Number of elements in collection.
Definition: ChunkedList.cs:68
void AddRangeFirst(IEnumerable< T > Collection)
Adds a range of elements first to the list.
void Add(T Item)
Adds an item to the collection.
Definition: ChunkedList.cs:272
T[] ToArray()
Returns an array containing all elements of the collection.
Contains static methods
Definition: Files.cs:14
static async Task< string > ReadAllTextAsync(string FileName)
Reads a text file asynchronously.
Definition: Files.cs:84
Computes the difference between two sequences of symbols.
Definition: Difference.cs:10
Represents an Edit-script, converting one sequence of symbols to another.
Definition: EditScript.cs:11
Represents a sub-sequence of symbols.
Definition: Step.cs:12
EditOperation Operation
Edit operation being performed.
Definition: Step.cs:55
T[] Symbols
Sequence of symbols.
Definition: Step.cs:37
Base class for all types of elements.
Definition: Element.cs:14
Class managing a script expression.
Definition: Expression.cs:41
async Task< object > EvaluateAsync(Variables Variables)
Evaluates the expression, using the variables provided in the Variables collection....
Definition: Expression.cs:4461
bool ContainsImplicitPrint
If the expression contains implicit print operations.
Definition: Expression.cs:4516
static bool IsNullOrVoid(object Result)
Checks if a result object value is equal to null or void (i.e. its type equal to System....
Definition: Expression.cs:4721
bool ReferencesImplicitPrint(Variables Variables)
If the expression, or any function call references, contain implicit print operations.
Definition: Expression.cs:4523
Base class for graphs.
Definition: Graph.cs:88
Contains pixel information
Base class for all nodes in a parsed script tree.
Definition: ScriptNode.cs:69
string SubExpression
Sub-expression defining the node.
Definition: ScriptNode.cs:183
Collection of variables.
Definition: Variables.cs:25
TextWriter ConsoleOut
Console out interface. Can be used by functions and script to output data to the console.
Definition: Variables.cs:223
virtual Variable Add(string Name, object Value)
Adds a variable to the collection.
Definition: Variables.cs:126
ValuePrinter Printer
Delegate that converts values to strings for (implicit) printing. Default is null,...
Definition: Variables.cs:242
Interface for Emoji sources. Emoji sources provide emojis to content providers.
Definition: IEmojiSource.cs:10
Interface for reporting progress about an encoding or decoding.
Task HeaderProcessed()
Called when the header has been processed.
Task EarlyHint(string Resource, string Relation, params KeyValuePair< string, string >[] AdditionalParameters)
Reports an early hint of a resource the recipient may need to process, in order to be able to process...
Task BodyProcessed()
Called when the body has been processed.
Basic interface for resources having a FileName property.
Provides a JSON Encoding hint for an object that implements this interface.
Interface for elements containing editable text.
MarkdownElement Assemble(MarkdownDocument Document, string Text)
Assembles a markdown element from a sequence of atoms.
Interface for all markdown handlers of multimedia content.
bool EmbedInlineLink(string Url)
If the link provided should be embedded in a multi-media construct automatically.
Interface for all XML visalizers.
Task< object > TransformXml(XmlDocument Xml, Variables Variables)
Transforms the XML document before visualizing it.
Interface for multimedia content HTML renderers.
Interface for Markdown renderers.
Definition: IRenderer.cs:12
Task RenderDocument(MarkdownDocument Document, bool Inclusion)
Renders a document.
Basic interface for matrices.
Definition: IMatrix.cs:7
Interface for objects that can be converted into matrices.
Definition: IToMatrix.cs:9
Definition: ImplTypes.g.cs:58
TextAlignment
Text alignment of contents.
delegate bool MarkdownElementHandler(MarkdownElement Element, object State)
Delegate for markdown element callback methods.
delegate Task AsyncMarkdownProcessing(object State)
Delegate used for callback methods performing asynchronous Markdown processing
EventType
Type of event.
Definition: EventType.cs:7
Grade
Grade enumeration
Definition: Grade.cs:7
EditOperation
Type of edit-operation
Prefix
SI prefixes. http://physics.nist.gov/cuu/Units/prefixes.html
Definition: Prefixes.cs:11
delegate Task< string > ValuePrinter(object Value, Variables Variables)
Converts a value to a printable string.