/// <summary> /// Creates a new <tt>Template</tt> object from liquid source code /// </summary> /// <param name="source">The Liquid Template string</param> /// <param name="syntaxCompatibilityLevel">The Liquid syntax flag used for backward compatibility</param> /// <returns></returns> public static Template Parse(string source, SyntaxCompatibility syntaxCompatibilityLevel) { Template template = new Template(); template.ParseInternal(source, syntaxCompatibilityLevel); return(template); }
public static void AssertTemplateResult(string expected, string template, SyntaxCompatibility syntax = SyntaxCompatibility.DotLiquid20) { AssertTemplateResult(expected: expected, template: template, localVariables: null, syntax: syntax); }
public static void AssertTemplateResult(string expected, string template, Hash localVariables, IEnumerable <Type> localFilters, SyntaxCompatibility syntax = SyntaxCompatibility.DotLiquid20) { var parameters = new RenderParameters(System.Globalization.CultureInfo.CurrentCulture) { LocalVariables = localVariables, SyntaxCompatibilityLevel = syntax, Filters = localFilters }; Assert.AreEqual(expected, Template.Parse(template).Render(parameters)); }
public static void AssertTemplateResult(string expected, string template, object anonymousObject, INamingConvention namingConvention, SyntaxCompatibility syntax = SyntaxCompatibility.DotLiquid20) { LockTemplateStaticVars(namingConvention, () => { var localVariables = anonymousObject == null ? null : Hash.FromAnonymousObject(anonymousObject); var parameters = new RenderParameters(System.Globalization.CultureInfo.CurrentCulture) { LocalVariables = localVariables, SyntaxCompatibilityLevel = syntax }; Assert.AreEqual(expected, Template.Parse(template).Render(parameters)); }); }
/// <summary> /// Parse source code. /// Returns self for easy chaining /// </summary> /// <param name="source">The source code.</param> /// <param name="syntaxCompatibilityLevel">The Liquid syntax flag used for backward compatibility</param> /// <returns>The template.</returns> internal Template ParseInternal(string source, SyntaxCompatibility syntaxCompatibilityLevel) { this.Root = new Document(); this.Root.Initialize(tagName: null, markup: null, tokens: Tokenizer.Tokenize(source, syntaxCompatibilityLevel)); return(this); }
/// <summary> /// Splits a string into an array of `tokens` that represent either a tag, object/variable, or literal string /// </summary> /// <param name="source">The Liquid Template string</param> /// <param name="syntaxCompatibilityLevel">The Liquid syntax flag used for backward compatibility</param> /// <exception cref="SyntaxException"></exception> internal static List <string> Tokenize(string source, SyntaxCompatibility syntaxCompatibilityLevel) { if (string.IsNullOrEmpty(source)) { return(new List <string>()); } // Trim leading whitespace - backward compatible list of chars var whitespaceChars = syntaxCompatibilityLevel < SyntaxCompatibility.DotLiquid22 ? WhitespaceCharsV20 : WhitespaceCharsV22; // Trim trailing whitespace - new lines or spaces/tabs but not both if (syntaxCompatibilityLevel < SyntaxCompatibility.DotLiquid22) { source = DotLiquid.Tags.Literal.FromShortHand(source); source = DotLiquid.Tags.Comment.FromShortHand(source); source = Regex.Replace(source, string.Format(@"-({0}|{1})(\n|\r\n|[ \t]+)?", Liquid.VariableEnd, Liquid.TagEnd), "$1", RegexOptions.None, Template.RegexTimeOut); } var tokens = new List <string>(); using (var markupEnumerator = new CharEnumerator(source)) { var match = LiquidAnyStartingTagRegex.Match(source, markupEnumerator.Position); while (match.Success) { // Check if there was a literal before the tag if (match.Index > markupEnumerator.Position) { var tokenBeforeMatch = ReadChars(markupEnumerator, match.Index - markupEnumerator.Position); if (match.Groups[2].Success) { tokenBeforeMatch = tokenBeforeMatch.TrimEnd(whitespaceChars); } if (tokenBeforeMatch != string.Empty) { tokens.Add(tokenBeforeMatch); } } var isTag = match.Groups[1].Value == "{%"; // Ignore hyphen in tag name, add the tag/variable itself var nextToken = new StringBuilder(markupEnumerator.Remaining); nextToken.Append(match.Groups[1].Value); ReadChars(markupEnumerator, match.Length); // Add the parameters and tag closure if (isTag) { if (!ReadToEndOfTag(nextToken, markupEnumerator, SearchQuoteOrTagEnd, syntaxCompatibilityLevel)) { throw new SyntaxException(Liquid.ResourceManager.GetString("BlockTagNotTerminatedException"), nextToken.ToString(), Liquid.TagEnd); } } else { if (!ReadToEndOfTag(nextToken, markupEnumerator, SearchQuoteOrVariableEnd, syntaxCompatibilityLevel)) { throw new SyntaxException(Liquid.ResourceManager.GetString("BlockVariableNotTerminatedException"), nextToken.ToString(), Liquid.VariableEnd); } } var token = nextToken.ToString(); tokens.Add(token); if (isTag) { var tagMatch = TagNameRegex.Match(token); if (tagMatch.Success) { var tagName = tagMatch.Groups[1].Value; if (Template.IsRawTag(tagName)) { var endTagRegex = EndTagRegexes.GetOrAdd(tagName, (key) => R.B(@"{0}-?\s*end{1}\s*-?{2}", Liquid.TagStart, key, Liquid.TagEnd)); var endTagMatch = endTagRegex.Match(source, markupEnumerator.Position); if (!endTagMatch.Success) { throw new SyntaxException(Liquid.ResourceManager.GetString("BlockTagNotClosedException"), tagName); } if (endTagMatch.Index > markupEnumerator.Position) //add tag of everything between start and end tags { tokens.Add(ReadChars(markupEnumerator, endTagMatch.Index - markupEnumerator.Position)); } } } } match = LiquidAnyStartingTagRegex.Match(source, markupEnumerator.Position); } if (markupEnumerator.Remaining > 0) { tokens.Add(ReadChars(markupEnumerator, markupEnumerator.Remaining)); } } return(tokens); }
/// <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); }