/// <summary> /// Reads a tag or object variable until the end sequence, <tt>%}</tt> or <tt>}}</tt> respectively and advances the enumerator position /// </summary> /// <param name="sb">The StringBuilder to write to</param> /// <param name="markupEnumerator">The string enumerator</param> /// <param name="searchChars">The character set to search for</param> /// <returns>True if reaches end sequence, otherwise false</returns> private static bool ReadToEndOfTag(StringBuilder sb, CharEnumerator markupEnumerator, HashSet <char> searchChars, SyntaxCompatibility syntaxCompatibilityLevel) { while (markupEnumerator.AppendNext(sb)) { char nextChar = markupEnumerator.Current; if (searchChars.Contains(nextChar)) { switch (nextChar) { case '\'': case '"': ReadToChar(sb, markupEnumerator, nextChar); break; case '}': case '%': if (markupEnumerator.Remaining > 0 && markupEnumerator.Next == '}') { var previousCharIsWhitespaceControl = syntaxCompatibilityLevel >= SyntaxCompatibility.DotLiquid22 && markupEnumerator.Previous == '-'; markupEnumerator.AppendNext(sb); if (previousCharIsWhitespaceControl) { // Remove hyphen from token sb.Remove(sb.Length - 3, 1); // Trim trailing whitespace by skipping ahead beyond the tag end while (markupEnumerator.Remaining > 0) { if (((uint)markupEnumerator.Next - '\t') <= 5 || markupEnumerator.Next == ' ') { markupEnumerator.MoveNext(); } else { break; } } } return(true); } break; } } } ; // Somehow we reached the end without finding the end character(s) return(false); }
/// <summary> /// Reads a fixed number of characters and advances the enumerator position /// </summary> /// <param name="markupEnumerator">The string enumerator</param> /// <param name="markupLength">The number of characters to read</param> private static string ReadChars(CharEnumerator markupEnumerator, int markupLength) { var sb = new StringBuilder(markupLength); for (var i = 0; i < markupLength; i++) { markupEnumerator.AppendNext(sb); } return(sb.ToString()); }
/// <summary> /// Reads a token until, and inclusive of, the end character and advances the enumerator position /// </summary> /// <param name="sb">The StringBuilder to write to</param> /// <param name="markupEnumerator">The string enumerator</param> /// <param name="endChar">The character that indicates end of token</param> /// <returns><see langword="true"/> if reaches <paramref name="endChar"/>, otherwise <see langword="false"/></returns> private static bool ReadToChar(StringBuilder sb, CharEnumerator markupEnumerator, char endChar) { while (markupEnumerator.AppendNext(sb)) { if (markupEnumerator.Current == endChar) { return(true); } } ; return(false); }
/// <summary> /// Reads a single word-character and advances the enumerator position /// </summary> /// <param name="sb">The StringBuilder to write to</param> /// <param name="markupEnumerator">The string enumerator</param> /// <returns>True if upcoming character is a word character, otherwise false</returns> private static bool ReadWordChar(StringBuilder sb, CharEnumerator markupEnumerator) { var nextChar = markupEnumerator.Next; if (nextChar < 128) // For better performance, avoid regex for standard ascii { if (!((uint)nextChar - '0' < 10 || (uint)nextChar - 'A' < 26 || (uint)nextChar - 'a' < 26 || nextChar == '_' || nextChar == '-')) { return(false); } } else if (!VariableSegmentRegex.IsMatch(nextChar.ToString())) { return(false); } markupEnumerator.AppendNext(sb); return(true); }
/// <summary> /// Enumerates over a variable sequence in dotted or bracket notation /// </summary> /// <param name="source">The Liquid Variable string</param> /// <exception cref="SyntaxException"></exception> internal static IEnumerator <string> GetVariableEnumerator(string source) { if (string.IsNullOrEmpty(source)) { yield break; } using (var markupEnumerator = new CharEnumerator(source)) { while (markupEnumerator.HasNext()) { var isComplete = false; var nextVariable = new StringBuilder(); switch (markupEnumerator.Next) { case '[': // Example Syntax: [var] or ["literal"] markupEnumerator.AppendNext(nextVariable); if (!markupEnumerator.HasNext()) { break; } switch (markupEnumerator.Next) { case '"': case '\'': markupEnumerator.AppendNext(nextVariable); isComplete = ReadToChar(nextVariable, markupEnumerator, markupEnumerator.Next) && ReadToChar(nextVariable, markupEnumerator, BracketEnd); break; default: isComplete = ReadToChar(nextVariable, markupEnumerator, BracketEnd); break; } break; default: isComplete = ReadWordChar(nextVariable, markupEnumerator) && ReadToEndOfVariable(nextVariable, markupEnumerator); break; } if (!isComplete) // Somehow we reached the end without finding the end character(s) { throw new SyntaxException(Liquid.ResourceManager.GetString("VariableNotTerminatedException"), source, Liquid.VariableEnd); } if (markupEnumerator.HasNext() && markupEnumerator.Next == '.' && markupEnumerator.Remaining > 1) // Don't include dot in tokens as it is a separator { markupEnumerator.MoveNext(); } if (nextVariable.Length > 0) { yield return(nextVariable.ToString()); } } ; } }