|
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