3
3
4
4
using System ;
5
5
using System . Diagnostics ;
6
+ using System . Globalization ;
6
7
using System . IO ;
8
+ using System . Text ;
7
9
using Microsoft . Framework . WebEncoders ;
8
10
9
11
namespace Microsoft . AspNet . Html . Abstractions
@@ -13,6 +15,82 @@ namespace Microsoft.AspNet.Html.Abstractions
13
15
/// </summary>
14
16
public static class HtmlContentBuilderExtensions
15
17
{
18
+ /// <summary>
19
+ /// Appends the specified <paramref name="format"/> to the existing content after replacing each format
20
+ /// item with the HTML encoded <see cref="string"/> representation of the corresponding item in the
21
+ /// <paramref name="args"/> array.
22
+ /// </summary>
23
+ /// <param name="format">
24
+ /// The composite format <see cref="string"/> (see http://msdn.microsoft.com/en-us/library/txafckwd.aspx).
25
+ /// The format string is assumed to be HTML encoded as-provided, and no further encoding will be performed.
26
+ /// </param>
27
+ /// <param name="args">
28
+ /// The object array to format. Each element in the array will be formatted and then HTML encoded.
29
+ /// </param>
30
+ /// <returns>A reference to this instance after the append operation has completed.</returns>
31
+ public static IHtmlContentBuilder AppendFormat (
32
+ this IHtmlContentBuilder builder ,
33
+ string format ,
34
+ params object [ ] args )
35
+ {
36
+ if ( builder == null )
37
+ {
38
+ throw new ArgumentNullException ( nameof ( builder ) ) ;
39
+ }
40
+
41
+ if ( format == null )
42
+ {
43
+ throw new ArgumentNullException ( nameof ( format ) ) ;
44
+ }
45
+
46
+ if ( args == null )
47
+ {
48
+ throw new ArgumentNullException ( nameof ( args ) ) ;
49
+ }
50
+
51
+ builder . Append ( new HtmlFormatString ( format , args ) ) ;
52
+ return builder ;
53
+ }
54
+
55
+ /// <summary>
56
+ /// Appends the specified <paramref name="format"/> to the existing content with information from the
57
+ /// <paramref name="provider"/> after replacing each format item with the HTML encoded <see cref="string"/>
58
+ /// representation of the corresponding item in the <paramref name="args"/> array.
59
+ /// </summary>
60
+ /// <param name="formatProvider">An object that supplies culture-specific formatting information.</param>
61
+ /// <param name="format">
62
+ /// The composite format <see cref="string"/> (see http://msdn.microsoft.com/en-us/library/txafckwd.aspx).
63
+ /// The format string is assumed to be HTML encoded as-provided, and no further encoding will be performed.
64
+ /// </param>
65
+ /// <param name="args">
66
+ /// The object array to format. Each element in the array will be formatted and then HTML encoded.
67
+ /// </param>
68
+ /// <returns>A reference to this instance after the append operation has completed.</returns>
69
+ public static IHtmlContentBuilder AppendFormat (
70
+ this IHtmlContentBuilder builder ,
71
+ IFormatProvider formatProvider ,
72
+ string format ,
73
+ params object [ ] args )
74
+ {
75
+ if ( builder == null )
76
+ {
77
+ throw new ArgumentNullException ( nameof ( builder ) ) ;
78
+ }
79
+
80
+ if ( format == null )
81
+ {
82
+ throw new ArgumentNullException ( nameof ( format ) ) ;
83
+ }
84
+
85
+ if ( args == null )
86
+ {
87
+ throw new ArgumentNullException ( nameof ( args ) ) ;
88
+ }
89
+
90
+ builder . Append ( new HtmlFormatString ( formatProvider , format , args ) ) ;
91
+ return builder ;
92
+ }
93
+
16
94
/// <summary>
17
95
/// Appends an <see cref="Environment.NewLine"/>.
18
96
/// </summary>
@@ -132,5 +210,141 @@ private string DebuggerToString()
132
210
}
133
211
}
134
212
}
213
+
214
+ [ DebuggerDisplay ( "{DebuggerToString()}" ) ]
215
+ private class HtmlFormatString : IHtmlContent
216
+ {
217
+ private readonly IFormatProvider _formatProvider ;
218
+ private readonly string _format ;
219
+ private readonly object [ ] _args ;
220
+
221
+ public HtmlFormatString ( string format , object [ ] args )
222
+ : this ( null , format , args )
223
+ {
224
+ }
225
+
226
+ public HtmlFormatString ( IFormatProvider formatProvider , string format , object [ ] args )
227
+ {
228
+ Debug . Assert ( format != null ) ;
229
+ Debug . Assert ( args != null ) ;
230
+
231
+ _formatProvider = formatProvider ?? CultureInfo . CurrentCulture ;
232
+ _format = format ;
233
+ _args = args ;
234
+ }
235
+
236
+ public void WriteTo ( TextWriter writer , IHtmlEncoder encoder )
237
+ {
238
+ if ( writer == null )
239
+ {
240
+ throw new ArgumentNullException ( nameof ( writer ) ) ;
241
+ }
242
+
243
+ if ( encoder == null )
244
+ {
245
+ throw new ArgumentNullException ( nameof ( encoder ) ) ;
246
+ }
247
+
248
+ var formatProvider = new EncodingFormatProvider ( _formatProvider , encoder ) ;
249
+ writer . Write ( string . Format ( formatProvider , _format , _args ) ) ;
250
+ }
251
+
252
+ private string DebuggerToString ( )
253
+ {
254
+ using ( var writer = new StringWriter ( ) )
255
+ {
256
+ WriteTo ( writer , HtmlEncoder . Default ) ;
257
+ return writer . ToString ( ) ;
258
+ }
259
+ }
260
+ }
261
+
262
+ // This class implements Html encoding via an ICustomFormatter. Passing an instance of this
263
+ // class into a string.Format method or anything similar will evaluate arguments implementing
264
+ // IHtmlContent without HTML encoding them, and will give other arguments the standard
265
+ // composite format string treatment, and then HTML encode the result.
266
+ //
267
+ // Plenty of examples of ICustomFormatter and the interactions with string.Format here:
268
+ // https://msdn.microsoft.com/en-us/library/system.string.format(v=vs.110).aspx#Format6_Example
269
+ private class EncodingFormatProvider : IFormatProvider , ICustomFormatter
270
+ {
271
+ private readonly IHtmlEncoder _encoder ;
272
+ private readonly IFormatProvider _formatProvider ;
273
+
274
+ public EncodingFormatProvider ( IFormatProvider formatProvider , IHtmlEncoder encoder )
275
+ {
276
+ Debug . Assert ( formatProvider != null ) ;
277
+ Debug . Assert ( encoder != null ) ;
278
+
279
+ _formatProvider = formatProvider ;
280
+ _encoder = encoder ;
281
+ }
282
+
283
+ public string Format ( string format , object arg , IFormatProvider formatProvider )
284
+ {
285
+ // This is the case we need to special case. We trust the IHtmlContent instance to do the
286
+ // right thing with encoding.
287
+ var htmlContent = arg as IHtmlContent ;
288
+ if ( htmlContent != null )
289
+ {
290
+ using ( var writer = new StringWriter ( ) )
291
+ {
292
+ htmlContent . WriteTo ( writer , _encoder ) ;
293
+ return writer . ToString ( ) ;
294
+ }
295
+ }
296
+
297
+ // If we get here then 'arg' is not an IHtmlContent, and we want to handle it the way a normal
298
+ // string.Format would work, but then HTML encode the result.
299
+ //
300
+ // First check for an ICustomFormatter - if the IFormatProvider is a CultureInfo, then it's likely
301
+ // that ICustomFormatter will be null.
302
+ var customFormatter = ( ICustomFormatter ) _formatProvider . GetFormat ( typeof ( ICustomFormatter ) ) ;
303
+ if ( customFormatter != null )
304
+ {
305
+ var result = customFormatter . Format ( format , arg , _formatProvider ) ;
306
+ if ( result != null )
307
+ {
308
+ return _encoder . HtmlEncode ( result ) ;
309
+ }
310
+ }
311
+
312
+ // Next check if 'arg' is an IFormattable (DateTime is an example).
313
+ //
314
+ // An IFormattable will likely call back into the IFormatterProvider and ask for more information
315
+ // about how to format itself. This is the typical case when IFormatterProvider is a CultureInfo.
316
+ var formattable = arg as IFormattable ;
317
+ if ( formattable != null )
318
+ {
319
+ var result = formattable . ToString ( format , _formatProvider ) ;
320
+ if ( result != null )
321
+ {
322
+ return _encoder . HtmlEncode ( result ) ;
323
+ }
324
+ }
325
+
326
+ // If we get here then there's nothing really smart left to try.
327
+ if ( arg != null )
328
+ {
329
+ var result = arg . ToString ( ) ;
330
+ if ( result != null )
331
+ {
332
+ return _encoder . HtmlEncode ( result ) ;
333
+ }
334
+ }
335
+
336
+ return string . Empty ;
337
+ }
338
+
339
+ public object GetFormat ( Type formatType )
340
+ {
341
+ if ( formatType == typeof ( ICustomFormatter ) )
342
+ {
343
+ return this ;
344
+ }
345
+
346
+ return null ;
347
+ }
348
+ }
135
349
}
136
350
}
0 commit comments