2009-07-07 20 views
5

Consulte los datos actualizados de entrada y salida en Edit-1.¿Cómo puedo analizar el texto marcado para su posterior procesamiento?

Lo que estoy tratando de lograr está convirtiendo

 
+ 1 
+ 1.1 
    + 1.1.1 
    - 1.1.1.1 
    - 1.1.1.2 
+ 1.2 
    - 1.2.1 
    - 1.2.2 
- 1.3 
+ 2 
- 3 

en una estructura de datos de Python como

[{'1': [{'1.1': {'1.1.1': ['1.1.1.1', '1.1.1.2']}, '1.2': ['1.2.1', '1.2.2']}, '1.3'], '2': {}}, ['3',]] 

He mirado en muchos idiomas diferentes marcas Wiki, rebaja, texto reestructurado, etc, pero todos son extremadamente complicados para mí para entender cómo funciona, ya que deben cubrir una gran cantidad de etiquetas y sintaxis (solo necesitaría las partes de "lista" de la mayoría de estos, pero convertidas a python en lugar de html, por supuesto).

También he echado un vistazo a tokenizers, lexers y analizadores, pero una vez más son mucho más complicados de lo que necesito y que puedo entender.

No tengo idea de por dónde empezar y agradecería cualquier ayuda posible sobre este tema. Gracias

Editar-1: Si el personaje al comienzo de las cuestiones de línea, desde la salida necesaria de antes y ahora se podía ver que el * denota un nodo raíz con los niños, la + tiene hijos y el - no tiene hijos (raíz o de otro tipo) y es solo información adicional perteneciente a ese nodo. El * no es importante y se pueden intercambiar con + (puedo obtener el estado de la raíz de otras maneras.)

Por lo tanto el nuevo requisito estaría utilizando solamente * para denotar un nodo con o sin niños y - no puede tener hijos. También lo he cambiado así que la clave no es el texto después de * ya que sin duda cambiará más tarde a un título real.

Por ejemplo

 
* 1 
* 1.1 
* 1.2 
    - Note for 1.2 
* 2 
* 3 
- Note for root 

daría

[{'title': '1', 'children': [{'title': '1.1', 'children': []}, {'title': '1.2', 'children': []}]}, {'title': '2', 'children': [], 'notes': ['Note for 1.2', ]}, {'title': '3', 'children': []}, 'Note for root'] 

O si usted tiene otra idea para representar el contorno de pitón luego llevarlo hacia adelante.

+0

Hecho y hecho. He editado ambas cosas. – Rigsby

Respuesta

6

Editar: gracias a la clarificación y el cambio en la especificación que he editado mi código, aún usando una clase explícita Node como un paso intermedio para mayor claridad - la lógica es convertir la lista de líneas en una lista de nodos, luego convierta esa lista de nodos en un árbol (utilizando su atributo de sangría de manera apropiada), luego imprima ese árbol en un formato legible (esto es solo un paso de "depuración de ayuda" para verificar que el árbol esté bien construido y por supuesto, puede ser comentado en la versión final del script, que, como por supuesto, tomará las líneas de un archivo en lugar de tenerlas codificadas para la depuración! -), finalmente construya la estructura de Python deseada e imprímala. Aquí está el código, y como veremos después de que el resultado es casi como el OP especifica con una excepción -, pero, primero el código:

import sys 

class Node(object): 
    def __init__(self, title, indent): 
    self.title = title 
    self.indent = indent 
    self.children = [] 
    self.notes = [] 
    self.parent = None 
    def __repr__(self): 
    return 'Node(%s, %s, %r, %s)' % (
     self.indent, self.parent, self.title, self.notes) 
    def aspython(self): 
    result = dict(title=self.title, children=topython(self.children)) 
    if self.notes: 
     result['notes'] = self.notes 
    return result 

def print_tree(node): 
    print ' ' * node.indent, node.title 
    for subnode in node.children: 
    print_tree(subnode) 
    for note in node.notes: 
    print ' ' * node.indent, 'Note:', note 

def topython(nodelist): 
    return [node.aspython() for node in nodelist] 

def lines_to_tree(lines): 
    nodes = [] 
    for line in lines: 
    indent = len(line) - len(line.lstrip()) 
    marker, body = line.strip().split(None, 1) 
    if marker == '*': 
     nodes.append(Node(body, indent)) 
    elif marker == '-': 
     nodes[-1].notes.append(body) 
    else: 
     print>>sys.stderr, "Invalid marker %r" % marker 

    tree = Node('', -1) 
    curr = tree 
    for node in nodes: 
    while node.indent <= curr.indent: 
     curr = curr.parent 
    node.parent = curr 
    curr.children.append(node) 
    curr = node 

    return tree 


data = """\ 
* 1 
* 1.1 
* 1.2 
    - Note for 1.2 
* 2 
* 3 
- Note for root 
""".splitlines() 

def main(): 
    tree = lines_to_tree(data) 
    print_tree(tree) 
    print 
    alist = topython(tree.children) 
    print alist 

if __name__ == '__main__': 
    main() 

Cuando se ejecuta, este emite:

1 
    1.1 
    1.2 
    Note: 1.2 
2 
3 
Note: 3 

[{'children': [{'children': [], 'title': '1.1'}, {'notes': ['Note for 1.2'], 'children': [], 'title': '1.2'}], 'title': '1'}, {'children': [], 'title': '2'}, {'notes': ['Note for root'], 'children': [], 'title': '3'}] 

Aparte de la ordenación de las llaves (que es inmaterial y no está garantizada en un diccionario, por supuesto), esto es casi conforme a lo solicitado - excepto que aquí todos notas aparecen como entradas de diccionario con una clave de notes y un valor que es un lista de cadenas (pero la entrada de notas se omite si la lista estaría vacía, más o menos como se hace en el ejemplo de la pregunta).

En la versión actual de la pregunta, la forma de representar las notas es poco clara; una nota aparece como una cadena independiente, otras como entradas cuyo valor es una cadena (en lugar de una lista de cadenas mientras estoy usando). No está claro lo que se supone que implica que la nota debe aparecer como una cadena independiente en un caso y como una entrada dic en todos los demás, por lo que este esquema que estoy usando es más regular; y si una nota (si existe) es una sola cadena en lugar de una lista, ¿eso significaría que es un error si aparece más de una nota para un nodo? En este último aspecto, este esquema que estoy usando es más general (permite que un nodo tenga cualquier número de notas desde 0 hacia arriba, en lugar de solo 0 o 1 como aparentemente implicado en la pregunta).

Habiendo escrito tanto código (la respuesta previa a la edición fue tan larga y me ayudó a aclarar y cambiar las especificaciones) para proporcionar (espero) el 99% de la solución deseada, espero que satisfaga el póster original, ya que ¡los últimos ajustes al código y/o las especificaciones para que coincidan entre sí deberían ser fáciles para él!

+0

He actualizado mi publicación para intentar aclarar cosas.Ahora muestro que la * o - materia y yo arreglamos la primera salida (el {'1.2.3'} debería haber sido solo una cadena y no una frase como lo hice). – Rigsby

1

Dado que se trata de una situación de contorno, puede simplificar las cosas con una pila. Básicamente, desea crear una pila que tenga dict s correspondiente a la profundidad del contorno. Cuando analiza una nueva línea y la profundidad del contorno ha aumentado, inserta un nuevo dict en la pila a la que hizo referencia el dict anterior en la parte superior de la pila. Cuando analiza una línea que tiene una profundidad menor, abre la pila para volver al padre. Y cuando encuentras una línea que tiene la misma profundidad, la agregas al dict en la parte superior de la pila.

+0

Y para ser realmente sofisticado, puede usar el contenido de los artículos y re.match para asegurarse de que el siguiente artículo comience con él más un punto más número (s). – Kurt

1

Las pilas son una estructura de datos muy útil cuando se analizan árboles. Simplemente mantenga la ruta desde el último nodo agregado hasta la raíz en la pila en todo momento para que pueda encontrar el padre correcto por la longitud del sangrado. Algo como esto debería funcionar para analizar el último ejemplo:

import re 
line_tokens = re.compile('(*)(\\*|-) (.*)') 

def parse_tree(data): 
    stack = [{'title': 'Root node', 'children': []}] 
    for line in data.split("\n"): 
     indent, symbol, content = line_tokens.match(line).groups()   
     while len(indent) + 1 < len(stack): 
      stack.pop() # Remove everything up to current parent 
     if symbol == '-': 
      stack[-1].setdefault('notes', []).append(content) 
     elif symbol == '*': 
      node = {'title': content, 'children': []} 
      stack[-1]['children'].append(node) 
      stack.append(node) # Add as the current deepest node 
    return stack[0] 
0

La sintaxis `re usando es muy similar a Yaml. Tiene algunas diferencias, pero es bastante fácil de aprender: su enfoque principal es ser legible por el ser humano (y escribible).

Mira el sitio web de Yaml. Hay algunos enlaces de python, documentación y otras cosas allí.

Cuestiones relacionadas