Ir al contenido
Docxcelerate

Nodos

El modelo de nodos

Qué tienen en común todos los nodos, en qué se diferencian y dónde está documentado cada uno.

Un documento es un árbol de nodos. Todo nodo es tres cosas: un id, una clase y una regla para producir contenido.

<Paragraph          // the kind
  id="greeting"     // the address
>
  Dear {state.name},  {/* the rule */}
</Paragraph>

Resuelto con tus datos, se convierte en JSON puro:

{ "id": "greeting", "kind": "paragraph", "mode": "static", "text": "Dear Adaeze Nkemelu," }

Sin estilos, sin maquetación. Eso le corresponde al renderizador.

Cada tipo tiene una página propia, con todas las opciones que admite y una vista previa de cada manera de escribirlo.

Estructura

Section Groups nodes under a titled heading.

The only construct that nests today. Its title carries into the document outline, so the structure you write is the structure the reader sees.

export const Opening: Section = () => (
  <Section id="opening" title="Your renewal">
    <Greeting />
    <PriceChange />
  </Section>
);

Referencia de Section, con vistas previas →

TableOfContents A marker for a contents list, ahead of the renderers that build one.

The kind is part of the document schema and both renderers accept it. What is missing is downstream rather than in the authoring: the element places the marker and carries its title, and building the entries from the surrounding section titles is a renderer's job.

export const Contents: TableOfContents = () => (
  <TableOfContents id="contents" title="What is in this letter" />
);

Referencia de Table of contents, con vistas previas →

Texto

Paragraph A block of prose, written from your data or generated from prompts.

The workhorse. A static paragraph holds the text as its children; a dynamic one carries prompts and a placeholder, and is filled at request time. Both land as the same node kind, differing only by `mode` — which is inferred from what you supply, never declared. Supplying both text and a prompt on one element is an error rather than a precedence rule.

Helpers: Paragraph, useSetPrompts, useSetPlaceholders

export const Greeting: Paragraph = () => {
  const [state] = useState((data: SampleData) => ({ name: data.memberName }));

  return <Paragraph id="greeting">Dear {state.name},</Paragraph>;
};

Referencia de Paragraph, con vistas previas →

Medios

Image A picture resolved from your data or described by a prompt.

A static image points at something you hold — a signature, a logo, a site photograph — with every field able to vary per recipient. A dynamic image describes what is wanted and leaves the endpoint to make it.

export const Signature: Image = () => {
  const [state] = useState((data: SampleData) => ({
    src: data.signatureUrl,
    manager: data.managerName,
  }));

  return (
    <Image
      id="signature"
      src={state.src}
      alt={`Signed by ${state.manager}`}
      width={180}
      height={60}
    />
  );
};

Referencia de Image, con vistas previas →

Shape A drawn rectangle with the document's own words on top of it.

Word draws a real rectangle and the paragraphs sit on its fill rather than beside it. What separates it from a paragraph with a background is the size: a shape is a box you decided the dimensions of and does not grow with its text, which is exactly what a banner needs — one that changed depth with its wording would be a different notice on every letter. When the box *should* grow with what is in it, a `Table` with one cell is the thing to reach for.

export const RenewalBanner: ShapeComponent = () => {
  const { date } = useFormat("en-GB");
  const [state] = useState((data: SampleData) => ({
    plan: data.plan,
    renewsOn: data.renewsOn,
  }));

  return (
    <Shape id="renewal-banner" variant="bannerAttention" height={44}>
      <Paragraph id="renewal-banner-line">
        {state.plan} renews on {date(state.renewsOn)}
      </Paragraph>
    </Shape>
  );
};

Referencia de Shape, con vistas previas →

clipArt Named visual blocks — rules, marks, callouts — drawn by the renderer. Planeado

Not built yet. Nothing is fetched: the node names a shape and the renderer draws it, so it stays sharp in the DOCX and ships no asset.

Datos

Graph A real Word chart, declared as data.

Charts are declared, never drawn: `graphType` fixes the form, `data` carries the payload. What lands in the `.docx` is a DrawingML chart part with every value cached in it — the chart Word builds from Insert → Chart — so a reader can select it, restyle it and open its numbers. Nothing is rasterised, which is why charts cost the package no drawing library and no native module.

export const VisitsByMonth: Graph = () => {
  const [state] = useState((data: SampleData) => ({
    centreName: data.centreName,
    months: data.visitsByMonth.map((entry) => entry.month),
    visits: data.visitsByMonth.map((entry) => entry.visits),
  }));

  return (
    <Graph
      id="visits-by-month"
      title={`Visits to ${state.centreName}`}
      graphType="bar"
      valueAxisTitle="Visits"
      data={{
        categories: state.months,
        series: [{ label: "Visits", values: state.visits }],
      }}
      caption={`Your visits to ${state.centreName}, last six months`}
    />
  );
};

Referencia de Graph, con vistas previas →

Table A grid of cells, with the columns declared once.

The columns belong to the table, because every row shares them — a table whose columns do not line up is not a table. Everything else is an ordinary node: a `.map()` produces rows, a condition drops one, and each names itself. That is what lets a published invoice carry one loop the engine walks rather than a table full of special cases.

Helpers: Table, Row, Cell

export const VisitLog: Table = () => {
  const { number } = useFormat("en-GB");
  const [state] = useState((data: SampleData) => ({ months: data.visitsByMonth }));

  return (
    <Table id="visit-log" columns={[{ width: "auto" }, { width: 28, align: "right" }]}>
      <Row header>
        <Cell>Month</Cell>
        <Cell>Visits</Cell>
      </Row>
      {state.months.map((entry) => (
        <Row>
          <Cell>{entry.month}</Cell>
          <Cell>{number(entry.visits)}</Cell>
        </Row>
      ))}
    </Table>
  );
};

Referencia de Table, con vistas previas →

Los ids son direcciones

El id de un nodo es la forma en que un endpoint de generación apunta a ese párrafo, y la forma en que dos artefactos de compilación se alinean en un diff. Renombrar uno rompe todo lo que lo señale desde fuera, igual que renombrar una ruta de API.

Mantenlos únicos dentro del documento. No cuesta nada y hace legibles los logs.

De dónde sale el contenido

Todo nodo o bien tiene su contenido o bien tiene prompts para producirlo, y eso lo decide el componente en lugar de declararse en ninguna parte. Dale texto a un párrafo — o un src a un Image, o sus data a un Graph — y se resuelve en tu máquina. Dale prompts y un marcador de posición en su lugar, y se rellena en el momento de la petición.

Ambos se resuelven al mismo kind y se diferencian por mode en el documento compilado. mode es salida, no entrada: la compilación lo deriva de lo que aportó el componente, y el paquete le dice a un motor qué nodos tiene que resolver. Aportar los dos en un mismo elemento es un error, no un cara o cruz.

Escribir nodos cubre las ranuras de prompt y qué muestran las vistas previas en su lugar.

Anidamiento

section es hoy el único nodo que contiene hijos, y acepta cualquier clase, incluidas otras secciones. Un nodo de tabla con nodos en sus celdas es lo siguiente, con el mismo principio: los contenedores contienen los componentes que ya escribes, en lugar de un segundo modelo de contenido al lado.

Sobre estas vistas previas

Cada vista previa de aquí es una compilación real. src/nodes/, en el repositorio de este sitio, guarda un archivo por variante, escrito contra el paquete publicado; un paso de compilación resuelve cada uno mediante buildDocument y lo renderiza con el renderizador que sirve dxcl dev. El código que se muestra es el archivo que se ejecutó, y el JSON es lo que devolvió — de modo que estas páginas se rompen a gritos en vez de quedarse silenciosamente obsoletas.


Editar esta página en GitHub ↗ Leer esta página en Markdown