using System.Linq; using Pidgin; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; using Robust.Shared.Utility; using static Pidgin.Parser; using static Pidgin.Parser; using static Robust.Client.UserInterface.Control; using static Robust.Client.UserInterface.Controls.BoxContainer; namespace Content.Client.Guidebook; public sealed partial class DocumentParsingManager { private const string ListBullet = " › "; #region Text Parsing #region Basic Text Parsing // Try look for an escaped character. If found, skip the escaping slash and return the character. private static readonly Parser TryEscapedChar = Try(Char('\\').Then(OneOf( Try(Char('<')), Try(Char('>')), Try(Char('\\')), Try(Char('-')), Try(Char('=')), Try(Char('"')), Try(Char(' ')), Try(Char('n')).ThenReturn('\n'), Try(Char('t')).ThenReturn('\t') ))); private static readonly Parser SkipNewline = Whitespace.SkipUntil(Char('\n')); private static readonly Parser TrySingleNewlineToSpace = Try(SkipNewline).Then(SkipWhitespaces).ThenReturn(' '); private static readonly Parser TextChar = OneOf( TryEscapedChar, // consume any backslashed being used to escape text TrySingleNewlineToSpace, // turn single newlines into spaces Any // just return the character. ); // like TextChar, but not skipping whitespace around newlines private static readonly Parser QuotedTextChar = OneOf(TryEscapedChar, Any); // Quoted text private static readonly Parser QuotedText = Char('"').Then(QuotedTextChar.Until(Try(Char('"'))).Select(string.Concat)).Labelled("quoted text"); #endregion #region rich text-end markers private static readonly Parser TryStartList = Try(SkipNewline.Then(SkipWhitespaces).Then(Char('-'))).Then(SkipWhitespaces); private static readonly Parser TryStartTag = Try(Char('<')).Then(SkipWhitespaces); private static readonly Parser TryStartParagraph = Try(SkipNewline.Then(SkipNewline)).Then(SkipWhitespaces); private static readonly Parser TryLookTextEnd = Lookahead(OneOf(TryStartTag, TryStartList, TryStartParagraph, Try(Whitespace.SkipUntil(End)))); #endregion // parses text characters until it hits a text-end private static readonly Parser TextParser = TextChar.AtLeastOnceUntil(TryLookTextEnd).Select(string.Concat); private static readonly Parser TextControlParser = Try(Map(text => { var rt = new RichTextLabel() { HorizontalExpand = true, Margin = new Thickness(0, 0, 0, 15.0f), }; var msg = new FormattedMessage(); // THANK YOU RICHTEXT VERY COOL // (text doesn't default to white). msg.PushColor(Color.White); msg.AddMarkup(text); msg.Pop(); rt.SetMessage(msg); return rt; }, TextParser).Cast()).Labelled("richtext"); #endregion #region Headers private static readonly Parser HeaderControlParser = Try(Char('#')).Then(SkipWhitespaces.Then(Map(text => new Label() { Text = text, StyleClasses = { "LabelHeadingBigger" } }, AnyCharExcept('\n').AtLeastOnceString()).Cast())).Labelled("header"); private static readonly Parser SubHeaderControlParser = Try(String("##")).Then(SkipWhitespaces.Then(Map(text => new Label() { Text = text, StyleClasses = { "LabelHeading" } }, AnyCharExcept('\n').AtLeastOnceString()).Cast())).Labelled("subheader"); private static readonly Parser TryHeaderControl = OneOf(SubHeaderControlParser, HeaderControlParser); #endregion // Parser that consumes a - and then just parses normal rich text with some prefix text (a bullet point). private static readonly Parser ListControlParser = Try(Char('-')).Then(SkipWhitespaces).Then(Map( control => new BoxContainer() { Children = { new Label() { Text = ListBullet, VerticalAlignment = VAlignment.Top, }, control }, Orientation = LayoutOrientation.Horizontal, }, TextControlParser).Cast()).Labelled("list"); #region Tag Parsing // closing brackets for tags private static readonly Parser TagEnd = Char('>').Then(SkipWhitespaces); private static readonly Parser ImmediateTagEnd = String("/>").Then(SkipWhitespaces); private static readonly Parser TryLookTagEnd = Lookahead(OneOf(Try(TagEnd), Try(ImmediateTagEnd))); //parse tag argument key. any normal text character up until we hit a "=" private static readonly Parser TagArgKey = LetterOrDigit.Until(Char('=')).Select(string.Concat).Labelled("tag argument key"); // parser for a singular tag argument. Note that each TryQuoteOrChar will consume a whole quoted block before the Until() looks for whitespace private static readonly Parser TagArgParser = Map((key, value) => (key, value), TagArgKey, QuotedText).Before(SkipWhitespaces); // parser for all tag arguments private static readonly Parser> TagArgsParser = TagArgParser.Until(TryLookTagEnd); // parser for an opening tag. private static readonly Parser TryOpeningTag = Try(Char('<')) .Then(SkipWhitespaces) .Then(TextChar.Until(OneOf(Whitespace.SkipAtLeastOnce(), TryLookTagEnd))) .Select(string.Concat).Labelled($"opening tag"); private static Parser> ParseTagArgs(string tag) { return TagArgsParser.Labelled($"{tag} arguments") .Select(x => x.ToDictionary(y => y.Item1, y => y.Item2)) .Before(SkipWhitespaces); } private static Parser TryTagTerminator(string tag) { return Try(String("