STXT Schema

Estado:
Aurora
última modificación:
2026-09-07

1. Introducción

Este documento es STXT-SCHEMA-SPEC; las demás especificaciones lo citan con ese nombre.

Este documento define la especificación del lenguaje STXT Schema, un mecanismo para validar documentos STXT mediante reglas semánticas formales.

Un schema:

  • Es un documento STXT con namespace @stxt.schema.
  • Define los nodos, tipos y cardinalidades del namespace objetivo.
  • No modifica la sintaxis base de STXT; opera sobre la estructura ya parseada.

Este documento define qué es un schema y cómo valida. La forma en que una herramienta localiza los schemas en el sistema de ficheros (directorios .stxt, niveles de usuario y sistema, STXT_PATH) se define en STXT-DISCOVERY-SPEC.

1.1 Versión de esta especificación

Esta especificación lleva su propia fecha y su propio estado en los campos Last modif y Status de su Metadata, independientes de los de las demás especificaciones de STXT y con el significado que fija STXT-SPEC §1.1. Está en Aurora. Depende de STXT-SPEC: un schema es un documento STXT.

2. Terminología

Las palabras clave "DEBE", "NO DEBE", "DEBERÍA", "NO DEBERÍA", y "PUEDE" deben interpretarse según RFC 2119 y RFC 8174: tienen ese significado únicamente cuando aparecen en mayúsculas, como aquí.

Términos como nodo, indentación, namespace, inline y bloque >> mantienen su significado en STXT-SPEC.

3. Relación entre STXT y Schema

La validación mediante schema ocurre después del parseo STXT:

  1. Parseo del documento a una estructura jerárquica STXT.
  2. Resolución del namespace efectivo de cada nodo.
  3. Aplicación del schema correspondiente.

Una implementación PUEDE aplicar la validación durante el proceso de parseo, siempre que dicha validación permanezca débilmente acoplada al parser base. Esto permite detectar errores antes de finalizar el parseo completo.

4. Estructura general de un Esquema

Un documento schema DEBE tener como nodo raíz:

Schema (@stxt.schema): <namespace_objetivo>

Reglas:

  • <namespace_objetivo> DEBE ser un namespace válido según STXT-SPEC.
  • El nodo raíz Schema DEBE pertenecer al namespace @stxt.schema.
  • El documento schema PUEDE incluir un nodo Description.
  • El documento schema DEBE incluir uno o más nodos Node.
  • Un documento schema es exactamente un nodo Schema: ni más de un nodo raíz, ni ninguno (sección 13, condición 12).

Ejemplo:

Schema (@stxt.schema): com.example.docs
    Description: Schema de ejemplo
    Node: Document
        Type: GROUP
        Children:
            Child: Autor
            Child: Fecha
                Max: 1
            Child: Content
                Min: 1
                Max: 1
            Child: Metadata (org.example.meta)
                Max: 1
    Node: Autor
    Node: Fecha
        Type: DATE
    Node: Content
        Type: TEXT

5. Un schema por namespace

Para cada namespace lógico:

  • NO DEBE existir más de un schema efectivo simultáneamente.
  • Si una implementación dispone de varios schemas candidatos para el mismo namespace, la elección la fija STXT-DISCOVERY-SPEC: gana el nivel más cercano al documento, y dos definiciones del mismo namespace en el mismo nivel dejan a ese namespace sin definición activa.
  • Para una validación concreta, sólo DEBE existir un único schema efectivo.

El namespace vacío no se valida. Un nodo cuyo namespace efectivo es "" (el de todo lo que no declara ni hereda namespace, STXT-SPEC §7) es válido por definición: un validador NO DEBE buscar schema para él ni emitir SCHEMA_NOT_FOUND por él. Como los hijos heredan el namespace, un documento sin namespace queda entero fuera de la validación; un nodo que declare un namespace dentro de él se valida, contra el schema de ese namespace. En consecuencia, ningún schema ni plantilla PUEDE tener "" como namespace objetivo: no hay nada que pueda resolverse contra él.

6. Modelo de contenido cerrado

STXT Schema usa un modelo de contenido cerrado. Esto significa que, para cada nodo del documento, sólo se permiten los hijos directos declarados explícitamente en el schema.

Reglas:

  • Si un Node declara un bloque Children, sus instancias en el documento sólo PUEDEN tener como hijos directos los nodos declarados mediante Child (cada uno identificado por su par lógico nombre canónico + namespace efectivo).
  • Si un Node no declara Children, sus instancias en el documento NO PUEDEN tener ningún hijo directo (cierre total).
  • La aparición de un hijo directo no declarado DEBE provocar un error de validación.

Cualquier Node puede ser raíz de un documento. El schema no distingue entre nodos "raíz" y nodos "hijo": un documento cuyo nodo raíz es cualquier Node del namespace es válido si ese nodo cumple sus reglas. Así, contra el schema de la sección 15.5, tanto Document (com.example.docs): … como Title (com.example.docs): … son raíces válidas. Esto permite documentos parciales (un fragmento, un fichero por sección) sin schemas adicionales; un schema que quiera restringir la raíz lo hace por convención, no por declaración.

Este modelo es coherente con la filosofía de STXT de fallar de forma ruidosa: un nodo mal escrito (por ejemplo Titel en lugar de Title) se detecta como hijo no declarado en lugar de aceptarse silenciosamente.

Nota sobre evolución. Un schema admite ampliaciones compatibles —añadir un Child de cardinalidad mínima cero, rebajar un Min, ampliar un Max: cambios que permiten más casos, no menos— sin que ningún documento existente deje de validar. El matiz es que la compatibilidad es respecto a la definición ampliada: un documento que use lo añadido no valida contra una copia anterior del schema, de modo que si los consumidores del namespace no actualizan su copia a la vez, el mismo documento puede validar en uno y fallar en otro. Decidir si una ampliación necesita versión depende, por tanto, del caso de uso: de cómo se distribuye la definición, no solo del cambio. Para esas situaciones, y siempre para los cambios incompatibles —un Child obligatorio nuevo, retirar uno, estrechar una cardinalidad—, la práctica recomendada es versionar el namespace en el propio namespace (por ejemplo com.example.docs.v1com.example.docs.v2), de modo que cada versión tenga su schema y los documentos declaren la versión que usan.

Nodos cross-namespace no resueltos. Declarar un Child que apunta a otro namespace no requiere disponer del schema de ese namespace: la carga de un schema nunca mira dentro de otros schemas, de modo que el resultado es independiente del orden de carga. La comprobación se hace al validar un documento: si un nodo de ese namespace aparece, su schema DEBE estar disponible y el nodo DEBE existir en él como Node:; en caso contrario, el documento es inválido. Un hijo cross-namespace declarado pero nunca usado no plantea ningún problema (ver sección 8.1).

7. Definición de Nodos (Node:)

7.1 Forma básica

Node: Nombre Nodo
    Description: Descripción del nodo
    Type: INLINE
    Children:
        Child: Nombre Hijo (otro.namespace)
            Min: 1
            Max: 1

Reglas:

  • El valor inline de Node DEBE ser un nombre de nodo válido según STXT-SPEC.
  • El emparejamiento entre un Node y los nodos del documento se hace por nombre canónico (STXT-SPEC §6): Node: Nombre Nodo valida nombre-nodo, NOMBRE_NODO y Nombre Nodo, y lo mismo vale para los Child.
  • Cada Node DEBE ser único dentro del schema a nivel de nombre canónico.
  • Cada Node define la semántica del nodo en el namespace objetivo del schema.
  • Si Type se omite, el tipo por defecto es INLINE.
  • Un Node NO DEBE contener más de un nodo Description, más de un nodo Type, más de un nodo Children ni más de un nodo Values.
  • Sólo los tipos que admiten hijos (ver sección 9) PUEDEN declarar Children. Declarar Children en un tipo que no admite hijos DEBE provocar un error de schema.
  • El nombre de un Child PUEDE incluir un namespace cuando el hijo pertenece a otro namespace (sección 8).
  • Min y Max son opcionales dentro de un Child (sección 10).

7.2 Valores en tipos ENUM

Node: Nombre Nodo
    Description: Descripción del nodo
    Type: ENUM
    Values:
        Value: valor 1
        Value: valor 2
        Value: valor 3

El tipo ENUM, y sólo ENUM, PUEDE especificar un nodo Values con los valores permitidos mediante nodos Value. Si existe Values, DEBE contener al menos un nodo Value. Si un Node declara Type: ENUM, DEBE incluir Values. Cada Value DEBE tener un valor inline no vacío tras el recorte: un Value: vacío DEBE provocar un error de schema (VALUE_EMPTY), porque una enumeración cuyo único valor válido es la cadena vacía no tiene sentido. Por la misma razón un Child: sin valor es un error (INVALID_NODE_NAME, sección 13.1).

Como ENUM no admite hijos (sección 9), un Node de tipo ENUM NO DEBE declarar Children.

8. Hijos (Children:) y namespaces cruzados

Un nodo PUEDE tener una entrada Children. Si existe Children, DEBE contener uno o más nodos Child con la información de los hijos permitidos.

Un Child PUEDE pertenecer a otro namespace, en cuyo caso se indica en el nombre del propio Child. Ejemplo:

Node: nombre del nodo
    Children:
        Child: nombre del hijo (namespace.del.hijo)
            Min: 0
            Max: 1
  • Si se omite el namespace, el Child pertenece al namespace objetivo del schema actual.
  • Si se indica un namespace explícito, el Child pertenece a ese namespace concreto.
  • Dentro de un mismo nodo Children, una implementación NO DEBE aceptar dos nodos Child que apunten al mismo par lógico nombre canónico + namespace efectivo.

8.1 Nodos definidos explícitamente

Todo nodo que aparezca en Children debe tener una definición propia como Node: en su schema correspondiente. El cuándo se comprueba depende del namespace del hijo: para hijos del mismo namespace del schema, en la carga del schema; para hijos de otro namespace, en la validación del documento (cuando el nodo aparece).

Esta regla excluye los hijos sin definición y garantiza que todos los nodos tienen semántica definida.

Esto implica:

  • Un Child sin namespace explícito DEBE tener su definición Node: en el propio schema; su ausencia DEBE provocar un error de schema en la carga (sección 13).
  • Declarar Child: Metadata (org.example.meta) no requiere disponer del schema de org.example.meta: la carga de un schema nunca mira dentro de otros schemas (independencia del orden de carga).
  • Si un nodo Metadata (org.example.meta) aparece en un documento, el schema de org.example.meta DEBE estar disponible y DEBE definir Node: Metadata; en caso contrario, es un error de validación del documento (sección 13). Un hijo cross-namespace declarado pero nunca usado no plantea ningún problema.

9. Tipos

Los tipos definen:

  1. La forma del valor del nodo (inline, bloque >>, ambas o ninguna).
  2. Si el nodo admite hijos.
  3. La validación del contenido.

Se definen dentro de Node, mediante un elemento Type. Ejemplo:

Node: nombre del nodo
	Type: TIPO_DEL_NODO
	Children:
		Child: Nombre Hijo

9.1 Modelo de dos propiedades independientes

Cada tipo se describe mediante dos propiedades independientes:

  • Forma del valor: INLINE, BLOCK, INLINE/BLOCK o NONE.
  • Admite hijos: SÍ o NO.

Estas dos propiedades son independientes entre sí. En particular, "admite hijos" no se deriva de la forma del valor. La regla de compatibilidad es la siguiente:

Sólo los dos tipos estructurales genéricos — INLINE y GROUP — admiten hijos. Todos los tipos con validación de contenido específico son hojas (no admiten hijos).

Razón: en cuanto un tipo declara que valida un dato concreto (NUMBER, DATE, BOOLEAN, ENUM, etc.), ese nodo es un dato, y un dato es una hoja. Si se necesita valor más estructura, se usa el tipo genérico INLINE, que existe precisamente para eso; y si se necesita sólo estructura, se usa GROUP.

Otras consideraciones:

  • El tipo NO controla obligatoriedad; sólo forma y validez del valor. La obligatoriedad de aparición se controla mediante cardinalidad.
  • El valor de Type DEBE coincidir exactamente con uno de los tipos definidos en esta sección.
  • Declarar Children en un Node cuyo tipo no admite hijos DEBE provocar un error de schema.

9.2 Tipos estructurales básicos

Una implementación conforme DEBE admitir estos tipos y DEBE validar su estructura.

Tipo Forma del valor Admite hijos Descripción / Validación
INLINE INLINE Texto inline :. Tipo por defecto. Valor opcional sin validación específica. Admite hijos.
GROUP NONE No admite valor textual. Sólo hijos estructurados.
BLOCK BLOCK NO Sólo bloque >> de texto. No admite hijos.
TEXT INLINE/BLOCK NO Texto genérico. Puede ser inline : o bloque >>. No admite hijos.

9.3 Tipos básicos de contenido INLINE

Una implementación conforme DEBE admitir estos tipos y DEBE validar su estructura.

Tipo Forma del valor Admite hijos Descripción / Validación
BOOLEAN INLINE NO true o false.
NUMBER INLINE NO Número decimal, con signo y exponente opcionales (ver 9.4).
ENUM INLINE NO Sólo valores especificados (ver 9.6).

9.4 Tipos ampliados de contenido INLINE

Una implementación conforme DEBE admitir estos tipos y DEBERÍA validar su estructura.

Tipo Forma del valor Admite hijos Descripción / Validación
INTEGER INLINE NO Entero con signo opcional.
NATURAL INLINE NO Entero sin signo (≥ 0).
DATE INLINE NO Fecha AAAA-MM-DD, válida en el calendario.
TIME INLINE NO Hora hh:mm:ss, en rango.
TIMESTAMP INLINE NO Fecha y hora ISO 8601, AAAA-MM-DDThh:mm[:ss[.f…]][Z|±hh:mm].
UUID INLINE NO UUID 8-4-4-4-12 hexadecimal, sin distinguir mayúsculas.
URL INLINE NO URL absoluta: esquema y host obligatorios (ver abajo).
EMAIL INLINE NO Dirección de correo, sola o con nombre (ver abajo).

Gramática de los tipos de 9.3 y 9.4. Cada tipo acepta exactamente las cadenas de su regla, sobre el valor inline tal como lo entrega el parser (ya recortado). Es la gramática normativa: dos implementaciones conformes aceptan y rechazan los mismos valores.

BOOLEAN   = "true" | "false"                              ; exacto, sensible a mayúsculas
NUMBER    = [ signo ] ( dígitos [ "." [ dígitos ] ] | "." dígitos ) [ ( "e" | "E" ) [ signo ] dígitos ]
INTEGER   = [ signo ] dígitos
NATURAL   = dígitos                                       ; sin signo
DATE      = AAAA "-" MM "-" DD                            ; fecha existente del calendario gregoriano
                                                          ; (proléptico), año 0000–9999
TIME      = hh ":" mm ":" ss                              ; 00–23, 00–59, 00–59; sin fracción ni zona
TIMESTAMP = DATE "T" hh ":" mm [ ":" ss [ "." dígitos ] ] [ "Z" | signo hh ":" mm ]
                                                          ; mismos rangos; segundos, fracción y zona opcionales
UUID      = 8hex "-" 4hex "-" 4hex "-" 4hex "-" 12hex     ; hex = [0-9A-Fa-f]; no se comprueban versión ni variante
signo     = "+" | "-"
dígitos   = dígito { dígito }                             ; solo ASCII 0–9; se admiten ceros a la izquierda

Precisiones:

  • NUMBER no es el número de JSON: admite +1, 1., .5 y 007. Es un valor textual: no se comprueba que quepa en ningún tipo numérico de la plataforma ni se redondea.
  • DATE y TIMESTAMP exigen una fecha existente: 2026-02-30 y 2026-13-01 son inválidas, 2024-02-29 es válida. TIME y TIMESTAMP exigen hora en rango: 24:00:00 y 10:60:00 son inválidas. No se admiten segundos intercalares (ss = 60).
  • TIMESTAMP: la fracción tiene uno o más dígitos; sin zona, la interpretación (hora local u otra) es de la aplicación; la zona se valida solo sintácticamente (±hh:mm en rango), no se comprueba que exista.
  • Las expresiones de esta sección y las de 9.5 son las que debe implementar un puerto; no se delega en los analizadores de fechas o URL de la plataforma, que discrepan entre sí.

El tipo EMAIL admite dos formas, y en las dos la dirección se valida igual:

  • La dirección sola: [email protected].
  • La dirección con un nombre para mostrar delante y la dirección entre < y >: Joan Costa <[email protected]>. El nombre es cualquier texto no vacío que no contenga < ni > (si lleva comillas, forman parte del nombre; no se interpretan), y el blanco entre el nombre y < es opcional.

Una implementación conforme DEBE aceptar las dos formas y NO DEBE aceptar < y > sin nombre delante, desparejados, ni seguidos de nada. Es la forma habitual de las cabeceras de correo (el mailbox de RFC 5322), sin las comillas ni los comentarios de esa gramática. La gramática normativa es esta (blanco es el de STXT-SPEC §4: U+0020 o U+0009):

EMAIL     = Direccion
          | Nombre { Blanco } "<" Direccion ">"
Nombre    = texto no vacío sin "<" ni ">", cuyo último carácter no es un blanco
Direccion = Local "@" Dominio                     ; 254 caracteres como máximo en total
Local     = 1*64( atext | "." )                   ; de 1 a 64 caracteres
atext     = letra o dígito ASCII, o uno de:  ! # $ % & ' * + / = ? ^ _ ` { | } ~ -
Dominio   = 1*( letra o dígito ASCII | "." | "-" ) "." TLD
TLD       = 2*63( letra ASCII )                   ; de 2 a 63 letras

Precisiones sobre EMAIL:

  • Solo ASCII: las direcciones internacionalizadas (EAI) y los dominios no ASCII sin convertir a punycode no se admiten. Es deliberado: una única gramática exacta, sin tablas Unicode que cada plataforma versiona a su manera.
  • Permisivo dentro de esos límites: no se valida el dot-atom completo de RFC 5322 ([email protected] o [email protected] pasan) ni las etiquetas del dominio una a una (a..b.example.com pasa). El valor del tipo está en cazar el @ ausente, los blancos, el dominio sin punto o el TLD olvidado, no en replicar RFC 5322.
  • Los límites de longitud son los prácticos de RFC 5321: 64 caracteres la parte local y 254 la dirección completa; el TLD, de 2 a 63 letras.

El tipo URL acepta únicamente URL absolutas con esquema y host. La gramática es propia de esta especificación, no la del analizador de URL de cada plataforma, para que todas las implementaciones acepten exactamente lo mismo:

URL       = Esquema "://" [ Usuario "@" ] Host [ ":" Puerto ] [ "/" Ruta ] [ "?" Consulta ] [ "#" Fragmento ]

Esquema   = letra { letra | dígito | "+" | "-" | "." }        ; sin distinguir mayúsculas
Usuario   = uno o más caracteres que no sean blanco, "/", "?", "#" ni "@"
Host      = "[" uno o más de ( hex | ":" | "." ) "]"             ; IPv6 entre corchetes
          | uno o más caracteres que no sean blanco, "/", "?", "#", "@", ":", "[" ni "]"
Puerto    = dígito { dígito }
Ruta      = caracteres que no sean blanco, "?" ni "#"           ; puede quedar vacía
Consulta  = caracteres que no sean blanco ni "#"                ; puede quedar vacía
Fragmento = caracteres que no sean blanco                       ; puede quedar vacío

Reglas:

  • Una implementación conforme DEBE aceptar cualquier esquema con esa forma (https, ftp, git+ssh, …): no hay lista de esquemas permitidos.
  • El host DEBE ser no vacío; no se exige dominio de primer nivel (http://localhost valida) ni se resuelve nada. Los caracteres no ASCII se aceptan tal cual, en el host y en el resto (un IDN no se convierte a punycode).
  • Una implementación NO DEBE aceptar un valor sin esquema (stxt.dev, www.stxt.dev/x), con esquema pero sin // y host (mailto:[email protected], urn:isbn:…, tel:…, file:///etc/hosts), con blancos interiores, ni con puerto no numérico.
  • La comprobación es sintáctica: no se normaliza, no se decodifican los %xx ni se comprueba que el recurso exista.

Nota de diseño (no normativa): no hay un tipo URI general a propósito. Una URI según RFC 3986 es casi cualquier cadena con dos puntos (a:b lo es), así que un tipo así apenas validaría más que TEXT. El valor de URL está en cazar el esquema olvidado o el host mal escrito. Para identificadores que no son direcciones web (urn:isbn:…, tel:…) se usa TEXT o un ENUM; para correo, EMAIL.

9.5 Tipos ampliados de contenido binario INLINE/BLOCK

Una implementación conforme DEBE admitir estos tipos y PUEDE validar su estructura.

Tipo Forma del valor Admite hijos Descripción / Validación
HEXADECIMAL INLINE/BLOCK NO [0-9A-Fa-f]+. Cadena hexadecimal.
BINARY INLINE/BLOCK NO [01]+. Cadena binaria.
BASE64 INLINE/BLOCK NO Base64 estándar: alfabeto A–Z a–z 0–9 + /, relleno = final opcional, sin bits sobrantes.

En los tres tipos, y en ambas formas, la gramática se aplica sobre el valor sin ningún blanco: antes de validar se eliminan todos los espacios (U+0020) y tabuladores (U+0009), estén donde estén, y en la forma BLOCK también los saltos de línea y las líneas vacías, de modo que la validación se aplica sobre la concatenación de las líneas. Esto permite agrupar los dígitos (DE AD BE EF, 1010 1010) y partir en varias líneas contenidos largos (p. ej. Base64 a 76 columnas). Ningún otro carácter se ignora: un separador distinto del blanco (DE:AD, DE-AD) es un valor inválido.

BASE64 es el alfabeto estándar de RFC 4648 §4 (no el URL-safe con - y _). El relleno = final PUEDE omitirse; el contenido DEBE decodificarse sin bits sobrantes, es decir, volver a codificarse en la misma cadena (sin contar el relleno). Una cadena vacía no es válida en ninguno de los tres tipos.

9.6 Tipo ENUM

El tipo ENUM permite enumerar de forma explícita los valores permitidos para un nodo. Reglas:

  • La comparación DEBE hacerse sobre el valor inline tal como lo entrega el parser (ya recortado a izquierda y derecha, STXT-SPEC §10.1).
  • La comparación DEBE ser exacta y CASE-SENSITIVE.
  • La comparación NO DEBE aplicar canonización adicional, eliminación de diacríticos ni normalizaciones equivalentes al nombre canónico de los nodos.
  • El nodo DEBE definir Values con nodos Value, que representan los valores permitidos.
  • Cada Value DEBE ser único y no vacío (sección 7.2).
  • ENUM no admite hijos: un Node de tipo ENUM NO DEBE declarar Children.

Ejemplo:

Node: Nombre Nodo
    Type: ENUM
    Values:
        Value: valor 1
        Value: valor 2
        Value: valor 3

Una implementación conforme DEBE comprobar los tipos ENUM contra sus valores permitidos y DEBE rechazar cualquier valor que no coincida exactamente con uno de ellos.

Nota de interoperabilidad (no normativa). La comparación exacta es por puntos de código. Los valores son literales y no se normalizan a NFC (a diferencia de los nombres de nodo, STXT-SPEC §4.3): café en forma precompuesta (NFC) y café en forma descompuesta (NFD) se ven idénticos pero no coinciden. Algunos flujos de entrada — históricamente, macOS — producen NFD. Quien defina valores ENUM con diacríticos debería escribirlos en NFC, que es lo que producen los editores habituales.

9.7 Tipo MARKDOWN

Una implementación conforme DEBE admitir este tipo.

Tipo Forma del valor Admite hijos Descripción / Validación
MARKDOWN INLINE/BLOCK NO Texto que se interpreta como Markdown. Sin validación estructural.

Reglas:

  • A efectos de validación, MARKDOWN equivale a TEXT: todo texto es Markdown válido, así que una implementación NO DEBE rechazar ningún valor por su contenido.
  • Su función es ser un contrato de interpretación para los consumidores (renderizadores, exportadores, editores): el texto DEBERÍA interpretarse según CommonMark. Las extensiones (p. ej. tablas) son decisión de la aplicación.
  • En la forma BLOCK, los saltos de línea y las líneas vacías se conservan (a diferencia de los tipos binarios de la sección 9.5): forman parte del texto Markdown.
  • Como todo tipo con contenido específico, MARKDOWN no admite hijos (sección 9.1).

Nota de diseño (no normativa): MARKDOWN es el único formato incrustado que STXT Schema define, porque cubre el único dominio que STXT delega por diseño — el formato de la prosa (negrita, enlaces, listas dentro del texto) — y porque su forma cruda sigue siendo legible, coherente con el principio Human-First. Los tipos para formatos de datos incrustados (JSON, XML, YAML, TOML, …) son un no-objetivo explícito (sección 11): STXT ya es el formato de estructura, y un tipo así enterraría estructura en blobs opacos. La estructura se expresa en STXT.

10. Cardinalidades

Las cardinalidades se expresan mediante los nodos Min y Max dentro de cada Child. Son enteros no negativos opcionales que indican el número mínimo o máximo de apariciones permitidas de ese hijo.

Reglas:

  • Si Min se omite, el mínimo efectivo es 0.
  • Si Max se omite, el máximo efectivo es ilimitado.
  • Min y Max NO DEBEN superar 4294967295 (2³² − 1); un valor mayor DEBE provocar un error de schema (CARDINALITY_NOT_VALID). La cota es independiente de la plataforma y está muy por encima de cualquier recuento de hijos alcanzable con los límites recomendados del parser (STXT-SPEC §11.2); quien quiera «sin máximo» lo dice omitiendo Max, no con un número gigante.
  • Min y Max NO DEBEN aparecer más de una vez dentro del mismo Child.
  • Si existen ambos, Min NO DEBE ser mayor que Max.
  • Max: 0 es legal y declara un hijo prohibido: el Child existe en el modelo pero ninguna aparición es válida. Sirve, por ejemplo, para retirar en una versión de un namespace un hijo que otra permitía. Una implementación NO DEBE tratarlo como error.
  • La cardinalidad se aplica por instancia del nodo padre.
  • La cardinalidad cuenta sólo hijos directos con el mismo nombre canónico y el mismo namespace efectivo.
  • La validación de cardinalidad es independiente del orden de los hijos: cuenta apariciones, no posiciones (ver sección 11).
  • Una implementación conforme DEBE comprobar las cardinalidades.

11. Orden de los hijos

STXT Schema no valida el orden de los hijos. La validación de cardinalidad y de presencia es independiente de la posición de cada hijo dentro de su padre: sólo cuenta cuántas veces aparece cada hijo, no en qué orden.

Dos documentos con los mismos hijos en distinto orden validan exactamente igual. Esto es una decisión de diseño coherente con el principio Human-First: el autor de un documento no debería tener que recordar el orden de los campos.

Orden preservado, no validado. Aunque el orden no se valida, el orden de aparición de los hijos se preserva en el árbol parseado (garantía del núcleo, STXT-SPEC). Por tanto, una aplicación que necesite significado posicional (por ejemplo, secciones consecutivas de un documento) lo obtiene del propio árbol, no de la validación. La semántica del orden corresponde a la aplicación.

La validación de orden secuencial es un no-objetivo explícito de STXT Schema: introducir modelos de contenido ordenados (al estilo de xs:sequence) complicaría los validadores (autómatas, ambigüedad de partículas) sin un beneficio claro para los casos de uso objetivo.

Otros no-objetivos explícitos de STXT Schema son: patrones o expresiones regulares sobre valores, valores por defecto, reglas condicionales entre campos ("si aparece A, entonces B"), y los tipos para formatos de datos incrustados (JSON, XML, YAML, TOML, …; ver la nota de diseño de la sección 9.7). Mantener el modelo pequeño y predecible es un objetivo de diseño: esa semántica pertenece a la aplicación.

12. Ejemplos Normativos

12.1 Schema con referencias cross-namespace

Schema (@stxt.schema): com.example.docs
    Node: Document
        Type: GROUP
        Children:
            Child: Metadata (org.example.meta)
                Max: 1
            Child: Content
                Min: 1
                Max: 1
    Node: Content
        Type: BLOCK

Y en org.example.meta:

Schema (@stxt.schema): org.example.meta
    Node: Metadata
        Type: INLINE

12.2 Documento válido

Document (com.example.docs):
    Metadata (org.example.meta): info
    Content >>
        Línea 1
        Línea 2

12.3 Estructura recursiva

El modelo cerrado admite recursión: un Node puede declararse como hijo de sí mismo o de un ancestro.

Schema (@stxt.schema): com.example.docs
    Node: Section
        Type: GROUP
        Children:
            Child: Title
                Min: 1
                Max: 1
            Child: Section
    Node: Title
        Type: INLINE

Aquí Section puede contener más nodos Section anidados a cualquier profundidad (limitada por el límite de profundidad recomendado en STXT-SPEC, sección de seguridad).

13. Errores de Schema

Un schema es inválido si:

  1. El nodo raíz no tiene la forma Schema (@stxt.schema): <namespace_objetivo>, o el namespace objetivo está vacío o no es válido según STXT-SPEC.
  2. No define ningún nodo Node.
  3. Define dos Node con el mismo nombre canónico.
  4. Usa un Type desconocido.
  5. Define Children en un Node cuyo tipo no admite hijos (sección 9).
  6. Define Values en un Node cuyo tipo no es ENUM (sección 7.2).
  7. La cardinalidad es inválida (Min > Max, valor no entero no negativo, valor mayor que 4294967295, Min/Max duplicados).
  8. Un Node de tipo ENUM no define Values, o Values no contiene ningún Value.
  9. Aparece un Value duplicado tras la normalización inline por trim.
  10. Aparecen dos nodos Child equivalentes (mismo nombre canónico + namespace efectivo) dentro del mismo Children.
  11. Aparece un hijo en Children del mismo namespace del schema cuyo Node no está definido en el propio schema.
  12. El documento schema tiene más de un nodo raíz, o ninguno: un documento schema es exactamente un nodo Schema.
  13. Un nodo del schema (Node, Child, Type, Min…) está escrito en forma >>; todos son inline salvo Description, que es TEXT.
  14. Un Value de un ENUM está vacío (sección 7.2).

Un documento es inválido frente a un schema si:

  • Un nodo presenta un hijo directo no declarado en el Children de su definición (modelo cerrado, sección 6).
  • Un nodo sin Children declarado presenta cualquier hijo directo.
  • Se incumple una cardinalidad declarada.
  • El valor de un nodo no cumple la validación de su tipo.
  • La forma del nodo (inline o bloque >>, valor presente o ausente, con o sin hijos) no es la de su tipo (sección 9).
  • Un valor ENUM no coincide exactamente con ninguno de sus Value.
  • Aparece un nodo con namespace no vacío que no tiene schema disponible (SCHEMA_NOT_FOUND; los nodos con namespace "" no se validan, sección 5), o cuyo nombre no existe como Node: en el schema de su namespace (sección 8.1).

13.1 Códigos de error

Como en STXT-SPEC §11.1, cada error lleva un código estable e idéntico en todas las implementaciones, y no se renombra. Un documento schema se valida primero como documento contra el meta-esquema de la sección 15, por lo que sus errores de forma (un Node sin valor, dos Type, un Min que no es NATURAL…) llegan con los códigos de la segunda tabla; después se aplican las reglas propias de la primera.

Errores del schema (primera lista de esta sección):

Código Condición
SCHEMA_ROOT_NOT_VALID 1: la raíz no es Schema (@stxt.schema): …, o el namespace objetivo no es válido
SCHEMA_NAMESPACE_EMPTY 1: el namespace objetivo está vacío
SCHEMA_MULTIPLE_ROOTS 12: el documento schema tiene más de un nodo raíz, o ninguno
SCHEMA_NODE_NOT_INLINE 13: un nodo del schema está escrito en forma >>
NODE_DUPLICATED 3: dos Node con el mismo nombre canónico
INVALID_NODE_NAME el valor de un Node o un Child no es un nombre válido (sección 7.1)
TYPE_NOT_VALID 4: tipo desconocido (el meta-esquema lo detecta antes como INVALID_VALUE del ENUM Type; TYPE_NOT_VALID queda para una implementación cuyo registro de tipos no tenga el tipo)
CHILDREN_NOT_ALLOWED_FOR_TYPE 5: Children en un tipo que no admite hijos
VALUES_NOT_ALLOWED_FOR_TYPE 6: Values en un tipo distinto de ENUM
CARDINALITY_NOT_VALID 7: Min o Max no es un entero no negativo, o supera 4294967295
MIN_GREATER_THAN_MAX 7: Min mayor que Max
VALUES_REQUIRED 8: ENUM sin Values, o Values sin ningún Value
VALUE_DUPLICATED 9: dos Value iguales tras el recorte
VALUE_EMPTY 14: un Value de un ENUM sin valor
VALUES_DUPLICATED dos nodos Values en el mismo Node
CHILD_DUPLICATED 10: dos Child con el mismo par nombre canónico + namespace
CHILD_NOT_DEFINED 11: un Child del mismo namespace sin su Node

Errores del documento frente al schema (segunda lista):

Código Condición
SCHEMA_NOT_FOUND nodo con namespace no vacío sin schema disponible
NODE_NOT_DEFINED_IN_SCHEMA el nombre del nodo no existe como Node en el schema de su namespace
CHILD_NOT_DECLARED hijo directo no declarado en Children (modelo cerrado), o cualquier hijo bajo un Node sin Children
TOO_FEW_CHILDREN menos apariciones de un hijo que su Min
TOO_MANY_CHILDREN más apariciones de un hijo que su Max
INVALID_VALUE el valor no cumple la gramática de su tipo, o no es uno de los Value de un ENUM
BLOCK_FORM_REQUIRED forma inline donde el tipo exige bloque >> (BLOCK)
BLOCK_FORM_NOT_ALLOWED forma bloque >> donde el tipo exige inline
VALUE_NOT_ALLOWED valor en un nodo de tipo GROUP
CHILDREN_NOT_ALLOWED hijos bajo un nodo de tipo TEXT o MARKDOWN
TYPE_NOT_VALID el tipo declarado en el schema no existe en la implementación
UNEXPECTED_ERROR excepción no prevista de la implementación, con la línea del nodo

14. Conformidad

Una implementación es conforme si:

  • Implementa íntegramente este documento.
  • Valida tipos, formas de valor, cardinalidades y valores permitidos (ENUM).
  • Aplica el modelo de contenido cerrado (sección 6).
  • Aplica la regla de compatibilidad de hijos por tipo (sólo INLINE y GROUP admiten hijos).
  • Aplica la regla estricta de definición obligatoria de todos los nodos referenciados en Children (en la carga del schema para hijos del mismo namespace; en la validación del documento para hijos cross-namespace, sección 8.1).
  • Valida cardinalidades de forma independiente del orden.
  • Selecciona, para cada validación, un único schema efectivo por namespace, y no valida los nodos de namespace vacío (sección 5).
  • Rechaza documentos y schemas inválidos.

15. Schema del Schema (@stxt.schema)

Esta sección define el schema oficial del propio sistema de schemas: el meta-schema que valida todos los documentos del namespace @stxt.schema.

Nivel de garantía. El meta-schema valida la forma de un documento schema (qué nodos existen, sus tipos, sus cardinalidades). No puede expresar las reglas condicionales o cruzadas de este documento (por ejemplo "Values sólo si Type: ENUM", o "Min ≤ Max"), que pertenecen al lenguaje pero no al meta-schema. Por tanto, "validar contra el meta-schema" es condición necesaria pero no suficiente para ser un schema válido: además deben cumplirse las reglas de la sección 13.

15.1 Consideraciones

  • Todo documento schema es: Schema (@stxt.schema): <namespace-objetivo>
  • Un schema contiene:
    • Opcionalmente una Description.
    • Uno o más nodos Node.
  • Cada Node:
    • Tiene valor inline (el nombre del nodo del namespace objetivo).
    • Puede tener opcionalmente:
      • Description
      • Type
      • Children
      • Values
  • Cada Child (elemento de Children) define el nombre (y opcionalmente un namespace distinto) y puede tener:
    • Min: Número mínimo de nodos que deben aparecer. Si no existe el nodo no hay un mínimo establecido.
    • Max: Número máximo de nodos que pueden aparecer. Si no existe el nodo no hay un máximo establecido.
  • Cada Values:
    • Sólo puede aparecer en nodos Node de tipo ENUM.
    • Contiene uno o más nodos Value.
  • Los nombres (Schema, Node, Type, Children, Child, Description, Min, Max, Values, Value) pertenecen al namespace @stxt.schema.

15.2 Meta-Schema completo

Schema (@stxt.schema): @stxt.schema
    Node: Schema
        Children:
            Child: Description
                Max: 1
            Child: Node
                Min: 1
    Node: Node
        Children:
            Child: Type
                Max: 1
            Child: Children
                Max: 1
            Child: Description
                Max: 1
            Child: Values
                Max: 1
    Node: Children
        Type: GROUP
        Children:
            Child: Child
                Min: 1
    Node: Description
        Type: TEXT
    Node: Child
        Children:
            Child: Min
                Max: 1
            Child: Max
                Max: 1
    Node: Min
        Type: NATURAL
    Node: Max
        Type: NATURAL
    Node: Type
        Type: ENUM
        Values:
            Value: INLINE
            Value: BLOCK
            Value: TEXT
            Value: GROUP
            Value: BOOLEAN
            Value: NUMBER
            Value: ENUM
            Value: INTEGER
            Value: NATURAL
            Value: DATE
            Value: TIME
            Value: TIMESTAMP
            Value: UUID
            Value: URL
            Value: EMAIL
            Value: HEXADECIMAL
            Value: BINARY
            Value: BASE64
            Value: MARKDOWN
    Node: Values
        Type: GROUP
        Children:
            Child: Value
                Min: 1
    Node: Value

Nota: en el meta-schema, Node y Child son de tipo por defecto INLINE (su valor inline es el nombre del nodo objetivo o del hijo) y por eso admiten hijos. Esto ilustra que INLINE es el tipo que combina valor y estructura, el núcleo del sistema.

15.3 Lectura rápida

  • Schema Valor inline = namespace objetivo (ej. com.example.docs). Hijos: Description (?), Node (+).

  • Node Valor inline = nombre del nodo objetivo (ej. Document, Autor). Hijos opcionales:

    • Type: tipo concreto (si falta ⇒ INLINE).
    • Children: Nodo con listado de Child permitidos.
    • Description: texto explicativo.
    • Values: Valores permitidos (sólo tipo ENUM).
  • Type Inline (ENUM), con el nombre del tipo (GROUP, INLINE, NUMBER, etc.).

  • Children GROUP: contiene uno o más nodos Child.

  • Description TEXT: puede ser inline o multiline.

  • Values GROUP: contiene uno o más nodos Value.

  • Value Valor inline con uno de los valores permitidos para el ENUM.

15.4 Ejemplo mínimo válido

Schema (@stxt.schema): com.example.docs
    Node: Document

15.5 Ejemplo completo

Schema (@stxt.schema): com.example.docs
    Description: Schema de ejemplo
    Node: Document
        Type: GROUP
        Children:
            Child: Title
                Min: 1
                Max: 1
            Child: Author
            Child: Metadata (org.example.meta)
                Max: 1
    Node: Title
        Type: INLINE
    Node: Author
        Type: INLINE