|
6 | 6 | using System; |
7 | 7 | using System.Globalization; |
8 | 8 | using System.Linq; |
9 | | -using System.Text; |
10 | 9 | using Microsoft.AspNetCore.Razor.Language.Intermediate; |
| 10 | +using Microsoft.AspNetCore.Razor.PooledObjects; |
11 | 11 |
|
12 | 12 | namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration; |
13 | 13 |
|
@@ -251,62 +251,55 @@ public override void WriteHtmlContent(CodeRenderingContext context, HtmlContentI |
251 | 251 | { |
252 | 252 | const int MaxStringLiteralLength = 1024; |
253 | 253 |
|
254 | | - var builder = new StringBuilder(); |
255 | | - for (var i = 0; i < node.Children.Count; i++) |
| 254 | + using var htmlContentBuilder = new PooledArrayBuilder<ReadOnlyMemory<char>>(); |
| 255 | + |
| 256 | + var length = 0; |
| 257 | + foreach (var child in node.Children) |
256 | 258 | { |
257 | | - if (node.Children[i] is IntermediateToken token && token.IsHtml) |
| 259 | + if (child is IntermediateToken token && token.IsHtml) |
258 | 260 | { |
259 | | - builder.Append(token.Content); |
| 261 | + var htmlContent = token.Content.AsMemory(); |
| 262 | + |
| 263 | + htmlContentBuilder.Add(htmlContent); |
| 264 | + length += htmlContent.Length; |
260 | 265 | } |
261 | 266 | } |
262 | 267 |
|
263 | | - var content = builder.ToString(); |
| 268 | + // Can't use a pooled builder here as the memory will be stored in the context. |
| 269 | + var content = new char[length]; |
| 270 | + var contentIndex = 0; |
| 271 | + foreach (var htmlContent in htmlContentBuilder) |
| 272 | + { |
| 273 | + htmlContent.Span.CopyTo(content.AsSpan(contentIndex)); |
| 274 | + contentIndex += htmlContent.Length; |
| 275 | + } |
264 | 276 |
|
265 | | - WriteHtmlLiteral(context, MaxStringLiteralLength, content); |
| 277 | + WriteHtmlLiteral(context, MaxStringLiteralLength, content.AsMemory()); |
266 | 278 | } |
267 | 279 |
|
268 | 280 | // Internal for testing |
269 | | - internal void WriteHtmlLiteral(CodeRenderingContext context, int maxStringLiteralLength, string literal) |
| 281 | + internal void WriteHtmlLiteral(CodeRenderingContext context, int maxStringLiteralLength, ReadOnlyMemory<char> literal) |
270 | 282 | { |
271 | | - if (literal.Length <= maxStringLiteralLength) |
| 283 | + while (literal.Length > maxStringLiteralLength) |
272 | 284 | { |
273 | | - WriteLiteral(literal); |
274 | | - return; |
275 | | - } |
| 285 | + // String is too large, render the string in pieces to avoid Roslyn OOM exceptions at compile time: https://github.com/aspnet/External/issues/54 |
| 286 | + var lastCharBeforeSplit = literal.Span[maxStringLiteralLength - 1]; |
276 | 287 |
|
277 | | - // String is too large, render the string in pieces to avoid Roslyn OOM exceptions at compile time: https://github.com/aspnet/External/issues/54 |
278 | | - var charactersConsumed = 0; |
279 | | - do |
280 | | - { |
281 | | - var charactersRemaining = literal.Length - charactersConsumed; |
282 | | - var charactersToSubstring = Math.Min(maxStringLiteralLength, charactersRemaining); |
283 | | - var lastCharBeforeSplitIndex = charactersConsumed + charactersToSubstring - 1; |
284 | | - var lastCharBeforeSplit = literal[lastCharBeforeSplitIndex]; |
| 288 | + // If character at splitting point is a high surrogate, take one less character this iteration |
| 289 | + // as we're attempting to split a surrogate pair. This can happen when something like an |
| 290 | + // emoji sits on the barrier between splits; if we were to split the emoji we'd end up with |
| 291 | + // invalid bytes in our output. |
| 292 | + var renderCharCount = char.IsHighSurrogate(lastCharBeforeSplit) ? maxStringLiteralLength - 1 : maxStringLiteralLength; |
285 | 293 |
|
286 | | - if (char.IsHighSurrogate(lastCharBeforeSplit)) |
287 | | - { |
288 | | - if (charactersRemaining > 1) |
289 | | - { |
290 | | - // Take one less character this iteration. We're attempting to split inbetween a surrogate pair. |
291 | | - // This can happen when something like an emoji sits on the barrier between splits; if we were to |
292 | | - // split the emoji we'd end up with invalid bytes in our output. |
293 | | - charactersToSubstring--; |
294 | | - } |
295 | | - else |
296 | | - { |
297 | | - // The user has an invalid file with a partial surrogate a the splitting point. |
298 | | - // We'll let the invalid character flow but we'll explode later on. |
299 | | - } |
300 | | - } |
| 294 | + WriteLiteral(literal[..renderCharCount]); |
301 | 295 |
|
302 | | - var textToRender = literal.Substring(charactersConsumed, charactersToSubstring); |
303 | | - |
304 | | - WriteLiteral(textToRender); |
| 296 | + literal = literal[renderCharCount..]; |
| 297 | + } |
305 | 298 |
|
306 | | - charactersConsumed += textToRender.Length; |
307 | | - } while (charactersConsumed < literal.Length); |
| 299 | + WriteLiteral(literal); |
| 300 | + return; |
308 | 301 |
|
309 | | - void WriteLiteral(string content) |
| 302 | + void WriteLiteral(ReadOnlyMemory<char> content) |
310 | 303 | { |
311 | 304 | context.CodeWriter |
312 | 305 | .WriteStartMethodInvocation(WriteHtmlContentMethod) |
|
0 commit comments