-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathUsingStreamReader.cs
More file actions
133 lines (114 loc) · 4.24 KB
/
UsingStreamReader.cs
File metadata and controls
133 lines (114 loc) · 4.24 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
using System.Buffers;
namespace ReplaceTextInStream;
public class UsingStreamReader : IStreamingReplacer
{
private readonly int _bufferLength;
public UsingStreamReader(int bufferLength = 1024)
{
_bufferLength = bufferLength;
}
public async Task Replace(Stream input, Stream output, string oldValue, string newValue,
CancellationToken cancellationToken = default)
{
var delimiters = new[]
{
char.ToUpperInvariant(oldValue[0]),
char.ToLowerInvariant(oldValue[0])
};
var inputBuffer = ArrayPool<char>.Shared.Rent(Math.Max(_bufferLength, oldValue.Length * 2));
try
{
using var reader = new StreamReader(input, leaveOpen: true);
await using var writer = new StreamWriter(output, reader.CurrentEncoding, leaveOpen: true);
var startIndex = 0;
while (true)
{
var memory = inputBuffer.AsMemory(startIndex, inputBuffer.Length - startIndex);
var charactersRead = await reader.ReadBlockAsync(memory, cancellationToken);
var sequence = new ReadOnlySequence<char>(inputBuffer[..(charactersRead + startIndex)]);
while (sequence.Length >= oldValue.Length)
{
if (FindCandidate(out var before, sequence, oldValue, delimiters, out var position))
{
await writer.WriteAsync(before.ToArray(), cancellationToken);
await writer.WriteAsync(newValue);
sequence = sequence.Slice(position);
}
else
{
await writer.WriteAsync(before.ToArray(), cancellationToken);
sequence = sequence.Slice(position);
}
}
if (reader.EndOfStream)
{
await writer.WriteAsync(sequence.ToArray(), cancellationToken);
break;
}
if (cancellationToken.IsCancellationRequested)
{
break;
}
startIndex = (int) sequence.Length;
Array.Copy(inputBuffer, inputBuffer.Length - startIndex, inputBuffer, 0, sequence.Length);
}
}
finally
{
ArrayPool<char>.Shared.Return(inputBuffer, true);
}
}
private bool FindCandidate(out ReadOnlySequence<char> before,
ReadOnlySequence<char> haystack,
string oldValue,
char[] delimiters,
out SequencePosition position)
{
var reader = new SequenceReader<char>(haystack);
while (true)
{
if (reader.TryReadToAny(out ReadOnlySequence<char> _, delimiters, advancePastDelimiter: false))
{
if (reader.Remaining < oldValue.Length)
{
position = reader.Position;
before = haystack.Slice(0, reader.Position);
return false;
}
var positionOfDelimiter = reader.Position;
if (CompareSequence(ref reader, oldValue))
{
position = reader.Position;
before = haystack.Slice(0, positionOfDelimiter);
return true;
}
}
else
{
reader.AdvanceToEnd();
position = reader.Position;
before = haystack;
return false;
}
}
}
private bool CompareSequence(ref SequenceReader<char> reader, string oldValue)
{
//Already checked the first character
reader.Advance(1);
//Otherwise loop over the characters
for (var i = 1; i < oldValue.Length; i++)
{
if (reader.TryRead(out var candidate) == false
|| Compare(candidate, oldValue[i]) == false)
{
return false;
}
}
return true;
}
private bool Compare(char candidate, char original)
{
return char.ToUpperInvariant(original) == char.ToUpperInvariant(candidate);
}
}