public void ConstructorTransfersChildrenFromBlockBuilder() { // Arrange var expected = new SpanBuilder(SourceLocation.Undefined) { Kind = SpanKindInternal.Code }.Build(); var builder = new BlockBuilder() { Type = BlockKindInternal.Statement }; builder.Children.Add(expected); // Act var block = builder.Build(); // Assert Assert.Same(expected, block.Children.Single()); }
public void ConstructorWithBlockBuilderSetsParent() { // Arrange var builder = new BlockBuilder() { Type = BlockKindInternal.Comment }; var span = new SpanBuilder(SourceLocation.Undefined) { Kind = SpanKindInternal.Code }.Build(); builder.Children.Add(span); // Act var block = builder.Build(); // Assert Assert.Same(block, span.Parent); }
public void Clone_ClonesSpan() { // Arrange var spanBuilder = new SpanBuilder(new SourceLocation(1, 2, 3)) { EditHandler = new SpanEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString), Kind = SpanKindInternal.Transition, ChunkGenerator = new ExpressionChunkGenerator(), }; spanBuilder.Accept(new CSharpSymbol("@", CSharpSymbolType.Transition)); var span = spanBuilder.Build(); // Act var copy = (Span)span.Clone(); // Assert Assert.Equal(span, copy); Assert.NotSame(span, copy); }
protected override SyntaxTreeNode RewriteBlock(BlockBuilder parent, Block block) { // Collect the content of this node var builder = new StringBuilder(); for (var i = 0; i < block.Children.Count; i++) { var childSpan = (Span)block.Children[i]; builder.Append(childSpan.Content); } // Create a new span containing this content var span = new SpanBuilder(block.Children[0].Start); span.EditHandler = SpanEditHandler.CreateDefault(HtmlLanguageCharacteristics.Instance.TokenizeString); Debug.Assert(block.Children.Count > 0); var start = ((Span)block.Children[0]).Start; FillSpan(span, start, builder.ToString()); return(span.Build()); }
public void ReplaceWith(SpanBuilder builder) { Kind = builder.Kind; Symbols = builder.Symbols; for (var i = 0; i < Symbols.Count; i++) { Symbols[i].Parent = this; } EditHandler = builder.EditHandler; ChunkGenerator = builder.ChunkGenerator ?? SpanChunkGenerator.Null; _start = builder.Start; _content = null; _length = null; Parent?.ChildChanged(); // Since we took references to the values in SpanBuilder, clear its references out builder.Reset(); }
public void ChildChanged_NotifiesParent() { // Arrange var spanBuilder = new SpanBuilder(SourceLocation.Zero); spanBuilder.Accept(new HtmlSymbol("hello", HtmlSymbolType.Text)); var span = spanBuilder.Build(); var blockBuilder = new BlockBuilder() { Type = BlockKindInternal.Markup, }; blockBuilder.Children.Add(span); var childBlock = blockBuilder.Build(); blockBuilder = new BlockBuilder() { Type = BlockKindInternal.Markup, }; blockBuilder.Children.Add(childBlock); var parentBlock = blockBuilder.Build(); var originalBlockLength = parentBlock.Length; spanBuilder = new SpanBuilder(SourceLocation.Zero); spanBuilder.Accept(new HtmlSymbol("hi", HtmlSymbolType.Text)); span.ReplaceWith(spanBuilder); // Wire up parents now so we can re-trigger ChildChanged to cause cache refresh. span.Parent = childBlock; childBlock.Parent = parentBlock; // Act childBlock.ChildChanged(); // Assert Assert.Equal(5, originalBlockLength); Assert.Equal(2, parentBlock.Length); }
// This method handles cases when the attribute is a simple span attribute such as // class="something moresomething". This does not handle complex attributes such as // class="@myclass". Therefore the span.Content is equivalent to the entire attribute. private static TryParseResult TryParseSpan( Span span, IEnumerable <TagHelperDescriptor> descriptors, ErrorSink errorSink, HashSet <string> processedBoundAttributeNames) { var afterEquals = false; var builder = new SpanBuilder(span.Start) { ChunkGenerator = span.ChunkGenerator, EditHandler = span.EditHandler, Kind = span.Kind }; // Will contain symbols that represent a single attribute value: <input| class="btn"| /> var htmlSymbols = span.Symbols.OfType <HtmlSymbol>().ToArray(); var capturedAttributeValueStart = false; var attributeValueStartLocation = span.Start; // Default to DoubleQuotes. We purposefully do not persist NoQuotes ValueStyle to stay consistent with the // TryParseBlock() variation of attribute parsing. var attributeValueStyle = AttributeStructure.DoubleQuotes; // The symbolOffset is initialized to 0 to expect worst case: "class=". If a quote is found later on for // the attribute value the symbolOffset is adjusted accordingly. var symbolOffset = 0; string name = null; // Iterate down through the symbols to find the name and the start of the value. // We subtract the symbolOffset so we don't accept an ending quote of a span. for (var i = 0; i < htmlSymbols.Length - symbolOffset; i++) { var symbol = htmlSymbols[i]; if (afterEquals) { // We've captured all leading whitespace, the attribute name, and an equals with an optional // quote/double quote. We're now at: " asp-for='|...'" or " asp-for=|..." // The goal here is to capture all symbols until the end of the attribute. Note this will not // consume an ending quote due to the symbolOffset. // When symbols are accepted into SpanBuilders, their locations get altered to be offset by the // parent which is why we need to mark our start location prior to adding the symbol. // This is needed to know the location of the attribute value start within the document. if (!capturedAttributeValueStart) { capturedAttributeValueStart = true; attributeValueStartLocation = symbol.Start; } builder.Accept(symbol); } else if (name == null && HtmlMarkupParser.IsValidAttributeNameSymbol(symbol)) { // We've captured all leading whitespace prior to the attribute name. // We're now at: " |asp-for='...'" or " |asp-for=..." // The goal here is to capture the attribute name. var nameBuilder = new StringBuilder(); // Move the indexer past the attribute name symbols. for (var j = i; j < htmlSymbols.Length; j++) { var nameSymbol = htmlSymbols[j]; if (!HtmlMarkupParser.IsValidAttributeNameSymbol(nameSymbol)) { break; } nameBuilder.Append(nameSymbol.Content); i++; } i--; name = nameBuilder.ToString(); attributeValueStartLocation = SourceLocationTracker.Advance(attributeValueStartLocation, name); } else if (symbol.Type == HtmlSymbolType.Equals) { // We've captured all leading whitespace and the attribute name. // We're now at: " asp-for|='...'" or " asp-for|=..." // The goal here is to consume the equal sign and the optional single/double-quote. // The coming symbols will either be a quote or value (in the case that the value is unquoted). SourceLocation symbolStartLocation; // Skip the whitespace preceding the start of the attribute value. do { i++; // Start from the symbol after '='. } while (i < htmlSymbols.Length && (htmlSymbols[i].Type == HtmlSymbolType.WhiteSpace || htmlSymbols[i].Type == HtmlSymbolType.NewLine)); // Check for attribute start values, aka single or double quote if (i < htmlSymbols.Length && IsQuote(htmlSymbols[i])) { if (htmlSymbols[i].Type == HtmlSymbolType.SingleQuote) { attributeValueStyle = AttributeStructure.SingleQuotes; } symbolStartLocation = htmlSymbols[i].Start; // If there's a start quote then there must be an end quote to be valid, skip it. symbolOffset = 1; } else { // We are at the symbol after equals. Go back to equals to ensure we don't skip past that symbol. i--; symbolStartLocation = symbol.Start; } attributeValueStartLocation = new SourceLocation( symbolStartLocation.FilePath, symbolStartLocation.AbsoluteIndex + 1, symbolStartLocation.LineIndex, symbolStartLocation.CharacterIndex + 1); afterEquals = true; } else if (symbol.Type == HtmlSymbolType.WhiteSpace) { // We're at the start of the attribute, this branch may be hit on the first iterations of // the loop since the parser separates attributes with their spaces included as symbols. // We're at: "| asp-for='...'" or "| asp-for=..." // Note: This will not be hit even for situations like asp-for ="..." because the core Razor // parser currently does not know how to handle attributes in that format. This will be addressed // by https://github.com/aspnet/Razor/issues/123. attributeValueStartLocation = SourceLocationTracker.Advance(attributeValueStartLocation, symbol.Content); } } // After all symbols have been added we need to set the builders start position so we do not indirectly // modify the span's start location. builder.Start = attributeValueStartLocation; if (name == null) { // We couldn't find a name, if the original span content was whitespace it ultimately means the tag // that owns this "attribute" is malformed and is expecting a user to type a new attribute. // ex: <myTH class="btn"| | if (!string.IsNullOrWhiteSpace(span.Content)) { errorSink.OnError( span.Start, LegacyResources.TagHelperBlockRewriter_TagHelperAttributeListMustBeWellFormed, span.Content.Length); } return(null); } var result = CreateTryParseResult(name, descriptors, processedBoundAttributeNames); // If we're not after an equal then we should treat the value as if it were a minimized attribute. Span attributeValue = null; if (afterEquals) { attributeValue = CreateMarkupAttribute(builder, result); } else { attributeValueStyle = AttributeStructure.Minimized; } result.AttributeValueNode = attributeValue; result.AttributeStructure = attributeValueStyle; return(result); }
private void CommentSpanConfig(SpanBuilder span) { span.ChunkGenerator = SpanChunkGenerator.Null; span.EditHandler = SpanEditHandler.CreateDefault(Language.TokenizeString); }
public override SyntaxTreeNode Clone() { var spanBuilder = new SpanBuilder(this); return(spanBuilder.Build()); }
public Span(SpanBuilder builder) { ReplaceWith(builder); }
protected void Initialize(SpanBuilder span) { SpanConfig?.Invoke(span); }