private void AttributeValue(HtmlSymbolType quote) { var prefixStart = CurrentLocation; var prefix = ReadWhile(sym => sym.Type == HtmlSymbolType.WhiteSpace || sym.Type == HtmlSymbolType.NewLine); if (At(HtmlSymbolType.Transition)) { if (NextIs(HtmlSymbolType.Transition)) { // Wrapping this in a block so that the ConditionalAttributeCollapser doesn't rewrite it. using (Context.StartBlock(BlockType.Markup)) { Accept(prefix); // Render a single "@" in place of "@@". Span.ChunkGenerator = new LiteralAttributeChunkGenerator( prefix.GetContent(prefixStart), new LocationTagged <string>(CurrentSymbol.GetContent(), CurrentLocation)); AcceptAndMoveNext(); Output(SpanKind.Markup, AcceptedCharacters.None); Span.ChunkGenerator = SpanChunkGenerator.Null; AcceptAndMoveNext(); Output(SpanKind.Markup, AcceptedCharacters.None); } } else { Accept(prefix); var valueStart = CurrentLocation; PutCurrentBack(); // Output the prefix but as a null-span. DynamicAttributeBlockChunkGenerator will render it Span.ChunkGenerator = SpanChunkGenerator.Null; // Dynamic value, start a new block and set the chunk generator using (Context.StartBlock(BlockType.Markup)) { Context.CurrentBlock.ChunkGenerator = new DynamicAttributeBlockChunkGenerator(prefix.GetContent(prefixStart), valueStart); OtherParserBlock(); } } } else if (At(HtmlSymbolType.Text) && CurrentSymbol.Content.Length > 0 && CurrentSymbol.Content[0] == '~' && NextIs(HtmlSymbolType.ForwardSlash)) { Accept(prefix); // Virtual Path value var valueStart = CurrentLocation; VirtualPath(); Span.ChunkGenerator = new LiteralAttributeChunkGenerator( prefix.GetContent(prefixStart), new LocationTagged <SpanChunkGenerator>(new ResolveUrlChunkGenerator(), valueStart)); } else { Accept(prefix); // Literal value // 'quote' should be "Unknown" if not quoted and symbols coming from the tokenizer should never have // "Unknown" type. var value = ReadWhile(sym => // These three conditions find separators which break the attribute value into portions sym.Type != HtmlSymbolType.WhiteSpace && sym.Type != HtmlSymbolType.NewLine && sym.Type != HtmlSymbolType.Transition && // This condition checks for the end of the attribute value (it repeats some of the checks above // but for now that's ok) !IsEndOfAttributeValue(quote, sym)); Accept(value); Span.ChunkGenerator = new LiteralAttributeChunkGenerator( prefix.GetContent(prefixStart), value.GetContent(prefixStart)); } Output(SpanKind.Markup); }
private void AttributePrefix(IEnumerable <HtmlSymbol> whitespace, IEnumerable <HtmlSymbol> nameSymbols) { // First, determine if this is a 'data-' attribute (since those can't use conditional attributes) LocationTagged <string> name = nameSymbols.GetContent(Span.Start); bool attributeCanBeConditional = !name.Value.StartsWith("data-", StringComparison.OrdinalIgnoreCase); // Accept the whitespace and name Accept(whitespace); Accept(nameSymbols); Assert(HtmlSymbolType.Equals); // We should be at "=" AcceptAndMoveNext(); HtmlSymbolType quote = HtmlSymbolType.Unknown; if (At(HtmlSymbolType.SingleQuote) || At(HtmlSymbolType.DoubleQuote)) { quote = CurrentSymbol.Type; AcceptAndMoveNext(); } // We now have the prefix: (i.e. ' foo="') LocationTagged <string> prefix = Span.GetContent(); if (attributeCanBeConditional) { Span.CodeGenerator = SpanCodeGenerator.Null; // The block code generator will render the prefix Output(SpanKind.Markup); // Read the values while (!EndOfFile && !IsEndOfAttributeValue(quote, CurrentSymbol)) { AttributeValue(quote); } // Capture the suffix LocationTagged <string> suffix = new LocationTagged <string>(String.Empty, CurrentLocation); if (quote != HtmlSymbolType.Unknown && At(quote)) { suffix = CurrentSymbol.GetContent(); AcceptAndMoveNext(); } if (Span.Symbols.Count > 0) { Span.CodeGenerator = SpanCodeGenerator.Null; // Again, block code generator will render the suffix Output(SpanKind.Markup); } // Create the block code generator Context.CurrentBlock.CodeGenerator = new AttributeBlockCodeGenerator( name, prefix, suffix); } else { // Not a "conditional" attribute, so just read the value SkipToAndParseCode(sym => IsEndOfAttributeValue(quote, sym)); if (quote != HtmlSymbolType.Unknown) { Optional(quote); } Output(SpanKind.Markup); } }
private void AttributePrefix( IEnumerable <HtmlSymbol> whitespace, IEnumerable <HtmlSymbol> nameSymbols, IEnumerable <HtmlSymbol> whitespaceAfterAttributeName) { // First, determine if this is a 'data-' attribute (since those can't use conditional attributes) var name = nameSymbols.GetContent(Span.Start); var attributeCanBeConditional = !name.Value.StartsWith("data-", StringComparison.OrdinalIgnoreCase); // Accept the whitespace and name Accept(whitespace); Accept(nameSymbols); // Since this is not a minimized attribute, the whitespace after attribute name belongs to this attribute. Accept(whitespaceAfterAttributeName); Assert(HtmlSymbolType.Equals); // We should be at "=" AcceptAndMoveNext(); var whitespaceAfterEquals = ReadWhile(sym => sym.Type == HtmlSymbolType.WhiteSpace || sym.Type == HtmlSymbolType.NewLine); var quote = HtmlSymbolType.Unknown; if (At(HtmlSymbolType.SingleQuote) || At(HtmlSymbolType.DoubleQuote)) { // Found a quote, the whitespace belongs to this attribute. Accept(whitespaceAfterEquals); quote = CurrentSymbol.Type; AcceptAndMoveNext(); } else if (whitespaceAfterEquals.Any()) { // No quotes found after the whitespace. Put it back so that it can be parsed later. PutCurrentBack(); PutBack(whitespaceAfterEquals); } // We now have the prefix: (i.e. ' foo="') var prefix = Span.GetContent(); if (attributeCanBeConditional) { Span.ChunkGenerator = SpanChunkGenerator.Null; // The block chunk generator will render the prefix Output(SpanKind.Markup); // Read the attribute value only if the value is quoted // or if there is no whitespace between '=' and the unquoted value. if (quote != HtmlSymbolType.Unknown || !whitespaceAfterEquals.Any()) { // Read the attribute value. while (!EndOfFile && !IsEndOfAttributeValue(quote, CurrentSymbol)) { AttributeValue(quote); } } // Capture the suffix var suffix = new LocationTagged <string>(string.Empty, CurrentLocation); if (quote != HtmlSymbolType.Unknown && At(quote)) { suffix = CurrentSymbol.GetContent(); AcceptAndMoveNext(); } if (Span.Symbols.Count > 0) { // Again, block chunk generator will render the suffix Span.ChunkGenerator = SpanChunkGenerator.Null; Output(SpanKind.Markup); } // Create the block chunk generator Context.CurrentBlock.ChunkGenerator = new AttributeBlockChunkGenerator( name, prefix, suffix); } else { // Output the attribute name, the equals and optional quote. Ex: foo=" Output(SpanKind.Markup); if (quote == HtmlSymbolType.Unknown && whitespaceAfterEquals.Any()) { return; } // Not a "conditional" attribute, so just read the value SkipToAndParseCode(sym => IsEndOfAttributeValue(quote, sym)); // Output the attribute value (will include everything in-between the attribute's quotes). Output(SpanKind.Markup); if (quote != HtmlSymbolType.Unknown) { Optional(quote); } Output(SpanKind.Markup); } }