// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. using System.Web.Razor.Editor; using System.Web.Razor.Generator; using System.Web.Razor.Parser; using System.Web.Razor.Parser.SyntaxTree; using System.Web.Razor.Test.Framework; using System.Web.Razor.Text; using Xunit; namespace System.Web.Razor.Test.Parser.Html { public class HtmlAttributeTest : CsHtmlMarkupParserTestBase { [Fact] public void SimpleLiteralAttribute() { ParseBlockTest("<a href='Foo' />", new MarkupBlock( Factory.Markup("<a"), new MarkupBlock(new AttributeBlockCodeGenerator(name: "href", prefix: new LocationTagged<string>(" href='", 2, 0, 2), suffix: new LocationTagged<string>("'", 12, 0, 12)), Factory.Markup(" href='").With(SpanCodeGenerator.Null), Factory.Markup("Foo").With(new LiteralAttributeCodeGenerator(prefix: new LocationTagged<string>(String.Empty, 9, 0, 9), value: new LocationTagged<string>("Foo", 9, 0, 9))), Factory.Markup("'").With(SpanCodeGenerator.Null)), Factory.Markup(" />").Accepts(AcceptedCharacters.None))); } [Fact] public void MultiPartLiteralAttribute() { ParseBlockTest("<a href='Foo Bar Baz' />", new MarkupBlock( Factory.Markup("<a"), new MarkupBlock(new AttributeBlockCodeGenerator(name: "href", prefix: new LocationTagged<string>(" href='", 2, 0, 2), suffix: new LocationTagged<string>("'", 20, 0, 20)), Factory.Markup(" href='").With(SpanCodeGenerator.Null), Factory.Markup("Foo").With(new LiteralAttributeCodeGenerator(prefix: new LocationTagged<string>(String.Empty, 9, 0, 9), value: new LocationTagged<string>("Foo", 9, 0, 9))), Factory.Markup(" Bar").With(new LiteralAttributeCodeGenerator(prefix: new LocationTagged<string>(" ", 12, 0, 12), value: new LocationTagged<string>("Bar", 13, 0, 13))), Factory.Markup(" Baz").With(new LiteralAttributeCodeGenerator(prefix: new LocationTagged<string>(" ", 16, 0, 16), value: new LocationTagged<string>("Baz", 17, 0, 17))), Factory.Markup("'").With(SpanCodeGenerator.Null)), Factory.Markup(" />").Accepts(AcceptedCharacters.None))); } [Fact] public void DoubleQuotedLiteralAttribute() { ParseBlockTest("<a href=\"Foo Bar Baz\" />", new MarkupBlock( Factory.Markup("<a"), new MarkupBlock(new AttributeBlockCodeGenerator(name: "href", prefix: new LocationTagged<string>(" href=\"", 2, 0, 2), suffix: new LocationTagged<string>("\"", 20, 0, 20)), Factory.Markup(" href=\"").With(SpanCodeGenerator.Null), Factory.Markup("Foo").With(new LiteralAttributeCodeGenerator(prefix: new LocationTagged<string>(String.Empty, 9, 0, 9), value: new LocationTagged<string>("Foo", 9, 0, 9))), Factory.Markup(" Bar").With(new LiteralAttributeCodeGenerator(prefix: new LocationTagged<string>(" ", 12, 0, 12), value: new LocationTagged<string>("Bar", 13, 0, 13))), Factory.Markup(" Baz").With(new LiteralAttributeCodeGenerator(prefix: new LocationTagged<string>(" ", 16, 0, 16), value: new LocationTagged<string>("Baz", 17, 0, 17))), Factory.Markup("\"").With(SpanCodeGenerator.Null)), Factory.Markup(" />").Accepts(AcceptedCharacters.None))); } [Fact] public void UnquotedLiteralAttribute() { ParseBlockTest("<a href=Foo Bar Baz />", new MarkupBlock( Factory.Markup("<a"), new MarkupBlock(new AttributeBlockCodeGenerator(name: "href", prefix: new LocationTagged<string>(" href=", 2, 0, 2), suffix: new LocationTagged<string>(String.Empty, 11, 0, 11)), Factory.Markup(" href=").With(SpanCodeGenerator.Null), Factory.Markup("Foo").With(new LiteralAttributeCodeGenerator(prefix: new LocationTagged<string>(String.Empty, 8, 0, 8), value: new LocationTagged<string>("Foo", 8, 0, 8)))), Factory.Markup(" Bar Baz />").Accepts(AcceptedCharacters.None))); } [Fact] public void SimpleExpressionAttribute() { ParseBlockTest("<a href='@foo' />", new MarkupBlock( Factory.Markup("<a"), new MarkupBlock(new AttributeBlockCodeGenerator(name: "href", prefix: new LocationTagged<string>(" href='", 2, 0, 2), suffix: new LocationTagged<string>("'", 13, 0, 13)), Factory.Markup(" href='").With(SpanCodeGenerator.Null), new MarkupBlock(new DynamicAttributeBlockCodeGenerator(new LocationTagged<string>(String.Empty, 9, 0, 9), 9, 0, 9), new ExpressionBlock( Factory.CodeTransition(), Factory.Code("foo") .AsImplicitExpression(CSharpCodeParser.DefaultKeywords) .Accepts(AcceptedCharacters.NonWhiteSpace))), Factory.Markup("'").With(SpanCodeGenerator.Null)), Factory.Markup(" />").Accepts(AcceptedCharacters.None))); } [Fact] public void MultiValueExpressionAttribute() { ParseBlockTest("<a href='@foo bar @baz' />", new MarkupBlock( Factory.Markup("<a"), new MarkupBlock(new AttributeBlockCodeGenerator(name: "href", prefix: new LocationTagged<string>(" href='", 2, 0, 2), suffix: new LocationTagged<string>("'", 22, 0, 22)), Factory.Markup(" href='").With(SpanCodeGenerator.Null), new MarkupBlock(new DynamicAttributeBlockCodeGenerator(new LocationTagged<string>(String.Empty, 9, 0, 9), 9, 0, 9), new ExpressionBlock( Factory.CodeTransition(), Factory.Code("foo") .AsImplicitExpression(CSharpCodeParser.DefaultKeywords) .Accepts(AcceptedCharacters.NonWhiteSpace))), Factory.Markup(" bar").With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(" ", 13, 0, 13), new LocationTagged<string>("bar", 14, 0, 14))), new MarkupBlock(new DynamicAttributeBlockCodeGenerator(new LocationTagged<string>(" ", 17, 0, 17), 18, 0, 18), Factory.Markup(" ").With(SpanCodeGenerator.Null), new ExpressionBlock( Factory.CodeTransition(), Factory.Code("baz") .AsImplicitExpression(CSharpCodeParser.DefaultKeywords) .Accepts(AcceptedCharacters.NonWhiteSpace))), Factory.Markup("'").With(SpanCodeGenerator.Null)), Factory.Markup(" />").Accepts(AcceptedCharacters.None))); } [Fact] public void VirtualPathAttributesWorkWithConditionalAttributes() { ParseBlockTest("<a href='@foo ~/Foo/Bar' />", new MarkupBlock( Factory.Markup("<a"), new MarkupBlock(new AttributeBlockCodeGenerator(name: "href", prefix: new LocationTagged<string>(" href='", 2, 0, 2), suffix: new LocationTagged<string>("'", 23, 0, 23)), Factory.Markup(" href='").With(SpanCodeGenerator.Null), new MarkupBlock(new DynamicAttributeBlockCodeGenerator(new LocationTagged<string>(String.Empty, 9, 0, 9), 9, 0, 9), new ExpressionBlock( Factory.CodeTransition(), Factory.Code("foo") .AsImplicitExpression(CSharpCodeParser.DefaultKeywords) .Accepts(AcceptedCharacters.NonWhiteSpace))), Factory.Markup(" ~/Foo/Bar") .WithEditorHints(EditorHints.VirtualPath) .With(new LiteralAttributeCodeGenerator( new LocationTagged<string>(" ", 13, 0, 13), new LocationTagged<SpanCodeGenerator>(new ResolveUrlCodeGenerator(), 14, 0, 14))), Factory.Markup("'").With(SpanCodeGenerator.Null)), Factory.Markup(" />").Accepts(AcceptedCharacters.None))); } [Fact] public void UnquotedAttributeWithCodeWithSpacesInBlock() { ParseBlockTest("<input value=@foo />", new MarkupBlock( Factory.Markup("<input"), new MarkupBlock(new AttributeBlockCodeGenerator(name: "value", prefix: new LocationTagged<string>(" value=", 6, 0, 6), suffix: new LocationTagged<string>(String.Empty, 17, 0, 17)), Factory.Markup(" value=").With(SpanCodeGenerator.Null), new MarkupBlock(new DynamicAttributeBlockCodeGenerator(new LocationTagged<string>(String.Empty, 13, 0, 13), 13, 0, 13), new ExpressionBlock( Factory.CodeTransition(), Factory.Code("foo") .AsImplicitExpression(CSharpCodeParser.DefaultKeywords) .Accepts(AcceptedCharacters.NonWhiteSpace)))), Factory.Markup(" />").Accepts(AcceptedCharacters.None))); } [Fact] public void UnquotedAttributeWithCodeWithSpacesInDocument() { ParseDocumentTest("<input value=@foo />", new MarkupBlock( Factory.Markup("<input"), new MarkupBlock(new AttributeBlockCodeGenerator(name: "value", prefix: new LocationTagged<string>(" value=", 6, 0, 6), suffix: new LocationTagged<string>(String.Empty, 17, 0, 17)), Factory.Markup(" value=").With(SpanCodeGenerator.Null), new MarkupBlock(new DynamicAttributeBlockCodeGenerator(new LocationTagged<string>(String.Empty, 13, 0, 13), 13, 0, 13), new ExpressionBlock( Factory.CodeTransition(), Factory.Code("foo") .AsImplicitExpression(CSharpCodeParser.DefaultKeywords) .Accepts(AcceptedCharacters.NonWhiteSpace)))), Factory.Markup(" />"))); } [Fact] public void ConditionalAttributeCollapserDoesNotRemoveUrlAttributeValues() { // Act ParserResults results = ParseDocument("<a href='~/Foo/Bar' />"); Block rewritten = new ConditionalAttributeCollapser(new HtmlMarkupParser().BuildSpan).Rewrite(results.Document); rewritten = new MarkupCollapser(new HtmlMarkupParser().BuildSpan).Rewrite(rewritten); // Assert Assert.Equal(0, results.ParserErrors.Count); EvaluateParseTree(rewritten, new MarkupBlock( Factory.Markup("<a"), new MarkupBlock(new AttributeBlockCodeGenerator(name: "href", prefix: new LocationTagged<string>(" href='", 2, 0, 2), suffix: new LocationTagged<string>("'", 18, 0, 18)), Factory.Markup(" href='").With(SpanCodeGenerator.Null), Factory.Markup("~/Foo/Bar") .WithEditorHints(EditorHints.VirtualPath) .With(new LiteralAttributeCodeGenerator( new LocationTagged<string>(String.Empty, 9, 0, 9), new LocationTagged<SpanCodeGenerator>(new ResolveUrlCodeGenerator(), 9, 0, 9))), Factory.Markup("'").With(SpanCodeGenerator.Null)), Factory.Markup(" />"))); } [Fact] public void ConditionalAttributesDoNotCreateExtraDataForEntirelyLiteralAttribute() { // Arrange const string code = #region Big Block o' code @"<div class=""sidebar""> <h1>Title</h1> <p> As the author, you can <a href=""/Photo/Edit/photoId"">edit</a> or <a href=""/Photo/Remove/photoId"">remove</a> this photo. </p> <dl> <dt class=""description"">Description</dt> <dd class=""description""> The uploader did not provide a description for this photo. </dd> <dt class=""uploaded-by"">Uploaded by</dt> <dd class=""uploaded-by""><a href=""/User/View/user.UserId"">user.DisplayName</a></dd> <dt class=""upload-date"">Upload date</dt> <dd class=""upload-date"">photo.UploadDate</dd> <dt class=""part-of-gallery"">Gallery</dt> <dd><a href=""/View/gallery.Id"" title=""View gallery.Name gallery"">gallery.Name</a></dd> <dt class=""tags"">Tags</dt> <dd class=""tags""> <ul class=""tags""> <li>This photo has no tags.</li> </ul> <a href=""/Photo/EditTags/photoId"">edit tags</a> </dd> </dl> <p> <a class=""download"" href=""/Photo/Full/photoId"" title=""Download: (photo.FileTitle + photo.FileExtension)"">Download full photo</a> ((photo.FileSize / 1024) KB) </p> </div> <div class=""main""> <img class=""large-photo"" alt=""photo.FileTitle"" src=""/Photo/Thumbnail"" /> <h2>Nobody has commented on this photo</h2> <ol class=""comments""> <li> <h3 class=""comment-header""> <a href=""/User/View/comment.UserId"" title=""View comment.DisplayName's profile"">comment.DisplayName</a> commented at comment.CommentDate: </h3> <p class=""comment-body"">comment.CommentText</p> </li> </ol> <form method=""post"" action=""""> <fieldset id=""addComment""> <legend>Post new comment</legend> <ol> <li> <label for=""newComment"">Comment</label> <textarea id=""newComment"" name=""newComment"" title=""Your comment"" rows=""6"" cols=""70""></textarea> </li> </ol> <p class=""form-actions""> <input type=""submit"" title=""Add comment"" value=""Add comment"" /> </p> </fieldset> </form> </div>"; #endregion // Act ParserResults results = ParseDocument(code); Block rewritten = new ConditionalAttributeCollapser(new HtmlMarkupParser().BuildSpan).Rewrite(results.Document); rewritten = new MarkupCollapser(new HtmlMarkupParser().BuildSpan).Rewrite(rewritten); // Assert Assert.Equal(0, results.ParserErrors.Count); EvaluateParseTree(rewritten, new MarkupBlock(Factory.Markup(code))); } [Fact] public void ConditionalAttributesAreDisabledForDataAttributesInBlock() { ParseBlockTest("<span data-foo='@foo'></span>", new MarkupBlock( Factory.Markup("<span"), new MarkupBlock( Factory.Markup(" data-foo='"), new ExpressionBlock( Factory.CodeTransition(), Factory.Code("foo") .AsImplicitExpression(CSharpCodeParser.DefaultKeywords) .Accepts(AcceptedCharacters.NonWhiteSpace)), Factory.Markup("'")), Factory.Markup("></span>").Accepts(AcceptedCharacters.None))); } [Fact] public void ConditionalAttributesAreDisabledForDataAttributesInDocument() { ParseDocumentTest("<span data-foo='@foo'></span>", new MarkupBlock( Factory.Markup("<span"), new MarkupBlock( Factory.Markup(" data-foo='"), new ExpressionBlock( Factory.CodeTransition(), Factory.Code("foo") .AsImplicitExpression(CSharpCodeParser.DefaultKeywords) .Accepts(AcceptedCharacters.NonWhiteSpace)), Factory.Markup("'")), Factory.Markup("></span>"))); } } }