通过大量帮助,我得出了以下代码:
if (ofd.ShowDialog() == DialogResult.OK)
{
using (StreamReader reader = new StreamReader(ofd.FileName, Encoding.GetEncoding("ISO-8859-1")))
{
XmlReaderSettings settings = new XmlReaderSettings();
settings.ConformanceLevel = ConformanceLevel.Fragment;
using (XmlReader xreader = XmlReader.Create(reader, settings))
{
while (xreader.Read())
{
DoSomething();
}
}
}
我只剩下一个问题,那就是'xml文档'中某些地方有一个&符号:
<name>Me & You</name>
我知道它不是一个有效的XML片段,并且&符号应该转义&符号。但是,在我的方案中,更改文档不是一个选项。如何在不更改文档的情况下以XML格式阅读此内容?有可能改变StreamReader吗?
答案 0 :(得分:1)
继承TextReader
并覆盖ReadLine
方法,以在必要时替换每行的字符。将您的新流阅读器传递给XmlReader
,这将永远不会看到无效的实体。
修改:虽然XmlReader
支持TextReader
,但它似乎不会调用ReadLine
方法...相反,我们可能需要直接将Stream
子类化并使用byte[] buffer
。
编辑:以下是我认为应该过滤通用流并用编码实体替换&符号。由于它使用流字节,因此它没有任何编码概念,因此采用ASCII。它目前只处理符号,但编码其他实体也应该是微不足道的。它在2.5 Gb文件上尝试了它,发现它只增加了大约3%的处理时间,并且内存负载似乎没有受到影响。话虽如此,它肯定没有经过充分测试。我建议您确保它生成的文件与缓冲区大小不同的输入文件相同。 E.g:
using (var output = File.Open(@"out.xml", FileMode.Create))
using (var input = new AmpersandFilterText(File.Open(@"in.xml", FileMode.Open))
input.CopyTo(output);
然后对文件进行二进制比较。
编辑#3:我误读了TextReader
上的方法签名,XmlReader
只是调用Read(char[], int, int)
。无论如何,请参阅下面的实现,它不依赖于字符编码。
class TestProgram
{
static void Main(string[] args)
{
var stringReader = new MemoryStream(Encoding.ASCII.GetBytes("<name>Me & You</name>"));
var textReader = new AmpersandFilterText(stringReader);
XmlReaderSettings settings = new XmlReaderSettings();
settings.ConformanceLevel = ConformanceLevel.Fragment;
using (XmlReader xreader = XmlReader.Create(textReader, settings))
{
while (xreader.Read())
{
Console.WriteLine(xreader.Value);
}
}
Console.ReadLine();
}
}
class AmpersandFilterText : StreamReader
{
public AmpersandFilterText(Stream stream)
: base(stream)
{
}
private StringBuilder sbBuffer = null;
private int bufferOffset = 0;
public override string ReadLine()
{
var rawLine = base.ReadLine();
rawLine = rawLine.Replace("&", "&");
return rawLine;
}
public override int Read()
{
char cChar;
if (sbBuffer != null)
{
cChar = sbBuffer[bufferOffset];
bufferOffset++;
evalBuffer();
return cChar;
}
else
{
cChar = (char)base.Read();
if (cChar == '&')
sbBuffer = new StringBuilder("amp;");
return cChar;
}
}
public override int Read(char[] buffer, int index, int count)
{
int destOffset = 0;
const string replacement = "&";
//exhaust our buffer first
if (sbBuffer != null)
{
int bufferedToConsume = Math.Min(count, sbBuffer.Length - bufferOffset);
sbBuffer.CopyTo(bufferOffset, buffer, index, bufferedToConsume);
destOffset += bufferedToConsume;
bufferOffset += bufferedToConsume;
evalBuffer();
}
//then, if needed, read more data
if (destOffset < count)
{
char[] newBuffer = new char[count - destOffset];
var newRead = base.Read(newBuffer, 0, newBuffer.Length);
//process new data and return directly
int sourceOffset = 0;
while (sourceOffset < newRead && destOffset < count)
{
char tChar = newBuffer[sourceOffset];
if (tChar == '&')
{
int replacementOffset = 0;
while (replacementOffset < replacement.Length && destOffset < count)
{
buffer[destOffset + index] = replacement[replacementOffset];
destOffset++;
replacementOffset++;
}
sourceOffset++;
//we did not copy the entire replacement
if (replacementOffset < replacement.Length)
{
//put the remainder in next time
sbBuffer = new StringBuilder();
sbBuffer.Append(replacement, replacementOffset, replacement.Length - replacementOffset);
}
}
else
{
buffer[destOffset + index] = tChar;
destOffset++;
sourceOffset++;
}
}
//we have unused data
if (sourceOffset < newRead)
{
if (sbBuffer == null)
sbBuffer = new StringBuilder();
//add data after replace
for (; sourceOffset < newRead; sourceOffset++)
{
char tChar = newBuffer[sourceOffset];
if (tChar == '&')
sbBuffer.Append(replacement);
else
sbBuffer.Append(tChar);
}
}
}
return destOffset;
}
public override int ReadBlock(char[] buffer, int index, int count)
{
return this.Read(buffer, index, count);
}
public override int Peek()
{
if (sbBuffer != null)
return sbBuffer[bufferOffset];
return base.Peek();
}
public override string ReadToEnd()
{
string cLine;
StringBuilder sbTemp = new StringBuilder();
while ((cLine = this.ReadLine()) != null)
sbTemp.AppendLine(cLine);
return sbTemp.ToString();
}
private void evalBuffer()
{
if (bufferOffset == sbBuffer.Length)
{
//TODO: check perf on sbBuffer.Clear
sbBuffer = null;
bufferOffset = 0;
}
}
}