/// <summary>
 /// Matches a { "JSON" : "object" }, a ["JSON", "array"] or a terminal value (string, null, double, true or false)
 /// and any combination of them.
 /// Whitespaces and JS comments (//... or /* ... */) are skipped.
 /// </summary>
 /// <param name="this">This <see cref="VirtualStringMatcher"/>.</param>
 /// <param name="value">
 /// A list of objects (for array), a list of KeyValuePair&lt;string,object&gt; for object or
 /// a double, string, bollean or null (for null).</param>
 /// <returns>True on success, false on error.</returns>
 public static bool MatchJSONObject(this VirtualStringMatcher @this, out object value)
 {
     value = null;
     @this.SkipWhiteSpacesAndJSComments();
     if (@this.TryMatchChar('{'))
     {
         List <KeyValuePair <string, object> > c;
         if (!MatchJSONObjectContent(@this, out c))
         {
             return(false);
         }
         value = c;
         return(true);
     }
     if (@this.TryMatchChar('['))
     {
         List <object> t;
         if (!MatchJSONArrayContent(@this, out t))
         {
             return(false);
         }
         value = t;
         return(true);
     }
     if (TryMatchJSONTerminalValue(@this, out value))
     {
         return(true);
     }
     return(@this.SetError("JSON value."));
 }
 /// <summary>
 /// Matches a JSON array content: the match ends with the first ].
 /// Whitespaces and JS comments (//... or /* ... */) are skipped.
 /// </summary>
 /// <param name="this">This <see cref="VirtualStringMatcher"/>.</param>
 /// <param name="value">The list of objects on success.</param>
 /// <returns>True on success, false otherwise.</returns>
 public static bool MatchJSONArrayContent(this VirtualStringMatcher @this, out List <object> value)
 {
     value = null;
     while ([email protected])
     {
         @this.SkipWhiteSpacesAndJSComments();
         if (@this.TryMatchChar(']'))
         {
             if (value == null)
             {
                 value = new List <object>();
             }
             return(true);
         }
         object cell;
         if (!MatchJSONObject(@this, out cell))
         {
             return(false);
         }
         if (value == null)
         {
             value = new List <object>();
         }
         value.Add(cell);
         @this.SkipWhiteSpacesAndJSComments();
         // Allow trailing comma: ,] is valid.
         @this.TryMatchChar(',');
     }
     return(@this.SetError("JSON array definition but reached end of match."));
 }
        /// <summary>
        /// Matches a JSON terminal value: a "string", null, a number (double value), true or false.
        /// No error is set if match fails.
        /// </summary>
        /// <param name="this">This <see cref="VirtualStringMatcher"/>.</param>
        /// <param name="value">The parsed value. Can be null.</param>
        /// <returns>True if a JSON value has been matched, false otherwise.</returns>
        public static bool TryMatchJSONTerminalValue(this VirtualStringMatcher @this, out object value)
        {
            string s;

            if (@this.TryMatchJSONQuotedString(out s, true))
            {
                value = s;
                return(true);
            }
            double d;

            if (@this.TryMatchDoubleValue(out d))
            {
                value = d;
                return(true);
            }
            if (@this.TryMatchText("true"))
            {
                value = true;
                return(true);
            }
            if (@this.TryMatchText("false"))
            {
                value = false;
                return(true);
            }
            value = null;
            return(false);
        }
        /// <summary>
        /// Matches a quoted string without extracting its content.
        /// </summary>
        /// <param name="this">This <see cref="VirtualStringMatcher"/>.</param>
        /// <param name="allowNull">True to allow 'null'.</param>
        /// <returns><c>true</c> when matched, <c>false</c> otherwise.</returns>
        public static bool TryMatchJSONQuotedString(this VirtualStringMatcher @this, bool allowNull = false)
        {
            if (@this.IsEnd)
            {
                return(false);
            }
            long i = @this.StartIndex;

            if (@this.Text[i++] != '"')
            {
                return(allowNull && @this.TryMatchText("null"));
            }
            long len = @this.Length - 1;

            while (len >= 0)
            {
                if (len == 0)
                {
                    return(false);
                }
                char c = @this.Text[i++];
                --len;
                if (c == '"')
                {
                    break;
                }
                if (c == '\\')
                {
                    i++;
                    --len;
                }
            }
            return(@this.UncheckedMove(i - @this.StartIndex));
        }
 /// <summary>
 /// Matches a JSON terminal value: a "string", null, a number (double value), true or false.
 /// This method ignores the actual value and does not set any error if match fails.
 /// </summary>
 /// <param name="this">This <see cref="VirtualStringMatcher"/>.</param>
 /// <returns>True if a JSON value has been matched, false otherwise.</returns>
 public static bool TryMatchJSONTerminalValue(this VirtualStringMatcher @this)
 {
     return(@this.TryMatchJSONQuotedString(true) ||
            @this.TryMatchDoubleValue() ||
            @this.TryMatchText("true") ||
            @this.TryMatchText("false"));
 }
 /// <summary>
 /// Matches a Guid. No error is set if match fails.
 /// </summary>
 /// <remarks>
 /// Any of the 5 forms of Guid can be matched:
 /// <list type="table">
 /// <item><term>N</term><description>00000000000000000000000000000000</description></item>
 /// <item><term>D</term><description>00000000-0000-0000-0000-000000000000</description></item>
 /// <item><term>B</term><description>{00000000-0000-0000-0000-000000000000}</description></item>
 /// <item><term>P</term><description>(00000000-0000-0000-0000-000000000000)</description></item>
 /// <item><term>X</term><description>{0x00000000,0x0000,0x0000,{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}}</description></item>
 /// </list>
 /// </remarks>
 /// <param name="this">This <see cref="VirtualStringMatcher"/>.</param>
 /// <param name="id">The result Guid. <see cref="Guid.Empty"/> on failure.</param>
 /// <returns><c>true</c> when matched, <c>false</c> otherwise.</returns>
 public static bool TryMatchGuid(this VirtualStringMatcher @this, out Guid id)
 {
     id = Guid.Empty;
     if (@this.Length < 32)
     {
         return(false);
     }
     if (@this.Head == '{')
     {
         // Form "B" or "X".
         if (@this.Length < 38)
         {
             return(false);
         }
         if (@this.Text[@this.StartIndex + 37] == '}')
         {
             // The "B" form.
             if (Guid.TryParseExact(@this.Text.GetText(@this.StartIndex, 38), "B", out id))
             {
                 return(@this.UncheckedMove(38));
             }
             return(false);
         }
         // The "X" form.
         if (@this.Length >= 68 && Guid.TryParseExact(@this.Text.GetText(@this.StartIndex, 68), "X", out id))
         {
             return(@this.UncheckedMove(68));
         }
         return(false);
     }
     if (@this.Head == '(')
     {
         // Can only be the "P" form.
         if (@this.Length >= 38 && Guid.TryParseExact(@this.Text.GetText(@this.StartIndex, 38), "P", out id))
         {
             return(@this.UncheckedMove(38));
         }
         return(false);
     }
     if (@this.Head.HexDigitValue() >= 0)
     {
         // The "N" or "D" form.
         if (@this.Length >= 36 && @this.Text[@this.StartIndex + 8] == '-')
         {
             // The ""D" form.
             if (Guid.TryParseExact(@this.Text.GetText(@this.StartIndex, 36), "D", out id))
             {
                 return(@this.UncheckedMove(36));
             }
             return(false);
         }
         if (Guid.TryParseExact(@this.Text.GetText(@this.StartIndex, 32), "N", out id))
         {
             return(@this.UncheckedMove(32));
         }
     }
     return(false);
 }
 /// <summary>
 /// Skips any white spaces or JS comments (//... or /* ... */) and always returns true.
 /// </summary>
 /// <param name="this">This <see cref="VirtualStringMatcher"/>.</param>
 /// <returns>Always true to ease composition.</returns>
 public static bool SkipWhiteSpacesAndJSComments(this VirtualStringMatcher @this)
 {
     @this.MatchWhiteSpaces(0);
     while (@this.TryMatchJSComment())
     {
         @this.MatchWhiteSpaces(0);
     }
     return(true);
 }
        /// <summary>
        /// Matches Int32 values that must not start with '0' ('0' is valid but '0d', where d is any digit, is not).
        /// A signed integer starts with a '-'. '-0' is valid but '-0d' (where d is any digit) is not.
        /// If the value is to big for an Int32, it fails.
        /// </summary>
        /// <param name="this">This <see cref="VirtualStringMatcher"/>.</param>
        /// <param name="i">The result integer. 0 on failure.</param>
        /// <param name="minValue">Optional minimal value.</param>
        /// <param name="maxValue">Optional maximal value.</param>
        /// <returns><c>true</c> when matched, <c>false</c> otherwise.</returns>
        public static bool MatchInt32(this VirtualStringMatcher @this, out int i, int minValue = int.MinValue, int maxValue = int.MaxValue)
        {
            i = 0;
            long savedIndex = @this.StartIndex;
            long value      = 0;
            bool signed;

            if (@this.IsEnd)
            {
                return(@this.SetError());
            }
            if ((signed = @this.TryMatchChar('-')) && @this.IsEnd)
            {
                return(@this.BackwardAddError(savedIndex));
            }
            char c;

            if (@this.TryMatchChar('0'))
            {
                if ([email protected] && (c = @this.Head) >= '0' && c <= '9')
                {
                    return(@this.BackwardAddError(savedIndex, "0...9"));
                }
                return(@this.SetSuccess());
            }
            unchecked
            {
                long iMax = Int32.MaxValue;
                if (signed)
                {
                    iMax = iMax + 1;
                }
                while ([email protected] && (c = @this.Head) >= '0' && c <= '9')
                {
                    value = value * 10 + (c - '0');
                    if (value > iMax)
                    {
                        break;
                    }
                    @this.UncheckedMove(1);
                }
            }
            if (@this.StartIndex > savedIndex)
            {
                if (signed)
                {
                    value = -value;
                }
                if (value < minValue || value > maxValue)
                {
                    return(@this.BackwardAddError(savedIndex, String.Format(CultureInfo.InvariantCulture, "value between {0} and {1}", minValue, maxValue)));
                }
                i = (int)value;
                return(@this.SetSuccess());
            }
            return(@this.SetError());
        }
        /// <summary>
        /// Matches a double without getting its value nor setting an error if match fails.
        /// This uses <see cref="RegexDouble"/> with a string's length of 24.
        /// </summary>
        /// <param name="this">This <see cref="VirtualStringMatcher"/>.</param>
        /// <returns><c>true</c> when matched, <c>false</c> otherwise.</returns>
        public static bool TryMatchDoubleValue(this VirtualStringMatcher @this)
        {
            long  len = Math.Min(@this.Length, maxDoubleLength);
            Match m   = RegexDouble.Match(@this.Text.GetText(@this.StartIndex, (int)len));

            if (!m.Success)
            {
                return(false);
            }
            return(@this.UncheckedMove(m.Length));
        }
        /// <summary>
        /// Matches a double and gets its value. No error is set if match fails.
        /// This uses <see cref="RegexDouble"/> with a string's length of 24.
        /// </summary>
        /// <param name="this">This <see cref="VirtualStringMatcher"/>.</param>
        /// <param name="value">The read value on success.</param>
        /// <returns><c>true</c> when matched, <c>false</c> otherwise.</returns>
        public static bool TryMatchDoubleValue(this VirtualStringMatcher @this, out double value)
        {
            long  len = Math.Min(@this.Length, maxDoubleLength);
            Match m   = RegexDouble.Match(@this.Text.GetText(@this.StartIndex, (int)len));

            if (!m.Success)
            {
                value = 0;
                return(false);
            }
            if (!double.TryParse(@this.Text.GetText(@this.StartIndex, m.Length), NumberStyles.Float, CultureInfo.InvariantCulture, out value))
            {
                return(false);
            }
            return(@this.UncheckedMove(m.Length));
        }
 /// <summary>
 /// Matches a very simple version of a JSON object content: this match stops at the first closing }.
 /// Whitespaces and JS comments (//... or /* ... */) are skipped.
 /// </summary>
 /// <param name="this">This <see cref="VirtualStringMatcher"/>.</param>
 /// <param name="o">The read object on success as a list of KeyValuePair.</param>
 /// <returns>True on success, false on error.</returns>
 public static bool MatchJSONObjectContent(this VirtualStringMatcher @this, out List <KeyValuePair <string, object> > o)
 {
     o = null;
     while ([email protected])
     {
         @this.SkipWhiteSpacesAndJSComments();
         string propName;
         object value;
         if (@this.TryMatchChar('}'))
         {
             if (o == null)
             {
                 o = new List <KeyValuePair <string, object> >();
             }
             return(true);
         }
         if ([email protected](out propName))
         {
             o = null;
             return(@this.SetError("Quoted Property Name."));
         }
         @this.SkipWhiteSpacesAndJSComments();
         if ([email protected](':') || !MatchJSONObject(@this, out value))
         {
             o = null;
             return(false);
         }
         if (o == null)
         {
             o = new List <KeyValuePair <string, object> >();
         }
         o.Add(new KeyValuePair <string, object>(propName, value));
         @this.SkipWhiteSpacesAndJSComments();
         // This accepts e trailing comma at the end of a property list: ..."a":0,} is not an error.
         @this.TryMatchChar(',');
     }
     return(@this.SetError("JSON object definition but reached end of match."));
 }
 /// <summary>
 /// Tries to match a //.... or /* ... */ comment.
 /// Proper termination of comment (by a new line or the closing */) is not required:
 /// a ending /*... is considered valid.
 /// </summary>
 /// <param name="this">This <see cref="VirtualStringMatcher"/>.</param>
 /// <returns>True on success, false if the <see cref="VirtualStringMatcher.Head"/> is not on a /.</returns>
 public static bool TryMatchJSComment(this VirtualStringMatcher @this)
 {
     if ([email protected]('/'))
     {
         return(false);
     }
     if (@this.TryMatchChar('/'))
     {
         while ([email protected] && @this.Head != '\n')
         {
             @this.UncheckedMove(1);
         }
         if ([email protected])
         {
             @this.UncheckedMove(1);
         }
         return(true);
     }
     else if (@this.TryMatchChar('*'))
     {
         while ([email protected])
         {
             if (@this.Head == '*')
             {
                 @this.UncheckedMove(1);
                 if (@this.IsEnd || @this.TryMatchChar('/'))
                 {
                     return(true);
                 }
             }
             @this.UncheckedMove(1);
         }
         return(true);
     }
     @this.UncheckedMove(-1);
     return(false);
 }
        /// <summary>
        /// Matches a JSON quoted string without setting an error if match fails.
        /// </summary>
        /// <param name="this">This <see cref="VirtualStringMatcher"/>.</param>
        /// <param name="content">Extracted content.</param>
        /// <param name="allowNull">True to allow 'null'.</param>
        /// <returns><c>true</c> when matched, <c>false</c> otherwise.</returns>
        public static bool TryMatchJSONQuotedString(this VirtualStringMatcher @this, out string content, bool allowNull = false)
        {
            content = null;
            if (@this.IsEnd)
            {
                return(false);
            }
            long i = @this.StartIndex;

            if (@this.Text[i++] != '"')
            {
                return(allowNull && @this.TryMatchText("null"));
            }
            long          len = @this.Length - 1;
            StringBuilder b   = null;

            while (len >= 0)
            {
                if (len == 0)
                {
                    return(false);
                }
                char c = @this.Text[i++];
                --len;
                if (c == '"')
                {
                    break;
                }
                if (c == '\\')
                {
                    if (len == 0)
                    {
                        return(false);
                    }
                    if (b == null)
                    {
                        b = new StringBuilder(@this.Text.GetText(@this.StartIndex + 1, (int)(i - @this.StartIndex - 2)));
                    }
                    switch ((c = @this.Text[i++]))
                    {
                    case 'r': c = '\r'; break;

                    case 'n': c = '\n'; break;

                    case 'b': c = '\b'; break;

                    case 't': c = '\t'; break;

                    case 'f': c = '\f'; break;

                    case 'u':
                    {
                        if (--len == 0)
                        {
                            return(false);
                        }
                        int cN = ReadHexDigit(@this.Text[i++]);
                        if (cN < 0 || cN > 15)
                        {
                            return(false);
                        }
                        int val = cN << 12;
                        if (--len == 0)
                        {
                            return(false);
                        }
                        cN = ReadHexDigit(@this.Text[i++]);
                        if (cN < 0 || cN > 15)
                        {
                            return(false);
                        }
                        val |= cN << 8;
                        if (--len == 0)
                        {
                            return(false);
                        }
                        cN = ReadHexDigit(@this.Text[i++]);
                        if (cN < 0 || cN > 15)
                        {
                            return(false);
                        }
                        val |= cN << 4;
                        if (--len == 0)
                        {
                            return(false);
                        }
                        cN = ReadHexDigit(@this.Text[i++]);
                        if (cN < 0 || cN > 15)
                        {
                            return(false);
                        }
                        val |= cN;
                        c    = (char)val;
                        break;
                    }
                    }
                }
                if (b != null)
                {
                    b.Append(c);
                }
            }
            long lenS = i - @this.StartIndex;

            if (b != null)
            {
                content = b.ToString();
            }
            else
            {
                content = @this.Text.GetText(@this.StartIndex + 1, (int)lenS - 2);
            }
            return(@this.UncheckedMove(lenS));
        }
Esempio n. 14
0
 /// <summary>
 /// Initialize a new <see cref="JSONVirtualVisitor"/> bound to a <see cref="Matcher"/>.
 /// </summary>
 /// <param name="m">The virtual string matcher.</param>
 public JSONVirtualVisitor(VirtualStringMatcher m)
 {
     _m    = m;
     _path = new List <Parent>();
 }