/// <summary>
        /// Gets the property associated with the specified key, if
        /// present.
        /// </summary>
        /// <param name="name">The name of the property to get.</param>
        /// <param name="result">When this method returns, contains the
        /// value associated with the specified key, if the key is found;
        /// otherwise, the default <see cref="MarkupValue"/>. This
        /// parameter is passed uninitialized.</param>
        /// <returns><see langword="true"/> if the <see
        /// cref="MarkupAttributeMarker"/> contains an element with the
        /// specified key; otherwise, <see langword="false"/>.</returns>
        public bool TryGetProperty(string name, out MarkupValue result)
        {
            foreach (var prop in this.Properties)
            {
                if (prop.Name.Equals(name))
                {
                    result = prop.Value;
                    return(true);
                }
            }

            result = default;
            return(false);
        }
 /// <summary>
 /// Initializes a new instance of the <see cref="MarkupProperty"/>
 /// struct.
 /// </summary>
 /// <param name="name">The name of the property.</param>
 /// <param name="value">The value of the property.</param>
 internal MarkupProperty(string name, MarkupValue value)
 {
     this.Name  = name;
     this.Value = value;
 }
Exemple #3
0
        /// <summary>Parses a line of text, and produces a
        /// <see cref="MarkupParseResult"/> containing the processed
        /// text.</summary>
        /// <param name="input">The text to parse.</param>
        /// <returns>The resulting markup information.</returns>
        internal MarkupParseResult ParseMarkup(string input)
        {
            if (string.IsNullOrEmpty(input))
            {
                // We got a null input; return an empty markup parse result
                return(new MarkupParseResult
                {
                    Text = string.Empty,
                    Attributes = new List <MarkupAttribute>(),
                });
            }

            this.input = input.Normalize();

            this.stringReader = new StringReader(this.input);

            var stringBuilder = new StringBuilder();

            var markers = new List <MarkupAttributeMarker>();

            int nextCharacter;

            char lastCharacter = char.MinValue;

            // Read the entirety of the line
            while ((nextCharacter = this.stringReader.Read()) != -1)
            {
                char c = (char)nextCharacter;

                if (c == '\\')
                {
                    // This may be the start of an escaped bracket ("\[" or
                    // "\]"). Peek ahead to see if it is.
                    var nextC = (char)this.stringReader.Peek();

                    if (nextC == '[' || nextC == ']')
                    {
                        // It is! We'll discard this '\', and read the next
                        // character as plain text.
                        c = (char)this.stringReader.Read();
                        stringBuilder.Append(c);
                        this.sourcePosition += 1;
                        continue;
                    }
                    else
                    {
                        // It wasn't an escaped bracket. Continue on, and
                        // parse the '\' as a normal character.
                    }
                }

                if (c == '[')
                {
                    // How long is our current string, in text elements
                    // (i.e. visible glyphs)?
                    this.position = new System.Globalization.StringInfo(stringBuilder.ToString()).LengthInTextElements;

                    // The start of a marker!
                    MarkupAttributeMarker marker = this.ParseAttributeMarker();

                    markers.Add(marker);

                    var hadPrecedingWhitespaceOrLineStart = this.position == 0 || char.IsWhiteSpace(lastCharacter);

                    bool wasReplacementMarker = false;

                    // Is this a replacement marker?
                    if (marker.Name != null && this.markerProcessors.ContainsKey(marker.Name))
                    {
                        wasReplacementMarker = true;

                        // Process it and get the replacement text!
                        var replacementText = this.ProcessReplacementMarker(marker);

                        // Insert it into our final string and update our
                        // position accordingly
                        stringBuilder.Append(replacementText);
                    }

                    bool trimWhitespaceIfAble = false;

                    if (hadPrecedingWhitespaceOrLineStart)
                    {
                        // By default, self-closing markers will trim a
                        // single trailing whitespace after it if there was
                        // preceding whitespace. This doesn't happen if the
                        // marker was a replacement marker, or it has a
                        // property "trimwhitespace" (which must be
                        // boolean) set to false. All markers can opt-in to
                        // trailing whitespace trimming by having a
                        // 'trimwhitespace' property set to true.
                        if (marker.Type == TagType.SelfClosing)
                        {
                            trimWhitespaceIfAble = !wasReplacementMarker;
                        }

                        if (marker.TryGetProperty(TrimWhitespaceProperty, out var prop))
                        {
                            if (prop.Type != MarkupValueType.Bool)
                            {
                                throw new MarkupParseException($"Error parsing line {this.input}: attribute {marker.Name} at position {this.position} has a {prop.Type.ToString().ToLower()} property \"{TrimWhitespaceProperty}\" - this property is required to be a boolean value.");
                            }

                            trimWhitespaceIfAble = prop.BoolValue;
                        }
                    }

                    if (trimWhitespaceIfAble)
                    {
                        // If there's trailing whitespace, and we want to
                        // remove it, do so
                        if (this.PeekWhitespace())
                        {
                            // Consume the single trailing whitespace
                            // character (and don't update position)
                            this.stringReader.Read();
                            this.sourcePosition += 1;
                        }
                    }
                }
                else
                {
                    // plain text! add it to the resulting string and
                    // advance the parser's plain-text position
                    stringBuilder.Append(c);
                    this.sourcePosition += 1;
                }

                lastCharacter = c;
            }

            var attributes = this.BuildAttributesFromMarkers(markers);

            var characterAttributeIsPresent = false;

            foreach (var attribute in attributes)
            {
                if (attribute.Name == CharacterAttribute)
                {
                    characterAttributeIsPresent = true;
                }
            }

            if (characterAttributeIsPresent == false)
            {
                // Attempt to generate a character attribute from the start
                // of the string to the first colon
                var match = EndOfCharacterMarker.Match(this.input);

                if (match.Success)
                {
                    var endRange      = match.Index + match.Length;
                    var characterName = this.input.Substring(0, match.Index);

                    MarkupValue nameValue = new MarkupValue
                    {
                        Type        = MarkupValueType.String,
                        StringValue = characterName,
                    };

                    MarkupProperty nameProperty = new MarkupProperty(CharacterAttributeNameProperty, nameValue);

                    var characterAttribute = new MarkupAttribute(0, 0, endRange, CharacterAttribute, new[] { nameProperty });

                    attributes.Add(characterAttribute);
                }
            }

            return(new MarkupParseResult
            {
                Text = stringBuilder.ToString(),
                Attributes = attributes,
            });
        }