python pyparsing

python - Pyparsing: espacios en blanco a veces importan... a veces no



(1)

Me gustaría crear una gramática para un archivo que contiene varias secciones (como PARAGRAPH a continuación).

Una sección comienza con su palabra clave (por ejemplo, PARAGRAPH), seguida por un encabezado (título aquí) y tiene su contenido en las siguientes líneas, una línea de contenido es una fila de la sección. Como es, es como una tabla con encabezado, columnas y filas.

En el siguiente ejemplo (archivo de tabla), limitaré las secciones para que tengan una columna y una línea.

Top-Down BNF de Tablefile:

tablefile := paragraph* paragraph := PARAGRAPH title CR TAB content title, content := /w+

Pyrarsing gramática:

Como necesito que se manejen los saltos de línea y la tabulación, tendré que configurar los espacios en blanco por defecto a ''''.

def grammar(): '''''' Bottom-up grammar definition '''''' ParserElement.setDefaultWhitespaceChars('' '') TAB = White("/t").suppress() CR = LineEnd().setName("Carriage Return").suppress() PARAGRAPH = ''PARAGRAPH'' title = Word(alphas) content = Word(alphas) paragraph = (PARAGRAPH + title + CR + TAB + content) tablefile = OneOrMore(paragraph) tablefile.parseWithTabs() return tablefile

Aplicando ejemplos

Este ejemplo falso coincide fácilmente:

PARAGRAPH someTitle thisIsContent

Este otro menos:

PARAGRAPH someTitle thisIsContent PARAGRAPH otherTitle thisIsOtherContent

Espera un PARAGRAPH justo después del primer contenido y tropieza con un salto de línea (recuerda setDefaultWhitespaceChars('' '') ). ¿Estoy obligado a agregar CR? al final de un paragraph ? ¿Cuál sería una mejor manera de ignorar los últimos saltos de línea?

Además, me gustaría permitir que las pestañas y espacios estén en cualquier parte del archivo sin perturbaciones. El único comportamiento necesario es comenzar un contenido de párrafo con TAB y PARAGRAPH para iniciar la línea. Eso también implicaría omitir líneas en blanco (con pestañas y espacios o nada) en y entre los párrafos.

Por lo tanto, agregué esta línea:

tablefile.ignore(LineStart() + ZeroOrMore(White('' /t'')) + LineEnd())

Pero cada demanda que acabo de exponer, parece estar en contra de mi necesidad de establecer espacios en blanco predeterminados a '' '' y ponerme en un callejón sin salida.

De hecho, esto haría que todo se descompusiera:

tablefile.ignore(CR) tablefile.ignore(TAB)

Pega PARAGRAPH y TAB al inicio de la línea

Si quiero /t ser ignorado como donde sea en el texto pero al comienzo de las líneas. Tendré que agregarlos a los caracteres de espacio en blanco por defecto.

Por lo tanto, he encontrado una forma de prohibir cada personaje de espacio en blanco al comienzo de la línea. Al usar el método leaveWhitespace . Este método mantiene los espacios en blanco que encuentra antes de coincidir con el token. Por lo tanto, puedo pegar algunos tokens al comienzo de la línea.

ParserElement.setDefaultWhitespaceChars(''/t '') SOL = LineStart().suppress() EOL = LineEnd().suppress() title = Word() content = Word() PARAGRAPH = Keyword(''PARAGRAPH'').leaveWhitespace() TAB = Literal(''/t'').leaveWhitespace() paragraph = (SOL + PARAGRAPH + title + EOL + SOL + TAB + content + EOL)

Con esta solución, resolví mi problema con TABs en cualquier parte del texto.

Separando párrafos

Llegué a la solución de PaulMcGuire ( delimitedList ) después de pensarlo un poco. Y encontré un problema con eso.

De hecho, aquí hay dos formas diferentes de declarar separadores de salto de línea entre dos párrafos. En mi opinión, deberían ser equivalentes. ¿En la práctica, no lo son?

Prueba de bloqueo (no olvides cambiar los espacios con pestañas si lo ejecutas):

PARAGRAPH titleone content1 PARAGRAPH titletwo content2

Parte común entre los dos ejemplos:

ParserElement.setDefaultWhitespaceChars(''/t '') SOL = LineStart().suppress() EOL = LineEnd().suppress() title = Word() content = Word() PARAGRAPH = Keyword(''PARAGRAPH'').leaveWhitespace() TAB = Literal(''/t'').leaveWhitespace()

Primer ejemplo, trabajando uno:

paragraph = (SOL + PARAGRAPH + title + EOL + SOL + TAB + content + EOL) tablefile = ZeroOrMore(paragraph)

Segundo ejemplo, no funciona:

paragraph = (SOL + PARAGRAPH + title + EOL + SOL + TAB + content) tablefile = delimitedList(paragraph, delim=EOL)

¿No deberían ser equivalentes? La segunda excepción de aumento:

Expected end of text (at char 66), (line:4, col:1)

No es un gran problema para mí, ya que finalmente puedo retroceder para poner EOL al final de cada sección de párrafo de mi gramática. Pero quería resaltar este punto.

Ignorando la línea en blanco que contiene espacios en blanco

Otra demanda que tenía, era ignorar las líneas en blanco, que contenían espacios en blanco ( '' /t'' ).

Una gramática simple para esto sería:

ParserElement.setDefaultWhitespaceChars('' /t'') SOL = LineStart().suppress() EOL = LineEnd().suppress() word = Word(''a'') entry = SOL + word + EOL grammar = ZeroOrMore(entry) grammar.ignore(SOL + EOL)

Al final, el archivo puede contener una palabra por línea, con cualquier espacio en blanco en cualquier lugar. Y debe ignorar las líneas en blanco.

Felizmente, lo hace. Pero no se ve afectado por la declaración predeterminada de espacios en blanco. Y una línea en blanco que contenga espacios o pestañas hará que el analizador genere una excepción de análisis.

Este comportamiento no es el que esperaba. ¿Es el especificado? ¿Hay algún error bajo este simple intento?

Puedo ver en este hilo que PaulMcGuire no intentó ignorar las líneas en blanco, sino que las tokensó en su lugar, en un analizador gramatical similar a un archivo ( NL = LineEnd().suppress() ).

¿Algún módulo python para el analizador BNF personalizado?

makefile_parser = ZeroOrMore( symbol_assignment | task_definition | NL )

La única solución que tengo por ahora es preprocesar el archivo y eliminar los espacios en blanco contenidos en una línea en blanco, ya que pyparsing ignora correctamente la línea en blanco sin espacios en blanco.

import os preprocessed_file = os.tmpfile() with open(filename, ''r'') as file: for line in file: # Use rstrip to preserve heading TAB at start of a paragraph line preprocessed_file.write(line.rstrip() + ''/n'') preprocessed_file.seek(0) grammar.parseFile(preprocessed_file, parseAll=True)


Su BNF contiene solo CR, pero usted analiza el código para terminar usando LF. Es eso intencional? BNF admite EOL LF (Unix), CR (Mac) y CRLF (Win):

Rule_|_Def.__|_Meaning___ CR | %x0D | carriage return LF | %x0A | linefeed CRLF | CR LF | Internet standard newline