
Ejemplo de un generador DATA-GEN
En este ejemplo, un generador genera una tabla HTML para la operación DATA-GEN.
Si desea intentar ejecutar el código en el ejemplo, consulte Sentencias SQL para crear el archivo utilizado por el ejemplo para las sentencias SQL para crear el file utilizado por el programa.
Consulte Salida generada por las operaciones DATA-GEN en el ejemplo para el HTML generado por el programa.
Programa RPG con operaciones DATA-GEN
A continuación se muestra el programa RPG que utiliza la operación DATA-GEN.
- Las estructuras de datos se definen con un subcampo para cada fila esperada en la tabla HTML.
La estructura de datos descrita externamente utiliza sentencias EXTFLD para establecer las mayúsculas y minúsculas de los nombres necesarios para las cabeceras de columna en la tabla HTML. Para el campo ITEMPRICE , la sentencia EXTFLD también añade un subrayado. El generador sustituye los subrayados por espacios en blanco al generar las cabeceras de columna.
- Para las tres primeras operaciones DATA-GEN, el archivo de salida se especifica en el primer operando de la función incorporada %DATA. La opción "doc = file" indica que el primer operando es el nombre de un archivo.
- El programa que realiza la generación se especifica como el primer operando de la función incorporada %GEN. Consulte Programa para generar una tabla HTML para el origen del programa. El programa que realiza la generación soporta un carácter opcional o un valor UCS-2 como segundo operando de la función incorporada %GEN. Este valor se utiliza como título de la tabla.
- La operación DATA-GEN *START inicia una secuencia DATA-GEN.
- La siguiente operación DATA-GEN continúa la secuencia. Escribe una fila en la tabla HTML.
- La operación DATA-GEN *END finaliza la secuencia.
- El DATA-GEN final no forma parte de una secuencia. El resultado de la operación DATA-GEN se coloca en la variable especificada como primer operando de la función incorporada %DATA.
**free
DCL-C FILENAME 'MYORDERS';
DCL-F orders EXTDESC(FILENAME)
EXTFILE(*EXTDESC);
DCL-DS order EXTNAME(FILENAME : *INPUT) // 1
QUALIFIED;
Name extfld('NAME');
Type extfld('ITEMTYPE');
Item_Price extfld('ITEMPRICE');
END-DS;
DCL-DS customer QUALIFIED; // 1
Name VARCHAR(30);
Address VARCHAR(100);
Zip_Code PACKED(9);
END-DS;
DCL-S customerTable VARCHAR(1000);
DATA-GEN *START %DATA('order.html' : 'doc=file') // 2, 4
%GEN('GENHTMLTAB'
: 'Order for ' + %CHAR(%DATE()));// 3
READ orders order;
DOW NOT %EOF;
DATA-GEN order %DATA('order.html'
: 'doc=file output=continue') // 2, 5
%GEN('GENHTMLTAB'); // 3
READ orders order;
ENDDO;
DATA-GEN *END %DATA('order.html' : 'doc=file') // 2, 6
%GEN('GENHTMLTAB'); // 3
customer.Name = 'A. Smith';
customer.Address = '123 Elm Street';
customer.Zip_Code = 11111;
DATA-GEN customer %DATA(customerTable) // 7
%GEN('GENHTMLTAB'); // 3
*INLR = '1';
Programa para generar una tabla HTML
Tenga en cuenta los siguientes aspectos de la sección inicial del módulo:
- Este generador es un programa.
- Puesto que los subcampos nombre y valor del parámetro pasado al generador tienen datos UTF-16 , es conveniente establecer el CCSID predeterminado para los elementos UCS-2 en UTF-16.
- Copiar miembro QRNDTAGEN en el archivo QOAR/QRPGLESRC define el parámetro pasado al generador y las constantes con nombre para otra información que necesita el generador.
- La estructura de datos state_t define la información utilizada por el generador para realizar un seguimiento de la generación.
- Se definen varios códigos de error y mensajes coincidentes para los errores detectados por este generador.
**free
CTL-OPT OPTION(*SRCSTMT);
CTL-OPT MAIN(genHtmlTab); // 1
CTL-OPT CCSID(*UCS2 : *UTF16); // 2
/IF DEFINED(*CRTBNDRPG)
CTL-OPT DFTACTGRP(*NO);
/ENDIF
/copy QOAR/QRPGLESRC,QRNDTAGEN // 3
DCL-C MAX_CAPTION 10000;
DCL-DS state_t qualified template; // 4
haveHeader IND;
inDataStructure IND;
dataStructureName LIKE(QrnDgName_t);
numSubfields INT(10);
caption VARUCS2(MAX_CAPTION);
haveCaption IND;
END-DS;
DCL-DS errorCodes qualified; // 5
DCL-DS nestedStructNotAllowed;
code INT(10) INZ(1);
msg VARCHAR(100) INZ('Nested structures are not allowed.');
END-DS;
DCL-DS differentStruct;
code INT(10) INZ(2);
msg VARCHAR(100)
INZ('The data structure is not the same.');
END-DS;
DCL-DS eventNotSupported;
code INT(10) INZ(3);
msg VARCHAR(100) INZ('The event is not supported.');
END-DS;
DCL-DS valueNotInStruct;
code INT(10) INZ(4);
msg VARCHAR(100) INZ('All values must be subfields.');
END-DS;
DCL-DS userParmTypeNotSupported;
code INT(10) INZ(4);
msg VARCHAR(100) INZ('The user-parm must be UTF-16 or alphanumeric with the job CCSID.');
END-DS;
END-DS;
Procedimiento principal
Tenga en cuenta los siguientes aspectos del procedimiento:
- Se pasa un único parámetro al generador.
- La estructura de datos de estado contiene información de estado mantenida por el generador para todas las llamadas al generador para la operación DATA-GEN o la secuencia de operaciones DATA-GEN.
La estructura de datos se basa en un puntero, puesto que el almacenamiento para la estructura de datos se asignará desde el almacenamiento dinámico.
- Establecer el puntero pEnv desde el subcampo env del parámetro pasado al generador permite al generador llamar a los procedimientos de devolución de llamadaAviso: El puntero env es nulo para el suceso QrnDgEvent_12_Terminate .
No intente llamar a ningún procedimiento de devolución de llamada durante este suceso.
- El generador necesita desasignar el puntero de estado cuando la generación se ha completado, por lo que habilita el suceso QrnDgEvent_12_Terminate .
- Las devoluciones de llamada no están permitidas durante el suceso QrnDgEvent_12_Terminate , por lo que el generador sólo desasigna el puntero de estado y devuelve.
- Si la información de estado no se ha asignado todavía, el generador asigna e inicializa el puntero generatorParm en el parámetro pasado al generador.
- El generador establece el puntero de base para la información de estado del puntero generatorParm .
- Este suceso indica el inicio de una secuencia de operaciones DATA-GEN.
Si se ha especificado el segundo operando de %GEN, se pasa en el subcampo userParm del parámetro pasado al generador. El generador llamará al procedimiento getCaption para obtener el valor de userParm.
- Este suceso señala el final de una secuencia de operaciones DATA-GEN. El generador genera el final de la tabla HTML.
- Este suceso indica el inicio de los sucesos para una operación DATA-GEN relacionada con una variable.
Si se ha especificado el segundo operando de %GEN, se pasa en el subcampo userParm del parámetro pasado al generador. Si el título todavía no ha sido determinado por una llamada anterior al generador, el generador llamará al procedimiento getCaption para obtener el valor de userParm.
- Este suceso señala el final de los sucesos para una operación DATA-GEN relacionada con una variable. Si la operación DATA-GEN no forma parte de una secuencia de operaciones DATA-GEN, el generador genera el final de la tabla HTML.
- Estos sucesos indican el inicio de una matriz de estructuras de datos o una única estructura de datos. Como puede ser la primera vez que el generador vea algo relacionado con una estructura de datos, llama al procedimiento genStartTable para generar la cabecera de la tabla HTML, si es necesario.
Si el procedimiento genStartTable detecta un error en las llamadas al generador, informará del error a DATA-GEN, y el control no volverá a la sentencia que sigue a la llamada al procedimiento genStartTable.
- Este suceso señala el final de una matriz de estructuras de datos. Este generador no necesita hacer nada durante este suceso.
- El generador genera el principio de una fila en la tabla HTML.
- Este suceso señala el final de una estructura de datos. Este generador genera el final de la fila en la tabla HTML.
- Este suceso señala un valor escalar. Si el generador detecta que no es un subcampo, emite un error.
- El generador genera el principio de la columna en la tabla HTML.
- El generador llama al procedimiento de devolución de llamada QrnDgAddText para generar el valor para la columna. El parámetro valor del parámetro pasado al generador apunta a datos UTF-16 y el subcampo valueLenChars tiene el número de caracteres de doble byte en los datos UTF-16. Estos parámetros se pueden pasar directamente al procedimiento QrnDgAddText , ya que espera un puntero a los datos UTF-16 y el número de caracteres de doble byte.
- El generador genera el final de la columna en la tabla HTML.
- El generador no da soporte a ningún otro suceso.
DCL-PROC genHtmlTab;
DCL-PI *N;
parm LIKEDS(QrnDgParm_t); // 1
END-PI;
DCL-DS state LIKEDS(state_t) based(pState); // 2
pQrnDgEnv = parm.env; // 3
parm.doTerminateEvent = *ON; // 4
IF parm.event = QrnDgEvent_12_Terminate;
DEALLOC(N) parm.generatorState; // 5
RETURN;
ENDIF;
IF parm.generatorState = *NULL; // 6
parm.generatorState = %ALLOC(%SIZE(state_t));
pState = parm.generatorState;
CLEAR state;
ENDIF;
pState = parm.generatorState; // 7
IF parm.event = QrnDgEvent_01_StartMultiple; // 8
IF parm.userParm <> *NULL;
state.caption = getCaption (parm : state);
state.haveCaption = *ON;
ENDIF;
ELSEIF parm.event = QrnDgEvent_02_EndMultiple; // 9
genEndTable (parm : state);
ELSEIF parm.event = QrnDgEvent_03_Start; // 10
IF parm.userParm <> *NULL
AND state.haveCaption = *OFF;
state.caption = getCaption (parm : state);
state.haveCaption = *ON;
ENDIF;
ELSEIF parm.event = QrnDgEvent_04_End; // 11
IF NOT parm.isPartOfSequence;
genEndTable (parm : state);
ENDIF;
ELSEIF parm.event = QrnDgEvent_09_StartStructArray; // 12
IF parm.userParm <> *NULL
AND state.haveCaption = *OFF;
state.caption = getCaption (parm : state);
state.haveCaption = *ON;
ENDIF;
genStartTable (parm : state : parm.array.numSubfields);
// Control might not return here
ELSEIF parm.event = QrnDgEvent_10_EndStructArray; // 13
ELSEIF parm.event = QrnDgEvent_05_StartStruct; // 12
IF parm.userParm <> *NULL
AND state.haveCaption = *OFF;
state.caption = getCaption (parm : state);
state.haveCaption = *ON;
ENDIF;
genStartTable (parm : state : parm.ds.numSubfields);
// Control might not return here
state.inDataStructure = *ON; // 14
writeLine (parm : '<tr>');
ELSEIF parm.event = QrnDgEvent_06_EndStruct; // 15
writeLine (parm : '</tr>');
state.inDataStructure = *OFF;
ELSEIF parm.event = QrnDgEvent_11_ScalarValue; // 16
IF NOT state.inDataStructure;
error (parm : state
: errorCodes.valueNotInStruct.code
: errorCodes.valueNotInStruct.msg);
// Control will not return here
ENDIF;
writeLine (parm : '<td>' : *ON); // 17
// The text for the column is written out in the same
// UTF-16 CCSID as it was passed to the generator
QrnDgAddText (parm.handle // 18
: parm.scalar.value
: parm.scalar.valueLenChars);
writeLine (parm : '</td>'); // 19
ELSE;
error (parm : state // 20
: errorCodes.eventNotSupported.code
: errorCodes.eventNotSupported.msg);
// Control will not return here
ENDIF;
END-PROC genHtmlTab;
getCaption procedimiento
Este procedimiento obtiene el valor del título de la tabla del subcampo userParm del parámetro pasado al generador. Este subcampo se establece a partir del segundo parámetro de la función incorporada %GEN para la operación DATA-GEN.
Tenga en cuenta los siguientes aspectos del procedimiento:
- El parámetro pasado al generador tiene toda la información que necesita un generador para interpretar el subcampo userParm si el segundo operando de %GEN era un tipo de serie. Consulte Tipos del parámetro de usuario pasado al generador DATA-GEN.
Sin embargo, este generador no admite todos los tipos posibles. No da soporte a valores alfanuméricos si el CCSID no es el CCSID del trabajo. No da soporte a los valores UCS-2 si el CCSID no es UTF-16.
La estructura de datos userParm se basa en el puntero parm.userParm . Un subcampo se define en la posición 1 de la estructura de datos para cada tipo de serie que soporta este generador.
- La función incorporada %STR devuelve el valor de una serie terminada en nulo.
- Si el valor alfanumérico está en el CCSID del trabajo, el generador determina la longitud del subcampo parm.userParmSize y utiliza dicha longitud para controlar la cantidad de datos devueltos por la función incorporada %SUBST.
- Si el valor alfanumérico está en el CCSID del trabajo, el generador no necesita utilizar la información de parm.userParmSize , porque el campo de longitud variable contiene su propia longitud en su prefijo de longitud variable.
- Para un valor UCS-2 o UTF-16 , parm.userParmSize debe dividirse por 2 para determinar el número de caracteres para la función incorporada %SUBST.
- Si el tipo o CCSID del parámetro de usuario no está soportado por el generador, éste emite un error.
DCL-PROC getCaption;
DCL-PI *N VARUCS2(10000) EXTPROC(*DCLCASE);
parm LIKEDS(QrnDgParm_t);
state LIKEDS(state_t);
END-PI;
DCL-DS userParm QUALIFIED BASED(P); // 1
charFixed CHAR(MAX_CAPTION) POS(1);
charVar2 VARCHAR(MAX_CAPTION) POS(1);
charVar4 VARCHAR(MAX_CAPTION:4) POS(1);
utf16Fixed UCS2(MAX_CAPTION) POS(1);
utf16Var2 VARUCS2(MAX_CAPTION) POS(1);
utf16Var4 VARUCS2(MAX_CAPTION:4) POS(1);
END-DS;
DCL-S CAPTION VARUCS2(MAX_CAPTION);
DCL-S LEN INT(10);
p = parm.userParm;
IF parm.userParmType = QrnUserParmType_nullTerminatedString; // 2
caption = %STR(parm.userParm);
ELSEIF parm.userParmType = QrnUserParmType_char // 3
AND parm.userParmCcsid = QrnDg_JOB_CCSID;
len = parm.userParmSize;
caption = %TRIM(%SUBST(userParm.charFixed : 1 : len));
ELSEIF parm.userParmType = QrnUserParmType_varchar_2 // 4
AND parm.userParmCcsid = QrnDg_JOB_CCSID;
caption = userParm.charVar2;
ELSEIF parm.userParmType = QrnUserParmType_varchar_4
AND parm.userParmCcsid = QrnDg_JOB_CCSID;
caption = userParm.charVar4;
ELSEIF parm.userParmType = QrnUserParmType_ucs2
AND parm.userParmCcsid = 1200;
len = parm.userParmSize / 2; // 5
caption = %TRIM(%SUBST(userParm.utf16Fixed : 1 : len));
ELSEIF parm.userParmType = QrnUserParmType_varucs2_2
AND parm.userParmCcsid = 1200;
caption = userParm.utf16var2;
ELSEIF parm.userParmType = QrnUserParmType_varucs2_4
AND parm.userParmCcsid = 1200;
caption = userParm.utf16var4;
ELSE;
error (parm : state
: errorCodes.userParmTypeNotSupported.code
: errorCodes.userParmTypeNotSupported.msg);
// Control will not return here
ENDIF;
RETURN caption;
END-PROC getCaption;
genStartTable procedimiento
Este procedimiento genera la cabecera para la tabla HTML, utilizando los nombres de subcampo de la estructura de datos RPG.
Tenga en cuenta los siguientes aspectos del procedimiento:
- Si el generador ya está procesando una estructura de datos, el generador genera una condición de error.
- Si el generador ya ha generado la cabecera, y el nombre o el número de subcampos de la estructura de datos actual es diferente de la estructura de datos utilizada para generar la cabecera, el generador genera una condición de error.
- El generador establece el nombre y el número de subcampos de la estructura de datos actual en su información de estado, para que pueda asegurarse de que las estructuras de datos que encuentra son todas las mismas.
- El generador genera el principio de la tabla HTML.
- El generador genera una fila en la cabecera de la tabla HTML para cada subcampo de la estructura de datos.
- El generador llama al procedimiento de devolución de llamada QrnDgGetSubfieldName para obtener el nombre del subcampo.
- El generador genera el final de la cabecera para la tabla HTML.
DCL-PROC genStartTable;
DCL-PI *n extproc(*dclcase);
parm LIKEDS(QrnDgParm_t);
state LIKEDS(state_t);
numSubfields int(10) value;
END-PI;
DCL-S i INT(10);
IF state.inDataStructure; // 1
error (parm : state
: errorCodes.nestedStructNotAllowed.code
: errorCodes.nestedStructNotAllowed.msg);
// Control will not return here
ENDIF;
if state.haveHeader;
if parm.name <> state.dataStructureName // 2
or numSubfields <> state.numSubfields;
error (parm : state
: errorCodes.nestedStructNotAllowed.code
: errorCodes.nestedStructNotAllowed.msg);
endif;
return;
endif;
state.dataStructureName = parm.name; // 3
state.numSubfields = numSubfields;
state.haveHeader = *ON;
writeLine (parm // 4
: '<table border="1">');
if state.haveCaption;
writeLine (parm : '<caption>' : *on);
QrnDgAddText (parm.handle
: %addr(state.caption : *data)
: %len(state.caption));
writeLine (parm : '</caption>');
endif;
writeLine (parm
: '<thead>'
+ '<tr>');
FOR i = 1 TO numSubfields; // 5
writeLine (parm
: '<td><b>'
: *ON); // skip the newline for this output
genColumnName (parm
: state
: QrnDgGetSubfieldName (parm.handle : i)); // 6
writeLine (parm : '</b></td>');
ENDFOR;
writeLine (parm // 7
: '</tr>'
+ '</thead>'
+ '<tbody>');
END-PROC genStartTable;
genColumnName procedimiento
Este procedimiento genera el nombre de una columna a partir del nombre de un subcampo.
Tenga en cuenta los siguientes aspectos del procedimiento:
- Este procedimiento espera que un subcampo tenga sus palabras separadas por subrayados, con las palabras en mayúsculas según sea necesario para la tabla; por ejemplo, Column_Heading. Para crear el nombre de columna, este procedimiento cambia los subrayados por espacios.
Consulte el código del programa que utiliza este generador DATA-GEN para ver cómo define los nombres de subcampo.
- La variable columnName es una variable UTF-16 de longitud variable, por lo que el generador puede utilizar el procedimiento de devolución de llamada QrnDgAddText para generar el nombre de la columna.
DCL-PROC genColumnName;
DCL-PI *n extproc(*dclcase);
parm LIKEDS(QrnDgParm_t);
state LIKEDS(state_t);
name like(QrnDgName_T) const;
END-PI;
DCL-S columnName LIKE(name);
DCL-C underscore %UCS2('_');
DCL-C BLANK %UCS2(' ');
columnName = %XLATE(UNDERSCORE : BLANK : name); // 1
QrnDgAddText (parm.handle // 2
: %ADDR(columnName : *DATA)
: %LEN(columnName));
END-PROC genColumnName;
genEndTable procedimiento
Este procedimiento genera el final de la tabla HTML.
DCL-PROC genEndTable;
DCL-PI *n extproc(*dclcase);
parm LIKEDS(QrnDgParm_t);
state LIKEDS(state_t);
END-PI;
writeLine (parm
: '</tbody>'
+ '</table>');
END-PROC genEndTable;
writeLine procedimiento
Este procedimiento genera texto en el CCSID del trabajo.
Tenga en cuenta los siguientes aspectos del procedimiento:
- El generador determina si debe generar una nueva línea después de generar el texto. Primero utiliza el subcampo outputIsToFile del parámetro pasado al generador para determinar si el texto generado está destinado a un archivo de flujo. Si no es así, no es necesaria una nueva línea.
Sin embargo, el autor de la llamada podría haber indicado que este procedimiento no debe generar una nueva línea en el parámetro opcional skipNewLine.
- Puesto que el texto está en el CCSID del trabajo, el generador puede utilizar el procedimiento de devolución de llamada simple QrnDgAddTextString para generar el texto.
- El generador añade el carácter de nueva línea utilizando el procedimiento de devolución de llamada QrnDgAddTextsNewLine .
DCL-PROC writeLine;
DCL-PI *N EXTPROC(*DCLCASE);
parm LIKEDS(QrnDgParm_t);
line pointer VALUE OPTIONS(*STRING);
skipNewLine IND CONST OPTIONS(*NOPASS);
END-PI;
DCL-S doNewLine IND INZ(*ON);
doNewLine = parm.outputIsToFile; // 1
IF %PARMS() >= %PARMNUM(skipNewLine)
AND skipNewLine;
doNewLine = *OFF;
ENDIF;
QrnDgAddTextString (parm.handle : line); // 2
IF doNewLine;
QrnDgAddTextNewline (parm.handle); // 3
ENDIF;
END-PROC writeLine;
error procedimiento
Este procedimiento emite una condición de error que hará que falle la operación DATA-GEN.
Tenga en cuenta los siguientes aspectos del procedimiento:
- En primer lugar, este procedimiento genera un mensaje de rastreo utilizando el procedimiento de devolución de llamada QrnDgTrace . Si el usuario está rastreando la operación DATA-GEN, el mensaje de rastreo explicará el código de error que aparecerá a continuación en el rastreo.
- El procedimiento de devolución de llamada QrnDgReportError se utiliza para informar de la condición de error. Se pasa *ON para el parámetro anidado , lo que indica que el mensaje de rastreo debe estar anidado dentro de otra información del rastreo.
El control no volverá a este procedimiento después de la llamada al procedimiento QrnDgReportError . Se volverá a llamar al generador para el suceso final QrnDgEvent_12_Terminate .
DCL-PROC error;
DCL-PI *n extproc(*dclcase);
parm LIKEDS(QrnDgParm_t);
state LIKEDS(state_t);
errorCode INT(10) VALUE;
errorMessage VARCHAR(100) CONST;
END-PI;
QrnDgTrace (parm.handle // 1
: errorMessage
: '1');
QrnDgReportError (parm.handle // 2
: errorCode);
// Control will not return here
END-PROC error;
Sentencias SQL para crear el archivo utilizado por el ejemplo
CREATE TABLE QGPL/MYORDERS
(NAME VARCHAR (25 ) NOT NULL WITH DEFAULT,
ITEMTYPE VARCHAR (25 ) NOT NULL WITH DEFAULT,
ITEMPRICE DECIMAL (9 , 2) NOT NULL WITH DEFAULT)
INSERT INTO QGPL/MYORDERS
VALUES('Refrigerator', 'Appliance', 525.95)
INSERT INTO QGPL/MYORDERS
VALUES('Shirt', 'Clothing', 5.95)
INSERT INTO QGPL/MYORDERS
VALUES('Rake', 'Gardening', 15.95)
Salida generada por las operaciones DATA-GEN en el ejemplo
A continuación se muestra la tabla HTML generada por la secuencia DATA-GEN.
<table border="1">
<caption>Order 2019-11-15</caption>
<thead><tr>
<td><b>Name</b></td>
<td><b>Type</b></td>
<td><b>Item Price</b></td>
</tr></thead><tbody>
<tr>
<td>Refrigerator</td>
<td>Appliance</td>
<td>525.95</td>
</tr>
<tr>
<td>Shirt</td>
<td>Clothing</td>
<td>5.95</td>
<td>5.95</td>
</tr>
<tr>
<td>Rake</td>
<td>Gardening</td>
<td>15.95</td>
</tr>
</tbody></table>
A continuación se muestra cómo aparece la tabla:
| Nombre | Tipo | PRECIO ART |
|---|---|---|
| Refrigerador | dispositivo | 525.95 |
| Camisa | Ropa | 5.95 |
| Rastrillo | Jardinería | 15.95 |
A continuación se muestra el valor de la variable customerTable después de la operación final DATA-GEN en el programa, tal como se muestra en el depurador.
> EVAL customerTable
CUSTOMERTABLE =
....5...10...15...20...25...30...35...40...45...50...55...60
1 '<table border="1"><thead><tr><td><b>Name</b></td><td><b>Addr'
61 'ess</b></td><td><b>Zip Code</b></td></tr></thead><tbody><tr>'
121 '<td>A. Smith</td><td>123 Elm Street</td><td>11111</td></tr><'
181 '/tbody></table> '
A continuación se muestra cómo aparece la tabla:
| Nombre | Dirección | Código postal |
|---|---|---|
| A Smith | calle Elm 123 | 11111 |
