private bool ParseSingleLineBlock() { Context.UpdateSeenValidEmailPrefix(); // Output the ":" as a transition token Context.AcceptCurrent(); End(MetaCodeSpan.Create); while (!EndOfFile) { if (!TryStartCodeParser(isSingleLineMarkup: true)) { if (ParserHelpers.IsNewLine(CurrentCharacter)) { Context.AcceptNewLine(); return(false); } else { Context.UpdateSeenValidEmailPrefix(); Context.AcceptCurrent(); } } } return(true); }
private bool ParseEndTag(Stack <TagInfo> tags, TagInfo tag, bool acceptUnmatchedEndTag) { Debug.Assert(tag.IsEndTag, "ParseEndTag requires an end tag"); // Read the tag (no attributes allowed in an end tag) AppendUntilAndParseCode(c => c == '>' || c == '<'); // Make sure we're actually in a valid tag if (CurrentCharacter == '>') { //OnTagFinished(new TagFinishedEventArgs(context, tagName, true, false, tagStartLocation)); Context.AcceptCurrent(); // '>' // Verify the tag if (tags.Count == 0 && !acceptUnmatchedEndTag) { // Started with an end tag OnError(tag.Start, RazorResources.ParseError_UnexpectedEndTag, tag.Name); } else { UpdateTagStack(tags, tag, !acceptUnmatchedEndTag); } // Successfully terminated return(false); } // Not terminated, return true to indicate unterminated return(true); }
protected void ParseBlockWithOtherParser(SpanFactory previousSpanFactory, bool collectTransitionToken) { // Capture the current span if we have one if (TryParseComment(previousSpanFactory)) { return; } Span prev = null; if (HaveContent) { prev = previousSpanFactory(Context); Context.ResetBuffers(); } // Skip over the switch token if requested if (collectTransitionToken) { Context.AcceptCurrent(); } if (collectTransitionToken && CurrentCharacter == RazorParser.TransitionCharacter) { // We were told to handle the transition token and found another transition character ==> Escape sequence // Output the previous content and a hidden token so that the first at of the escape sequence doesn't get rendered if (prev != null) { Output(prev); } Span span = previousSpanFactory(Context); Context.ResetBuffers(); span.Hidden = true; Output(span); // Now accept the current transition token so it doesn't get treated as a transition and return to the current context Context.AcceptCurrent(); } else { if (prev != null) { Output(prev); } // Switch to the other parser Context.SwitchActiveParser(); // Have the other parser parse a block starting with the character after the '@' Context.ActiveParser.ParseBlock(); // Switch back Context.SwitchActiveParser(); // Once we're done, start a new span Context.ResetBuffers(); } }
protected AcceptedCharacters AcceptDottedExpression(bool isWithinCode, bool expectIdentifierFirst, params char[] allowedBrackets) { if (!expectIdentifierFirst || ParserHelpers.IsIdentifierStart(CurrentCharacter)) { do { // Parse Parentheses or Brackets if we see them do { if (!allowedBrackets.Any(c => CurrentCharacter == c)) { break; } // Dev10 884975 - Incorrect Error Messaging SourceLocation bracketStart = CurrentLocation; char bracket = CurrentCharacter; if (!BalanceBrackets(allowTransition: true, spanFactory: CreateImplicitExpressionSpanFactory(isWithinCode))) { // Balancing terminated because of EOF char terminator = _bracketPairs[bracket]; Context.AcceptCurrent(); TryRecover(RecoveryModes.Any); OnError(bracketStart, RazorResources.ParseError_Expected_CloseBracket_Before_EOF, bracket, terminator); return(AcceptedCharacters.Any); } } while (!EndOfFile); // If the next character is a dot, followed by an identifier start, keep on parsing using (Context.StartTemporaryBuffer()) { if (CurrentCharacter != '.') { break; } Context.AcceptCurrent(); if (!ParserHelpers.IsIdentifierStart(CurrentCharacter)) { if (isWithinCode) { Context.AcceptTemporaryBuffer(); } break; } Context.AcceptTemporaryBuffer(); // Put the dot in the primary buffer } // Parse an identifier Context.AcceptIdentifier(); } while (!EndOfFile); } return(AcceptedCharacters.NonWhiteSpace); }
public virtual bool NextIsTransition(bool allowImplicit, bool allowExplicit) { using (Context.StartTemporaryBuffer()) { Context.AcceptCurrent(); return((allowExplicit && IsAtExplicitTransition()) || (allowImplicit && IsAtImplicitTransition())); } }
private void AcceptOrSkipCurrent(bool appendOuter, int nesting) { if (nesting > 0 || appendOuter) { Context.AcceptCurrent(); } else { Context.SkipCurrent(); } }
protected bool TryRecover(RecoveryModes mode, Predicate <char> condition, bool allowTransition, SpanFactory previousSpanFactory) { bool anyNewLines = false; while (!EndOfFile && !condition(CurrentCharacter)) { // Eat whitespace Context.AcceptWhiteSpace(false); if (mode.HasFlag(RecoveryModes.Markup)) { // If we see new lines before a '<' then assume it's markup. // This isn't 100% correct but it is the 80/90% case when typing razor code if (anyNewLines && Context.MarkupParser.IsStartTag()) { return(true); } // End tags are mostly unambiguous // REVIEW: Does this make sense? if (Context.MarkupParser.IsEndTag()) { return(true); } } if (mode.HasFlag(RecoveryModes.Code)) { if (TryRecover(allowTransition, previousSpanFactory)) { return(true); } } if (mode.HasFlag(RecoveryModes.Transition)) { if (CurrentCharacter == RazorParser.TransitionCharacter) { return(true); } } anyNewLines = ParserHelpers.IsNewLine(CurrentCharacter); Context.AcceptCurrent(); } return(false); }
protected virtual void AcceptUntilUnquoted(Predicate <char> condition) { while (!EndOfFile) { if (!TryAcceptStringOrComment()) { if (condition(CurrentCharacter)) { return; } Context.AcceptCurrent(); } } }
protected void AcceptTypeName(bool allowGenerics) { do { if (CurrentCharacter == '.') { Context.AcceptCurrent(); } Context.AcceptIdentifier(); if (allowGenerics) { AcceptGenericArgument(); } } while (CurrentCharacter == '.'); }
private void AppendUntilAndParseCode(Func <char, bool> terminator) { while (!EndOfFile && !terminator(CurrentCharacter)) { if (!TryStartCodeParser()) { Context.UpdateSeenValidEmailPrefix(); Context.AcceptCurrent(); } } // The terminator may or may not be a valid prefix/suffix character, and since the else block above was skipped if we reached the terminator // we should check it Context.UpdateSeenValidEmailPrefix(); }
protected bool RequireSingleWhiteSpace() { if (Char.IsWhiteSpace(CurrentCharacter)) { if (ParserHelpers.IsNewLine(CurrentCharacter)) { Context.AcceptNewLine(); } else { Context.AcceptCurrent(); } return(true); } return(false); }
private void ParseEndPsuedoTag(Stack <TagInfo> tags, TagInfo tag, bool inDocument) { Debug.Assert(tag.IsEndTag, "ParseEndPsuedoTag requires an end tag"); // Collect what we've seen so far, since the "</text>" is not a markup span, it's a transition span Span prev = null; if (HaveContent) { prev = MarkupSpan.Create(Context); Context.ResetBuffers(); } // Accept the "</text>" Context.Expect("<"); Context.AcceptWhiteSpace(includeNewLines: true); Context.Expect("/text"); bool complete = CurrentCharacter == '>'; if (!complete) { OnError(tag.Start, RazorResources.ParseError_TextTagCannotContainAttributes); } else { Context.AcceptCurrent(); } // Remove the tag UpdateTagStack(tags, tag, !inDocument); if (tags.Count == 0) { // That was the top-level tag, output the markup then a transition span for the </text> if (prev != null) { Output(prev); } End(TransitionSpan.Create(Context, hidden: false, acceptedCharacters: complete ? AcceptedCharacters.None : AcceptedCharacters.Any)); } else { // Wasn't top-level, so resume the original span Context.ResumeSpan(prev); } }
private bool ParseSgmlDeclaration() { // Just parse until the '>' AppendUntilAndParseCode(c => c == '>' || c == '<'); // Make sure we're actually in a valid tag if (EndOfFile) { return(true); } if (CurrentCharacter == '>') { Context.AcceptCurrent(); } return(false); }
private TagInfo ParseStartOfTag() { bool isEndTag = false; SourceLocation tagStart = CurrentLocation; using (Context.StartTemporaryBuffer()) { Context.AcceptCurrent(); // Accept the "<" // Is this an end tag? if (CurrentCharacter == '/') { Context.AcceptCurrent(); // Skip the "/" isEndTag = true; } // Parse the tag name return(new TagInfo(AcceptTagName(), tagStart, isEndTag)); } }
private bool ParseHtmlComment() { // Parse until the '-->' while (!EndOfFile) { AppendUntilAndParseCode(c => c == '-'); if (CurrentCharacter == '-') { Context.AcceptCurrent(); // Peek only needs to check for "->" because we've already seen the first '-' if (Context.Peek("->", caseSensitive: true)) { // End of the Comment Context.AcceptUntilInclusive('>'); return(false); } } } return(true); }
private bool ParseCData() { // Parse until the ']]>' while (!EndOfFile) { AppendUntilAndParseCode(c => c == ']'); if (CurrentCharacter == ']') { Context.AcceptCurrent(); if (Context.Peek("]>", caseSensitive: true)) { // End of the CData section Context.AcceptUntilInclusive('>'); // Done parsing the CData return(false); } } } return(true); }
private bool ParseProcessingInstruction() { // Parse until '?>' while (!EndOfFile) { AppendUntilAndParseCode(c => c == '?'); if (CurrentCharacter == '?') { Context.AcceptCurrent(); if (CurrentCharacter == '>') { // End of the PI Context.AcceptCurrent(); // Done parsing the PI return(false); } } } return(true); }
private void AppendToEndOfTag(TagInfo tag) { char?balancingQuote = null; do { // Read until a quoted literal or the end of the tag (and handle code we find in between) AppendUntilAndParseCode(c => c == '\"' || c == '\'' || c == '/' || c == '>' || c == '<'); if (balancingQuote != null) { if (balancingQuote.Value == CurrentCharacter) { balancingQuote = null; } Context.AcceptCurrent(); } else if (CurrentCharacter == '\"' || CurrentCharacter == '\'') { balancingQuote = CurrentCharacter; Context.AcceptCurrent(); } else if (CurrentCharacter == '/') { using (Context.Source.BeginLookahead()) { Context.SkipCurrent(); if (CurrentCharacter == '>') { return; } } Context.AcceptCurrent(); // "/" } } while (!EndOfFile && (balancingQuote != null || (CurrentCharacter != '>' && CurrentCharacter != '<'))); if (CurrentCharacter != '>' && CurrentCharacter != '<') { OnError(tag.Start, RazorResources.ParseError_UnfinishedTag, tag.Name); } }
private void ParseRootBlock(Tuple <string, string> nestingSequences, bool caseSensitive = true) { // We're only document level there are no nesting sequences bool documentLevel = nestingSequences == null; // Start a markup block using (StartBlock(BlockType.Markup)) { int nesting = 1; do { if (nestingSequences != null && nestingSequences.Item1 != null && Context.Peek(nestingSequences.Item1, caseSensitive: caseSensitive)) { nesting++; Context.Expect(nestingSequences.Item1, outputError: true, errorMessage: null, caseSensitive: caseSensitive); } else if (nestingSequences != null && Context.Peek(nestingSequences.Item2, caseSensitive: caseSensitive)) { nesting--; if (nesting > 0) { Context.Expect(nestingSequences.Item2, outputError: true, errorMessage: null, caseSensitive: caseSensitive); } } else if (!TryStartCodeParser(documentLevel: documentLevel)) { Context.UpdateSeenValidEmailPrefix(); Context.AcceptCurrent(); } } while (!EndOfFile && (nestingSequences == null || nesting > 0)); if (!Context.PreviousSpanCanGrow || HaveContent) { var span = MarkupSpan.Create(Context); span.DocumentLevel = documentLevel; End(span); } } }
protected void ParseComment() { using (StartBlock(BlockType.Comment)) { SourceLocation startLocation = CurrentLocation; Context.Expect(RazorParser.StartCommentSequence[0]); End(TransitionSpan.Create(Context, hidden: false, acceptedCharacters: AcceptedCharacters.None)); Context.Expect(RazorParser.StartCommentSequence[1]); End(MetaCodeSpan.Create(Context, hidden: false, acceptedCharacters: AcceptedCharacters.None)); bool inComment = true; while (inComment && !EndOfFile) { Context.AcceptUntil(RazorParser.EndCommentSequence[0]); if (Context.Peek(RazorParser.EndCommentSequence, caseSensitive: true)) { inComment = false; } else { Context.AcceptCurrent(); } } End(CommentSpan.Create); if (EndOfFile) { OnError(startLocation, RazorResources.ParseError_RazorComment_Not_Terminated); } else { Context.Expect(RazorParser.EndCommentSequence[0]); End(MetaCodeSpan.Create(Context, hidden: false, acceptedCharacters: AcceptedCharacters.None)); Context.Expect(RazorParser.EndCommentSequence[1]); End(TransitionSpan.Create(Context, hidden: false, acceptedCharacters: AcceptedCharacters.None)); } } }
private bool ParseStartTag(Stack <TagInfo> tags, TagInfo tag) { Debug.Assert(!tag.IsEndTag, "ParseStartTag requires a start tag"); // Append Until the end of the tag AppendToEndOfTag(tag); // Make sure we're actually in a valid tag bool unterminated = false; switch (CurrentCharacter) { case '>': //OnTagFinished(new TagFinishedEventArgs(context, tagName, false, false, tagStartLocation)); Context.AcceptCurrent(); // Start tag, push it on the stack tags.Push(tag); break; case '/': Context.AcceptCurrent(); // Accept the "/" //OnTagFinished(new TagFinishedEventArgs(context, tagName, false, true, tagStartLocation)); // We know that it's followed immediately by '>' because that's what AppendToEndOfTag expects, so accept that Context.AcceptCurrent(); // An empty tag has no content, so don't push anything onto the stack (no need to set the "End" property on the tag either, since it's going away) break; default: unterminated = true; break; } return(unterminated); }
public override void ParseBlock() { if (Context == null) { throw new InvalidOperationException(RazorResources.Parser_Context_Not_Set); } using (StartBlock(BlockType.Markup)) { // An HTML block starts with a start tag and ends with the matching end tag. For example, each of the following lines are HTML blocks: // <li>foo</li> // <li>foo<b>bar</b></li> // <text>This block uses the <pre><text></pre> pseudo-tag which is not emitted, but is used for balancing tags</text> // Or, if it starts with a ":", then it is part of the "@:" single-line markup construct. The ":" is discarded and the rest of the line is markup // For example, each of the following lines are HTML blocks: // :this is all markup, except for the initial ':' // :you <b>can</b> put tags in here <em>and</em> they don't have to be <img src="foo.jpg"> balanced! SpanFactory spanFactory = MarkupSpan.Create; Context.AcceptWhiteSpace(includeNewLines: true); if (CurrentCharacter == RazorParser.TransitionCharacter) { if (HaveContent) { End(MarkupSpan.Create); } Context.AcceptCurrent(); Debug.Assert(HaveContent); End(TransitionSpan.Create(Context, hidden: false, acceptedCharacters: AcceptedCharacters.None)); if (CurrentCharacter == RazorParser.TransitionCharacter) { // Second "@" is for VB // TODO: Refactor!!! Context.AcceptCurrent(); End(MetaCodeSpan.Create); } } bool complete = false; if (CurrentCharacter == ':') { // Parse a single line of markup spanFactory = SingleLineMarkupSpan.Create; Context.WhiteSpaceIsImportantToAncestorBlock = true; complete = !ParseSingleLineBlock(); Context.WhiteSpaceIsImportantToAncestorBlock = false; } else if (CurrentCharacter == '<') { complete = !ParseTagBlock(false); } else { // First non-whitespace character in a block must be a start tag or ':' OnError(CurrentLocation, RazorResources.ParseError_MarkupBlock_Must_Start_With_Tag); return; } // Output anything that's left over // If we have content ==> Output // If the previous span can't grow ==> Output UNLESS we're "complete" if ((!complete && !Context.PreviousSpanCanGrow) || HaveContent) { Span span = spanFactory(Context); span.AcceptedCharacters = complete ? AcceptedCharacters.None : AcceptedCharacters.Any; End(span); } } }
private bool ParseTagBlock(bool inDocument) { // For tracking end tags Stack <TagInfo> tags = new Stack <TagInfo>(); bool startedByPseudoTag = false; bool?canGrow = null; do { // Append until the next tag, processing code as we find it AppendUntilAndParseCode(c => c == '<'); // Read the tag name in lookahead since we might not actually want to accept it TagInfo tag = ParseStartOfTag(); // Special case for "<text>" tag as the first tag we've seen if (IsPsuedoTagValidHere(inDocument, tags, startedByPseudoTag, tag)) { if (!tag.IsEndTag) { startedByPseudoTag = true; // Set a flag to indicate that a </text> is a valid end tag if (ParseStartPsuedoTag(tags, tag)) { // Can't just do "canGrow = !ParseStartPsuedoTag(...)" because we can't canGrow to // stay null if we get false from ParseStartPsuedoTag canGrow = false; } } else { ParseEndPsuedoTag(tags, tag, inDocument); canGrow = false; } } // It wasn't a "<text>" OR it was but it was within a block, so we don't do anything special else { // We're at the '<' Context.AcceptCurrent(); // "<" if (tag.IsEndTag) { Context.AcceptCurrent(); // "/" } Context.AcceptCharacters(tag.Name.Length); // tag name // Invalid tag name? Not a real tag if (!String.IsNullOrEmpty(tag.Name)) { // What kind of tag is it bool?unterminated = null; switch (tag.Name[0]) { case '!': unterminated = ParseBangTag(tag.Name); break; case '?': unterminated = ParseProcessingInstruction(); break; default: if (tag.IsEndTag) { unterminated = ParseEndTag(tags, tag, inDocument); } else { unterminated = ParseStartTag(tags, tag); } break; } if (tags.Count == 0 && unterminated != null) { canGrow = unterminated.Value; } } else { canGrow = true; if (tags.Count == 0) { OnError(CurrentLocation, RazorResources.ParseError_OuterTagMissingName); } } } } while (!EndOfFile && tags.Count > 0); if (canGrow == null) { canGrow = tags.Count > 0; } if (tags.Count > 0) { // Ended because of EOF, not matching close tag. Throw error for last tag while (tags.Count > 1) { tags.Pop(); } TagInfo tag = tags.Pop(); OnError(tag.Start, RazorResources.ParseError_MissingEndTag, tag.Name); } // Add the remaining whitespace (up to and including the next newline) to the token if run-time mode if (!DesignTimeMode) { // Dev10 Bug 884969 - Emit space between markup and code Context.AcceptWhiteSpace(includeNewLines: false); if (Char.IsWhiteSpace(CurrentCharacter)) { Context.AcceptLine(includeNewLineSequence: true); } } else if (canGrow.Value) { Context.AcceptWhiteSpace(includeNewLines: false); if (ParserHelpers.IsNewLine(CurrentCharacter)) { Context.AcceptNewLine(); } } return(canGrow.Value); }
private bool ParseStartPsuedoTag(Stack <TagInfo> tags, TagInfo tag) { Debug.Assert(!tag.IsEndTag, "ParseStartPsuedoTag requires a start tag"); // Output what we've seen so far, since the "<text>" is not a markup token, it's a transition token if (HaveContent) { End(MarkupSpan.Create); } // Accept the "<text>" Context.Expect("<"); Context.AcceptWhiteSpace(includeNewLines: true); Context.Expect("text"); bool isValid = false; using (Context.StartTemporaryBuffer()) { Context.AcceptWhiteSpace(includeNewLines: true); if (CurrentCharacter == '/' || CurrentCharacter == '>') { isValid = true; Context.AcceptTemporaryBuffer(); } } bool transitionComplete = false; bool isEmpty = false; if (!isValid) { OnError(tag.Start, RazorResources.ParseError_TextTagCannotContainAttributes); } else { isEmpty = CurrentCharacter == '/'; Context.AcceptCurrent(); if (isEmpty) { if (CurrentCharacter != '>') { OnError(CurrentLocation, RazorResources.ParseError_SlashInEmptyTagMustBeFollowedByCloseAngle); } else { transitionComplete = true; Context.AcceptCurrent(); } } else { transitionComplete = true; } } // Output the transition End(TransitionSpan.Create(Context, hidden: false, acceptedCharacters: transitionComplete ? AcceptedCharacters.None : AcceptedCharacters.Any)); // Push it on to the stack and continue if (!isEmpty) { tags.Push(tag); } return(isEmpty); }
protected virtual bool BalanceBrackets(bool allowTransition, SpanFactory spanFactory, bool appendOuter, char?bracket, bool useTemporaryBuffer) { spanFactory = spanFactory ?? CodeSpan.Create; if (useTemporaryBuffer) { Context.StartTemporaryBuffer(); } int nesting = 0; // Nesting level bool callerReadBracket = true; if (bracket == null) { callerReadBracket = false; bracket = CurrentCharacter; } else { // The caller already read the bracket, so start at nesting level 1, and also, don't append the outer bracket nesting = 1; } char terminator = _bracketPairs[bracket.Value]; do { // Gather whitespace Context.StartTemporaryBuffer(); AcceptWhiteSpaceByLines(); if (CurrentCharacter == RazorParser.TransitionCharacter) { if (Context.Peek(RazorParser.StartCommentSequence, caseSensitive: true)) { Context.AcceptTemporaryBuffer(); if (useTemporaryBuffer) { Context.AcceptTemporaryBuffer(); } End(spanFactory); ParseComment(); if (useTemporaryBuffer) { Context.StartTemporaryBuffer(); } } else if (allowTransition) { Context.RejectTemporaryBuffer(); if (!HandleTransition(spanFactory)) { Context.AcceptWhiteSpace(includeNewLines: true); if (!TryAcceptStringOrComment()) { Context.AssertCurrent(RazorParser.TransitionCharacter); Context.AcceptCurrent(); } } else if (useTemporaryBuffer) { // Start a new outer temporary buffer Context.StartTemporaryBuffer(); } } else { Context.AcceptTemporaryBuffer(); if (!TryAcceptStringOrComment()) { Context.AcceptCurrent(); } } } else { Context.AcceptTemporaryBuffer(); } AcceptUntilUnquoted(c => Char.IsWhiteSpace(c) || c == bracket || c == terminator || c == RazorParser.TransitionCharacter); if (CurrentCharacter == terminator) { // If the nesting level is 1 and no bracket was specified, don't read the terminator, but we are done if (nesting == 1 && callerReadBracket) { nesting--; } else { AcceptOrSkipCurrent(appendOuter, --nesting); } } else if (CurrentCharacter == bracket) { AcceptOrSkipCurrent(appendOuter, nesting++); } } while (!EndOfFile && nesting > 0); if (useTemporaryBuffer) { if (nesting > 0) { Context.RejectTemporaryBuffer(); } else { Context.AcceptTemporaryBuffer(); } } Debug.Assert(!InTemporaryBuffer); return(nesting == 0); // Return a boolean indicating if we exited because of EOF or because of the end bracket }