// {{#if ...}} // ^ public static ReadOnlyMemory <char> ParseTemplateScriptBlock(this ReadOnlyMemory <char> literal, ScriptContext context, out PageBlockFragment blockFragment) { literal = literal.ParseVarName(out var blockNameSpan); PageBlockFragment statement; var blockName = blockNameSpan.ToString(); var endBlock = "{{/" + blockName + "}}"; var endExprPos = literal.IndexOf("}}"); if (endExprPos == -1) { throw new SyntaxErrorException($"Unterminated '{blockName}' block expression, near '{literal.DebugLiteral()}'"); } var argument = literal.Slice(0, endExprPos).Trim(); literal = literal.Advance(endExprPos + 2); var language = context.ParseAsLanguage.TryGetValue(blockName, out var lang) ? lang : ScriptTemplate.Language; if (language.Name == ScriptVerbatim.Language.Name) { var endBlockPos = literal.IndexOf(endBlock); if (endBlockPos == -1) { throw new SyntaxErrorException($"Unterminated end block '{endBlock}'"); } var body = literal.Slice(0, endBlockPos); literal = literal.Advance(endBlockPos + endBlock.Length).TrimFirstNewLine(); blockFragment = language.ParseVerbatimBlock(blockName, argument, body); return(literal); } literal = literal.ParseTemplateBody(blockNameSpan, out var bodyText); var bodyFragments = language.Parse(context, bodyText); var elseBlocks = new List <PageElseBlock>(); while (literal.StartsWith("{{else")) { literal = literal.ParseTemplateElseBlock(blockName, out var elseArgument, out var elseBody); var elseBlock = new PageElseBlock(elseArgument, language.Parse(context, elseBody)); elseBlocks.Add(elseBlock); } literal = literal.Advance(2 + 1 + blockName.Length + 2); //remove new line after partial block end tag literal = literal.TrimFirstNewLine(); blockFragment = new PageBlockFragment(blockName, argument, bodyFragments, elseBlocks); return(literal); }
// {{#name}} {{else if a=b}} {{else}} {{/name}} // ^ // returns ^ ^ static ReadOnlyMemory <char> ParseStatementBody(this ReadOnlyMemory <char> literal, ReadOnlyMemory <char> blockName, out List <PageFragment> body) { var inStatements = 0; var pos = 0; while (true) { pos = literal.IndexOf("{{", pos); if (pos == -1) { throw new SyntaxErrorException($"End block for '{blockName}' not found."); } var c = literal.SafeGetChar(pos + 2); if (c == '#') { inStatements++; pos = literal.IndexOf("}}", pos) + 2; //end of expression continue; } if (c == '/') { if (inStatements == 0) { literal.Slice(pos + 2 + 1).ParseVarName(out var name); if (name.EqualsOrdinal(blockName)) { body = ParseTemplatePage(literal.Slice(0, pos).TrimFirstNewLine()); return(literal.Slice(pos)); } } inStatements--; } else if (literal.Slice(pos + 2).StartsWith("else")) { if (inStatements == 0) { body = ParseTemplatePage(literal.Slice(0, pos).TrimFirstNewLine()); return(literal.Slice(pos)); } } pos += 2; } }
public static ReadOnlyMemory <char> RightPart(this ReadOnlyMemory <char> strVal, string needle) { if (strVal.IsEmpty) { return(strVal); } var pos = strVal.IndexOf(needle); return(pos == -1 ? strVal : strVal.Slice(pos + needle.Length)); }
public static ReadOnlyMemory <char> LeftPart(this ReadOnlyMemory <char> strVal, char needle) { if (strVal.IsEmpty) { return(strVal); } var pos = strVal.IndexOf(needle); return(pos == -1 ? strVal : strVal.Slice(0, pos)); }
// #if ... // ^ public static ReadOnlyMemory <char> ParseCodeScriptBlock(this ReadOnlyMemory <char> literal, ScriptContext context, out PageBlockFragment blockFragment) { literal = literal.ParseVarName(out var blockNameSpan); var endArgumentPos = literal.IndexOf('\n'); var argument = literal.Slice(0, endArgumentPos).Trim(); literal = literal.Slice(endArgumentPos + 1); var blockName = blockNameSpan.ToString(); var language = context.ParseAsLanguage.TryGetValue(blockName, out var lang) ? lang : ScriptCode.Language; if (language.Name == ScriptVerbatim.Language.Name) { literal = literal.ParseCodeBody(blockNameSpan, out var body); body = body.ChopNewLine(); blockFragment = language.ParseVerbatimBlock(blockName, argument, body); return(literal); } literal = literal.ParseCodeBody(blockNameSpan, out var bodyText); var bodyFragments = language.Parse(context, bodyText); var elseBlocks = new List <PageElseBlock>(); literal = literal.AdvancePastWhitespace(); while (literal.StartsWith("else")) { literal = literal.ParseCodeElseBlock(blockNameSpan, out var elseArgument, out var elseBody); var elseBlock = new PageElseBlock(elseArgument, language.Parse(context, elseBody)); elseBlocks.Add(elseBlock); literal = literal.AdvancePastWhitespace(); } blockFragment = new PageBlockFragment(blockName, argument, bodyFragments, elseBlocks); return(literal); }
// {{else if a=b}} {{else}} {{/name}} // ^ // returns ^ ^ static ReadOnlyMemory <char> ParseElseStatement(this ReadOnlyMemory <char> literal, string blockName, out PageElseBlock statement) { var inStatements = 0; var pos = 0; statement = null; var statementPos = -1; var elseExpr = default(ReadOnlyMemory <char>); while (true) { pos = literal.IndexOf("{{", pos); if (pos == -1) { throw new SyntaxErrorException($"End block for 'else' not found."); } var c = literal.SafeGetChar(pos + 2); if (c == '#') { inStatements++; pos = literal.IndexOf("}}", pos) + 2; //end of expression } else if (c == '/') { if (inStatements == 0) { literal.Slice(pos + 2 + 1).ParseVarName(out var name); if (name.EqualsOrdinal(blockName)) { var body = ParseTemplatePage(literal.Slice(statementPos, pos - statementPos).TrimFirstNewLine()); statement = new PageElseBlock(elseExpr, body); return(literal.Slice(pos)); } } inStatements--; } else if (literal.Slice(pos + 2).StartsWith("else")) { if (inStatements == 0) { if (statementPos >= 0) { var bodyText = literal.Slice(statementPos, pos - statementPos).TrimFirstNewLine(); var body = ParseTemplatePage(bodyText); statement = new PageElseBlock(elseExpr, body); return(literal.Slice(pos)); } var endExprPos = literal.IndexOf("}}", pos); if (endExprPos == -1) { throw new SyntaxErrorException($"End expression for 'else' not found."); } var exprStartPos = pos + 2 + 4; //= {{else... elseExpr = literal.Slice(exprStartPos, endExprPos - exprStartPos).Trim(); statementPos = endExprPos + 2; } } pos += 2; } }
public static List <PageFragment> ParseTemplatePage(ReadOnlyMemory <char> text) { var to = new List <PageFragment>(); if (text.IsNullOrWhiteSpace()) { return(to); } int pos; var lastPos = 0; while ((pos = text.IndexOf("{{", lastPos)) != -1) { var block = text.Slice(lastPos, pos - lastPos); if (!block.IsNullOrEmpty()) { to.Add(new PageStringFragment(block)); } var varStartPos = pos + 2; var firstChar = text.Span[varStartPos]; if (firstChar == '*') //comment { lastPos = text.IndexOf("*}}", varStartPos) + 3; } else if (firstChar == '#') //block statement { var literal = text.Slice(varStartPos + 1); literal = literal.ParseVarName(out var blockNameSpan); var blockName = blockNameSpan.ToString(); var endExprPos = literal.IndexOf("}}"); if (endExprPos == -1) { throw new SyntaxErrorException($"Unterminated '{blockName}' block expression, near '{literal.DebugLiteral()}'"); } var blockExpr = literal.Slice(0, endExprPos).Trim(); literal = literal.Advance(endExprPos + 2); if (!ScriptConfig.DontEvaluateBlocksNamed.Contains(blockName)) { literal = literal.ParseStatementBody(blockNameSpan, out var body); var elseStatements = new List <PageElseBlock>(); while (literal.StartsWith("{{else")) { literal = literal.ParseElseStatement(blockName, out var elseStatement); elseStatements.Add(elseStatement); } literal = literal.Advance(2 + 1 + blockName.Length + 2); //remove new line after partial block end tag literal = literal.TrimFirstNewLine(); var length = text.Length - pos - literal.Length; var originalText = text.Slice(pos, length); lastPos = pos + length; var statement = new PageBlockFragment(originalText, blockName, blockExpr, body, elseStatements); to.Add(statement); } else { var endBlock = "{{/" + blockName + "}}"; var endBlockPos = literal.IndexOf(endBlock); if (endBlockPos == -1) { throw new SyntaxErrorException($"Unterminated end block '{endBlock}'"); } var endBlockBody = literal.Slice(0, endBlockPos); literal = literal.Advance(endBlockPos + endBlock.Length).TrimFirstNewLine(); var body = new List <PageFragment> { new PageStringFragment(endBlockBody) }; var length = text.Length - pos - literal.Length; var originalText = text.Slice(pos, length); lastPos = pos + length; var statement = new PageBlockFragment(originalText, blockName, blockExpr, body); to.Add(statement); } } else { var literal = text.Slice(varStartPos).Span; literal = literal.ParseJsExpression(out var expr, filterExpression: true); var filters = new List <JsCallExpression>(); if (!literal.StartsWith("}}")) { literal = literal.AdvancePastWhitespace(); if (literal.FirstCharEquals(FilterSep)) { literal = literal.Advance(1); while (true) { literal = literal.ParseJsCallExpression(out var filter, filterExpression: true); filters.Add(filter); literal = literal.AdvancePastWhitespace(); if (literal.IsNullOrEmpty()) { throw new SyntaxErrorException("Unterminated filter expression"); } if (literal.StartsWith("}}")) { literal = literal.Advance(2); break; } if (!literal.FirstCharEquals(FilterSep)) { throw new SyntaxErrorException( $"Expected filter separator '|' but was {literal.DebugFirstChar()}"); } literal = literal.Advance(1); } } else { if (!literal.IsNullOrEmpty()) { literal = literal.Advance(1); } } } else { literal = literal.Advance(2); } var length = text.Length - pos - literal.Length; var originalText = text.Slice(pos, length); lastPos = pos + length; var varFragment = new PageVariableFragment(originalText, expr, filters); to.Add(varFragment); var newLineLen = literal.StartsWith("\n") ? 1 : literal.StartsWith("\r\n") ? 2 : 0; if (newLineLen > 0) { var lastExpr = varFragment.FilterExpressions?.LastOrDefault(); var filterName = lastExpr?.Name ?? varFragment?.InitialExpression?.Name ?? varFragment.Binding; if (filterName != null && ScriptConfig.RemoveNewLineAfterFiltersNamed.Contains(filterName)) { lastPos += newLineLen; } } } } if (lastPos != text.Length) { var lastBlock = lastPos == 0 ? text : text.Slice(lastPos); to.Add(new PageStringFragment(lastBlock)); } return(to); }
public static List <PageFragment> ParseTemplate(this ScriptContext context, ReadOnlyMemory <char> text) { var to = new List <PageFragment>(); if (text.IsNullOrWhiteSpace()) { return(to); } int pos; var lastPos = 0; int nextPos() { var c1 = text.IndexOf("{{", lastPos); var c2 = text.IndexOf("{|", lastPos); if (c2 == -1) { return(c1); } return(c1 == -1 ? c2 : c1 < c2 ? c1 : c2); } while ((pos = nextPos()) != -1) { var block = text.Slice(lastPos, pos - lastPos); if (!block.IsNullOrEmpty()) { to.Add(new PageStringFragment(block)); } var varStartPos = pos + 2; if (varStartPos >= text.Span.Length) { throw new SyntaxErrorException($"Unterminated '{{{{' expression, near '{text.Slice(lastPos).DebugLiteral()}'"); } if (text.Span.SafeCharEquals(varStartPos - 1, '|')) // lang expression syntax {|lang ... |} https://flow.org/en/docs/types/objects/#toc-exact-object-types { var literal = text.Slice(varStartPos); ScriptLanguage lang = null; if (literal.SafeGetChar(0).IsValidVarNameChar()) { literal = literal.ParseVarName(out var langSpan); lang = context.GetScriptLanguage(langSpan.ToString()); if (lang != null) { var endPos = literal.IndexOf("|}"); if (endPos == -1) { throw new SyntaxErrorException($"Unterminated '|}}' expression, near '{text.Slice(varStartPos).DebugLiteral()}'"); } var exprStr = literal.Slice(0, endPos); var langExprFragment = lang.Parse(context, exprStr); to.AddRange(langExprFragment); } } if (lang == null) { var nextLastPos = text.IndexOf("|}", varStartPos) + 2; block = text.Slice(pos, nextLastPos - pos); if (!block.IsNullOrEmpty()) { to.Add(new PageStringFragment(block)); } } lastPos = text.IndexOf("|}", varStartPos) + 2; continue; } var firstChar = text.Span[varStartPos]; if (firstChar == '*') //comment { lastPos = text.IndexOf("*}}", varStartPos) + 3; if (text.Span.SafeCharEquals(lastPos, '\r')) { lastPos++; } if (text.Span.SafeCharEquals(lastPos, '\n')) { lastPos++; } } else if (firstChar == '#') //block statement { var literal = text.Slice(varStartPos + 1); literal = literal.ParseTemplateScriptBlock(context, out var blockFragment); var length = text.Length - pos - literal.Length; blockFragment.OriginalText = text.Slice(pos, length); lastPos = pos + length; to.Add(blockFragment); } else { var literal = text.Slice(varStartPos).Span; literal = literal.ParseJsExpression(out var expr, filterExpression: true); var filters = new List <JsCallExpression>(); if (!literal.StartsWith("}}")) { literal = literal.AdvancePastWhitespace(); if (literal.FirstCharEquals(FilterSep)) { literal = literal.AdvancePastPipeOperator(); while (true) { literal = literal.ParseJsCallExpression(out var filter, filterExpression: true); filters.Add(filter); literal = literal.AdvancePastWhitespace(); if (literal.IsNullOrEmpty()) { throw new SyntaxErrorException("Unterminated filter expression"); } if (literal.StartsWith("}}")) { literal = literal.Advance(2); break; } if (!literal.FirstCharEquals(FilterSep)) { throw new SyntaxErrorException( $"Expected pipeline operator '|>' but was {literal.DebugFirstChar()}"); } literal = literal.AdvancePastPipeOperator(); } } else if (!literal.AdvancePastWhitespace().IsNullOrEmpty()) { throw new SyntaxErrorException($"Unexpected syntax '{literal.ToString()}', Expected pipeline operator '|>'"); } } else { literal = literal.Advance(2); } var length = text.Length - pos - literal.Length; var originalText = text.Slice(pos, length); lastPos = pos + length; var varFragment = new PageVariableFragment(originalText, expr, filters); to.Add(varFragment); var newLineLen = literal.StartsWith("\n") ? 1 : literal.StartsWith("\r\n") ? 2 : 0; if (newLineLen > 0) { var lastExpr = varFragment.FilterExpressions?.LastOrDefault(); var filterName = lastExpr?.Name ?? varFragment?.InitialExpression?.Name ?? varFragment.Binding; if ((filterName != null && context.RemoveNewLineAfterFiltersNamed.Contains(filterName)) || expr is JsVariableDeclaration) { lastPos += newLineLen; } } } } if (lastPos != text.Length) { var lastBlock = lastPos == 0 ? text : text.Slice(lastPos); to.Add(new PageStringFragment(lastBlock)); } return(to); }
internal static JsStatement[] ParseCodeStatements(this ScriptContext context, ReadOnlyMemory <char> code) { var to = new List <JsStatement>(); int startExpressionPos = -1; var cursorPos = 0; while (code.TryReadLine(out var line, ref cursorPos)) { var lineLength = line.Length; line = line.TrimStart(); var leftIndent = lineLength - line.Length; line = line.TrimEnd(); var rightIndent = lineLength - leftIndent - line.Length; if (line.IsEmpty) { continue; } var firstChar = line.Span[0]; // single-line comment if (firstChar == '*') { if (line.EndsWith("*")) { continue; } } // multi-line comment if (line.StartsWith("{{*")) { var endPos = code.IndexOf("*}}", cursorPos - lineLength); if (endPos == -1) { throw new SyntaxErrorException($"Unterminated multi-line comment, near {line.DebugLiteral()}"); } cursorPos = endPos + 3; // "*}}".Length continue; } // template block statement if (firstChar == '{' && line.Span.SafeCharEquals(1, '{') && line.Span.SafeCharEquals(2, '#')) { var fromLineStart = code.ToLineStart(cursorPos, lineLength).AdvancePastWhitespace(); var literal = fromLineStart.Slice(3); literal = literal.ParseTemplateScriptBlock(context, out var blockFragment); blockFragment.OriginalText = fromLineStart.Slice(0, fromLineStart.Length - literal.Length); to.Add(new JsPageBlockFragmentStatement(blockFragment)); cursorPos = code.Length - literal.Length; continue; } // code block statement if (firstChar == '#') { var fromLineStart = code.ToLineStart(cursorPos, lineLength).AdvancePastWhitespace(); var literal = fromLineStart.Slice(1); literal = literal.ParseCodeScriptBlock(context, out var blockFragment); to.Add(new JsPageBlockFragmentStatement(blockFragment)); blockFragment.OriginalText = fromLineStart.Slice(0, fromLineStart.Length - literal.Length); cursorPos = code.Length - literal.Length; continue; } const int delim = 2; // '}}'.length // multi-line expression if (startExpressionPos >= 0) { // multi-line end if (line.EndsWith("}}")) { if (code.Span.SafeCharEquals(startExpressionPos, '*')) { if (!line.EndsWith("*}}")) // not a closing block comment, continue { continue; } // ignore multi-line comment } else { var CRLF = code.Span.SafeCharEquals(cursorPos - 2, '\r') ? 2 : 1; var exprStr = code.Slice(startExpressionPos, cursorPos - startExpressionPos - rightIndent - delim - CRLF).Trim(); var afterExpr = exprStr.Span.ParseExpression(out var expr, out var filters); to.AddExpression(exprStr, expr, filters); } startExpressionPos = -1; } continue; } if (firstChar == '{' && line.Span.SafeCharEquals(1, '{')) { // single-line {{ expr }} if (line.EndsWith("}}")) { var exprStr = code.Slice(cursorPos - lineLength + leftIndent + delim); exprStr = exprStr.Slice(0, exprStr.IndexOf("}}")).Trim(); var afterExpr = exprStr.Span.ParseExpression(out var expr, out var filters); to.AddExpression(exprStr, expr, filters); continue; } // multi-line start var CRLF = code.Span.SafeCharEquals(cursorPos - 2, '\r') ? 2 : 1; startExpressionPos = cursorPos - lineLength - CRLF + leftIndent + delim; continue; } else { // treat line as an expression statement var afterExpr = line.Span.ParseExpression(out var expr, out var filters); afterExpr = afterExpr.AdvancePastWhitespace(); var isStatementDelim = afterExpr.FirstCharEquals(ScriptTemplateUtils.StatementsSep); if (!afterExpr.IsEmpty && !isStatementDelim) { throw new SyntaxErrorException($"Unexpected syntax after expression: {afterExpr.ToString()}, near {line.DebugLiteral()}"); } var exprSrc = line.SafeSlice(0, line.Length - afterExpr.Length); to.AddExpression(exprSrc, expr, filters); if (isStatementDelim) { cursorPos = cursorPos - afterExpr.Length + 1; } } } return(to.ToArray()); }
// {{#if ...}} // ^ public static ReadOnlyMemory <char> ParseTemplateScriptBlock(this ReadOnlyMemory <char> literal, ScriptContext context, out PageBlockFragment blockFragment) { literal = literal.ParseVarName(out var blockNameSpan); PageBlockFragment statement; var blockName = blockNameSpan.ToString(); var endBlock = "{{/" + blockName + "}}"; var endExprPos = literal.IndexOf("}}"); if (endExprPos == -1) { throw new SyntaxErrorException($"Unterminated '{blockName}' block expression, near '{literal.DebugLiteral()}'"); } var blockExpr = literal.Slice(0, endExprPos).Trim(); literal = literal.Advance(endExprPos + 2); if (context.ParseAsVerbatimBlock.Contains(blockName)) { var endBlockPos = literal.IndexOf(endBlock); if (endBlockPos == -1) { throw new SyntaxErrorException($"Unterminated end block '{endBlock}'"); } var blockBody = literal.Slice(0, endBlockPos); literal = literal.Advance(endBlockPos + endBlock.Length).TrimFirstNewLine(); var body = new List <PageFragment> { new PageStringFragment(blockBody) }; blockFragment = new PageBlockFragment(blockName, blockExpr, body); return(literal); } else { var parseAsCode = context.ParseAsCodeBlock.Contains(blockName); literal = literal.ParseTemplateBody(blockNameSpan, out var bodyText); List <PageFragment> bodyFragments = null; JsBlockStatement bodyStatements = null; if (!parseAsCode) { bodyFragments = context.ParseTemplate(bodyText); } else { bodyStatements = context.ParseCode(bodyText); } var elseBlocks = new List <PageElseBlock>(); while (literal.StartsWith("{{else")) { literal = literal.ParseTemplateElseBlock(blockName, out var elseArgument, out var elseBody); var elseBlock = !parseAsCode ? new PageElseBlock(elseArgument, context.ParseTemplate(elseBody)) : new PageElseBlock(elseArgument, context.ParseCode(elseBody)); elseBlocks.Add(elseBlock); } literal = literal.Advance(2 + 1 + blockName.Length + 2); //remove new line after partial block end tag literal = literal.TrimFirstNewLine(); blockFragment = !parseAsCode ? new PageBlockFragment(blockName, blockExpr, bodyFragments, elseBlocks) : new PageBlockFragment(blockName, blockExpr, bodyStatements, elseBlocks); return(literal); } }
public static List <PageFragment> ParseTemplate(this ScriptContext context, ReadOnlyMemory <char> text) { var to = new List <PageFragment>(); if (text.IsNullOrWhiteSpace()) { return(to); } int pos; var lastPos = 0; while ((pos = text.IndexOf("{{", lastPos)) != -1) { var block = text.Slice(lastPos, pos - lastPos); if (!block.IsNullOrEmpty()) { to.Add(new PageStringFragment(block)); } var varStartPos = pos + 2; if (varStartPos >= text.Span.Length) { throw new SyntaxErrorException($"Unterminated '{{{{' expression, near '{text.Slice(lastPos).DebugLiteral()}'"); } var firstChar = text.Span[varStartPos]; if (firstChar == '*') //comment { lastPos = text.IndexOf("*}}", varStartPos) + 3; if (text.Span.SafeCharEquals(lastPos, '\r')) { lastPos++; } if (text.Span.SafeCharEquals(lastPos, '\n')) { lastPos++; } } else if (firstChar == '#') //block statement { var literal = text.Slice(varStartPos + 1); literal = literal.ParseTemplateScriptBlock(context, out var blockFragment); var length = text.Length - pos - literal.Length; blockFragment.OriginalText = text.Slice(pos, length); lastPos = pos + length; to.Add(blockFragment); } else { var literal = text.Slice(varStartPos).Span; literal = literal.ParseJsExpression(out var expr, filterExpression: true); var filters = new List <JsCallExpression>(); if (!literal.StartsWith("}}")) { literal = literal.AdvancePastWhitespace(); if (literal.FirstCharEquals(FilterSep)) { literal = literal.Advance(1); while (true) { literal = literal.ParseJsCallExpression(out var filter, filterExpression: true); filters.Add(filter); literal = literal.AdvancePastWhitespace(); if (literal.IsNullOrEmpty()) { throw new SyntaxErrorException("Unterminated filter expression"); } if (literal.StartsWith("}}")) { literal = literal.Advance(2); break; } if (!literal.FirstCharEquals(FilterSep)) { throw new SyntaxErrorException( $"Expected filter separator '|' but was {literal.DebugFirstChar()}"); } literal = literal.Advance(1); } } else { if (!literal.IsNullOrEmpty()) { literal = literal.Advance(1); } } } else { literal = literal.Advance(2); } var length = text.Length - pos - literal.Length; var originalText = text.Slice(pos, length); lastPos = pos + length; var varFragment = new PageVariableFragment(originalText, expr, filters); to.Add(varFragment); var newLineLen = literal.StartsWith("\n") ? 1 : literal.StartsWith("\r\n") ? 2 : 0; if (newLineLen > 0) { var lastExpr = varFragment.FilterExpressions?.LastOrDefault(); var filterName = lastExpr?.Name ?? varFragment?.InitialExpression?.Name ?? varFragment.Binding; if (filterName != null && context.RemoveNewLineAfterFiltersNamed.Contains(filterName)) { lastPos += newLineLen; } } } } if (lastPos != text.Length) { var lastBlock = lastPos == 0 ? text : text.Slice(lastPos); to.Add(new PageStringFragment(lastBlock)); } return(to); }