如何执行缓冲的查找和替换?

2 投票
2 回答
1238 浏览
提问于 2025-04-17 03:56

我有一些XML文件,其中包含一些无效的字符序列,这些字符导致解析失败。它们的样子像这样:。为了解决这个问题,我把这些字符替换成一个转义序列: --> !#~10^。然后在我完成解析后,可以把它们恢复成原来的样子。

buffersize = 2**16   # 64 KB buffer

def escape(filename):
    out = file(filename + '_esc', 'w') 

    with open(filename, 'r') as f:
        buffer = 'x'     # is there a prettier way to handle the first one?
        while buffer != '':
            buffer = f.read(buffersize)
            out.write(re.sub(r'&#x([a-fA-F0-9]+);', r'!#~\1^', buffer))

    out.close()

这些文件非常大,所以我需要使用缓冲区(mmap让我遇到了MemoryError)。因为缓冲区的大小是固定的,所以当缓冲区恰好小到可以把一个字符序列分开时,我就会遇到问题。想象一下,缓冲区的大小是8,而文件内容是这样的:

 123456789
 hello!&x10;

缓冲区只会读取到hello!&x,这样就会让&x10;漏掉。那我该怎么解决这个问题呢?我想过如果最后几个字符看起来可能属于一个字符序列,就多读取一些字符,但我想到的逻辑实在是太复杂了。

2 个回答

1

如果你的序列长度是6个字符,你可以使用5个重叠字符的缓冲区。这样一来,你就能确保没有任何序列会在缓冲区之间漏掉。

这里有个例子可以帮助你理解:

--&#x10
  --

--
   #x10;--

至于具体的实现方法,只需要把上一个缓冲区的最后5个字符放到新的缓冲区前面:

buffer = buffer[-5:] + f.read(buffersize)

唯一的问题是,这样连接可能需要复制整个缓冲区。另一种解决方案是,如果你可以随机访问文件,可以稍微倒回去:

f.seek(-5, os.SEEK_CUR)

在这两种情况下,你都需要稍微修改脚本,以处理第一次迭代。

3

首先,不用费心去读写文件,你可以创建一个类似文件的对象,这个对象可以包裹你打开的文件,并在数据被解析器处理之前先对数据进行处理。其次,你的缓冲区可以只处理读取字节的开头和结尾部分。这里有一段可以用的代码:

class Wrapped(object):
    def __init__(self, f):
        self.f = f
        self.buffer = ""

    def read(self, size=0):
        buf = self.buffer + self.f.read(size)
        buf = buf.replace("!", "!!")
        buf = re.sub(r"&(#x[0-9a-fA-F]+;)", r"!\1", buf)
        # If there's an ampersand near the end, hold onto that piece until we
        # have more, to be sure we don't miss one.
        last_amp = buf.rfind("&", -10, -1)
        if last_amp > 0:
            self.buffer = buf[last_amp:]
            buf = buf[:last_amp]
        else:
            self.buffer = ""
        return buf

然后在你的代码中,把这个:

it = ET.iterparse(file(xml, "rb"))

替换成这个:

it = ET.iterparse(Wrapped(file(xml, "rb")))

第三,我用了一个替换方法,把"&"替换成"!",再把"!"替换成"!!",这样你可以在解析后再修复它们,而且不需要依赖一些不常见的序列。毕竟这是Stack Overflow的数据,所以可能会自然出现很多奇怪的随机标点符号。

撰写回答