texto - manejo de archivos en java netbeans
¿Cómo leer un archivo de texto con codificaciones mixtas en Scala o Java? (7)
Estoy tratando de analizar un archivo CSV, idealmente usando weka.core.converters.CSVLoader. Sin embargo, el archivo que tengo no es un archivo UTF-8 válido. Es principalmente un archivo UTF-8, pero algunos de los valores de campo tienen codificaciones diferentes, por lo que no hay codificación en la que todo el archivo sea válido, pero necesito analizarlo de todos modos. Además de usar bibliotecas de Java como Weka, estoy trabajando principalmente en Scala. Ni siquiera puedo leer el archivo usando scala.io.Source: por ejemplo
Source.
fromFile(filename)("UTF-8").
foreach(print);
arroja:
java.nio.charset.MalformedInputException: Input length = 1
at java.nio.charset.CoderResult.throwException(CoderResult.java:277)
at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:337)
at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:176)
at java.io.InputStreamReader.read(InputStreamReader.java:184)
at java.io.BufferedReader.fill(BufferedReader.java:153)
at java.io.BufferedReader.read(BufferedReader.java:174)
at scala.io.BufferedSource$$anonfun$iter$1$$anonfun$apply$mcI$sp$1.apply$mcI$sp(BufferedSource.scala:38)
at scala.io.Codec.wrap(Codec.scala:64)
at scala.io.BufferedSource$$anonfun$iter$1.apply(BufferedSource.scala:38)
at scala.io.BufferedSource$$anonfun$iter$1.apply(BufferedSource.scala:38)
at scala.collection.Iterator$$anon$14.next(Iterator.scala:150)
at scala.collection.Iterator$$anon$25.hasNext(Iterator.scala:562)
at scala.collection.Iterator$$anon$19.hasNext(Iterator.scala:400)
at scala.io.Source.hasNext(Source.scala:238)
at scala.collection.Iterator$class.foreach(Iterator.scala:772)
at scala.io.Source.foreach(Source.scala:181)
Estoy muy contento de tirar todos los caracteres inválidos o reemplazarlos por un muñeco. Voy a tener muchos textos como este para procesarlos de varias maneras y es posible que deba pasar los datos a varias bibliotecas de terceros. Una solución ideal sería algún tipo de configuración global que haría que todas las bibliotecas Java de bajo nivel ignorasen los bytes no válidos en el texto, de modo que pueda llamar bibliotecas de terceros en estos datos sin modificaciones.
SOLUCIÓN:
import java.nio.charset.CodingErrorAction
import scala.io.Codec
implicit val codec = Codec("UTF-8")
codec.onMalformedInput(CodingErrorAction.REPLACE)
codec.onUnmappableCharacter(CodingErrorAction.REPLACE)
val src = Source.
fromFile(filename).
foreach(print)
Gracias a + Esailija por señalarme en la dirección correcta. Esto me llevó a ¿Cómo detectar secuencias ilegales de bytes UTF-8 para reemplazarlas en el flujo de entrada de Java? que proporciona la solución core java. En Scala puedo hacer que este sea el comportamiento predeterminado al hacer que el códec esté implícito. Creo que puedo convertirlo en el comportamiento predeterminado para todo el paquete al ponerle la definición de códec implícita en el objeto del paquete.
Así es como logré hacerlo con java:
FileInputStream input;
String result = null;
try {
input = new FileInputStream(new File("invalid.txt"));
CharsetDecoder decoder = Charset.forName("UTF-8").newDecoder();
decoder.onMalformedInput(CodingErrorAction.IGNORE);
InputStreamReader reader = new InputStreamReader(input, decoder);
BufferedReader bufferedReader = new BufferedReader( reader );
StringBuilder sb = new StringBuilder();
String line = bufferedReader.readLine();
while( line != null ) {
sb.append( line );
line = bufferedReader.readLine();
}
bufferedReader.close();
result = sb.toString();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch( IOException e ) {
e.printStackTrace();
}
System.out.println(result);
El archivo no válido se crea con bytes:
0x68, 0x80, 0x65, 0x6C, 0x6C, 0xC3, 0xB6, 0xFE, 0x20, 0x77, 0xC3, 0xB6, 0x9C, 0x72, 0x6C, 0x64, 0x94
¿Qué es hellö wörld
en UTF-8 con 4 bytes no válidos mezclados en.
Con .REPLACE
verá el carácter de reemplazo estándar de Unicode que se utiliza:
//"h�ellö� wö�rld�"
Con .IGNORE
, ve los bytes no válidos ignorados:
//"hellö wörld"
Sin especificar .onMalformedInput
, obtienes
java.nio.charset.MalformedInputException: Input length = 1
at java.nio.charset.CoderResult.throwException(Unknown Source)
at sun.nio.cs.StreamDecoder.implRead(Unknown Source)
at sun.nio.cs.StreamDecoder.read(Unknown Source)
at java.io.InputStreamReader.read(Unknown Source)
at java.io.BufferedReader.fill(Unknown Source)
at java.io.BufferedReader.readLine(Unknown Source)
at java.io.BufferedReader.readLine(Unknown Source)
Cambiaré a un códec diferente si falla.
Para implementar el patrón, obtuve inspiración de esta otra pregunta de .
Utilizo una lista predeterminada de códecs, y los examino recursivamente. Si todos fallan, imprimo los bits de miedo:
private val defaultCodecs = List(
io.Codec("UTF-8"),
io.Codec("ISO-8859-1")
)
def listLines(file: java.io.File, codecs:Iterable[io.Codec] = defaultCodecs): Iterable[String] = {
val codec = codecs.head
val fileHandle = scala.io.Source.fromFile(file)(codec)
try {
val txtArray = fileHandle.getLines().toList
txtArray
} catch {
case ex: Exception => {
if (codecs.tail.isEmpty) {
println("Exception: " + ex)
println("Skipping file: " + file.getPath)
List()
} else {
listLines(file, codecs.tail)
}
}
} finally {
fileHandle.close()
}
}
Estoy aprendiendo Scala, por lo que el código puede no ser óptimo.
El códec de Scala tiene un campo decodificador que devuelve un java.nio.charset.CharsetDecoder
:
val decoder = Codec.UTF8.decoder.onMalformedInput(CodingErrorAction.IGNORE)
Source.fromFile(filename)(decoder).getLines().toList
El problema con ignorar los bytes no válidos es decidir cuándo son válidos de nuevo. Tenga en cuenta que UTF-8 permite codificaciones de byte de longitud variable para los caracteres, por lo que si un byte no es válido, debe comprender qué byte comenzar a leer para obtener una secuencia válida de caracteres nuevamente.
En resumen, no creo que encuentres una biblioteca que pueda "corregir" tal como se lee. Creo que un enfoque mucho más productivo es tratar de limpiar esos datos primero.
La solución para Scala''s Source (basada en @Esailija answer):
def toSource(inputStream:InputStream): scala.io.BufferedSource = {
import java.nio.charset.Charset
import java.nio.charset.CodingErrorAction
val decoder = Charset.forName("UTF-8").newDecoder()
decoder.onMalformedInput(CodingErrorAction.IGNORE)
scala.io.Source.fromInputStream(inputStream)(decoder)
}
Una solución simple sería interpretar su flujo de datos como ASCII, ignorar todos los caracteres que no sean de texto. Sin embargo, perdería incluso caracteres UTF8 codificados válidos. No sé si eso es aceptable para ti.
EDITAR: si sabe de antemano qué columnas son válidas para UTF-8, podría escribir su propio analizador de CSV que se pueda configurar qué estrategia usar en qué columna.
Use ISO-8859-1
como codificador; esto solo te dará valores de bytes empaquetados en una cadena. Esto es suficiente para analizar CSV para la mayoría de las codificaciones. (Si ha mezclado bloques de 8 y 16 bits, entonces está en problemas, todavía puede leer las líneas en ISO-8859-1, pero es posible que no pueda analizar la línea como un bloque).
Una vez que tenga los campos individuales como cadenas separadas, puede intentar
new String(oldstring.getBytes("ISO-8859-1"), "UTF-8")
para generar la cadena con la codificación adecuada (use el nombre de codificación apropiado por campo, si lo conoce).
Editar: tendrá que usar java.nio.charset.Charset.CharsetDecoder
si desea detectar errores. El mapeo a UTF-8 de esta manera solo le dará 0xFFFF en su cadena cuando haya un error.
val decoder = java.nio.charset.Charset.forName("UTF-8").newDecoder
// By default will throw a MalformedInputException if encoding fails
decoder.decode( java.nio.ByteBuffer.wrap(oldstring.getBytes("ISO-8859-1")) ).toString