-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathHtml.cs
321 lines (291 loc) · 11.7 KB
/
Html.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Text.Encodings.Web;
using System.Threading;
using System.Threading.Tasks;
using Eighty.Twenty;
namespace Eighty
{
/// <summary>
/// Represents HTML that can be written to a stream
/// </summary>
public abstract partial class Html
{
/// <summary>
/// Can this <see cref="Html"/> write itself asynchronously?
///
/// Returns false if the HTML contains any calls to <see cref="Builder(Func{HtmlBuilder})"/>.
///
/// If this returns false, <see cref="WriteAsync(TextWriter)"/> will throw <see cref="InvalidOperationException"/>
/// </summary>
/// <returns>A <see cref="System.Boolean"/> indicating whether this <see cref="Html"/> can write itself asynchronously</returns>
public bool CanWriteAsync { get; }
private protected Html(bool canWriteAsync)
{
CanWriteAsync = canWriteAsync;
}
internal abstract void WriteImpl(ref HtmlEncodingTextWriter writer);
internal abstract Task WriteAsyncImpl(AsyncHtmlEncodingTextWriter writer);
/// <summary>
/// Write the HTML to a <see cref="TextWriter"/>
/// </summary>
/// <param name="writer">The writer</param>
public void Write(TextWriter writer)
{
Write(writer, HtmlEncoder.Default);
}
/// <summary>
/// Write the HTML to a <see cref="TextWriter"/>, using an <see cref="HtmlEncoder"/> to encode input text
/// </summary>
/// <param name="writer">The writer</param>
/// <param name="htmlEncoder">The HTML encoder</param>
public void Write(TextWriter writer, HtmlEncoder htmlEncoder)
{
if (writer == null)
{
throw new ArgumentNullException(nameof(writer));
}
if (htmlEncoder == null)
{
throw new ArgumentNullException(nameof(htmlEncoder));
}
var htmlEncodingTextWriter = new HtmlEncodingTextWriter(writer, htmlEncoder);
WriteImpl(ref htmlEncodingTextWriter);
htmlEncodingTextWriter.FlushAndClear();
}
/// <summary>
/// Write the HTML to a <see cref="TextWriter"/>
/// </summary>
/// <param name="writer">The writer</param>
public Task WriteAsync(TextWriter writer)
=> WriteAsync(writer, HtmlEncoder.Default);
/// <summary>
/// Write the HTML to a <see cref="TextWriter"/>, using an <see cref="HtmlEncoder"/> to encode input text
/// </summary>
/// <param name="writer">The writer</param>
/// <param name="htmlEncoder">The HTML encoder</param>
/// <exception cref="InvalidOperationException">Thrown if <see cref="CanWriteAsync"/> is false (ie if this <see cref="Html"/> contains an <see cref="HtmlBuilder"/>.</exception>
public async Task WriteAsync(TextWriter writer, HtmlEncoder htmlEncoder)
{
if (writer == null)
{
throw new ArgumentNullException(nameof(writer));
}
if (htmlEncoder == null)
{
throw new ArgumentNullException(nameof(htmlEncoder));
}
if (!CanWriteAsync)
{
throw new InvalidOperationException("Can't write this HTML asynchronously because it contains an HtmlBuilder");
}
var htmlEncodingTextWriter = new AsyncHtmlEncodingTextWriter(writer, htmlEncoder);
await WriteAsyncImpl(htmlEncodingTextWriter).ConfigureAwait(false);
await htmlEncodingTextWriter.FlushAndClear().ConfigureAwait(false);
}
/// <summary>
/// Write the HTML to a <see cref="string"/>
/// </summary>
public override string ToString()
{
using (var writer = new StringWriter())
{
Write(writer);
return writer.ToString();
}
}
/// <summary>
/// Create a tag which takes children.
/// </summary>
public static TagBuilder Tag(string name, params Attr[] attrs)
=> Tag(name, attrs.AsEnumerable());
/// <summary>
/// Create a tag which takes children.
/// </summary>
public static TagBuilder Tag(string name, IEnumerable<Attr> attrs)
{
if (attrs == null)
{
throw new ArgumentNullException(nameof(attrs));
}
return new TagBuilder(name, attrs.ToImmutableArray(), true);
}
/// <summary>
/// Create a tag without any attributes.
/// </summary>
public static Html Tag_(string name, params Html[] children)
=> Tag_(name, children.AsEnumerable());
/// <summary>
/// Create a tag without any attributes.
/// </summary>
public static Html Tag_(string name, List<Html> children)
=> Tag_(name, children.AsEnumerable());
/// <summary>
/// Create a tag without any attributes.
/// </summary>
public static Html Tag_(string name, ImmutableList<Html> children)
=> Tag_(name, children.AsEnumerable());
/// <summary>
/// Create a tag without any attributes.
/// </summary>
public static Html Tag_(string name, IEnumerable<Html> children)
{
if (children == null)
{
throw new ArgumentNullException(nameof(children));
}
return Tag_(name, children.ToImmutableArray());
}
/// <summary>
/// Create a tag without any attributes.
/// </summary>
public static Html Tag_(string name, ImmutableArray<Html> children)
{
foreach (var child in children)
{
if (child == null)
{
throw new ArgumentNullException(nameof(children));
}
}
return new Tag(name, ImmutableArray.Create<Attr>(), children, true);
}
/// <summary>
/// Create a tag which does not take children.
/// </summary>
public static Html SelfClosingTag(string name, params Attr[] attrs)
=> SelfClosingTag(name, attrs.AsEnumerable());
/// <summary>
/// Create a tag which does not take children.
/// </summary>
public static Html SelfClosingTag(string name, IEnumerable<Attr> attrs)
{
if (attrs == null)
{
throw new ArgumentNullException(nameof(attrs));
}
return new SelfClosingTag(name, attrs.ToImmutableArray(), true);
}
/// <summary>
/// Render HTML-encoded text.
/// </summary>
/// <param name="text">The text to HTML-encode</param>
/// <returns>An instance of <see cref="Html"/>.</returns>
public static Html Text(string text)
{
if (text == null)
{
throw new ArgumentNullException(nameof(text));
}
return new Text(text);
}
/// <summary>
/// Render a string without HTML-encoding it first.
/// </summary>
/// <param name="rawHtml">The pre-encoded string</param>
/// <returns>An instance of <see cref="Html"/>.</returns>
public static Html Raw(string rawHtml)
{
if (rawHtml == null)
{
throw new ArgumentNullException(nameof(rawHtml));
}
return new Raw(rawHtml);
}
/// <summary>
/// Run the <see cref="HtmlBuilder"/> returned by <paramref name="builderFactory"/>.
///
/// The <see cref="Html"/> returned from this method cannot write itself asynchronously;
/// its <see cref="CanWriteAsync"/> will return false and its <see cref="WriteAsync(TextWriter)"/> method will throw <see cref="InvalidOperationException"/>.
///
/// <paramref name="builderFactory"/> should generally return a newly created <see cref="HtmlBuilder"/>,
/// not a cached instance. Returning a cached <see cref="HtmlBuilder"/> is risky if it's possible that this
/// <see cref="Html"/>'s <see cref="Write(TextWriter)"/> method will be called concurrently by multiple threads.
/// </summary>
/// <param name="builderFactory">A function to create an <see cref="HtmlBuilder"/></param>
/// <returns>An instance of <see cref="Html"/>.</returns>
public static Html Builder(Func<HtmlBuilder> builderFactory)
{
if (builderFactory == null)
{
throw new ArgumentNullException(nameof(builderFactory));
}
return new Builder(builderFactory);
}
/// <summary>
/// Put some siblings next to each other.
/// </summary>
public static Html _(params Html[] siblings)
=> _(siblings.AsEnumerable());
/// <summary>
/// Put some siblings next to each other.
/// </summary>
public static Html _(List<Html> siblings)
=> _(siblings.AsEnumerable());
/// <summary>
/// Put some siblings next to each other.
/// </summary>
public static Html _(ImmutableList<Html> siblings)
=> _(siblings.AsEnumerable());
/// <summary>
/// Put some siblings next to each other.
/// </summary>
public static Html _(IEnumerable<Html> siblings)
{
if (siblings == null)
{
throw new ArgumentNullException(nameof(siblings));
}
return _(siblings.ToImmutableArray());
}
/// <summary>
/// Put some siblings next to each other.
/// </summary>
public static Html _(ImmutableArray<Html> siblings)
{
foreach (var sibling in siblings)
{
if (sibling == null)
{
throw new ArgumentNullException(nameof(siblings));
}
}
return new Sequence(siblings);
}
/// <summary>
/// Render HTML-encoded text.
/// </summary>
/// <param name="text">The text to HTML-encode</param>
/// <returns>An instance of <see cref="Html"/>.</returns>
public static implicit operator Html(string text)
=> Text(text);
/// <summary>
/// Put some siblings next to each other.
/// </summary>
public static implicit operator Html(Html[] siblings)
=> _(siblings);
/// <summary>
/// Put some siblings next to each other.
/// </summary>
public static implicit operator Html(List<Html> siblings)
=> _(siblings);
/// <summary>
/// Put some siblings next to each other.
/// </summary>
public static implicit operator Html(ImmutableList<Html> siblings)
=> _(siblings);
/// <summary>
/// Put some siblings next to each other.
/// </summary>
public static implicit operator Html(ImmutableArray<Html> siblings)
=> _(siblings);
/// <summary>
/// Create a tag with no children.
/// </summary>
public static implicit operator Html(TagBuilder tagBuilder)
=> tagBuilder._();
}
}