la conversión de gran XML de ISO-8859-1 a UTF-8 con entidades de DTD externa

einsweniger:

Yo tengo:

  • 2.2GiB de XML sin comprimir en la norma ISO-8859-1, a partir de

<?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE dblp SYSTEM "dblp-2017-08-29.dtd">

  • la DTD correspondiente que define entidades como sigue:

<!ENTITY Aacute "&#193;" ><!-- capital A, acute accent -->

  • un equipo que no puede caber el XML analizado en la memoria RAM

quiero

Para importar el XML en Apache Solr, que ya está puesta en marcha y funcionando. Solr / Java (con razón) se quejan de demasiadas entidades ampliadas, que puedo elevar mediante el establecimiento -DentityExpansionLimit=2000000de la JVM, pero me gustaría tener que editar el importador de elevar el límite con System::setProperty.

Lo intenté

xmllint

xmllint --stream --loaddtd --encode utf8 --output dblp.utf8.xml dblp-2018-07-01.xml

sin --streamel proceso es matado por el núcleo, ya que trata de analizar la estructura en la memoria.

con el --streamque no escribirá el archivo de salida, sospecho que es sólo validar el XML con la DTD.

editar XML, pitón

No podía encontrar la manera de importar el DTD con el pitón y utilizarlo con el analizador, así que me he puesto las entidades en el <!DOCTYPE dblp [ … ]>y luego

import xml.etree.ElementTree
f = open('dblp-2018-07-01.xml')
out = open('dblp.utf8.xml','wb')
xml.etree.ElementTree.parse(f).write(out, encoding='UTF-8')

Esto consume aproximadamente 11 GiB de memoria y funciona para mí , pero:

Los detalles

Quiero que otros para reproducir lo que estoy haciendo, así que esto es lo que me gustaría tener:

  • sin editar el código XML manualmente para insertar entidades
  • un programa script o compilado que puede convertir la codificación
  • usar la menor cantidad de memoria como sea posible, tratar de mantenerlo por debajo de 6 GiB
  • una ventaja adicional sería la lectura y escritura de ficheros comprimidos para ahorrar espacio, pero eso no es necesario.

Yo prefiero Java para una solución programática (para que pueda incorporar el proceso de importación en Solr), pero mucho gusto voy a tomar cualquier otra solución (que me gustaría evitar JavaScript).

Si quieres joder con el mismo XML, los archivos se encuentran aquí:

Los ficheros comprimidos están a punto 430MiB en tamaño y se expanden para 2.2GiB de XML.

¡Gracias!

einsweniger:

He encontrado una solución a mí mismo, que es un poco lento (~ 11-12 minutos) pero estoy bien con ella:

import javax.xml.stream.*;
import java.io.*;
import java.util.zip.*;

public class ConvertToUtf8 {

  public static void main(String[] args) {
    System.setProperty("entityExpansionLimit", "10000000");
    XMLInputFactory inputFactory = XMLInputFactory.newFactory();
    XMLOutputFactory outputFactory = XMLOutputFactory.newFactory();

    try (
        FileInputStream ifs = new FileInputStream("dblp-2018-08-01.xml.gz"); 
        GZIPInputStream gzIn = new GZIPInputStream(ifs);
        FileOutputStream ofs = new FileOutputStream("dblp_utf8.xml.gz");
        GZIPOutputStream gzOut = new GZIPOutputStream(ofs, true);
        ) 
    {
      XMLEventReader inEvt = inputFactory.createXMLEventReader(gzIn);
      XMLEventWriter outEvt = outputFactory.createXMLEventWriter(gzOut, "UTF-8");
      outEvt.add(inEvent);
    } catch (IOException | XMLStreamException e) {
      e.printStackTrace();
    }
  }
}

Usando GZIP de entrada / salida que acelerará el proceso de forma significativa (6 veces más rápido en mi máquina) como la lectura desde el disco será cuello de botella el resto del sistema. Si desea replicar, asegúrese de que el DTD se encuentra en el directorio de trabajo, de lo contrario las entidades no serán reemplazados. Java insertará un comentario al XML, que indica que no se puede encontrar el DTD lo contrario.

Basándose en la respuesta de @janbrohl:

#! python3
import re
import gzip
from lxml import etree

# read the DTD with the lxml parser
dtd = etree.DTD('dblp-2017-08-29.dtd')
# build a dict with it for lookup
replacements = {x.name: x.content for x in dtd.entities()}

entity_re=re.compile('&(\w+);')

def resolve_entity(m):
    """
    This will replace the defined entities with their expansions from the DTD:
    '&Ouml;' will be replaced with '&#214;'.
    The entities that are already escaped with '&#[0-9]+;' should not be expanded,
    Ex: if some of the escapes produced the character '<', the XML would no longer be well formed.

    If the matched entity is not in the replacements, use the match as default
    """
    return replacements.get(m.group(1),f'&{m.group(1)};')

def expand_line(line):
    return entity_re.sub(resolve_entity,line)

def recode_file(src,dst):
    with gzip.open(src,mode='rt', encoding='ISO-8859-1', newline='\n') as src_file:
        # discard first line with wrong encoding
        print('discard: ' + src_file.readline())  
        with gzip.open(dst, mode='wt', encoding='UTF-8', newline='\n') as dst_file:
            # replace with correct encoding statement
            dst_file.write('<?xml version="1.0"?>\n')  
            for line in src_file:
                dst_file.write(expand_line(line))

recode_file('dblp-2018-08-01.xml.gz','dblp_recode.xml.gz')

He importado la salida producida por la expresión regular reemplazar, parece estar funcionando: D De acuerdo, es más rápido que la versión de Java, pero estoy todavía incierto si el XML resultante es la misma que la versión que fue a través de un analizador real . Voy a experimentar un poco.

edit: después de algunos experimentos, he descubierto algunos casos extremos que podrían modificar los datos. Voy a dejar la secuencia de comandos Python aquí, ya que es rápida. Sin embargo, yo prefiero usar la versión que realmente utiliza un analizador sintáctico: es fácil de seguir, sólo se utiliza la biblioteca estándar y es fácil de mantener. El caso extremo fue mi culpa, lo utiliza dict del pitón como me lo haces en C ++ mapas: donde el acceso replacements['val']crearía la entrada en C ++, replacements.at('val')lanzará. En Python, es al revés: replacements['val']arrojará, replacements.get('val')no lo hará y devuelve una cadena vacía si no se proporcionaron valores predeterminados.

Voy a dejar esta abierto para un poco más de tiempo, en caso de que alguien puede encontrar una solución más rápida. Edit: Si alguien puede encontrar una solución más rápida que va a utilizar un analizador XML: D

Supongo que te gusta

Origin http://43.154.161.224:23101/article/api/json?id=221302&siteId=1
Recomendado
Clasificación