(Nota: estoy usando Swift 3.0.1 en Xcode 8.2.1 con macOS Sierra 10.12.3)
Todas las respuestas que he visto aquí pasaron por alto que podría estar buscando LF o CRLF. Si todo va bien, él / ella podría simplemente coincidir en LF y verificar la cadena devuelta para ver si hay un CR adicional al final. Pero la consulta general implica varias cadenas de búsqueda. En otras palabras, el delimitador debe ser a Set<String>
, donde el conjunto no está vacío ni contiene la cadena vacía, en lugar de una sola cadena.
En mi primer intento en este último año, traté de hacer "lo correcto" y buscar un conjunto general de cadenas. Fue demasiado duro; necesita un analizador sintáctico completo y máquinas de estado y demás. Renuncié a él y al proyecto del que formaba parte.
Ahora estoy haciendo el proyecto de nuevo y me enfrento al mismo desafío nuevamente. Ahora voy a realizar búsquedas de código rígido en CR y LF. No creo que nadie necesite buscar dos caracteres semi-independientes y semi-dependientes como este fuera del análisis de CR / LF.
Estoy usando los métodos de búsqueda proporcionados por Data
, lo que no estoy haciendo codificaciones de cadenas y esas cosas aquí. Solo procesamiento binario sin procesar. Suponga que tengo un superconjunto ASCII, como ISO Latin-1 o UTF-8, aquí. Puede manejar la codificación de cadenas en la siguiente capa superior, y puede decidir si un CR / LF con puntos de código secundarios adjuntos todavía cuenta como CR o LF.
El algoritmo: siga buscando el siguiente CR y el siguiente LF de su desplazamiento de bytes actual.
- Si no se encuentra ninguno, considere que la siguiente cadena de datos es desde el desplazamiento actual hasta el final de los datos. Tenga en cuenta que la longitud del terminador es 0. Marque esto como el final de su ciclo de lectura.
- Si primero se encuentra un LF, o solo se encuentra un LF, considere que la siguiente cadena de datos es desde el desplazamiento actual al LF. Tenga en cuenta que la longitud del terminador es 1. Mueva el desplazamiento a después del LF.
- Si solo se encuentra un CR, haga como el caso LF (solo con un valor de byte diferente).
- De lo contrario, obtuvimos un CR seguido de un LF.
- Si los dos son adyacentes, maneje como el caso LF, excepto que la longitud del terminador será 2.
- Si hay un byte entre ellos, y dicho byte también es CR, entonces tenemos el problema "El desarrollador de Windows escribió un \ r \ n binario mientras estaba en modo texto, dando un \ r \ r \ n". También manipúlelo como el caso LF, excepto que la longitud del terminador será 3.
- De lo contrario, CR y LF no están conectados y se manejan como el caso de solo CR.
Aquí hay un código para eso:
struct DataInternetLineIterator: IteratorProtocol {
typealias LineLocation = (offset: Int, length: Int, terminatorLength: Int)
static let cr: UInt8 = 13
static let crData = Data(repeating: cr, count: 1)
static let lf: UInt8 = 10
static let lfData = Data(repeating: lf, count: 1)
let data: Data
private var lineStartOffset: Int = 0
init(data: Data) {
self.data = data
}
mutating func next() -> LineLocation? {
guard self.data.count - self.lineStartOffset > 0 else { return nil }
let nextCR = self.data.range(of: DataInternetLineIterator.crData, options: [], in: lineStartOffset..<self.data.count)?.lowerBound
let nextLF = self.data.range(of: DataInternetLineIterator.lfData, options: [], in: lineStartOffset..<self.data.count)?.lowerBound
var location: LineLocation = (self.lineStartOffset, -self.lineStartOffset, 0)
let lineEndOffset: Int
switch (nextCR, nextLF) {
case (nil, nil):
lineEndOffset = self.data.count
case (nil, let offsetLf):
lineEndOffset = offsetLf!
location.terminatorLength = 1
case (let offsetCr, nil):
lineEndOffset = offsetCr!
location.terminatorLength = 1
default:
lineEndOffset = min(nextLF!, nextCR!)
if nextLF! < nextCR! {
location.terminatorLength = 1
} else {
switch nextLF! - nextCR! {
case 2 where self.data[nextCR! + 1] == DataInternetLineIterator.cr:
location.terminatorLength += 1
fallthrough
case 1:
location.terminatorLength += 1
fallthrough
default:
location.terminatorLength += 1
}
}
}
self.lineStartOffset = lineEndOffset + location.terminatorLength
location.length += self.lineStartOffset
return location
}
}
Por supuesto, si tiene un Data
bloque de una longitud que es al menos una fracción significativa de un gigabyte, recibirá un golpe siempre que no exista más CR o LF del desplazamiento de bytes actual; siempre buscando infructuosamente hasta el final durante cada iteración. Leer los datos en fragmentos ayudaría:
struct DataBlockIterator: IteratorProtocol {
let data: Data
private(set) var blockOffset = 0
private(set) var bytesRemaining: Int
let blockSize: Int
init(data: Data, blockSize: Int) {
precondition(blockSize > 0)
self.data = data
self.bytesRemaining = data.count
self.blockSize = blockSize
}
mutating func next() -> Data? {
guard bytesRemaining > 0 else { return nil }
defer { blockOffset += blockSize ; bytesRemaining -= blockSize }
return data.subdata(in: blockOffset..<(blockOffset + min(bytesRemaining, blockSize)))
}
}
Tienes que mezclar estas ideas tú mismo, ya que todavía no lo he hecho. Considerar:
- Por supuesto, debe considerar las líneas completamente contenidas en un fragmento.
- Pero tienes que manejar cuando los extremos de una línea están en trozos adyacentes.
- O cuando los puntos finales tienen al menos un fragmento entre ellos
- La gran complicación es cuando la línea termina con una secuencia de varios bytes, ¡pero dicha secuencia se extiende a ambos lados! (Una línea que termina solo en CR que también es el último byte en el fragmento es un caso equivalente, ya que necesita leer el siguiente fragmento para ver si su solo-CR es realmente un CRLF o CR-CRLF. Hay travesuras similares cuando el fragmento termina con CR-CR.)
- Y necesita manejar cuando no hay más terminadores de su desplazamiento actual, pero el final de los datos está en un fragmento posterior.
¡Buena suerte!