--- /dev/null
+/*
+** 2025-10-20
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** Implementation of the Result-Format or "resfmt" utility library for SQLite.
+** See the resfmt.md documentation for additional information.
+*/
+#include "resfmt.h"
+
+/*
+** Private state information. Subject to change from one release to the
+** next.
+*/
+struct sqlite3_resfmt {
+ sqlite3_stmt *pStmt; /* The statement whose output is to be rendered */
+ sqlite3 *db; /* The corresponding database connection */
+ sqlite3_str *pErr; /* Error message, or NULL */
+ sqlite3_str *pOut; /* Accumulated output */
+ int iErr; /* Error code */
+ int nCol; /* Number of output columns */
+ sqlite3_int64 nRow; /* Number of rows handled so far */
+ sqlite3_resfmt_spec spec; /* Copy of the original spec */
+};
+
+
+/*
+** Free memory associated with pResfmt
+*/
+static void resfmtFree(sqlite3_resfmt *p){
+ if( p->pErr ) sqlite3_free(sqlite3_str_finish(p->pErr));
+ if( p->pOut ) sqlite3_free(sqlite3_str_finish(p->pOut));
+ sqlite3_free(p);
+}
+
+/*
+** Finish rendering the results
+*/
+int sqlite3_resfmt_finish(sqlite3_resfmt *p, int *piErr, char **pzErrMsg){
+ if( p==0 ){
+ return SQLITE_OK;
+ }
+ if( p->spec.pzOutput ){
+ *p->spec.pzOutput = sqlite3_str_finish(p->pOut);
+ p->pOut = 0;
+ }
+ if( piErr ){
+ *piErr = p->iErr;
+ }
+ if( pzErrMsg ){
+ *pzErrMsg = sqlite3_str_finish(p->pErr);
+ p->pErr = 0;
+ }
+ resfmtFree(p);
+ return SQLITE_OK;
+}
+
+/*
+** Render value pVal into p->pOut
+*/
+static void resfmtRenderValue(sqlite3_resfmt *p, sqlite3_value *pVal){
+ if( p->xRender ){
+ char *z = p->xRender(p->pRenderArg, pVal);
+ if( z ){
+ sqlite3_str_appendall(p->pOut, z);
+ sqlite3_free(z);
+ return;
+ }
+ }
+ switch( sqlite3_value_type(pVal) {
+ case SQLITE_INTEGER: {
+ sqlite3_str_appendf(p->pOut, "%lld", sqlite3_value_int64(pVal));
+ break;
+ }
+ case SQLITE_FLOAT: {
+ sqlite3_str_appendf(p->pOut, p->zFloatFmt, sqlite3_value_double(pVal));
+ break;
+ }
+ case SQLITE_BLOB: {
+ int iStart = sqlite3_str_length(p->pOut);
+ int nBlob = sqlite3_value_bytes(pVal);
+ int i, j;
+ char *zVal;
+ unsigned char *a = sqlite3_value_blob(pVal);
+ sqlite3_str_append(p->pOut, "x'", 2);
+ sqlite3_str_appendchar(p->pOut, nBlob, ' ');
+ sqlite3_str_appendchar(p->pOut, nBlob, ' ');
+ sqlite3_str_appendchar(p->pOut, 1, '\'');
+ if( sqlite3_str_errcode(p->pOut ) return;
+ zVal = sqlite3_str_value(p->pOut);
+ for(i=0, j=iStart+2; i<nBlob; i++, j+=2){
+ unsigned char c = a[i];
+ zVal[j] = "0123456789abcdef"[(c>>4)&0xf]);
+ zVal[j+1] = "0123456789abcdef"[(c)&0xf]);
+ }
+ break;
+ }
+ case SQLITE_NULL: {
+ sqlite3_str_appendall(p->pOut, p->zNull);
+ break;
+ }
+ case SQLITE_TEXT: {
+ if( p->spec.bQuote ){
+ sqlite3_str_appendf(p->pOut, "%Q", sqlite3_value_text(pVal));
+ }else{
+ sqlite3_str_appendall(p->pOut, sqlite3_value_text(pVal));
+ }
+ break;
+ }
+ }
+}
+
+/*
+** If xWrite is defined, send all content of pOut to xWrite and
+** reset pOut.
+*/
+static void resfmtWrite(sqlite3_resfmt *p){
+ int n;
+ if( p->spec.xWrite && (n = sqlite3_str_length(p->pOut))>0 ){
+ p->spec.xWrite(p->spec.pWriteArg,
+ (const unsigned char*)sqlite3_str_value(p->pOut),
+ (size_t)n);
+ sqlite3_str_reset(p->pOut);
+ }
+}
+
+/*
+** Create a new rendering object
+*/
+sqlite3_resfmt *sqlite3_resfmt_begin(
+ sqlite3_stmt *pStmt,
+ sqlite3_resfmt_spec *pSpec
+){
+ sqlite3_resfmt *p; /* The new sqlite3_resfmt being created */
+ size_t sz; /* Size of pSpec[], based on pSpec->iVersion */
+
+ if( pStmt==0 ) return 0;
+ if( pSpec==0 ) return 0;
+ if( pSpec->iVersion!=1 ) return 0;
+ p = sqlite3_malloc64( sizeof(*p) );
+ if( p==0 ) return 0;
+ p->pStmt = pStmt;
+ p->db = sqlite3_db_handle(pStmt);
+ p->pErr = 0;
+ p->pOut = 0;
+ p->pBuf = sqlite3_str_new(p->db);
+ if( p->pBuf==0 ){
+ reffmtFree(p);
+ return 0;
+ }
+ if( pSpec->pzOutput ){
+ p->pOut = sqlite3_str_new(p->db);
+ if( p->pOut==0 ){
+ reffmtFree(p);
+ return 0;
+ }
+ }
+ p->iErr = 0;
+ p->nCol = sqlite3_column_count(p->pStmt);
+ p->nRow = 0;
+ sz = sizeof(sqlite3_resfmt_spec); break;
+ memcpy(&p->spec, pSpec, sz);
+ return p;
+}
+
+/*
+** Output text in the manner requested by the spec (either by direct
+** write to
+
+/*
+** Render a single row of output.
+*/
+int sqlite3_resfmt_row(sqlite3_resfmt *p){
+ int rc = SQLITE_OK;
+ int i;
+ if( p==0 ) return SQLITE_DONE;
+ switch( p->spec.eFormat ){
+ default: { /* RESFMT_List */
+ sqlite3_str_reset(p->pOut);
+ for(i=0; i<p->nCol; i++){
+ if( i>0 ) sqlite3_str_appendall(p->pOut, p->spec.zColumnSep);
+ resfmtRenderValue(p, sqlite3_column_value(p->pStmt, i));
+ }
+ sqlite3_str_appendall(p->pOut, p->spec.zRowSep);
+ resfmtWrite(p);
+ break;
+ }
+ }
+ return rc;
+}
--- /dev/null
+/*
+** 2025-10-20
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** Header file for the Result-Format or "resfmt" utility library for SQLite.
+** See the resfmt.md documentation for additional information.
+*/
+#include "sqlite3.h"
+
+/*
+** Specification used by clients to define the output format they want
+*/
+typedef struct sqlite3_resfmt_spec sqlite3_resfmt_spec;
+struct sqlite3_resfmt_spec {
+ int iVersion; /* Version number of this structure */
+ int eFormat; /* Output format */
+ unsigned char bShowCNames; /* True to show column names */
+ unsigned char eEscMode; /* How to deal with control characters */
+ unsigned char bQuote; /* Quote output values as SQL literals */
+ unsigned char bWordWrap; /* Try to wrap on word boundaries */
+ int mxWidth; /* Maximum column width in columnar modes */
+ const char *zColumnSep; /* Alternative column separator */
+ const char *zRowSep; /* Alternative row separator */
+ const char *zTableName; /* Output table name */
+ const char *zNull; /* Rendering of NULL */
+ const char *zFloatFmt; /* printf-style string for rendering floats */
+ int nWidth; /* Number of column width parameters */
+ short int *aWidth; /* Column widths */
+ char *(*xRender)(void*,sqlite3_value*); /* Render a value */
+ ssize_t (*xWrite)(void*,const unsigned char*,ssize_t); /* Write callback */
+ void *pRenderArg; /* First argument to the xRender callback */
+ void *pWriteArg; /* First argument to the xWrite callback */
+ char **pzOutput; /* Storage location for output string */
+ /* Additional fields may be added in the future */
+};
+
+/*
+** Opaque state structure used by this library.
+*/
+typedef struct sqlite3_resfmt sqlite3_resfmt;
+
+/*
+** Interfaces
+*/
+sqlite3_resfmt *sqlite3_resfmt_begin(sqlite3_stmt*, sqlite3_resfmt_spec*);
+int sqlite3_resfmt_row(sqlite3_resfmt*)
+int sqlite3_resfmt_finish(sqlite3_resfmt*,int*,char**);
+
+/*
+** Output styles:
+*/
+#define RESFMT_Line 0 /* One column per line. */
+#define RESFMT_Column 1 /* One record per line in neat columns */
+#define RESFMT_List 2 /* One record per line with a separator */
+#define RESFMT_Html 3 /* Generate an XHTML table */
+#define RESFMT_Insert 4 /* Generate SQL "insert" statements */
+#define RESFMT_Tcl 5 /* Generate ANSI-C or TCL quoted elements */
+#define RESFMT_Csv 6 /* Quote strings, numbers are plain */
+#define RESFMT_Explain 7 /* Like RESFMT_Column, but do not truncate data */
+#define RESFMT_Pretty 8 /* Pretty-print schemas */
+#define RESFMT_EQP 9 /* Converts EXPLAIN QUERY PLAN output into a graph */
+#define RESFMT_Json 10 /* Output JSON */
+#define RESFMT_Markdown 11 /* Markdown formatting */
+#define RESFMT_Table 12 /* MySQL-style table formatting */
+#define RESFMT_Box 13 /* Unicode box-drawing characters */
+#define RESFMT_Count 14 /* Output only a count of the rows of output */
+#define RESFMT_Off 15 /* No query output shown */
+#define RESFMT_ScanExp 16 /* Like RESFMT_Explain, but for ".scanstats vm" */
+#define RESFMT_Www 17 /* Full web-page output */
prepared statement, use code similar to the following:
> ~~~
-ResfmtSpec spec; /* Formatter spec */
-Resfmt *pFmt; /* Formatter object */
-int errCode; /* Error code */
-char *zErrMsg; /* Text error message (optional) */
+sqlite3_resfmt_spec spec; /* Formatter spec */
+sqlite3_resfmt *pFmt; /* Formatter object */
+int errCode; /* Error code */
+char *zErrMsg; /* Text error message (optional) */
memset(&spec, 0, sizeof(spec));
// Additional spec initialization here
sqlite3_free(zErrMsg);
~~~
-The `ResfmtSpec` structure (defined below) describes the desired
-output format. The `pFmt` variable is a pointer to an opaque Resfmt
+The `sqlite3_resfmt_spec` structure (defined below) describes the desired
+output format. The `pFmt` variable is a pointer to an opaque sqlite3_resfmt
object that maintains the statement of the formatter.
The pFmt object is used as the first parameter to two other
routines, `sqlite3_resfmt_row()` and `sqlite3_resfmt_finish()`, and
`sqlite3_resfmt_finish()` interface serves as a destructor for
the pFmt object.
-## 2.0 The `ResfmtSpec` object
+## 2.0 The `sqlite3_resfmt_spec` object
A pointer to an instance of the following structure is the second
parameter to the `sqlite3_resfmt_begin()` interface. This structure
defines how the rules of the statement are to be formatted.
> ~~~
-typedef struct ResfmtSpec ResfmtSpec;
-struct ResfmtSpec {
+typedef struct sqlite3_resfmt_spec sqlite3_resfmt_spec;
+struct sqlite3_resfmt_spec {
int iVersion; /* Version number of this structure */
int eFormat; /* Output format */
unsigned char bShowCNames; /* True to show column names */
const char *zColumnSep; /* Alternative column separator */
const char *zRowSep; /* Alternative row separator */
const char *zTableName; /* Output table name */
+ const char *zNull; /* Rendering of NULL */
+ const char *zFloatFmt; /* printf-style string for rendering floats */
int nWidth; /* Number of column width parameters */
short int *aWidth; /* Column widths */
- char *(*pRender)(void*,sqlite3_value*); /* Render a value */
- ssize_t (*pWrite)(void*,const unsigned char*,ssize_t); /* Write callback */
- void *pWriteArg; /* First argument to write callback */
+ char *(*xRender)(void*,sqlite3_value*); /* Render a value */
+ ssize_t (*xWrite)(void*,const unsigned char*,ssize_t); /* Write callback */
+ void *pRenderArg; /* First argument to the xRender callback */
+ void *pWriteArg; /* First argument to the xWrite callback */
char **pzOutput; /* Storage location for output string */
/* Additional fields may be added in the future */
};
~~~
-The ResfmtSpec object must be fully initialized prior
+The sqlite3_resfmt_spec object must be fully initialized prior
to calling `sqlite3_resfmt_begin()` and its value must not change
by the application until after the corresponding call to
`sqlite3_resfmt_finish()`. Note that the result formatter itself
-might change values in the ResfmtSpec object as it runs.
+might change values in the sqlite3_resfmt_spec object as it runs.
But the application should not try to change or use any fields of
-the ResfmtSpec object while the formatter is running.
+the sqlite3_resfmt_spec object while the formatter is running.
### 2.1 Structure Version Number
-The ResfmtSpec.iVersion field must be 1. Future enhancements to this
-subsystem might add new fields onto the bottom of the ResfmtSpec object.
-Those new fields will only be accessible if the iVersion is greater than 1.
-Thus the iVersion field is used to support upgradability.
+The sqlite3_resfmt_spec.iVersion field must be 1. Future enhancements to this
+subsystem might add new fields onto the bottom of the sqlite3_resfmt_spec
+object. Those new fields will only be accessible if the iVersion is greater
+than 1. Thus the iVersion field is used to support upgradability.
### 2.2 Output Deposition
The formatted output can either be sent to a callback function
or accumulated into an output buffer in memory obtained
-from system malloc(). If the ResfmtSpec.pWrite column is not NULL,
-then that function is invoked (using ResfmtSpec.pWriteArg as its
+from system malloc(). If the sqlite3_resfmt_spec.xWrite column is not NULL,
+then that function is invoked (using sqlite3_resfmt_spec.xWriteArg as its
first argument) to transmit the formatted output. Or, if
-ResfmtSpec.pzOutput points to a pointer to a character, then that
+sqlite3_resfmt_spec.pzOutput points to a pointer to a character, then that
pointer is made to point to memory obtained from malloc() that
contains the complete text of the formatted output.
When `sqlite3_resfmt_begin()` is called,
-one of ResfmtSpec.pWrite and ResfmtSpec.pzOutput must be non-NULL
-and the other must be NULL.
+one of sqlite3_resfmt_spec.xWrite and sqlite3_resfmt_spec.pzOutput must be
+non-NULL and the other must be NULL.
Output might be generated row by row, on each call to
`sqlite3_resfmt_row()` or it might be written all at once
### 2.3 Output Format
-The ResfmtSpec.eFormat field is an integer code that defines the
+The sqlite3_resfmt_spec.eFormat field is an integer code that defines the
specific output format that will be generated. See the
output format describes below for additional detail.
### 2.4 Show Column Names
-The ResfmtSpec.bShowCNames field is a boolean. If true, then column
+The sqlite3_resfmt_spec.bShowCNames field is a boolean. If true, then column
names appear in the output. If false, column names are omitted.
### 2.5 Control Character Escapes
-The ResfmtSpec.eEscMode determines how ASCII control characters are
+The sqlite3_resfmt_spec.eEscMode determines how ASCII control characters are
formatted in the output. If this value is zero, then the control character
with value X is displayed as ^Y where Y is X+0x40. Hence, a
backspace character (U+0008) is shown as "^H". This is the default.
sequence are always output literally and are not mapped to alternative
display values, regardless of this setting.
-### 2.6 Word Wrapping In Columnar Modes
+### 2.6 Render output as SQL literals
+
+If the sqlite3_resfmt_spec.bQuote field is non-zero (true), then outputs
+are rendered as SQL literal values. String are written within `'...'`
+with appropriate escapes. BLOBs are of the form `x'...'`. NULL values
+come out as "NULL".
+
+### 2.7 Word Wrapping In Columnar Modes
For output modes that attempt to display equal-width columns, the
-ResfmtSpec.bWordWrap boolean determines whether long values are broken
-at word boundaries, or at arbitrary characters. The ResfmtSpec.mxWidth
-determines the maximum width of an output column.
+sqlite3_resfmt_spec.bWordWrap boolean determines whether long values
+are broken at word boundaries, or at arbitrary characters. The
+sqlite3_resfmt_spec.mxWidth determines the maximum width of an output column.
-### 2.7 Row and Column Separator Strings
+### 2.8 Row and Column Separator Strings
-The ResfmtSpec.zColumnSep and ResfmtSpec.zRowSep strings are alternative
-column and row separator character sequences. If not specified (if these
-pointers are left as NULL) then appropriate defaults are used.
+The sqlite3_resfmt_spec.zColumnSep and sqlite3_resfmt_spec.zRowSep strings
+are alternative column and row separator character sequences. If not
+specified (if these pointers are left as NULL) then appropriate defaults
+are used.
-### 2.8 The Output Table Name
+### 2.9 The Output Table Name
-The ResfmtSpec.zTableName value is the name of the output table
+The sqlite3_resfmt_spec.zTableName value is the name of the output table
when the MODE_Insert output mode is used.
-### 2.9 Column Widths And Alignments
+### 2.10 The Rendering Of NULL
+
+If a value is NULL and sqlite3_resfmt_spec.xRender() does not exist
+or declines to provide a rendering, then show the NULL using the string
+found in sqlite3_resfmt_spec.zNull. If zNull is itself a NULL pointer
+then NULL values are rendered as an empty string.
+
+### 2.11 Floating Point Value Format
-The ResfmtSpec.aWidth[] array, if specified, is an array of
+If a value is a floating-point number other than Infinity or NaN, then
+the value is rendered using the printf-style format given by the
+sqlite3_resfmt_spec.zFloatFmt string.
+
+### 2.12 Column Widths And Alignments
+
+The sqlite3_resfmt_spec.aWidth[] array, if specified, is an array of
16-bit signed integers that specify the minimum column width and
the alignment for all columns in columnar output modes. Negative
values mean right-justify. The
These widths are deliberately stored in a 16-bit signed integer
as no good can come from having column widths greater than 32767.
-The ResfmtSpec.nWidth field is the number of values in the aWidth[]
+The sqlite3_resfmt_spec.nWidth field is the number of values in the aWidth[]
array. Any column beyond the nWidth-th column are assumed to have
a minimum width of 0.
-### 2.10 Optional Value Rendering Callback
+### 2.13 Optional Value Rendering Callback
-If the ResfmtSpec.pRender field is not NULL, then each rendered
-sqlite3_value is first passed to the pRender function, giving it
-an opportunity to render the results. If pRender chooses to render,
+If the sqlite3_resfmt_spec.xRender field is not NULL, then each rendered
+sqlite3_value is first passed to the xRender function, giving it
+an opportunity to render the results. If xRender chooses to render,
it should write the rendering into memory obtained from sqlite3_malloc()
-and return a pointer to that memory. The pRender function can decline
+and return a pointer to that memory. The xRender function can decline
to render (for example, based on the sqlite3_value_type() or other
characteristics of the value) in which case it can simply return a
NULL pointer and the usual default rendering will be used instead.
returned string and will pass the returned string to sqlite3_free()
when it has finished with it.
+The bQuote and eEscMode settings above become no-ops if the xRender
+routine returns non-NULL. In other words, the application-supplied
+xRender routine is expected to do its own bQuote and eEscMode processing
+if it chooses to render values.
+
## 3.0 The `sqlite3_resfmt_begin()` Interface
Invoke the `sqlite3_resfmt_begin(P,S)` interface to begin formatting
the output of prepared statement P using format specification S.
-This routine returns a pointer to an opaque Resfmt object that is
+This routine returns a pointer to an opaque sqlite3_resfmt object that is
the current state of the formatter. The `sqlite3_resfmt_finish()`
-routine is the destructor for the Resfmt object and must be called
+routine is the destructor for the sqlite3_resfmt object and must be called
to prevent a memory leak.
If an out-of-memory fault occurs while allocating space for the
-Resfmt object, then `sqlite3_resfmt_begin()` will return a NULL
+sqlite3_resfmt object, then `sqlite3_resfmt_begin()` will return a NULL
pointer. The application need not check for this case as the
-other routines that use a pointer to the Resfmt object all
-interpret a NULL parameter in place of the Resfmt pointer as
+other routines that use a pointer to the sqlite3_resfmt object all
+interpret a NULL parameter in place of the sqlite3_resfmt pointer as
a harmless no-op.
## 4.0 The `sqlite3_resfmt_step()` Interface
-Invoke the `sqlite3_resfmt_step(F,P)` interface for each row
-in the prepared statement that is to be output. The prepared
-statement pointer P must be the same as the P argument passed
-into `sqlite3_resfmt_begin()`, or unpredictable things can happen.
+Invoke the `sqlite3_resfmt_step(F)` interface for each row
+in the prepared statement that is to be output.
The formatter might choose to output some content as each row
is processed, or it might accumulate the output and send it all
application to invoke sqlite3_free() on this error message to
reclaim the space.
+If the F argument to `sqlite3_resfmt_finish(F,C,E)` is a NULL pointer,
+then this interface is a harmless no-op.
+
### 6.0 Output Modes
The result formatter supports a variety of output modes. The
The following output modes are currently defined:
> ~~~
-#define MODE_Line 0 /* One column per line. */
-#define MODE_Column 1 /* One record per line in neat columns */
-#define MODE_List 2 /* One record per line with a separator */
-#define MODE_Semi 3 /* Same as MODE_List but append ";" to each line */
-#define MODE_Html 4 /* Generate an XHTML table */
-#define MODE_Insert 5 /* Generate SQL "insert" statements */
-#define MODE_Quote 6 /* Quote values as for SQL */
-#define MODE_Tcl 7 /* Generate ANSI-C or TCL quoted elements */
-#define MODE_Csv 8 /* Quote strings, numbers are plain */
-#define MODE_Explain 9 /* Like MODE_Column, but do not truncate data */
-#define MODE_Ascii 10 /* Use ASCII unit and record separators (0x1F/0x1E) */
-#define MODE_Pretty 11 /* Pretty-print schemas */
-#define MODE_EQP 12 /* Converts EXPLAIN QUERY PLAN output into a graph */
-#define MODE_Json 13 /* Output JSON */
-#define MODE_Markdown 14 /* Markdown formatting */
-#define MODE_Table 15 /* MySQL-style table formatting */
-#define MODE_Box 16 /* Unicode box-drawing characters */
-#define MODE_Count 17 /* Output only a count of the rows of output */
-#define MODE_Off 18 /* No query output shown */
-#define MODE_ScanExp 19 /* Like MODE_Explain, but for ".scanstats vm" */
-#define MODE_Www 20 /* Full web-page output */
+#define RESFMT_Line 0 /* One column per line. */
+#define RESFMT_Column 1 /* One record per line in neat columns */
+#define RESFMT_List 2 /* One record per line with a separator */
+#define RESFMT_Html 3 /* Generate an XHTML table */
+#define RESFMT_Insert 4 /* Generate SQL "insert" statements */
+#define RESFMT_Tcl 5 /* Generate ANSI-C or TCL quoted elements */
+#define RESFMT_Csv 6 /* Quote strings, numbers are plain */
+#define RESFMT_Explain 7 /* Like RESFMT_Column, but do not truncate data */
+#define RESFMT_Pretty 8 /* Pretty-print schemas */
+#define RESFMT_EQP 9 /* Converts EXPLAIN QUERY PLAN output into a graph */
+#define RESFMT_Json 10 /* Output JSON */
+#define RESFMT_Markdown 11 /* Markdown formatting */
+#define RESFMT_Table 12 /* MySQL-style table formatting */
+#define RESFMT_Box 13 /* Unicode box-drawing characters */
+#define RESFMT_Count 14 /* Output only a count of the rows of output */
+#define RESFMT_Off 15 /* No query output shown */
+#define RESFMT_ScanExp 16 /* Like RESFMT_Explain, but for ".scanstats vm" */
+#define RESFMT_Www 17 /* Full web-page output */
~~~
Additional detail about the meaning of each of these output modes