Skip to content

Commit 5cf3d6b

Browse files
jhaygood86eXpl0it3r
authored andcommitted
Fixes <br>
1 parent 2ecdc19 commit 5cf3d6b

2 files changed

Lines changed: 33 additions & 65 deletions

File tree

‎Source/HtmlRenderer/Core/Dom/CssContentEngine.cs‎

Lines changed: 32 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@
1010
// - Sun Tsu,
1111
// "The Art of War"
1212

13+
using System;
14+
using System.Linq;
1315
using System.Text;
16+
using TheArtOfDev.HtmlRenderer.Core.CssEngine;
17+
using TheArtOfDev.HtmlRenderer.Core.Parse;
1418
using TheArtOfDev.HtmlRenderer.Core.Utils;
1519

1620
namespace TheArtOfDev.HtmlRenderer.Core.Dom
@@ -23,7 +27,12 @@ namespace TheArtOfDev.HtmlRenderer.Core.Dom
2327
/// <c>attr()</c>, and the four quote keywords are supported; <c>counter()</c>/<c>counters()</c>/
2428
/// <c>string()</c>/<c>content()</c> are not (CSS counters and named strings remain out of scope
2529
/// for this backport - an unrecognized component is simply skipped, the same graceful-ignore
26-
/// behavior this engine already applies to unsupported CSS elsewhere).
30+
/// behavior this engine already applies to unsupported CSS elsewhere).<br/>
31+
/// Tokenizes via the shared CSS lexer (<see cref="CssValueParser.GetCssTokens"/>) rather than
32+
/// hand-parsing the CSS text, so string literals (e.g. <c>"\A"</c>, the standard UA-stylesheet
33+
/// line-break trick used by <c>br:before</c>) get the same correct escape decoding the lexer
34+
/// already applies when the stylesheet is first parsed - see CalcParser/BackgroundPositionGrammar
35+
/// for the same "don't write two independent parsers for the same grammar" precedent.
2736
/// </summary>
2837
internal static class CssContentEngine
2938
{
@@ -50,82 +59,41 @@ public static void ApplyContent(CssBox box)
5059
private static string Resolve(CssBox box, string content)
5160
{
5261
var sb = new StringBuilder();
53-
var i = 0;
54-
while (i < content.Length)
62+
63+
foreach (var token in CssValueParser.GetCssTokens(content))
5564
{
56-
if (char.IsWhiteSpace(content[i]))
65+
if (token is StringToken stringToken)
5766
{
58-
i++;
59-
continue;
67+
sb.Append(stringToken.Data);
6068
}
61-
62-
if (content[i] == '"' || content[i] == '\'')
69+
else if (token is FunctionToken functionToken &&
70+
functionToken.Data.Equals(FunctionNames.Attr, StringComparison.OrdinalIgnoreCase))
6371
{
64-
var quote = content[i];
65-
var start = i + 1;
66-
var j = start;
67-
while (j < content.Length && content[j] != quote)
72+
if (functionToken.ArgumentTokens.FirstOrDefault() is KeywordToken attrNameToken)
6873
{
69-
if (content[j] == '\\' && j + 1 < content.Length)
70-
j++;
71-
j++;
74+
var sourceBox = box.IsPseudoElement && box.ParentBox != null ? box.ParentBox : box;
75+
var attrValue = sourceBox.GetAttribute(attrNameToken.Data, string.Empty);
76+
sb.Append(attrValue);
7277
}
73-
sb.Append(Unescape(content.Substring(start, j - start)));
74-
i = j + 1;
75-
continue;
7678
}
77-
78-
var tokenEnd = i;
79-
while (tokenEnd < content.Length && !char.IsWhiteSpace(content[tokenEnd]))
80-
tokenEnd++;
81-
var token = content.Substring(i, tokenEnd - i);
82-
i = tokenEnd;
83-
84-
if (token.StartsWith("attr(", System.StringComparison.OrdinalIgnoreCase) && token.EndsWith(")"))
79+
else if (token is KeywordToken keywordToken)
8580
{
86-
var attrName = token.Substring(5, token.Length - 6).Trim();
87-
var sourceBox = box.IsPseudoElement && box.ParentBox != null ? box.ParentBox : box;
88-
var attrValue = sourceBox.GetAttribute(attrName, string.Empty);
89-
sb.Append(attrValue);
90-
}
91-
else if (token.Equals("open-quote", System.StringComparison.OrdinalIgnoreCase))
92-
{
93-
sb.Append('“');
94-
}
95-
else if (token.Equals("close-quote", System.StringComparison.OrdinalIgnoreCase))
96-
{
97-
sb.Append('”');
98-
}
99-
else if (token.Equals("no-open-quote", System.StringComparison.OrdinalIgnoreCase) ||
100-
token.Equals("no-close-quote", System.StringComparison.OrdinalIgnoreCase))
101-
{
102-
// produces no text, but still counts as a quoting depth change per spec - depth
103-
// tracking isn't implemented here (no "quotes" property support), so this is a no-op.
81+
if (keywordToken.Data.Equals(Keywords.OpenQuote, StringComparison.OrdinalIgnoreCase))
82+
{
83+
sb.Append('“');
84+
}
85+
else if (keywordToken.Data.Equals(Keywords.CloseQuote, StringComparison.OrdinalIgnoreCase))
86+
{
87+
sb.Append('”');
88+
}
89+
// no-open-quote/no-close-quote produce no text, but still count as a quoting depth
90+
// change per spec - depth tracking isn't implemented here (no "quotes" property
91+
// support), so they (like any other unrecognized keyword) are a no-op.
10492
}
10593
// counter()/counters()/string()/content() and anything else unrecognized: skip.
10694
}
10795

10896
return sb.ToString();
10997
}
110-
111-
private static string Unescape(string s)
112-
{
113-
if (s.IndexOf('\\') < 0) return s;
114-
115-
var sb = new StringBuilder(s.Length);
116-
for (var i = 0; i < s.Length; i++)
117-
{
118-
if (s[i] == '\\' && i + 1 < s.Length)
119-
{
120-
i++;
121-
sb.Append(s[i]);
122-
}
123-
else
124-
{
125-
sb.Append(s[i]);
126-
}
127-
}
128-
return sb.ToString();
129-
}
13098
}
13199
}

‎Source/HtmlRenderer/Core/Parse/CssValueParser.cs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ public static bool IsValidLength(string value)
126126
/// skipping whitespace/end-of-file tokens - the same "just enough tokenization to recognize a
127127
/// single top-level function call" approach used to detect calc()-family expressions.
128128
/// </summary>
129-
private static List<Token> GetCssTokens(string value)
129+
internal static List<Token> GetCssTokens(string value)
130130
{
131131
var lexer = new Lexer(value);
132132
var tokens = new List<Token>();

0 commit comments

Comments
 (0)