mirror of
https://github.com/FirebirdSQL/firebird.git
synced 2025-01-24 03:23:03 +01:00
1446 lines
34 KiB
C++
1446 lines
34 KiB
C++
/*
|
|
* PROGRAM: JRD Command Oriented Query Language
|
|
* MODULE: format.cpp
|
|
* DESCRIPTION: Print planner and formatter
|
|
*
|
|
* The contents of this file are subject to the Interbase Public
|
|
* License Version 1.0 (the "License"); you may not use this file
|
|
* except in compliance with the License. You may obtain a copy
|
|
* of the License at http://www.Inprise.com/IPL.html
|
|
*
|
|
* Software distributed under the License is distributed on an
|
|
* "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express
|
|
* or implied. See the License for the specific language governing
|
|
* rights and limitations under the License.
|
|
*
|
|
* The Original Code was created by Inprise Corporation
|
|
* and its predecessors. Portions created by Inprise Corporation are
|
|
* Copyright (C) Inprise Corporation.
|
|
*
|
|
* All Rights Reserved.
|
|
* Contributor(s): ______________________________________.
|
|
*/
|
|
|
|
#include "firebird.h"
|
|
#include "../jrd/ib_stdio.h"
|
|
#include <string.h>
|
|
#include "../jrd/jrd_time.h"
|
|
#include "../qli/dtr.h"
|
|
#include "../qli/exe.h"
|
|
#include "../jrd/gds.h"
|
|
#include "../qli/compile.h"
|
|
#include "../qli/report.h"
|
|
#include "../qli/format.h"
|
|
#include "../qli/all_proto.h"
|
|
#include "../qli/err_proto.h"
|
|
#include "../qli/eval_proto.h"
|
|
#include "../qli/exe_proto.h"
|
|
#include "../qli/forma_proto.h"
|
|
#include "../qli/mov_proto.h"
|
|
#include "../qli/picst_proto.h"
|
|
|
|
extern USHORT QLI_columns, QLI_lines;
|
|
|
|
#ifdef DEV_BUILD
|
|
extern USHORT QLI_hex_output;
|
|
|
|
inline bool is_printable(const char x)
|
|
{
|
|
return ((x >= ' ') && (x <= 127)) ||
|
|
(x == '\n') ||
|
|
(x == '\t') ||
|
|
(x == '\r') ||
|
|
(x == '\f');
|
|
}
|
|
#endif
|
|
|
|
static USHORT decompose_header(SCHAR*, SCHAR**, USHORT*);
|
|
static void format_index(ITM, QLI_NOD, const bool);
|
|
static TEXT* format_report(VEC, USHORT, USHORT*);
|
|
static void format_value(ITM, int);
|
|
static TEXT* get_buffer(STR*, TEXT*, USHORT);
|
|
static bool match_expr(const qli_nod*, const qli_nod*);
|
|
static void print_blobs(PRT, itm**, itm**);
|
|
static int print_line(itm*, TEXT**);
|
|
static void put_line(PRT, TEXT**, TEXT*, TEXT);
|
|
static void report_break(BRK, VEC*, const bool);
|
|
static void report_item(ITM, VEC*, USHORT*);
|
|
static void report_line(QLI_NOD, VEC*);
|
|
|
|
static STR global_fmt_buffer, global_blob_buffer;
|
|
|
|
#define BOTTOM_INIT get_buffer (&global_fmt_buffer, NULL, 1024)
|
|
#define BOTTOM_CHECK(ptr, length) ptr = get_buffer (&global_fmt_buffer, ptr, length)
|
|
#define BOTTOM_LINE global_fmt_buffer->str_data
|
|
|
|
#define BUFFER_INIT get_buffer (&global_fmt_buffer, NULL, 1024)
|
|
#define BUFFER_CHECK(ptr, length) ptr = get_buffer (&global_fmt_buffer, ptr, length)
|
|
#define BUFFER_BEGINNING global_fmt_buffer->str_data
|
|
#define BUFFER_REMAINING(ptr) (global_fmt_buffer->str_length - (ptr - global_fmt_buffer->str_data))
|
|
|
|
int FMT_expression( QLI_NOD node)
|
|
{
|
|
/**************************************
|
|
*
|
|
* F M T _ e x p r e s s i o n
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Handle formatting for FORMAT expression. Return editted
|
|
* length.
|
|
*
|
|
**************************************/
|
|
QLI_NOD sub = node->nod_arg[e_fmt_value];
|
|
PICS picture = PIC_analyze((TEXT*) node->nod_arg[e_fmt_edit], &sub->nod_desc);
|
|
node->nod_arg[e_fmt_picture] = (QLI_NOD) picture;
|
|
|
|
if (node->nod_type == nod_reference)
|
|
node = node->nod_arg[0];
|
|
|
|
QLI_FLD field;
|
|
if (!(picture->pic_missing) && (node->nod_type == nod_field) &&
|
|
(field = (QLI_FLD) node->nod_arg[e_fld_field]) && field->fld_missing)
|
|
PIC_missing(field->fld_missing, picture);
|
|
|
|
return picture->pic_length;
|
|
}
|
|
|
|
|
|
TEXT* FMT_format(LLS stack)
|
|
{
|
|
/**************************************
|
|
*
|
|
* F M T _ f o r m a t
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Format a print line. Figure out spacing, print formats, and
|
|
* headers. Return a pointer to the header string.
|
|
*
|
|
**************************************/
|
|
ITM item, item2;
|
|
QLI_NOD value;
|
|
SSHORT segment;
|
|
USHORT j, l, n, lengths[10], *ptr;
|
|
TEXT *p, *q, *segments[10];
|
|
|
|
// Start by inverting the item stack into an item que
|
|
|
|
LLS temp = stack;
|
|
stack = NULL;
|
|
|
|
if (global_fmt_buffer) {
|
|
ALL_release((FRB) global_fmt_buffer);
|
|
global_fmt_buffer = NULL;
|
|
}
|
|
|
|
while (temp) {
|
|
item = (ITM) LLS_POP(&temp);
|
|
LLS_PUSH(item, &stack);
|
|
}
|
|
|
|
/* Make a pass thru print items computing print lengths and header
|
|
lengths, and the number of header segments. */
|
|
|
|
USHORT offset, max_offset, number_segments;
|
|
number_segments = offset = max_offset = 0;
|
|
TEXT* bottom = BOTTOM_INIT;
|
|
|
|
for (temp = stack; temp; temp = temp->lls_next) {
|
|
item = (ITM) temp->lls_object;
|
|
switch (item->itm_type) {
|
|
case item_column:
|
|
offset = (item->itm_count) ? item->itm_count - 1 : 0;
|
|
continue;
|
|
|
|
case item_skip:
|
|
case item_new_page:
|
|
offset = 0;
|
|
continue;
|
|
|
|
case item_space:
|
|
offset += item->itm_count;
|
|
continue;
|
|
|
|
case item_tab:
|
|
offset = (offset & ~7) + item->itm_count * 8;
|
|
continue;
|
|
}
|
|
|
|
if (item->itm_type == item_value && (value = item->itm_value)) {
|
|
if (value->nod_type == nod_reference)
|
|
value = value->nod_arg[0];
|
|
format_index(item, value, true);
|
|
}
|
|
|
|
if (item->itm_query_header)
|
|
if (*item->itm_query_header == '-')
|
|
item->itm_query_header = NULL;
|
|
else {
|
|
n =
|
|
decompose_header(item->itm_query_header, segments,
|
|
lengths);
|
|
number_segments = MAX(n, number_segments);
|
|
for (j = 0, ptr = lengths; j < n; j++, ptr++)
|
|
item->itm_header_length =
|
|
MAX(item->itm_header_length, *ptr);
|
|
}
|
|
|
|
format_value(item, 0);
|
|
|
|
// If the item would overflow the line, reset to beginning of line
|
|
|
|
if (offset + MAX(item->itm_print_length, item->itm_header_length) >
|
|
QLI_columns) offset = 0;
|
|
|
|
/* Before we blindly format the header, make sure there already isn't
|
|
header information in the same location */
|
|
|
|
if (item->itm_query_header) {
|
|
n = MAX(item->itm_print_length, item->itm_header_length);
|
|
BOTTOM_CHECK(bottom, offset);
|
|
q = BOTTOM_LINE + offset;
|
|
while (bottom < q)
|
|
*bottom++ = ' ';
|
|
bool flag = true;
|
|
if (offset && q[-1] != ' ')
|
|
flag = false;
|
|
else if (l = MIN(n, bottom - q)) {
|
|
p = bottom;
|
|
while (--l)
|
|
if (*p++ != ' ') {
|
|
flag = false;
|
|
break;
|
|
}
|
|
if (flag && p < bottom && *p != ' ')
|
|
flag = false;
|
|
}
|
|
if (flag && (l = n)) {
|
|
BOTTOM_CHECK(bottom, bottom - BOTTOM_LINE + n);
|
|
do {
|
|
*bottom++ = '=';
|
|
} while (--l);
|
|
}
|
|
else {
|
|
item->itm_query_header = NULL;
|
|
item->itm_header_length = 0;
|
|
}
|
|
}
|
|
|
|
/* Now that have settled the issue of header, decide where to put
|
|
the field and header */
|
|
|
|
n = MAX(item->itm_print_length, item->itm_header_length);
|
|
item->itm_print_offset = offset + (n - item->itm_print_length) / 2;
|
|
item->itm_header_offset = offset + n / 2;
|
|
offset += n + 1;
|
|
max_offset = MAX(max_offset, offset);
|
|
}
|
|
|
|
// Make another pass checking for overlapping fields
|
|
|
|
for (temp = stack; temp; temp = temp->lls_next) {
|
|
item = (ITM) temp->lls_object;
|
|
if (item->itm_type != item_value)
|
|
continue;
|
|
for (LLS temp2 = temp->lls_next; temp2; temp2 = temp2->lls_next) {
|
|
item2 = (ITM) temp2->lls_object;
|
|
if (item2->itm_type != item_value)
|
|
continue;
|
|
if (item2->itm_print_offset <
|
|
item->itm_print_offset + item->itm_print_length) {
|
|
item->itm_flags |= ITM_overlapped;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (number_segments == 0)
|
|
return NULL;
|
|
|
|
// Allocate a string block big enough to hold all lines of the print header
|
|
|
|
const ULONG size = (max_offset + 1) * (number_segments + 1) + 2;
|
|
|
|
if (size >= 60000)
|
|
ERRQ_print_error(482, (TEXT *)(ULONG) max_offset,
|
|
(TEXT *) (number_segments + 1), NULL, NULL, NULL);
|
|
|
|
STR header = (STR) ALLOCDV(type_str, size);
|
|
p = header->str_data;
|
|
|
|
// Generate the various lines of the header line at a time.
|
|
|
|
for (j = 0; j < number_segments; j++) {
|
|
*p++ = '\n';
|
|
TEXT* const line = p;
|
|
for (temp = stack; temp; temp = temp->lls_next) {
|
|
item = (ITM) temp->lls_object;
|
|
if (item->itm_type != item_value)
|
|
continue;
|
|
n = decompose_header(item->itm_query_header, segments, lengths);
|
|
segment = j - (number_segments - n);
|
|
if (segment < 0)
|
|
continue;
|
|
l = lengths[segment];
|
|
q = line + item->itm_header_offset - l / 2;
|
|
|
|
while (p < q)
|
|
*p++ = ' ';
|
|
q = segments[segment];
|
|
if (l)
|
|
do {
|
|
*p++ = *q++;
|
|
} while (--l);
|
|
}
|
|
}
|
|
|
|
// Make one last pass to put in underlining of headers
|
|
|
|
if (l = bottom - BOTTOM_LINE) {
|
|
*p++ = '\n';
|
|
bottom = BOTTOM_LINE;
|
|
do {
|
|
*p++ = *bottom++;
|
|
} while (--l);
|
|
}
|
|
|
|
*p++ = '\n';
|
|
*p++ = '\n';
|
|
*p = 0;
|
|
|
|
return header->str_data;
|
|
}
|
|
|
|
|
|
QLI_NOD FMT_list(QLI_NOD list)
|
|
{
|
|
/**************************************
|
|
*
|
|
* F M T _ l i s t
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Rebuild and format a list of stuff for vertical formatting.
|
|
*
|
|
**************************************/
|
|
ITM new_item, *item, *end;
|
|
SYM name;
|
|
QLI_FLD field;
|
|
QLI_NOD value;
|
|
|
|
QLI_NOD new_nod = (QLI_NOD) ALLOCDV(type_nod, list->nod_count * 2 + 1);
|
|
new_nod->nod_type = nod_list;
|
|
ITM* new_ptr = (ITM*) new_nod->nod_arg;
|
|
USHORT column = 0;
|
|
|
|
for (item = (ITM*) list->nod_arg, end = item + list->nod_count;
|
|
item < end; item++)
|
|
{
|
|
if ((*item)->itm_type != item_value || !(value = (*item)->itm_value))
|
|
continue;
|
|
(*item)->itm_flags |= ITM_overlapped;
|
|
format_value(*item, PIC_suppress_blanks);
|
|
if (value->nod_type == nod_reference)
|
|
value = value->nod_arg[0];
|
|
bool expression = true;
|
|
if (value->nod_type == nod_field ||
|
|
value->nod_type == nod_variable ||
|
|
value->nod_type == nod_function)
|
|
{
|
|
expression = false;
|
|
if (value->nod_type != nod_function) {
|
|
field = (QLI_FLD) value->nod_arg[e_fld_field];
|
|
name = field->fld_name;
|
|
format_index(*item, value, false);
|
|
}
|
|
else
|
|
name = ((FUN) value->nod_arg[e_fun_function])->fun_symbol;
|
|
}
|
|
*new_ptr++ = new_item = (ITM) ALLOCD(type_itm);
|
|
new_item->itm_type = item_value;
|
|
new_item->itm_value = value = (QLI_NOD) ALLOCDV(type_nod, 0);
|
|
value->nod_type = nod_constant;
|
|
value->nod_flags |= NOD_local;
|
|
value->nod_desc.dsc_dtype = dtype_text;
|
|
TEXT* q;
|
|
if (!expression && (!(q = (*item)->itm_query_header) || *q != '-')) {
|
|
if (q) {
|
|
if (*q != '"' && *q != '\'')
|
|
value->nod_desc.dsc_address = (UCHAR *) q;
|
|
else {
|
|
STR header = (STR) ALLOCDV(type_str, strlen(q));
|
|
TEXT* p = header->str_data;
|
|
value->nod_desc.dsc_address = (UCHAR*) p;
|
|
TEXT c;
|
|
while (c = *q++) {
|
|
while (*q != c)
|
|
*p++ = *q++;
|
|
*p++ = ' ';
|
|
q++;
|
|
}
|
|
p[-1] = 0;
|
|
}
|
|
value->nod_desc.dsc_length =
|
|
strlen((char*) value->nod_desc.dsc_address);
|
|
}
|
|
else {
|
|
value->nod_desc.dsc_length = name->sym_length;
|
|
value->nod_desc.dsc_address = (UCHAR *) name->sym_string;
|
|
}
|
|
column = MAX(column, value->nod_desc.dsc_length);
|
|
new_item->itm_picture = PIC_analyze(0, &value->nod_desc);
|
|
}
|
|
else {
|
|
const dsc* desc = EVAL_value(value);
|
|
new_item->itm_picture = PIC_analyze(0, desc);
|
|
}
|
|
|
|
if (!new_item->itm_picture->pic_missing &&
|
|
value->nod_type == nod_field && field->fld_missing)
|
|
PIC_missing(field->fld_missing, new_item->itm_picture);
|
|
|
|
*new_ptr++ = *item;
|
|
}
|
|
|
|
*new_ptr++ = new_item = (ITM) ALLOCD(type_itm);
|
|
new_item->itm_type = item_skip;
|
|
new_item->itm_count = 1;
|
|
column += 2;
|
|
|
|
for (item = (ITM *) list->nod_arg, end = item + list->nod_count;
|
|
item < end; item++)
|
|
{
|
|
if ((*item)->itm_type != item_value || !(value = (*item)->itm_value))
|
|
continue;
|
|
if (value->nod_type == nod_reference)
|
|
value = value->nod_arg[0];
|
|
(*item)->itm_print_offset = column;
|
|
}
|
|
|
|
new_nod->nod_count = new_ptr - (ITM *) new_nod->nod_arg;
|
|
|
|
return new_nod;
|
|
}
|
|
|
|
|
|
void FMT_print( QLI_NOD list, PRT print)
|
|
{
|
|
/**************************************
|
|
*
|
|
* F M T _ p r i n t
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Format a print line. Return the number of lines printed.
|
|
*
|
|
**************************************/
|
|
USHORT l;
|
|
QLI_NOD* ptr;
|
|
RPT report;
|
|
|
|
// Now go thru and make up the first line
|
|
|
|
if (!list)
|
|
return;
|
|
|
|
TEXT* buffer = NULL;
|
|
TEXT* p = BUFFER_INIT;
|
|
qli_nod** const end = list->nod_arg + list->nod_count;
|
|
|
|
for (ptr = list->nod_arg; ptr < end; ptr++) {
|
|
ITM item = (ITM) *ptr;
|
|
|
|
/* Handle formating directives. Most have been translated into
|
|
column assignments and are no-ops. */
|
|
|
|
buffer = BUFFER_BEGINNING;
|
|
|
|
switch (item->itm_type) {
|
|
case item_value:
|
|
break;
|
|
|
|
case item_new_page:
|
|
if (print->prt_new_page) {
|
|
put_line(print, &p, buffer, '\n');
|
|
(*print->prt_new_page) (print, false);
|
|
}
|
|
else {
|
|
put_line(print, &p, buffer, '\f');
|
|
QLI_skip_line = FALSE;
|
|
}
|
|
continue;
|
|
|
|
case item_skip:
|
|
put_line(print, &p, buffer, '\n');
|
|
print_blobs(print, (ITM*) list->nod_arg, (ITM*) ptr);
|
|
for (l = item->itm_count - 1; l > 0; --l)
|
|
put_line(print, &p, buffer, '\n');
|
|
QLI_skip_line = FALSE;
|
|
continue;
|
|
|
|
case item_column_header:
|
|
if ((report = print->prt_report) && report->rpt_column_header)
|
|
FMT_put(report->rpt_column_header, print);
|
|
continue;
|
|
|
|
case item_report_header:
|
|
if ((report = print->prt_report) && report->rpt_header)
|
|
FMT_put(report->rpt_header, print);
|
|
continue;
|
|
|
|
case item_column:
|
|
case item_tab:
|
|
case item_space:
|
|
default:
|
|
continue;
|
|
}
|
|
|
|
/* Handle print items. Start by by spacing out to the correct column,
|
|
forcing a new line if required. */
|
|
|
|
BUFFER_CHECK(p, item->itm_print_offset + item->itm_print_length + 2);
|
|
buffer = BUFFER_BEGINNING;
|
|
const TEXT* const q = buffer + item->itm_print_offset;
|
|
if (p > q) {
|
|
put_line(print, &p, buffer, '\n');
|
|
print_blobs(print, (ITM*) list->nod_arg, (ITM*) ptr);
|
|
}
|
|
while (p < q)
|
|
*p++ = ' ';
|
|
|
|
// Next, handle simple formated values
|
|
|
|
if (item->itm_dtype != dtype_blob) {
|
|
const dsc* desc = EVAL_value(item->itm_value);
|
|
if (!(desc->dsc_missing & DSC_missing))
|
|
PIC_edit(desc, item->itm_picture, &p, BUFFER_REMAINING(p));
|
|
else if (item->itm_picture->pic_missing)
|
|
PIC_edit(desc, item->itm_picture->pic_missing, &p,
|
|
BUFFER_REMAINING(p));
|
|
continue;
|
|
}
|
|
|
|
// Finally, handle blobs
|
|
|
|
if (!(item->itm_stream = EXEC_open_blob(item->itm_value)))
|
|
continue;
|
|
|
|
if (print_line(item, &p) != EOF)
|
|
if (item->itm_flags & ITM_overlapped)
|
|
for (;;) {
|
|
put_line(print, &p, buffer, '\n');
|
|
while (p < q)
|
|
*p++ = ' ';
|
|
if (print_line(item, &p) == EOF)
|
|
break;
|
|
}
|
|
}
|
|
|
|
put_line(print, &p, buffer, '\n');
|
|
|
|
// Now go back until all blobs have been fetched
|
|
|
|
print_blobs(print, (ITM*) list->nod_arg, (ITM*) end);
|
|
|
|
// Finish by closing all blobs
|
|
ISC_STATUS_ARRAY status_vector;
|
|
for (ptr = list->nod_arg; ptr < end; ptr++) {
|
|
ITM item = (ITM) *ptr;
|
|
if (item->itm_dtype == dtype_blob && item->itm_stream)
|
|
gds__close_blob(status_vector, &item->itm_stream);
|
|
}
|
|
}
|
|
|
|
|
|
void FMT_put(const TEXT* line, PRT print)
|
|
{
|
|
/**************************************
|
|
*
|
|
* F M T _ p u t
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Write out an output file. Write
|
|
* fewer than 256 characters at a time
|
|
* to avoid annoying VMS.
|
|
*
|
|
**************************************/
|
|
TEXT buffer[256];
|
|
|
|
for (const TEXT* pnewline = line; *pnewline; pnewline++)
|
|
if (*pnewline == '\n' || *pnewline == '\f')
|
|
--print->prt_lines_remaining;
|
|
|
|
TEXT* const end = buffer + sizeof(buffer) - 1;
|
|
const TEXT* q = line;
|
|
TEXT* p;
|
|
|
|
if (print && print->prt_file)
|
|
while (*q) {
|
|
for (p = buffer; p < end && *q;)
|
|
*p++ = *q++;
|
|
*p = 0;
|
|
ib_fprintf((IB_FILE *) print->prt_file, "%s", buffer);
|
|
}
|
|
else {
|
|
while (*q) {
|
|
for (p = buffer; p < end && *q;)
|
|
*p++ = *q++;
|
|
*p = 0;
|
|
#ifdef DEV_BUILD
|
|
if (QLI_hex_output) {
|
|
// Hex mode output to assist debugging of multicharset work
|
|
|
|
for (p = buffer; p < end && *p; p++)
|
|
if (is_printable(*p))
|
|
ib_fprintf(ib_stdout, "%c", *p);
|
|
else
|
|
ib_fprintf(ib_stdout, "[%2.2X]", *(UCHAR *) p);
|
|
}
|
|
else
|
|
#endif
|
|
ib_fprintf(ib_stdout, "%s", buffer);
|
|
}
|
|
QLI_skip_line = TRUE;
|
|
}
|
|
}
|
|
|
|
|
|
void FMT_report( RPT report)
|
|
{
|
|
/**************************************
|
|
*
|
|
* F M T _ r e p o r t
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Format a report.
|
|
*
|
|
**************************************/
|
|
if (global_fmt_buffer) {
|
|
ALL_release((FRB) global_fmt_buffer);
|
|
global_fmt_buffer = NULL;
|
|
}
|
|
|
|
USHORT width = report->rpt_columns;
|
|
vec* columns_vec = (VEC) ALLOCDV(type_vec, 256);
|
|
columns_vec->vec_count = 256;
|
|
columns_vec->vec_object[0] = NULL;
|
|
|
|
report_break(report->rpt_top_rpt, &columns_vec, false);
|
|
report_break(report->rpt_top_page, &columns_vec, false);
|
|
report_break(report->rpt_top_breaks, &columns_vec, false);
|
|
|
|
QLI_NOD list = report->rpt_detail_line;
|
|
if (list)
|
|
report_line(list, &columns_vec);
|
|
|
|
report_break(report->rpt_bottom_breaks, &columns_vec, true);
|
|
report_break(report->rpt_bottom_page, &columns_vec, true);
|
|
report_break(report->rpt_bottom_rpt, &columns_vec, true);
|
|
|
|
report->rpt_column_header = format_report(columns_vec, width, &width);
|
|
|
|
// Handle report name, if any
|
|
|
|
if (report->rpt_name) {
|
|
USHORT lengths[16];
|
|
TEXT* segments[16];
|
|
const USHORT n =
|
|
decompose_header(report->rpt_name, segments, lengths);
|
|
USHORT i;
|
|
for (i = 0; i < n; i++)
|
|
width = MAX(width, lengths[i] + 15);
|
|
|
|
STR string = (STR) ALLOCDV(type_str, width * n);
|
|
TEXT* p = string->str_data;
|
|
report->rpt_header = p;
|
|
for (i = 0; i < n; i++) {
|
|
USHORT column = (width - lengths[i]) / 2;
|
|
if (column > 0)
|
|
do {
|
|
*p++ = ' ';
|
|
} while (--column);
|
|
const TEXT* q = segments[i];
|
|
const TEXT* const end = q + lengths[i];
|
|
while (q < end)
|
|
*p++ = *q++;
|
|
*p++ = '\n';
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static USHORT decompose_header(SCHAR* string,
|
|
SCHAR** segments,
|
|
USHORT* lengths)
|
|
{
|
|
/**************************************
|
|
*
|
|
* d e c o m p o s e _ h e a d e r
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Decompose a header string (aka field name) into segments.
|
|
* Return the address of and length of each segment (in arrays)
|
|
* and the number of segments.
|
|
*
|
|
**************************************/
|
|
TEXT c;
|
|
|
|
if (!string)
|
|
return 0;
|
|
|
|
USHORT n = 0;
|
|
|
|
// Handle simple name first
|
|
|
|
if (*string != '"' && *string != '\'')
|
|
while (*string) {
|
|
*segments = string;
|
|
while (*string && *string != '_')
|
|
string++;
|
|
*lengths++ = string - *segments++;
|
|
++n;
|
|
if (*string == '_')
|
|
string++;
|
|
}
|
|
else
|
|
while (c = *string++) {
|
|
*segments = string;
|
|
while (*string++ != c);
|
|
*lengths++ = string - *segments++ - 1;
|
|
++n;
|
|
}
|
|
|
|
return n;
|
|
}
|
|
|
|
|
|
static void format_index( ITM item, QLI_NOD field, const bool print_flag)
|
|
{
|
|
/**************************************
|
|
*
|
|
* f o r m a t _ i n d e x
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Format the label of a subscripted item.
|
|
*
|
|
**************************************/
|
|
QLI_NOD args;
|
|
|
|
/* Don't bother with anything except non-indexed fields. Also
|
|
ignore subscripted fields with user specified query headers. */
|
|
|
|
{
|
|
const TEXT* qh;
|
|
if (field->nod_type != nod_field ||
|
|
!(args = field->nod_arg[e_fld_subs]) ||
|
|
((qh = item->itm_query_header) && (*qh == '"' || *qh == '\'')))
|
|
return;
|
|
}
|
|
|
|
// Start the label with the current query header, if any
|
|
|
|
USHORT l;
|
|
const TEXT* q;
|
|
if (item->itm_query_header) {
|
|
q = item->itm_query_header;
|
|
l = strlen(item->itm_query_header);
|
|
}
|
|
else {
|
|
q = ((QLI_FLD) field->nod_arg[e_fld_field])->fld_name->sym_string;
|
|
l = ((QLI_FLD) field->nod_arg[e_fld_field])->fld_name->sym_length;
|
|
}
|
|
|
|
USHORT length = l + 2;
|
|
STR str = NULL;
|
|
TEXT* p = get_buffer(&str, NULL, length + 32);
|
|
while (l--)
|
|
*p++ = *q++;
|
|
|
|
// Loop through the subscripts, adding to the label
|
|
|
|
const TEXT* r;
|
|
if (print_flag) {
|
|
r = "_[";
|
|
length++;
|
|
}
|
|
else
|
|
r = "[";
|
|
|
|
TEXT s[32];
|
|
QLI_NOD *ptr, *end;
|
|
for (ptr = args->nod_arg, end = ptr + args->nod_count; ptr < end; ptr++) {
|
|
QLI_NOD subscript = *ptr;
|
|
switch (subscript->nod_type) {
|
|
case nod_constant:
|
|
sprintf(s, "%ld", MOVQ_get_long(&subscript->nod_desc, 0));
|
|
q = s;
|
|
l = strlen(s);
|
|
break;
|
|
|
|
case nod_variable:
|
|
case nod_field:
|
|
q = ((QLI_FLD) subscript->nod_arg[e_fld_field])->fld_name->sym_string;
|
|
l = ((QLI_FLD) subscript->nod_arg[e_fld_field])->fld_name->sym_length;
|
|
break;
|
|
|
|
default:
|
|
// Punt on anything but constants, fields, and variables
|
|
|
|
ALL_release((FRB) str);
|
|
return;
|
|
}
|
|
|
|
length += l + 1;
|
|
p = get_buffer(&str, p, length);
|
|
while (*r)
|
|
*p++ = *r++;
|
|
while (l--)
|
|
*p++ = *q++;
|
|
r = ",";
|
|
}
|
|
|
|
if (*r == ',')
|
|
*p++ = ']';
|
|
*p = 0;
|
|
item->itm_query_header = str->str_data;
|
|
}
|
|
|
|
|
|
static TEXT* format_report( VEC columns_vec, USHORT width, USHORT* max_width)
|
|
{
|
|
/**************************************
|
|
*
|
|
* f o r m a t _ r e p o r t
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Format a report. Figure out spacing, print formats, and
|
|
* headers. Return a pointer to the header string.
|
|
*
|
|
**************************************/
|
|
ITM item;
|
|
QLI_NOD node;
|
|
USHORT j, l, n, lengths[10], *ptr;
|
|
TEXT *p, *q, *segments[10];
|
|
|
|
/* Make a pass thru print items computing print lengths and header
|
|
lengths, and the number of header segments. */
|
|
|
|
USHORT number_segments, offset, max_offset;
|
|
number_segments = offset = max_offset = 0;
|
|
TEXT* bottom = BOTTOM_INIT;
|
|
|
|
LLS* col = (LLS*) columns_vec->vec_object;
|
|
LLS* col_end;
|
|
LLS temp;
|
|
for (col_end = col + columns_vec->vec_count; col < col_end && *col; col++) {
|
|
USHORT column_width = 0, max_print_width = 0;
|
|
bool right_adjust = false;
|
|
for (temp = *col; temp; temp = temp->lls_next) {
|
|
item = (ITM) temp->lls_object;
|
|
switch (item->itm_type) {
|
|
case item_column:
|
|
offset = (item->itm_count) ? item->itm_count - 1 : 0;
|
|
continue;
|
|
|
|
case item_skip:
|
|
case item_new_page:
|
|
case item_report_header:
|
|
case item_column_header:
|
|
offset = 0;
|
|
continue;
|
|
|
|
case item_space:
|
|
offset += item->itm_count;
|
|
continue;
|
|
|
|
case item_tab:
|
|
offset = (offset & ~7) + item->itm_count * 8;
|
|
continue;
|
|
|
|
case item_value:
|
|
max_print_width =
|
|
MAX(max_print_width, item->itm_print_length);
|
|
node = item->itm_value;
|
|
if (node->nod_desc.dsc_dtype >= dtype_short &&
|
|
node->nod_desc.dsc_dtype <= dtype_double)
|
|
right_adjust = true;
|
|
}
|
|
|
|
if (item->itm_query_header) {
|
|
n =
|
|
decompose_header(item->itm_query_header, segments,
|
|
lengths);
|
|
number_segments = MAX(n, number_segments);
|
|
for (j = 0, ptr = lengths; j < n; j++, ptr++)
|
|
item->itm_header_length =
|
|
MAX(item->itm_header_length, *ptr);
|
|
}
|
|
|
|
format_value(item, 0);
|
|
n = MAX(item->itm_print_length, item->itm_header_length);
|
|
column_width = MAX(column_width, n);
|
|
}
|
|
|
|
if (offset + column_width > width)
|
|
offset = 0;
|
|
|
|
const USHORT right_offset = column_width - max_print_width / 2;
|
|
|
|
for (temp = *col; temp; temp = temp->lls_next) {
|
|
item = (ITM) temp->lls_object;
|
|
if (item->itm_type != item_value)
|
|
continue;
|
|
|
|
if (right_adjust)
|
|
item->itm_print_offset =
|
|
offset + right_offset - item->itm_print_length;
|
|
else
|
|
item->itm_print_offset =
|
|
offset + (column_width - item->itm_print_length) / 2;
|
|
|
|
item->itm_header_offset = offset + column_width / 2;
|
|
|
|
/* Before we blindly format the header, make sure there already isn't
|
|
header information in the same location */
|
|
|
|
if (item->itm_query_header) {
|
|
BOTTOM_CHECK(bottom, offset);
|
|
q = BOTTOM_LINE + offset;
|
|
while (bottom < q)
|
|
*bottom++ = ' ';
|
|
bool flag = true;
|
|
if (offset && q[-1] != ' ')
|
|
flag = false;
|
|
else if (l = MIN(column_width, bottom - q)) {
|
|
p = bottom;
|
|
while (--l)
|
|
if (*p++ != ' ') {
|
|
flag = false;
|
|
break;
|
|
}
|
|
if (flag && p < bottom && *p != ' ')
|
|
flag = false;
|
|
}
|
|
if (flag) {
|
|
BOTTOM_CHECK(bottom, offset + column_width);
|
|
for (q = BOTTOM_LINE + offset + column_width; bottom < q;)
|
|
*bottom++ = '=';
|
|
}
|
|
else
|
|
item->itm_query_header = NULL;
|
|
}
|
|
}
|
|
offset += column_width + 1;
|
|
max_offset = MAX(max_offset, offset);
|
|
}
|
|
|
|
*max_width = max_offset;
|
|
|
|
if (number_segments == 0)
|
|
return NULL;
|
|
|
|
// Allocate a string block big enough to hold all lines of the print header
|
|
|
|
l = bottom - BOTTOM_LINE;
|
|
STR header = (STR) ALLOCDV(type_str,
|
|
(max_offset + 1) * (number_segments + 1) + 2 + l);
|
|
p = header->str_data;
|
|
|
|
// Generate the various lines of the header line at a time.
|
|
|
|
for (j = 0; j < number_segments; j++) {
|
|
*p++ = '\n';
|
|
TEXT* const line = p;
|
|
col = (LLS*) columns_vec->vec_object;
|
|
for (col_end = col + columns_vec->vec_count; col < col_end && *col;
|
|
col++)
|
|
{
|
|
for (temp = *col; temp; temp = temp->lls_next) {
|
|
item = (ITM) temp->lls_object;
|
|
if (item->itm_type != item_value)
|
|
continue;
|
|
n =
|
|
decompose_header(item->itm_query_header, segments,
|
|
lengths);
|
|
SSHORT segment = j - (number_segments - n);
|
|
if (segment < 0)
|
|
continue;
|
|
l = lengths[segment];
|
|
q = line + item->itm_header_offset - l / 2;
|
|
while (p < q)
|
|
*p++ = ' ';
|
|
q = segments[segment];
|
|
if (l)
|
|
do {
|
|
*p++ = *q++;
|
|
} while (--l);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Make one last pass to put in underlining of headers
|
|
|
|
if (l = bottom - BOTTOM_LINE) {
|
|
*p++ = '\n';
|
|
bottom = BOTTOM_LINE;
|
|
do {
|
|
*p++ = *bottom++;
|
|
} while (--l);
|
|
}
|
|
|
|
*p++ = '\n';
|
|
*p++ = '\n';
|
|
*p = 0;
|
|
|
|
return header->str_data;
|
|
}
|
|
|
|
|
|
static void format_value( ITM item, int flags)
|
|
{
|
|
/**************************************
|
|
*
|
|
* f o r m a t _ v a l u e
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
*
|
|
**************************************/
|
|
QLI_NOD node = item->itm_value;
|
|
dsc* desc = &node->nod_desc;
|
|
item->itm_dtype = desc->dsc_dtype;
|
|
item->itm_sub_type = desc->dsc_sub_type;
|
|
|
|
if (desc->dsc_dtype == dtype_blob) {
|
|
item->itm_print_length = 40;
|
|
if (node->nod_type == nod_reference)
|
|
node = node->nod_arg[0];
|
|
if (node->nod_type == nod_field) {
|
|
const qli_fld* field = (QLI_FLD) node->nod_arg[e_fld_field];
|
|
if (field->fld_segment_length)
|
|
item->itm_print_length = field->fld_segment_length;
|
|
}
|
|
}
|
|
else {
|
|
PICS picture = PIC_analyze(item->itm_edit_string, desc);
|
|
item->itm_picture = picture;
|
|
|
|
if (node->nod_type == nod_reference)
|
|
node = node->nod_arg[0];
|
|
|
|
const qli_fld* field;
|
|
if (node->nod_type == nod_field) {
|
|
field = (QLI_FLD) node->nod_arg[e_fld_field];
|
|
if ((field->fld_flags & FLD_array) && !node->nod_arg[e_fld_subs])
|
|
ERRQ_print_error(480, field->fld_name->sym_string, NULL, NULL,
|
|
NULL, NULL); // msg 480 can not format unsubscripted array %s
|
|
}
|
|
|
|
if (!(item->itm_picture->pic_missing) &&
|
|
(node->nod_type == nod_field) &&
|
|
(field = (QLI_FLD) node->nod_arg[e_fld_field]) && field->fld_missing)
|
|
{
|
|
PIC_missing(field->fld_missing, picture);
|
|
}
|
|
item->itm_print_length = picture->pic_length;
|
|
picture->pic_flags |= flags;
|
|
}
|
|
}
|
|
|
|
|
|
static TEXT* get_buffer(STR* str, TEXT* ptr, USHORT length)
|
|
{
|
|
/**************************************
|
|
*
|
|
* g e t _ b u f f e r
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Make sure we have a large enough buffer.
|
|
* If the current one is too short, copy the
|
|
* current buffer to the new one.
|
|
*
|
|
**************************************/
|
|
USHORT l;
|
|
|
|
if (!*str) {
|
|
*str = (STR) ALLOCPV(type_str, length);
|
|
(*str)->str_length = length;
|
|
return (*str)->str_data;
|
|
}
|
|
|
|
if (length <= (*str)->str_length)
|
|
return (ptr) ? ptr : (*str)->str_data;
|
|
|
|
STR temp_str = (STR) ALLOCPV(type_str, length);
|
|
temp_str->str_length = length;
|
|
TEXT* p = temp_str->str_data;
|
|
const TEXT* q = (*str)->str_data;
|
|
|
|
if (ptr && (l = ptr - q))
|
|
do {
|
|
*p++ = *q++;
|
|
} while (--l);
|
|
|
|
ALL_release((FRB) *str);
|
|
*str = temp_str;
|
|
|
|
return p;
|
|
}
|
|
|
|
|
|
static bool match_expr(const qli_nod* node1, const qli_nod* node2)
|
|
{
|
|
/**************************************
|
|
*
|
|
* m a t c h _ e x p r
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Compare two nodes for equality of value.
|
|
*
|
|
**************************************/
|
|
|
|
// If either is missing, they can't match.
|
|
|
|
if (!node1 || !node2)
|
|
return false;
|
|
|
|
if (node1->nod_type == nod_reference)
|
|
node1 = node1->nod_arg[0];
|
|
|
|
if (node2->nod_type == nod_reference)
|
|
node2 = node2->nod_arg[0];
|
|
|
|
// A constant more or less matches anything
|
|
|
|
if (node1->nod_type == nod_constant)
|
|
return true;
|
|
|
|
// Hasn't matched yet. Check for statistical expression
|
|
|
|
switch (node1->nod_type) {
|
|
case nod_average:
|
|
case nod_max:
|
|
case nod_min:
|
|
case nod_total:
|
|
|
|
case nod_rpt_average:
|
|
case nod_rpt_max:
|
|
case nod_rpt_min:
|
|
case nod_rpt_total:
|
|
case nod_running_total:
|
|
|
|
case nod_agg_average:
|
|
case nod_agg_max:
|
|
case nod_agg_min:
|
|
case nod_agg_total:
|
|
return match_expr(node1->nod_arg[e_stt_value], node2);
|
|
}
|
|
|
|
switch (node2->nod_type) {
|
|
case nod_average:
|
|
case nod_max:
|
|
case nod_min:
|
|
case nod_total:
|
|
|
|
case nod_rpt_average:
|
|
case nod_rpt_max:
|
|
case nod_rpt_min:
|
|
case nod_rpt_total:
|
|
case nod_running_total:
|
|
|
|
case nod_agg_average:
|
|
case nod_agg_max:
|
|
case nod_agg_min:
|
|
case nod_agg_total:
|
|
return match_expr(node1, node2->nod_arg[e_stt_value]);
|
|
}
|
|
|
|
if (node1->nod_type == node2->nod_type) {
|
|
if (node1->nod_type == nod_field) {
|
|
if (node1->nod_arg[e_fld_field] != node2->nod_arg[e_fld_field] ||
|
|
node1->nod_arg[e_fld_context] !=
|
|
node2->nod_arg[e_fld_context])
|
|
{
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
const qli_nod* const* ptr1 = node1->nod_arg;
|
|
const qli_nod* const* ptr2 = node2->nod_arg;
|
|
for (const qli_nod* const* end = ptr1 + node1->nod_count; ptr1 < end;
|
|
++ptr1, ++ptr2)
|
|
{
|
|
if (!match_expr(*ptr1, *ptr2))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
static void print_blobs( PRT print, itm** first, itm** last)
|
|
{
|
|
/**************************************
|
|
*
|
|
* p r i n t _ b l o b s
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Print any blobs still active in item list.
|
|
*
|
|
**************************************/
|
|
if (QLI_abort)
|
|
return;
|
|
|
|
itm** ptr;
|
|
|
|
USHORT length = 0;
|
|
for (ptr = first; ptr < last; ptr++) {
|
|
const itm* item = *ptr;
|
|
if (item->itm_dtype == dtype_blob && item->itm_stream)
|
|
length =
|
|
MAX(length,
|
|
item->itm_print_offset + item->itm_print_length + 2);
|
|
}
|
|
|
|
|
|
TEXT* buffer = get_buffer(&global_blob_buffer, NULL, length);
|
|
|
|
while (!QLI_abort) {
|
|
bool blob_active = false;
|
|
TEXT* p = buffer;
|
|
bool do_line = false;
|
|
for (ptr = first; ptr < last; ptr++) {
|
|
itm* item = *ptr;
|
|
if (item->itm_dtype != dtype_blob || !item->itm_stream)
|
|
continue;
|
|
const TEXT* const end = buffer + item->itm_print_offset;
|
|
while (p < end)
|
|
*p++ = ' ';
|
|
const TEXT* const pp = p;
|
|
const int c = print_line(item, &p);
|
|
if (c != EOF)
|
|
blob_active = true;
|
|
if (pp != p || c == '\n')
|
|
do_line = true;
|
|
}
|
|
if (do_line)
|
|
put_line(print, &p, buffer, '\n');
|
|
if (!blob_active)
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
static int print_line( itm* item, TEXT** ptr)
|
|
{
|
|
/**************************************
|
|
*
|
|
* p r i n t _ l i n e
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Print a line of a blob or scratch file. The
|
|
* last thing printed.
|
|
*
|
|
**************************************/
|
|
EXEC_poll_abort();
|
|
|
|
// If we're already at end of stream, there's nothing to do
|
|
|
|
if (!item->itm_stream)
|
|
return EOF;
|
|
|
|
TEXT* p = *ptr;
|
|
const USHORT l = item->itm_print_length;
|
|
|
|
USHORT length;
|
|
ISC_STATUS_ARRAY status_vector;
|
|
const ISC_STATUS status = gds__get_segment(status_vector, &item->itm_stream,
|
|
&length, l, p);
|
|
if (status && status != gds_segment) {
|
|
ISC_STATUS* null_status = 0;
|
|
gds__close_blob(null_status, &item->itm_stream);
|
|
if (status != gds_segstr_eof)
|
|
ERRQ_database_error(0, status_vector);
|
|
return EOF;
|
|
}
|
|
|
|
/* If this is not a partial segment and the last character
|
|
is a newline, throw away the newline */
|
|
|
|
if (!status && length && p[length - 1] == '\n')
|
|
if (length > 1)
|
|
--length;
|
|
else
|
|
p[0] = ' ';
|
|
|
|
*ptr = p + length;
|
|
|
|
/* Return the last character in the segment.
|
|
If the segment is null, return a newline. */
|
|
|
|
return (length) ? p[length - 1] : '\n';
|
|
}
|
|
|
|
|
|
static void put_line( PRT print, TEXT** ptr, TEXT* buffer, TEXT terminator)
|
|
{
|
|
/**************************************
|
|
*
|
|
* p u t _ l i n e
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Given a file descriptor, a running output pointer, and the address
|
|
* of the original buffer, write out the current line and reset the
|
|
* pointer.
|
|
*
|
|
**************************************/
|
|
|
|
*(*ptr)++ = terminator;
|
|
**ptr = 0;
|
|
FMT_put(buffer, print);
|
|
*ptr = buffer;
|
|
}
|
|
|
|
|
|
static void report_break( BRK control, VEC* columns_vec, const bool bottom_flag)
|
|
{
|
|
/**************************************
|
|
*
|
|
* r e p o r t _ b r e a k
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Handle formatting for a chain of control breaks.
|
|
*
|
|
**************************************/
|
|
|
|
if (!control)
|
|
return;
|
|
|
|
if (bottom_flag) {
|
|
if (control->brk_next)
|
|
report_break(control->brk_next, columns_vec, bottom_flag);
|
|
if (control->brk_line)
|
|
report_line((QLI_NOD) control->brk_line, columns_vec);
|
|
return;
|
|
}
|
|
|
|
for (; control; control = control->brk_next)
|
|
if (control->brk_line)
|
|
report_line((QLI_NOD) control->brk_line, columns_vec);
|
|
}
|
|
|
|
|
|
static void report_item( ITM item, VEC* columns_vec, USHORT* col_ndx)
|
|
{
|
|
/**************************************
|
|
*
|
|
* r e p o r t _ i t e m
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Insert a report item into a logical column. It it fits
|
|
* someplace reasonable, stick it there.
|
|
*
|
|
**************************************/
|
|
if (item->itm_query_header && *item->itm_query_header == '-')
|
|
item->itm_query_header = NULL;
|
|
|
|
// If it's a constant, dump it in the next logical column
|
|
|
|
QLI_NOD node;
|
|
VEC columns = *columns_vec;
|
|
if (columns->vec_object[*col_ndx] &&
|
|
(node = item->itm_value) && node->nod_type == nod_constant)
|
|
{
|
|
LLS_PUSH(item, (LLS*) (columns->vec_object + *col_ndx));
|
|
return;
|
|
}
|
|
|
|
/* Loop thru remaining logical columns looking for an equivalent
|
|
expression. If we find one, the item beSLONGs in that column;
|
|
otherwise, someplace else. */
|
|
|
|
LLS* col = (LLS*) (columns->vec_object + *col_ndx);
|
|
LLS* const col_end = (LLS*) (columns->vec_object + columns->vec_count);
|
|
for (; col < col_end && *col; col++)
|
|
for (LLS temp = *col; temp; temp = temp->lls_next) {
|
|
ITM item2 = (ITM) temp->lls_object;
|
|
if (match_expr(item->itm_value, item2->itm_value)) {
|
|
LLS_PUSH(item, col);
|
|
*col_ndx = col - (LLS *) columns->vec_object;
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Didn't fit -- make a new logical column
|
|
|
|
const USHORT new_index = col - (LLS *) columns->vec_object;
|
|
*col_ndx = new_index;
|
|
if (new_index >= columns->vec_count) {
|
|
ALLQ_extend((BLK*) columns_vec, new_index + 16);
|
|
(*columns_vec)->vec_count = new_index + 16;
|
|
}
|
|
|
|
LLS_PUSH(item, (LLS*) ((*columns_vec)->vec_object + new_index));
|
|
}
|
|
|
|
|
|
static void report_line( QLI_NOD list, VEC * columns_vec)
|
|
{
|
|
/**************************************
|
|
*
|
|
* r e p o r t _ l i n e
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Process a report line.
|
|
*
|
|
**************************************/
|
|
USHORT col_ndx = 0;
|
|
ITM* ptr = (ITM *) list->nod_arg;
|
|
for (ITM* const end = ptr + list->nod_count; ptr < end; ptr++) {
|
|
ITM item = *ptr;
|
|
report_item(item, columns_vec, &col_ndx);
|
|
switch (item->itm_type) {
|
|
case item_skip:
|
|
case item_new_page:
|
|
col_ndx = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|