/ prog

morgue

hace como cuatro meses, habia hecho la siguiente divagacion sobre “datos” y lenguajes de marcas ligeras para solicitar una asistencia que no tuvo respuesta… eso llevo un tiempo y ademas lo habia escrito con “el morg”. Mis deseo era publicarlo aqui tambien, pero por lo “simplote” que es el parser de markdown de hugo, se ropia al tomar la exportacion html y colocarla directamente.

No fue hasta hace un par de dias que me pregunte si aun estaba esta divagacion en el disco, y si, lo estaba. Tenia pereza de reescribirlo en markdown, y dudas de dejarlo suelto dentro de algun directorio. Aunque olvide el por que, su escritura me genero sierta satisfaccion.

Sin mas le ayude al parser, quitando lineas en blanco en los bloques de codigo, hasta que dio el siguiente resultado

Estrutura de datos para almacenar un documento

los documentos basados en el lenguaje de marcado SGML como lo son html utilizan un sistema de etiquetas (balanceadas), jerarquicas y con un orden especifico segun el significado y contexto de la etiqueta

<etiqueta>
  <hijo-etiqueta attr="un-atributo" attr="otro-atributo">
    <p>texto</p>
    <p>otro texto</p>
  </hijo-etiqueta>
</etiqueta>

encajar los elementos de un documento (parrafos, encabezados, listas, definiciones, ...) a esta uniformidad permite tanto extender el lenguaje con nuevas etiquetas, como agregar mucha meta informacion sobre cada uno de sus componentes y a su vez da indicios de como crear una estructura de datos sencilla para almacenar cada elemento. Por ejemplo, El lenguaje de programacion Go en el paquete x/net/html provee funcionalidades para analizar un documento html y almacenarlo en la siguiente estructura:

type Node struct {
  Parent, FirstChild, LastChild, PrevSibling, NextSibling *Node
  Type      NodeType
  Data      string
  Namespace string
  Attr      []Attribute
}

existe un nodo inicial del que descienden todos los elementos del documento, organizados sengun su nivel de anidamiento. Cada nodo puede tener un numero de hijos indefinido (almenos para la estructura y la logica del marcado SGML). Tambien puede tener hermanos, que son etiquetas en el mismo nivel de anidamiento dentro de una etiqueta. Pueden tener un padre, que es la etiqueta de la que desciende el nodo actual, un numero indefinido de Atributos y finalmente almacenar datos en forma de cadena de texto, que en ultima instancia seria el contenido del documento en si.

Recorrer y analizar el fichero de texto para almacenar la informacion en tal estructura siempre que las etiquetas esten balanceadas, aunque tiene varias dificultades, no es una tarea demaciado compleja y la estructura jerarquia resultante es relativamente sencilla de recorrer.

Pero, por otro lado, encajar los elementos de un documento en la logica de las etiquetas en no pocas ocaciones pudiera resultar en algo forzado y siempre se corre con el riesgo (sobre todo si el que escribe el documento es un humano) de no balancear las etiquetas en el lugar apropiado, lo cual en el caso mas optimista puede pasar desapersivido y en el peor resultar en una estructura de los datos incorrejiblemente incorrecta.

quiza entonces, no solo por la complejidad para un humano de escribir texto con etiquetas balanceadas, sino tambien por lo exigente de conocer a fondo un sin numero de etiquetas, atributos, ordenes y jerarquias de anidamiento, seria pues una mejor alternativa extraer las construcciones fundamentales del lenguaje escrito (utilizado en libros, documentos tecnicos, novelas y documentacion en general) para dar forma tanto a un nuevo lenguaje de documentacion, como a su programa adjunto que lo haga funcional.

En este momento, el desinformado diria: con lo sencillo que es, por que no utilizar word para escribir documentacion y el entendido, reponderia: por que word es un programa de dibujo, ineficiente, obscuro, torpe y un sin fin de sinsentidos mas que no vale la pena ni mencionar.

(Por la extension y en buena parte desconocimiento de puntos concretos de algunas otras alternativas para escribir documentacion, como son Groff o TeX omitire comentarlos, asi como tambien su acercamiento hacia la forma de abordar la escritura de documentacion. Las ideas posteriores suponen que se cuenta con alguna nocion en lenguajes de marcas ligeras. Se puede indagar en los siguientes enlaces: https://en.wikipedia.org/wiki/Comparison_of_document_markup_languages, https://en.wikipedia.org/wiki/Lightweight_markup_language.)

Supongamos que se cuenta con un formato de documentacion mas proximo a lo que un humano puede escribir e interpretar con naturalidad y que se tiene un conjunto limitado y bien definido de elementos posibles, no seria una alternativa mas apropiada almacenar (una vez analizado el documento) los elementos directamente como sus estructuras (Datos, o sengun el lenguaje de implementacion objetos o interfaces) y ademas (y solo cuando resulte conveniente) aprobechar la composicion jerargica descendiente?

me aclaro (espero) y pongo un ejemplo. En el lenguaje Go podria implementarse de la sigiente manera:

type Data interface {
  DataType() int
}
type Node struct {
  Data
  Parent, FirstChild, LastChild, PrevSibling, NextSibling *Node
}

aqui Node es la estructura en si, Data es un artilugio para incrustar cualquier estructura (que tenga un metodo DataType) dentro de ella. En cuanto al resto se conserva la jerarquia de hijos, hermanos y padre... en caso de que la alla. Mas tarde, al recorrer la jerarquia, bastaria con hacer un cast a Data para tratar con el elemento de cada nodo directamente

bueno... estube probando dicha estructura preeliminarmente, es bueno que los elementos anidados de la estructura sean punteros por que asi el asignar memoria para cada nodo es poco exigente con la pila de memoria.

Sin embargo, por la naturaleza del lenguaje Go (donde no se pueden comparar punteros), en adicion de que para algunos elementos es importante conocer la cantidad de elementos anidados (hijos), lo engorroso que seria contabilizarlos dentro de listas enlazadas y la poco elegante forma de iterar sobre la estructura descendente

for i := docNode.FirstChild; i != nil; i = i.NextSibling {
  // ...
  for j := i.FirstChild; j != nil; j = j.NextSibling {
    // ...
  }
  // ...
}

me llebaron a la conclusion de que en general tener punteros al padre y hermanos, es de poca utilidad y, es mas util substituir los punteros de listas enlazadas por un slice (un arreglo con crecimiento dinamico), como puede verse en la siguiente version

type Data interface {
  DataType() int
}
type DocNode struct {
  Data
  Cont []*DocNode
}

quedando la iteracion por los elementos anidados de la siguiente manera

for _, i := range docNode.Cont {
  // ...
  for _, k := range i.Cont {
    // ...
  }
  // ...
}

se utilizan punteros a los nodos para evitar la sobrecarga en las copias con cada expansion del slice (Cont). Se asume tambien para evitar comprobaciones de tipo (i != nil) que el parser del lenguaje de marcas nunca entregue ningun elemento dentro de un slice Cont (no nil) o Data que sea un puntero nulo (a menos que la especificacion indique lo contrario). Una ventaja adicional de utilizar un slice es que se conocen el numero de elementos anidados y ademas como con cualquier arreglo es posibre acceder a uno en especifico sin la iteracion entre los punteros.

Las desventajas (o no) de utilizar este acercamiento, es que los objetos Data deben tener bien definida su estructura, que el programador la conoce y que en ocaciones podria utilizarse el elemento Cont como una extension natural de Data si el elemento del documento se ajusta naturalmente a ello, como lo porian ser el caso de las listas, encabezados y tablas.

Pero bueno, no conosco todo lo que existe y en el campo de las estruturas de datos aun soy un novato, por lo que no puedo evitar preguntar si existe una alternativa mejor.

De hecho, en una indagacion sobre patrones de diseño por un momento crei que se podria llegar a almacenar directamente la estructura en objetos aplicando una especie de Command/Composite.

A modo de ejemplo utilizare el elemento @ del lenguaje de marcas supuesto.

el elemento @ puede tener las siguientes variantes

@c(texto)                              // comando y texto
@abc(texto)                            // comandos concatenados y texto
@c(texto @d(u) algo)                   // comando que contiene texto y comandos
@abc(texto @d(u) algo)                 // comandos concatenados que cotienen texto y comandos
@c(texto<>@d(comando)<>texto-3)        // comando con "ramas" donde cada una puede contener
                                       // texto, un comando o ambos
@abc(t<>@d(u)<>v)                      // comandos concatenados con "ramas" donde cada una
                                       // puede contener texto, otro comando o ambos

Cada objeto contiene solo lo minimo necesario y se encargara unicamente de ejecutar su funcion:

tipe Do interface { Exec() }
type Text     struct { Txt   string; next Do } // solo se encarga del texto
type Comand   struct { Type    rune; next Do } // para un comando uni-tipo
type Commands struct { Types []rune; next Do } // para comandos concatenados
type Rama     struct { Dos   []Do  ; next Do } // para la mezcla de texto/comando
type Ramas    struct { Dos   []Do  ; next Do } // para la division de ramas "<>"

type (x Text    ) Exect() { /* ... */ }
type (x Comand  ) Exect() { /* ... */ }
type (x Commands) Exect() { /* ... */ }
type (x Rama    ) Exect() { /* ... */ }
type (x Ramas   ) Exect() { /* ... */ }

(este codigo es solo una aproximacion, ya no me acuerdo como lo habia implementado)

estube explorando la opcion de que los Data (o almenos este elemento) echara mano de patrones de diseño para generalizar la estructura del documento y quiza presindir de la estructura jerarquica... para terminar descubriendo que los patrones de diseño y las estructuras de datos poco tiene que ver unos con otros, pues al aplicar este razonamiento tendrian que ligarse tanto el analizador del documento como su renderizador/exportador/modificador, es decir, se tendria que crear una version dedicada paca cada funcionalidad a aplicar y comunicar una funcionalidad con la de otro renderizador/exportador/modificador seria por lo menos complejo... o si se opta por un aglutinar todo en un sitio, cada nodo estaria copado de if-elses o switches extensos, o bueno, esa fue la conclusion a la que llegue. De momento no he encontrado una reconsiliacion entre tener una estructura de datos y la aplicacion de patrones de diseño

tras el fiasco anterior, llegue a la conclusion de que la forma optima de ajustar la idea del elemento @ dentro de la estructura DocNode, es la siguiente

const ( NodeMarkupCero = iota; NodeMarkupVerse )
type MarkupCero  struct { Str string }
type MarkupVerse struct {
  Type   []rune
  Brace  rune
  Forest bool
}
func (m MarkupCero ) DataType() int { return NodeMarkupCero  }
func (m MarkupVerse) DataType() int { return NodeMarkupVerse }

donde MarkupCero seria exclusivo para almacenar el texto y MarkupVerse se utiliza para indicar (dentro de la estructura DocNode) el tipo(s) de comando(s) y de una forma un poco criptica (con la variable Forest) si su Cont es una mezcla de texto/comando o una consecucion de ramas que almacenan texto/comando.

Por ejemplo, "texto", se representaria como la estructura

node := &DocNode{
  Data: MarkupCero{ "texto" },
  Cont: nil,
}

es decir, la Data principal del nodo es simple texto y no tiene ningun elemento anidado

para "@abc(texto y un @i(comando con texto))", seria:

node := &DocNode{
  Data: MarkupVerse{ Type: []rune{ 'a', 'b', 'c' }, Forest: false },
  Cont: []*DocNode{
    &DocNode{ Data: MarkupCero{ "texto y un " }, Cont: nil },
    &DocNode{ Data: MarkupVerse{ Type: []rune{ 'i', Forest: false },
                                 Cont: []*Docnode{ &Docnode{ Data: MarkupCero{ "comando con texto" },
                                                             Cont: nil,
                                                           },
                                 }
  },
}

heee, bueno, no tan sencillo... asi que omitire el ejemplo de una estructura de contiene ramas.

El recorrido, por otro lado es bastante trivial. He aqui el codigo necesario para reconstruir apartir de la estructura el documento de entrada

func (n *DocNode) Rebuild() (str string) {
  switch d := n.Data.(type) {
  case MarkupVerse:
    str = fmt.Sprintf( "@%s%c", string(d.Type), d.Brace )
    if d.Forest {
      sep := ""
      for _, i := range n.Cont {
        str += sep
        for _, j := range i.Cont { str += j.Rebuild() }
        sep = "<>"
      }
    } else {
      for _, i := range n.Cont { str += i.Rebuild() }
    }
    if d.Brace != 0 { str += fmt.Sprintf( "%c", comBrace( d.Brace ) ) }
  case MarkupCero : return d.Str
  }
  return
}

y bueno, despues de todo este lio mental sobre la forma ideal para almacenar un documento, vuelvo a plantear (si no lo hice anteriormente) la duda principal: es buena idea relegar la carga de cada elemento en el programador y encomendarle conocer tanto la estructura como la forma en que se aprobecha (o no) DocNode, o por el contrario, es mejor continuar las enseñansas de SGML ajustando los elementos a un flujo de etiquetas con significado y jerarquia especifico. O quiza, con un poco de paciencia y suerte esperar una nueva y audaz propuesta, quiza una que conjugue patrones y estructuras de datos

para terminar esta divagacion, aqui el boceto de como ha de ser la estructura global del documento analizado

type Doc struct {
  Toc         []*DocNode
  Title         Markup
  Subtitle      Markup
  Lang          string
  Licence       string
  Date          string
  ID            string
  Description   string
  Author      []string
  Translator  []string
  Source      []string
  Style       []string
  Tags        []string
  HShift        int
  BoolOptions   map[string]bool      // as ==> someOption || someOption()
  TextOptions   map[string]string    // as ==> someOption("text")
  NumbOptions   map[string]int       // as ==> someOption( numb )
  MultOptions   map[string][]Arg     // as ==> someOption( numb, "text", float, bool, ident, … )
  MoreConfigs   map[string][]string  // as ==> ..whatever > whatever
}

basicamente Toc es el documento en si, donde cada elemento del slice es un encabezado y cada uno de estos contiene sus listas, parrados, bloques de codigo, etc, etc.

El resto son la configuracion y meta ofresidas de una manera mas o menos conveniente.

/ prog