-
Notifications
You must be signed in to change notification settings - Fork 5.5k
Expand file tree
/
Copy pathXmlWriterAsync.cs
More file actions
596 lines (512 loc) · 24.8 KB
/
XmlWriterAsync.cs
File metadata and controls
596 lines (512 loc) · 24.8 KB
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
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Threading.Tasks;
using System.Xml.XPath;
using System.Xml.Schema;
using System.Diagnostics;
namespace System.Xml
{
// Represents a writer that provides fast non-cached forward-only way of generating XML streams containing XML documents
// that conform to the W3C Extensible Markup Language (XML) 1.0 specification and the Namespaces in XML specification.
public abstract partial class XmlWriter : IDisposable, IAsyncDisposable
{
// Write methods
// Writes out the XML declaration with the version "1.0".
public virtual Task WriteStartDocumentAsync()
{
throw new NotImplementedException();
}
//Writes out the XML declaration with the version "1.0" and the specified standalone attribute.
public virtual Task WriteStartDocumentAsync(bool standalone)
{
throw new NotImplementedException();
}
//Closes any open elements or attributes and puts the writer back in the Start state.
public virtual Task WriteEndDocumentAsync()
{
throw new NotImplementedException();
}
// Writes out the DOCTYPE declaration with the specified name and optional attributes.
public virtual Task WriteDocTypeAsync(string name, string? pubid, string? sysid, string? subset)
{
throw new NotImplementedException();
}
// Writes out the specified start tag and associates it with the given namespace and prefix.
public virtual Task WriteStartElementAsync(string? prefix, string localName, string? ns)
{
throw new NotImplementedException();
}
// Closes one element and pops the corresponding namespace scope.
public virtual Task WriteEndElementAsync()
{
throw new NotImplementedException();
}
// Closes one element and pops the corresponding namespace scope. Writes out a full end element tag, e.g. </element>.
public virtual Task WriteFullEndElementAsync()
{
throw new NotImplementedException();
}
// Writes out the attribute with the specified prefix, LocalName, NamespaceURI and value.
public Task WriteAttributeStringAsync(string? prefix, string localName, string? ns, string? value)
{
Task task = WriteStartAttributeAsync(prefix, localName, ns);
if (task.IsSuccess())
{
return WriteStringAsync(value).CallTaskFuncWhenFinishAsync(thisRef => thisRef.WriteEndAttributeAsync(), this);
}
return WriteAttributeStringAsyncHelper(task, value);
}
private async Task WriteAttributeStringAsyncHelper(Task task, string? value)
{
await task.ConfigureAwait(false);
await WriteStringAsync(value).ConfigureAwait(false);
await WriteEndAttributeAsync().ConfigureAwait(false);
}
// Writes the start of an attribute.
protected internal virtual Task WriteStartAttributeAsync(string? prefix, string localName, string? ns)
{
throw new NotImplementedException();
}
// Closes the attribute opened by WriteStartAttribute call.
protected internal virtual Task WriteEndAttributeAsync()
{
throw new NotImplementedException();
}
// Writes out a <![CDATA[...]]>; block containing the specified text.
public virtual Task WriteCDataAsync(string? text)
{
throw new NotImplementedException();
}
// Writes out a comment <!--...-->; containing the specified text.
public virtual Task WriteCommentAsync(string? text)
{
throw new NotImplementedException();
}
// Writes out a processing instruction with a space between the name and text as follows: <?name text?>
public virtual Task WriteProcessingInstructionAsync(string name, string? text)
{
throw new NotImplementedException();
}
// Writes out an entity reference as follows: "&"+name+";".
public virtual Task WriteEntityRefAsync(string name)
{
throw new NotImplementedException();
}
// Forces the generation of a character entity for the specified Unicode character value.
public virtual Task WriteCharEntityAsync(char ch)
{
throw new NotImplementedException();
}
// Writes out the given whitespace.
public virtual Task WriteWhitespaceAsync(string? ws)
{
throw new NotImplementedException();
}
// Writes out the specified text content.
public virtual Task WriteStringAsync(string? text)
{
throw new NotImplementedException();
}
// Write out the given surrogate pair as an entity reference.
public virtual Task WriteSurrogateCharEntityAsync(char lowChar, char highChar)
{
throw new NotImplementedException();
}
// Writes out the specified text content.
public virtual Task WriteCharsAsync(char[] buffer, int index, int count)
{
throw new NotImplementedException();
}
// Writes raw markup from the given character buffer.
public virtual Task WriteRawAsync(char[] buffer, int index, int count)
{
throw new NotImplementedException();
}
// Writes raw markup from the given string.
public virtual Task WriteRawAsync(string data)
{
throw new NotImplementedException();
}
// Encodes the specified binary bytes as base64 and writes out the resulting text.
public virtual Task WriteBase64Async(byte[] buffer, int index, int count)
{
throw new NotImplementedException();
}
// Encodes the specified binary bytes as bin hex and writes out the resulting text.
public virtual Task WriteBinHexAsync(byte[] buffer, int index, int count)
{
return BinHexEncoder.EncodeAsync(buffer, index, count, this);
}
// Flushes data that is in the internal buffers into the underlying streams/TextReader and flushes the stream/TextReader.
public virtual Task FlushAsync()
{
throw new NotImplementedException();
}
// Scalar Value Methods
// Writes out the specified name, ensuring it is a valid NmToken according to the XML specification
// (http://www.w3.org/TR/1998/REC-xml-19980210#NT-Name).
public virtual Task WriteNmTokenAsync(string name)
{
if (string.IsNullOrEmpty(name))
{
throw new ArgumentException(SR.Xml_EmptyName);
}
return WriteStringAsync(XmlConvert.VerifyNMTOKEN(name, ExceptionType.ArgumentException));
}
// Writes out the specified name, ensuring it is a valid Name according to the XML specification
// (http://www.w3.org/TR/1998/REC-xml-19980210#NT-Name).
public virtual Task WriteNameAsync(string name)
{
return WriteStringAsync(XmlConvert.VerifyQName(name, ExceptionType.ArgumentException));
}
// Writes out the specified namespace-qualified name by looking up the prefix that is in scope for the given namespace.
public virtual async Task WriteQualifiedNameAsync(string localName, string? ns)
{
if (!string.IsNullOrEmpty(ns))
{
string? prefix = LookupPrefix(ns);
if (prefix == null)
{
throw new ArgumentException(SR.Format(SR.Xml_UndefNamespace, ns));
}
await WriteStringAsync(prefix).ConfigureAwait(false);
await WriteStringAsync(":").ConfigureAwait(false);
}
await WriteStringAsync(localName).ConfigureAwait(false);
}
// XmlReader Helper Methods
// Writes out all the attributes found at the current position in the specified XmlReader.
public virtual Task WriteAttributesAsync(XmlReader reader, bool defattr)
{
ArgumentNullException.ThrowIfNull(reader);
return Core(reader, defattr);
async Task Core(XmlReader reader, bool defattr)
{
if (reader.NodeType is XmlNodeType.Element or XmlNodeType.XmlDeclaration)
{
if (reader.MoveToFirstAttribute())
{
await WriteAttributesAsync(reader, defattr).ConfigureAwait(false);
reader.MoveToElement();
}
}
else if (reader.NodeType != XmlNodeType.Attribute)
{
throw new XmlException(SR.Xml_InvalidPosition, string.Empty);
}
else
{
do
{
// we need to check both XmlReader.IsDefault and XmlReader.SchemaInfo.IsDefault.
// If either of these is true and defattr=false, we should not write the attribute out
if (defattr || !reader.IsDefaultInternal)
{
await WriteStartAttributeAsync(reader.Prefix, reader.LocalName, reader.NamespaceURI).ConfigureAwait(false);
while (reader.ReadAttributeValue())
{
if (reader.NodeType == XmlNodeType.EntityReference)
{
await WriteEntityRefAsync(reader.Name).ConfigureAwait(false);
}
else
{
await WriteStringAsync(reader.Value).ConfigureAwait(false);
}
}
await WriteEndAttributeAsync().ConfigureAwait(false);
}
}
while (reader.MoveToNextAttribute());
}
}
}
// Copies the current node from the given reader to the writer (including child nodes), and if called on an element moves the XmlReader
// to the corresponding end element.
public virtual Task WriteNodeAsync(XmlReader reader, bool defattr)
{
ArgumentNullException.ThrowIfNull(reader);
if (reader.Settings is { Async: true })
{
return WriteNodeAsync_CallAsyncReader(reader, defattr);
}
return WriteNodeAsync_CallSyncReader(reader, defattr);
}
// Copies the current node from the given reader to the writer (including child nodes), and if called on an element moves the XmlReader
// to the corresponding end element.
//use sync methods on the reader
internal async Task WriteNodeAsync_CallSyncReader(XmlReader reader, bool defattr)
{
bool canReadChunk = reader.CanReadValueChunk;
int d = reader.NodeType == XmlNodeType.None ? -1 : reader.Depth;
do
{
switch (reader.NodeType)
{
case XmlNodeType.Element:
await WriteStartElementAsync(reader.Prefix, reader.LocalName, reader.NamespaceURI).ConfigureAwait(false);
await WriteAttributesAsync(reader, defattr).ConfigureAwait(false);
if (reader.IsEmptyElement)
{
await WriteEndElementAsync().ConfigureAwait(false);
}
break;
case XmlNodeType.Text:
if (canReadChunk)
{
_writeNodeBuffer ??= new char[WriteNodeBufferSize];
int read;
while ((read = reader.ReadValueChunk(_writeNodeBuffer, 0, WriteNodeBufferSize)) > 0)
{
await WriteCharsAsync(_writeNodeBuffer, 0, read).ConfigureAwait(false);
}
}
else
{
await WriteStringAsync(reader.Value).ConfigureAwait(false);
}
break;
case XmlNodeType.Whitespace:
case XmlNodeType.SignificantWhitespace:
await WriteWhitespaceAsync(reader.Value).ConfigureAwait(false);
break;
case XmlNodeType.CDATA:
await WriteCDataAsync(reader.Value).ConfigureAwait(false);
break;
case XmlNodeType.EntityReference:
await WriteEntityRefAsync(reader.Name).ConfigureAwait(false);
break;
case XmlNodeType.XmlDeclaration:
case XmlNodeType.ProcessingInstruction:
await WriteProcessingInstructionAsync(reader.Name, reader.Value).ConfigureAwait(false);
break;
case XmlNodeType.DocumentType:
await WriteDocTypeAsync(reader.Name, reader.GetAttribute("PUBLIC"), reader.GetAttribute("SYSTEM"), reader.Value).ConfigureAwait(false);
break;
case XmlNodeType.Comment:
await WriteCommentAsync(reader.Value).ConfigureAwait(false);
break;
case XmlNodeType.EndElement:
await WriteFullEndElementAsync().ConfigureAwait(false);
break;
}
} while (reader.Read() && (d < reader.Depth || (d == reader.Depth && reader.NodeType == XmlNodeType.EndElement)));
}
// Copies the current node from the given reader to the writer (including child nodes), and if called on an element moves the XmlReader
// to the corresponding end element.
//use async methods on the reader
internal async Task WriteNodeAsync_CallAsyncReader(XmlReader reader, bool defattr)
{
bool canReadChunk = reader.CanReadValueChunk;
int d = reader.NodeType == XmlNodeType.None ? -1 : reader.Depth;
do
{
switch (reader.NodeType)
{
case XmlNodeType.Element:
await WriteStartElementAsync(reader.Prefix, reader.LocalName, reader.NamespaceURI).ConfigureAwait(false);
await WriteAttributesAsync(reader, defattr).ConfigureAwait(false);
if (reader.IsEmptyElement)
{
await WriteEndElementAsync().ConfigureAwait(false);
}
break;
case XmlNodeType.Text:
if (canReadChunk)
{
_writeNodeBuffer ??= new char[WriteNodeBufferSize];
int read;
while ((read = await reader.ReadValueChunkAsync(_writeNodeBuffer, 0, WriteNodeBufferSize).ConfigureAwait(false)) > 0)
{
await WriteCharsAsync(_writeNodeBuffer, 0, read).ConfigureAwait(false);
}
}
else
{
//reader.Value may block on Text or WhiteSpace node, use GetValueAsync
await WriteStringAsync(await reader.GetValueAsync().ConfigureAwait(false)).ConfigureAwait(false);
}
break;
case XmlNodeType.Whitespace:
case XmlNodeType.SignificantWhitespace:
await WriteWhitespaceAsync(await reader.GetValueAsync().ConfigureAwait(false)).ConfigureAwait(false);
break;
case XmlNodeType.CDATA:
await WriteCDataAsync(reader.Value).ConfigureAwait(false);
break;
case XmlNodeType.EntityReference:
await WriteEntityRefAsync(reader.Name).ConfigureAwait(false);
break;
case XmlNodeType.XmlDeclaration:
case XmlNodeType.ProcessingInstruction:
await WriteProcessingInstructionAsync(reader.Name, reader.Value).ConfigureAwait(false);
break;
case XmlNodeType.DocumentType:
await WriteDocTypeAsync(reader.Name, reader.GetAttribute("PUBLIC"), reader.GetAttribute("SYSTEM"), reader.Value).ConfigureAwait(false);
break;
case XmlNodeType.Comment:
await WriteCommentAsync(reader.Value).ConfigureAwait(false);
break;
case XmlNodeType.EndElement:
await WriteFullEndElementAsync().ConfigureAwait(false);
break;
}
} while (await reader.ReadAsync().ConfigureAwait(false) && (d < reader.Depth || (d == reader.Depth && reader.NodeType == XmlNodeType.EndElement)));
}
// Copies the current node from the given XPathNavigator to the writer (including child nodes).
public virtual Task WriteNodeAsync(XPathNavigator navigator, bool defattr)
{
ArgumentNullException.ThrowIfNull(navigator);
return Core(navigator, defattr);
async Task Core(XPathNavigator navigator, bool defattr)
{
int iLevel = 0;
navigator = navigator.Clone();
while (true)
{
bool mayHaveChildren = false;
XPathNodeType nodeType = navigator.NodeType;
switch (nodeType)
{
case XPathNodeType.Element:
await WriteStartElementAsync(navigator.Prefix, navigator.LocalName, navigator.NamespaceURI).ConfigureAwait(false);
// Copy attributes
if (navigator.MoveToFirstAttribute())
{
do
{
IXmlSchemaInfo? schemaInfo = navigator.SchemaInfo;
if (defattr || (schemaInfo == null || !schemaInfo.IsDefault))
{
await WriteStartAttributeAsync(navigator.Prefix, navigator.LocalName, navigator.NamespaceURI).ConfigureAwait(false);
// copy string value to writer
await WriteStringAsync(navigator.Value).ConfigureAwait(false);
await WriteEndAttributeAsync().ConfigureAwait(false);
}
} while (navigator.MoveToNextAttribute());
navigator.MoveToParent();
}
// Copy namespaces
if (navigator.MoveToFirstNamespace(XPathNamespaceScope.Local))
{
await WriteLocalNamespacesAsync(navigator).ConfigureAwait(false);
navigator.MoveToParent();
}
mayHaveChildren = true;
break;
case XPathNodeType.Attribute:
// do nothing on root level attribute
break;
case XPathNodeType.Text:
await WriteStringAsync(navigator.Value).ConfigureAwait(false);
break;
case XPathNodeType.SignificantWhitespace:
case XPathNodeType.Whitespace:
await WriteWhitespaceAsync(navigator.Value).ConfigureAwait(false);
break;
case XPathNodeType.Root:
mayHaveChildren = true;
break;
case XPathNodeType.Comment:
await WriteCommentAsync(navigator.Value).ConfigureAwait(false);
break;
case XPathNodeType.ProcessingInstruction:
await WriteProcessingInstructionAsync(navigator.LocalName, navigator.Value).ConfigureAwait(false);
break;
case XPathNodeType.Namespace:
// do nothing on root level namespace
break;
default:
Debug.Fail($"Unexpected node type {nodeType}");
break;
}
if (mayHaveChildren)
{
// If children exist, move down to next level
if (navigator.MoveToFirstChild())
{
iLevel++;
continue;
}
// EndElement
if (navigator.NodeType == XPathNodeType.Element)
{
if (navigator.IsEmptyElement)
{
await WriteEndElementAsync().ConfigureAwait(false);
}
else
{
await WriteFullEndElementAsync().ConfigureAwait(false);
}
}
}
// No children
while (true)
{
if (iLevel == 0)
{
// The entire subtree has been copied
return;
}
if (navigator.MoveToNext())
{
// Found a sibling, so break to outer loop
break;
}
// No siblings, so move up to previous level
iLevel--;
navigator.MoveToParent();
// EndElement
if (navigator.NodeType == XPathNodeType.Element)
await WriteFullEndElementAsync().ConfigureAwait(false);
}
}
}
}
// Element Helper Methods
// Writes out an attribute with the specified name, namespace URI, and string value.
public async Task WriteElementStringAsync(string? prefix, string localName, string? ns, string value)
{
await WriteStartElementAsync(prefix, localName, ns).ConfigureAwait(false);
if (!string.IsNullOrEmpty(value))
{
await WriteStringAsync(value).ConfigureAwait(false);
}
await WriteEndElementAsync().ConfigureAwait(false);
}
// Copy local namespaces on the navigator's current node to the raw writer. The namespaces are returned by the navigator in reversed order.
// The recursive call reverses them back.
private async Task WriteLocalNamespacesAsync(XPathNavigator nsNav)
{
string prefix = nsNav.LocalName;
string ns = nsNav.Value;
if (nsNav.MoveToNextNamespace(XPathNamespaceScope.Local))
{
await WriteLocalNamespacesAsync(nsNav).ConfigureAwait(false);
}
if (prefix.Length == 0)
{
await WriteAttributeStringAsync(string.Empty, "xmlns", XmlReservedNs.NsXmlNs, ns).ConfigureAwait(false);
}
else
{
await WriteAttributeStringAsync("xmlns", prefix, XmlReservedNs.NsXmlNs, ns).ConfigureAwait(false);
}
}
public async ValueTask DisposeAsync()
{
await DisposeAsyncCore().ConfigureAwait(false);
Dispose(false);
GC.SuppressFinalize(this);
}
protected virtual ValueTask DisposeAsyncCore()
{
if (WriteState != WriteState.Closed)
{
Dispose(true);
}
return default;
}
}
}