Add TagHelper parse level opt-out character '!'.
- Added the ability to opt-out of TagHelper parsing by adding a '!' to the beginning of a tag name. - Modified parsing logic to allow bangs in tags. - Bangs in tags are removed from output always and are handled as meta code. #187
This commit is contained in:
parent
32f0858e8f
commit
94230a5a14
|
|
@ -75,19 +75,19 @@ namespace Microsoft.AspNet.Razor.Runtime
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Parameter {0} must not contain null tag names.
|
/// Tag name cannot be null or whitespace.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string HtmlElementNameAttribute_AdditionalTagsCannotContainNull
|
internal static string HtmlElementNameAttribute_ElementNameCannotBeNullOrWhitespace
|
||||||
{
|
{
|
||||||
get { return GetString("HtmlElementNameAttribute_AdditionalTagsCannotContainNull"); }
|
get { return GetString("HtmlElementNameAttribute_ElementNameCannotBeNullOrWhitespace"); }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Parameter {0} must not contain null tag names.
|
/// Tag name cannot be null or whitespace.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string FormatHtmlElementNameAttribute_AdditionalTagsCannotContainNull(object p0)
|
internal static string FormatHtmlElementNameAttribute_ElementNameCannotBeNullOrWhitespace()
|
||||||
{
|
{
|
||||||
return string.Format(CultureInfo.CurrentCulture, GetString("HtmlElementNameAttribute_AdditionalTagsCannotContainNull"), p0);
|
return GetString("HtmlElementNameAttribute_ElementNameCannotBeNullOrWhitespace");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -122,6 +122,22 @@ namespace Microsoft.AspNet.Razor.Runtime
|
||||||
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorResolver_EncounteredUnexpectedError"), p0, p1, p2);
|
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorResolver_EncounteredUnexpectedError"), p0, p1, p2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tag helpers cannot target element name '{0}' because it contains a '{1}' character.
|
||||||
|
/// </summary>
|
||||||
|
internal static string HtmlElementNameAttribute_InvalidElementName
|
||||||
|
{
|
||||||
|
get { return GetString("HtmlElementNameAttribute_InvalidElementName"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tag helpers cannot target element name '{0}' because it contains a '{1}' character.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatHtmlElementNameAttribute_InvalidElementName(object p0, object p1)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, GetString("HtmlElementNameAttribute_InvalidElementName"), p0, p1);
|
||||||
|
}
|
||||||
|
|
||||||
private static string GetString(string name, params string[] formatterNames)
|
private static string GetString(string name, params string[] formatterNames)
|
||||||
{
|
{
|
||||||
var value = _resourceManager.GetString(name);
|
var value = _resourceManager.GetString(name);
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,17 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<root>
|
<root>
|
||||||
<!--
|
<!--
|
||||||
Microsoft ResX Schema
|
Microsoft ResX Schema
|
||||||
|
|
||||||
Version 2.0
|
Version 2.0
|
||||||
|
|
||||||
The primary goals of this format is to allow a simple XML format
|
The primary goals of this format is to allow a simple XML format
|
||||||
that is mostly human readable. The generation and parsing of the
|
that is mostly human readable. The generation and parsing of the
|
||||||
various data types are done through the TypeConverter classes
|
various data types are done through the TypeConverter classes
|
||||||
associated with the data types.
|
associated with the data types.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
... ado.net/XML headers & schema ...
|
... ado.net/XML headers & schema ...
|
||||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
<resheader name="version">2.0</resheader>
|
<resheader name="version">2.0</resheader>
|
||||||
|
|
@ -26,36 +26,36 @@
|
||||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
<comment>This is a comment</comment>
|
<comment>This is a comment</comment>
|
||||||
</data>
|
</data>
|
||||||
|
|
||||||
There are any number of "resheader" rows that contain simple
|
There are any number of "resheader" rows that contain simple
|
||||||
name/value pairs.
|
name/value pairs.
|
||||||
|
|
||||||
Each data row contains a name, and value. The row also contains a
|
Each data row contains a name, and value. The row also contains a
|
||||||
type or mimetype. Type corresponds to a .NET class that support
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
text/value conversion through the TypeConverter architecture.
|
text/value conversion through the TypeConverter architecture.
|
||||||
Classes that don't support this are serialized and stored with the
|
Classes that don't support this are serialized and stored with the
|
||||||
mimetype set.
|
mimetype set.
|
||||||
|
|
||||||
The mimetype is used for serialized objects, and tells the
|
The mimetype is used for serialized objects, and tells the
|
||||||
ResXResourceReader how to depersist the object. This is currently not
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
extensible. For a given mimetype the value must be set accordingly:
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
that the ResXResourceWriter will generate, however the reader can
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
read any of the formats listed below.
|
read any of the formats listed below.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.binary.base64
|
mimetype: application/x-microsoft.net.object.binary.base64
|
||||||
value : The object must be serialized with
|
value : The object must be serialized with
|
||||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.soap.base64
|
mimetype: application/x-microsoft.net.object.soap.base64
|
||||||
value : The object must be serialized with
|
value : The object must be serialized with
|
||||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||||
value : The object must be serialized into a byte array
|
value : The object must be serialized into a byte array
|
||||||
: using a System.ComponentModel.TypeConverter
|
: using a System.ComponentModel.TypeConverter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
-->
|
-->
|
||||||
|
|
@ -129,8 +129,8 @@
|
||||||
<data name="ScopeManager_EndCannotBeCalledWithoutACallToBegin" xml:space="preserve">
|
<data name="ScopeManager_EndCannotBeCalledWithoutACallToBegin" xml:space="preserve">
|
||||||
<value>Must call '{2}.{1}' before calling '{2}.{0}'.</value>
|
<value>Must call '{2}.{1}' before calling '{2}.{0}'.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="HtmlElementNameAttribute_AdditionalTagsCannotContainNull" xml:space="preserve">
|
<data name="HtmlElementNameAttribute_ElementNameCannotBeNullOrWhitespace" xml:space="preserve">
|
||||||
<value>Parameter {0} must not contain null tag names.</value>
|
<value>Tag name cannot be null or whitespace.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ArgumentCannotBeNullOrEmpty" xml:space="preserve">
|
<data name="ArgumentCannotBeNullOrEmpty" xml:space="preserve">
|
||||||
<value>The value cannot be null or empty.</value>
|
<value>The value cannot be null or empty.</value>
|
||||||
|
|
@ -138,4 +138,7 @@
|
||||||
<data name="TagHelperDescriptorResolver_EncounteredUnexpectedError" xml:space="preserve">
|
<data name="TagHelperDescriptorResolver_EncounteredUnexpectedError" xml:space="preserve">
|
||||||
<value>Encountered an unexpected error when attempting to resolve tag helper directive '{0}' with value '{1}'. Error: {2}</value>
|
<value>Encountered an unexpected error when attempting to resolve tag helper directive '{0}' with value '{1}'. Error: {2}</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="HtmlElementNameAttribute_InvalidElementName" xml:space="preserve">
|
||||||
|
<value>Tag helpers cannot target element name '{0}' because it contains a '{1}' character.</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|
@ -19,6 +19,8 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
/// <param name="tag">The HTML tag name for the <see cref="TagHelper"/> to target.</param>
|
/// <param name="tag">The HTML tag name for the <see cref="TagHelper"/> to target.</param>
|
||||||
public HtmlElementNameAttribute([NotNull] string tag)
|
public HtmlElementNameAttribute([NotNull] string tag)
|
||||||
{
|
{
|
||||||
|
ValidateTagName(tag, nameof(tag));
|
||||||
|
|
||||||
Tags = new[] { tag };
|
Tags = new[] { tag };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -29,12 +31,12 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
/// <param name="additionalTags">Additional HTML tag names for the <see cref="TagHelper"/> to target.</param>
|
/// <param name="additionalTags">Additional HTML tag names for the <see cref="TagHelper"/> to target.</param>
|
||||||
public HtmlElementNameAttribute([NotNull] string tag, [NotNull] params string[] additionalTags)
|
public HtmlElementNameAttribute([NotNull] string tag, [NotNull] params string[] additionalTags)
|
||||||
{
|
{
|
||||||
if (additionalTags.Contains(null))
|
ValidateTagName(tag, nameof(tag));
|
||||||
|
|
||||||
|
foreach (var tagName in additionalTags)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(
|
ValidateTagName(tagName, nameof(additionalTags));
|
||||||
nameof(additionalTags),
|
}
|
||||||
Resources.FormatHtmlElementNameAttribute_AdditionalTagsCannotContainNull(nameof(additionalTags)));
|
|
||||||
};
|
|
||||||
|
|
||||||
var allTags = new List<string>(additionalTags);
|
var allTags = new List<string>(additionalTags);
|
||||||
allTags.Add(tag);
|
allTags.Add(tag);
|
||||||
|
|
@ -45,6 +47,23 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An <see cref="IEnumerable{string}"/> of tag names for the <see cref="TagHelper"/> to target.
|
/// An <see cref="IEnumerable{string}"/> of tag names for the <see cref="TagHelper"/> to target.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IEnumerable<string> Tags { get; private set; }
|
public IEnumerable<string> Tags { get; }
|
||||||
|
|
||||||
|
private static void ValidateTagName(string tagName, string parameterName)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(tagName))
|
||||||
|
{
|
||||||
|
throw new ArgumentException(
|
||||||
|
Resources.HtmlElementNameAttribute_ElementNameCannotBeNullOrWhitespace,
|
||||||
|
parameterName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tagName.Contains('!'))
|
||||||
|
{
|
||||||
|
throw new ArgumentException(
|
||||||
|
Resources.FormatHtmlElementNameAttribute_InvalidElementName(tagName, '!'),
|
||||||
|
parameterName);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -133,7 +133,9 @@ namespace Microsoft.AspNet.Razor.Parser
|
||||||
IDisposable tagBlockWrapper = null;
|
IDisposable tagBlockWrapper = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!EndOfFile && !AtSpecialTag)
|
var atSpecialTag = AtSpecialTag;
|
||||||
|
|
||||||
|
if (!EndOfFile && !atSpecialTag)
|
||||||
{
|
{
|
||||||
// Start a Block tag. This is used to wrap things like <p> or <a class="btn"> etc.
|
// Start a Block tag. This is used to wrap things like <p> or <a class="btn"> etc.
|
||||||
tagBlockWrapper = Context.StartBlock(BlockType.Tag);
|
tagBlockWrapper = Context.StartBlock(BlockType.Tag);
|
||||||
|
|
@ -157,7 +159,7 @@ namespace Microsoft.AspNet.Razor.Parser
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
complete = AfterTagStart(tagStart, tags, tagBlockWrapper);
|
complete = AfterTagStart(tagStart, tags, atSpecialTag, tagBlockWrapper);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -187,6 +189,7 @@ namespace Microsoft.AspNet.Razor.Parser
|
||||||
|
|
||||||
private bool AfterTagStart(SourceLocation tagStart,
|
private bool AfterTagStart(SourceLocation tagStart,
|
||||||
Stack<Tuple<HtmlSymbol, SourceLocation>> tags,
|
Stack<Tuple<HtmlSymbol, SourceLocation>> tags,
|
||||||
|
bool atSpecialTag,
|
||||||
IDisposable tagBlockWrapper)
|
IDisposable tagBlockWrapper)
|
||||||
{
|
{
|
||||||
if (!EndOfFile)
|
if (!EndOfFile)
|
||||||
|
|
@ -197,9 +200,16 @@ namespace Microsoft.AspNet.Razor.Parser
|
||||||
// End Tag
|
// End Tag
|
||||||
return EndTag(tagStart, tags, tagBlockWrapper);
|
return EndTag(tagStart, tags, tagBlockWrapper);
|
||||||
case HtmlSymbolType.Bang:
|
case HtmlSymbolType.Bang:
|
||||||
// Comment
|
// Comment, CDATA, DOCTYPE, or a parser-escaped HTML tag.
|
||||||
Accept(_bufferedOpenAngle);
|
if (atSpecialTag)
|
||||||
return BangTag();
|
{
|
||||||
|
Accept(_bufferedOpenAngle);
|
||||||
|
return BangTag();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
goto default;
|
||||||
|
}
|
||||||
case HtmlSymbolType.QuestionMark:
|
case HtmlSymbolType.QuestionMark:
|
||||||
// XML PI
|
// XML PI
|
||||||
Accept(_bufferedOpenAngle);
|
Accept(_bufferedOpenAngle);
|
||||||
|
|
@ -275,30 +285,48 @@ namespace Microsoft.AspNet.Razor.Parser
|
||||||
{
|
{
|
||||||
// Accept "/" and move next
|
// Accept "/" and move next
|
||||||
Assert(HtmlSymbolType.ForwardSlash);
|
Assert(HtmlSymbolType.ForwardSlash);
|
||||||
var solidus = CurrentSymbol;
|
var forwardSlash = CurrentSymbol;
|
||||||
if (!NextToken())
|
if (!NextToken())
|
||||||
{
|
{
|
||||||
Accept(_bufferedOpenAngle);
|
Accept(_bufferedOpenAngle);
|
||||||
Accept(solidus);
|
Accept(forwardSlash);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var tagName = String.Empty;
|
var tagName = string.Empty;
|
||||||
if (At(HtmlSymbolType.Text))
|
HtmlSymbol bangSymbol = null;
|
||||||
|
|
||||||
|
if (At(HtmlSymbolType.Bang))
|
||||||
|
{
|
||||||
|
bangSymbol = CurrentSymbol;
|
||||||
|
|
||||||
|
var nextSymbol = Lookahead(count: 1);
|
||||||
|
|
||||||
|
if (nextSymbol != null && nextSymbol.Type == HtmlSymbolType.Text)
|
||||||
|
{
|
||||||
|
tagName = "!" + nextSymbol.Content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (At(HtmlSymbolType.Text))
|
||||||
{
|
{
|
||||||
tagName = CurrentSymbol.Content;
|
tagName = CurrentSymbol.Content;
|
||||||
}
|
}
|
||||||
|
|
||||||
var matched = RemoveTag(tags, tagName, tagStart);
|
var matched = RemoveTag(tags, tagName, tagStart);
|
||||||
|
|
||||||
if (tags.Count == 0 &&
|
if (tags.Count == 0 &&
|
||||||
String.Equals(tagName, SyntaxConstants.TextTagName, StringComparison.OrdinalIgnoreCase) &&
|
// Note tagName may contain a '!' escape character. This ensures </!text> doesn't match here.
|
||||||
|
// </!text> tags are treated like any other escaped HTML end tag.
|
||||||
|
string.Equals(tagName, SyntaxConstants.TextTagName, StringComparison.OrdinalIgnoreCase) &&
|
||||||
matched)
|
matched)
|
||||||
{
|
{
|
||||||
return EndTextTag(solidus, tagBlockWrapper);
|
return EndTextTag(forwardSlash, tagBlockWrapper);
|
||||||
}
|
}
|
||||||
Accept(_bufferedOpenAngle);
|
Accept(_bufferedOpenAngle);
|
||||||
Accept(solidus);
|
Accept(forwardSlash);
|
||||||
|
|
||||||
|
OptionalBangEscape();
|
||||||
|
|
||||||
AcceptUntil(HtmlSymbolType.CloseAngle);
|
AcceptUntil(HtmlSymbolType.CloseAngle);
|
||||||
|
|
||||||
|
|
@ -347,14 +375,22 @@ namespace Microsoft.AspNet.Razor.Parser
|
||||||
return seenCloseAngle;
|
return seenCloseAngle;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Special tags include <! and <? tags
|
// Special tags include <!--, <!DOCTYPE, <![CDATA and <? tags
|
||||||
private bool AtSpecialTag
|
private bool AtSpecialTag
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
return (At(HtmlSymbolType.OpenAngle) &&
|
if (At(HtmlSymbolType.OpenAngle))
|
||||||
(NextIs(HtmlSymbolType.Bang) ||
|
{
|
||||||
NextIs(HtmlSymbolType.QuestionMark)));
|
if (NextIs(HtmlSymbolType.Bang))
|
||||||
|
{
|
||||||
|
return !IsBangEscape(lookahead: 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NextIs(HtmlSymbolType.QuestionMark);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -651,20 +687,41 @@ namespace Microsoft.AspNet.Razor.Parser
|
||||||
|
|
||||||
private bool StartTag(Stack<Tuple<HtmlSymbol, SourceLocation>> tags, IDisposable tagBlockWrapper)
|
private bool StartTag(Stack<Tuple<HtmlSymbol, SourceLocation>> tags, IDisposable tagBlockWrapper)
|
||||||
{
|
{
|
||||||
// If we're at text, it's the name, otherwise the name is ""
|
HtmlSymbol bangSymbol = null;
|
||||||
HtmlSymbol tagName;
|
HtmlSymbol potentialTagNameSymbol;
|
||||||
if (At(HtmlSymbolType.Text))
|
|
||||||
|
if (At(HtmlSymbolType.Bang))
|
||||||
{
|
{
|
||||||
tagName = CurrentSymbol;
|
bangSymbol = CurrentSymbol;
|
||||||
|
|
||||||
|
potentialTagNameSymbol = Lookahead(count: 1);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
tagName = new HtmlSymbol(CurrentLocation, String.Empty, HtmlSymbolType.Unknown);
|
potentialTagNameSymbol = CurrentSymbol;
|
||||||
|
}
|
||||||
|
|
||||||
|
HtmlSymbol tagName;
|
||||||
|
|
||||||
|
if (potentialTagNameSymbol == null || potentialTagNameSymbol.Type != HtmlSymbolType.Text)
|
||||||
|
{
|
||||||
|
tagName = new HtmlSymbol(potentialTagNameSymbol.Start, string.Empty, HtmlSymbolType.Unknown);
|
||||||
|
}
|
||||||
|
else if (bangSymbol != null)
|
||||||
|
{
|
||||||
|
tagName = new HtmlSymbol(bangSymbol.Start, "!" + potentialTagNameSymbol.Content, HtmlSymbolType.Text);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
tagName = potentialTagNameSymbol;
|
||||||
}
|
}
|
||||||
|
|
||||||
Tuple<HtmlSymbol, SourceLocation> tag = Tuple.Create(tagName, _lastTagStart);
|
Tuple<HtmlSymbol, SourceLocation> tag = Tuple.Create(tagName, _lastTagStart);
|
||||||
|
|
||||||
if (tags.Count == 0 && String.Equals(tag.Item1.Content, SyntaxConstants.TextTagName, StringComparison.OrdinalIgnoreCase))
|
if (tags.Count == 0 &&
|
||||||
|
// Note tagName may contain a '!' escape character. This ensures <!text> doesn't match here.
|
||||||
|
// <!text> tags are treated like any other escaped HTML start tag.
|
||||||
|
string.Equals(tag.Item1.Content, SyntaxConstants.TextTagName, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
Output(SpanKind.Markup);
|
Output(SpanKind.Markup);
|
||||||
Span.CodeGenerator = SpanCodeGenerator.Null;
|
Span.CodeGenerator = SpanCodeGenerator.Null;
|
||||||
|
|
@ -709,7 +766,9 @@ namespace Microsoft.AspNet.Razor.Parser
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Accept(_bufferedOpenAngle);
|
Accept(_bufferedOpenAngle);
|
||||||
|
OptionalBangEscape();
|
||||||
Optional(HtmlSymbolType.Text);
|
Optional(HtmlSymbolType.Text);
|
||||||
return RestOfTag(tag, tags, tagBlockWrapper);
|
return RestOfTag(tag, tags, tagBlockWrapper);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using Microsoft.AspNet.Razor.Generator;
|
||||||
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
|
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
|
||||||
using Microsoft.AspNet.Razor.Tokenizer.Symbols;
|
using Microsoft.AspNet.Razor.Tokenizer.Symbols;
|
||||||
|
|
||||||
|
|
@ -42,58 +43,69 @@ namespace Microsoft.AspNet.Razor.Parser
|
||||||
{
|
{
|
||||||
if (NextIs(HtmlSymbolType.Bang))
|
if (NextIs(HtmlSymbolType.Bang))
|
||||||
{
|
{
|
||||||
AcceptAndMoveNext(); // Accept '<'
|
// Checking to see if we meet the conditions of a special '!' tag: <!DOCTYPE, <![CDATA[, <!--.
|
||||||
BangTag();
|
if (!IsBangEscape(lookahead: 1))
|
||||||
|
{
|
||||||
|
AcceptAndMoveNext(); // Accept '<'
|
||||||
|
BangTag();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We should behave like a normal tag that has a parser escape, fall through to the normal
|
||||||
|
// tag logic.
|
||||||
}
|
}
|
||||||
else if (NextIs(HtmlSymbolType.QuestionMark))
|
else if (NextIs(HtmlSymbolType.QuestionMark))
|
||||||
{
|
{
|
||||||
AcceptAndMoveNext(); // Accept '<'
|
AcceptAndMoveNext(); // Accept '<'
|
||||||
XmlPI();
|
XmlPI();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Output(SpanKind.Markup);
|
||||||
|
|
||||||
|
// Start tag block
|
||||||
|
var tagBlock = Context.StartBlock(BlockType.Tag);
|
||||||
|
|
||||||
|
AcceptAndMoveNext(); // Accept '<'
|
||||||
|
|
||||||
|
if (!At(HtmlSymbolType.ForwardSlash))
|
||||||
|
{
|
||||||
|
OptionalBangEscape();
|
||||||
|
|
||||||
|
// Parsing a start tag
|
||||||
|
var scriptTag = At(HtmlSymbolType.Text) &&
|
||||||
|
string.Equals(CurrentSymbol.Content, "script", StringComparison.OrdinalIgnoreCase);
|
||||||
|
Optional(HtmlSymbolType.Text);
|
||||||
|
TagContent(); // Parse the tag, don't care about the content
|
||||||
|
Optional(HtmlSymbolType.ForwardSlash);
|
||||||
|
Optional(HtmlSymbolType.CloseAngle);
|
||||||
|
|
||||||
|
if (scriptTag)
|
||||||
|
{
|
||||||
|
Output(SpanKind.Markup);
|
||||||
|
tagBlock.Dispose();
|
||||||
|
|
||||||
|
SkipToEndScriptAndParseCode();
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Output(SpanKind.Markup);
|
// Parsing an end tag
|
||||||
|
// This section can accept things like: '</p >' or '</p>' etc.
|
||||||
|
Optional(HtmlSymbolType.ForwardSlash);
|
||||||
|
|
||||||
// Start tag block
|
// Whitespace here is invalid (according to the spec)
|
||||||
var tagBlock = Context.StartBlock(BlockType.Tag);
|
OptionalBangEscape();
|
||||||
|
Optional(HtmlSymbolType.Text);
|
||||||
AcceptAndMoveNext(); // Accept '<'
|
AcceptAll(HtmlSymbolType.WhiteSpace);
|
||||||
|
Optional(HtmlSymbolType.CloseAngle);
|
||||||
if (!At(HtmlSymbolType.ForwardSlash))
|
|
||||||
{
|
|
||||||
// Parsing a start tag
|
|
||||||
var scriptTag = At(HtmlSymbolType.Text) &&
|
|
||||||
string.Equals(CurrentSymbol.Content, "script", StringComparison.OrdinalIgnoreCase);
|
|
||||||
Optional(HtmlSymbolType.Text);
|
|
||||||
TagContent(); // Parse the tag, don't care about the content
|
|
||||||
Optional(HtmlSymbolType.ForwardSlash);
|
|
||||||
Optional(HtmlSymbolType.CloseAngle);
|
|
||||||
|
|
||||||
if (scriptTag)
|
|
||||||
{
|
|
||||||
Output(SpanKind.Markup);
|
|
||||||
tagBlock.Dispose();
|
|
||||||
|
|
||||||
SkipToEndScriptAndParseCode();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Parsing an end tag
|
|
||||||
// This section can accept things like: '</p >' or '</p>' etc.
|
|
||||||
Optional(HtmlSymbolType.ForwardSlash);
|
|
||||||
// Whitespace here is invalid (according to the spec)
|
|
||||||
Optional(HtmlSymbolType.Text);
|
|
||||||
AcceptAll(HtmlSymbolType.WhiteSpace);
|
|
||||||
Optional(HtmlSymbolType.CloseAngle);
|
|
||||||
}
|
|
||||||
|
|
||||||
Output(SpanKind.Markup);
|
|
||||||
|
|
||||||
// End tag block
|
|
||||||
tagBlock.Dispose();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Output(SpanKind.Markup);
|
||||||
|
|
||||||
|
// End tag block
|
||||||
|
tagBlock.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -184,5 +184,38 @@ namespace Microsoft.AspNet.Razor.Parser
|
||||||
Initialize(Span);
|
Initialize(Span);
|
||||||
NextToken();
|
NextToken();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool IsBangEscape(int lookahead)
|
||||||
|
{
|
||||||
|
var potentialBang = Lookahead(lookahead);
|
||||||
|
|
||||||
|
if (potentialBang != null &&
|
||||||
|
potentialBang.Type == HtmlSymbolType.Bang)
|
||||||
|
{
|
||||||
|
var afterBang = Lookahead(lookahead + 1);
|
||||||
|
|
||||||
|
return afterBang != null &&
|
||||||
|
afterBang.Type == HtmlSymbolType.Text &&
|
||||||
|
!string.Equals(afterBang.Content, "DOCTYPE", StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OptionalBangEscape()
|
||||||
|
{
|
||||||
|
if (IsBangEscape(lookahead: 0))
|
||||||
|
{
|
||||||
|
Output(SpanKind.Markup);
|
||||||
|
|
||||||
|
// Accept the parser escape character '!'.
|
||||||
|
Assert(HtmlSymbolType.Bang);
|
||||||
|
AcceptAndMoveNext();
|
||||||
|
|
||||||
|
// Setup the metacode span that we will be outputing.
|
||||||
|
Span.CodeGenerator = SpanCodeGenerator.Null;
|
||||||
|
Output(SpanKind.MetaCode, AcceptedCharacters.None);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
|
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
|
||||||
using Microsoft.AspNet.Razor.Text;
|
using Microsoft.AspNet.Razor.Text;
|
||||||
|
|
@ -75,6 +76,43 @@ namespace Microsoft.AspNet.Razor.Parser
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected TSymbol Lookahead(int count)
|
||||||
|
{
|
||||||
|
if (count < 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(count));
|
||||||
|
}
|
||||||
|
else if (count == 0)
|
||||||
|
{
|
||||||
|
return CurrentSymbol;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We add 1 in order to store the current symbol.
|
||||||
|
var symbols = new TSymbol[count + 1];
|
||||||
|
var currentSymbol = CurrentSymbol;
|
||||||
|
|
||||||
|
symbols[0] = currentSymbol;
|
||||||
|
|
||||||
|
// We need to look forward "count" many times.
|
||||||
|
for (var i = 1; i <= count; i++)
|
||||||
|
{
|
||||||
|
NextToken();
|
||||||
|
symbols[i] = CurrentSymbol;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore Tokenizer's location to where it was pointing before the look-ahead.
|
||||||
|
for (var i = count; i >= 0; i--)
|
||||||
|
{
|
||||||
|
PutBack(symbols[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The PutBacks above will set CurrentSymbol to null. EnsureCurrent will set our CurrentSymbol to the
|
||||||
|
// next symbol.
|
||||||
|
EnsureCurrent();
|
||||||
|
|
||||||
|
return symbols[count];
|
||||||
|
}
|
||||||
|
|
||||||
protected internal bool NextToken()
|
protected internal bool NextToken()
|
||||||
{
|
{
|
||||||
PreviousSymbol = CurrentSymbol;
|
PreviousSymbol = CurrentSymbol;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue