Commit 3dc3ca92 authored by Carsten  Rose's avatar Carsten Rose
Browse files

Fixes #9929: New keyword '_noWrap' for column names (alias) - skips wrapping of fbeg/fskip/fend.

parent cb651bd8
Pipeline #3219 passed with stages
in 3 minutes and 57 seconds
......@@ -785,7 +785,9 @@ QFQ Keywords (Bodytext)
+-------------------+---------------------------------------------------------------------------------+
| <level>.fsep | Separator token between fields (=columns) |
+-------------------+---------------------------------------------------------------------------------+
| <level>.fskipwrap | Comma separated list of column id's. Skip wrapping of indexed columns. |
| <level>.fskipwrap | Skip wrapping (via fbeg, fsep, fend) of named columns. Comma separated list of |
| | column id's (starting at 1). See also the special column name '_noWrap' to |
| | suppress wrapping. |
+-------------------+---------------------------------------------------------------------------------+
| <level>.shead | Static start token for whole <level>, independent if records are selected |
| | Shown before `head`. |
......@@ -1621,7 +1623,7 @@ Get Parameter
Post Parameter
--------------
 
Per `FormElement` (HTML input) the default is to `htmlspecialchars()` the input. This means &<>'" will be encoded as htmlentity
Per `FormElement` (HTML input) the default is to `htmlspecialchars()` the input. This means ``&<>'"`` will be encoded as htmlentity
and saved as a htmlentity in the database. In case any of these characters (e.g. for HTML tags) are
required, the encoding can be disabled per FormElement: `encode=none` (default is `specialchar`).
 
......@@ -6057,7 +6059,7 @@ Special column names
Twig: respect that the 'special column name'-columns are rendered before Twig becomes active. The recommended
way by using Twig is *not to use* special column names at all. Use the Twig version *qfqLink*.
 
QFQ typically don't care about the content of any SQL-Query - it just copy the content to the output (=Browser).
QFQ don't care about the content of any SQL-Query - it just copy the content to the output (=Browser).
 
One exception are columns, whose name starts with '_'. E.g.::
 
......@@ -6068,6 +6070,16 @@ One exception are columns, whose name starts with '_'. E.g.::
content will be hidden.
* The fourth column (alias name 'link') uses a QFQ special column name. Here, only in this example, it has no
further meaning.
* All columns in a row with the same special column name (e.g. ``... AS _page``) will have the same column name: 'page'.
To access individual columns a uniq column title can be added::
10.sql = SELECT '..1..' AS '_page|column1', '..2..' AS '_page|column2'
Those columns can be accessed via ``{{10.column1}}`` , ``{{10.column2}}`` or ``{{column1:R}}`` , ``{{column2:R}}``
* To skip wrapping via ``fbeg``, ``fsep``, ``fend`` for dedicated columns, add the keyword ``|_noWrap`` to the column alias.
Example::
10.sql = SELECT 'world' AS 'title|_noWrap'
 
Summary:
 
......@@ -8206,7 +8218,7 @@ Best practice *recommendation* for using parameter - see `access-column-values`_
}
}
 
Create HTML tables::
Create HTML tables. Each column is wrapped in ``<td>``, each row is wrapped in ``<tr>``::
 
10 {
sql = SELECT p.firstName, p.lastName, p.country FROM Person AS p
......@@ -8218,9 +8230,9 @@ Create HTML tables::
fend = </td>
}
 
Maybe a few columns belongs together and should be in one column.
Maybe a few columns belongs together and should be in one table column.
 
Joining columns, variant A: firstName and lastName in one column::
Joining columns, variant A: firstName and lastName in one table column::
 
10 {
sql = SELECT CONCAT(p.firstName, ' ', p.lastName), p.country FROM Person AS p
......@@ -8232,7 +8244,7 @@ Joining columns, variant A: firstName and lastName in one column::
fend = </td>
}
 
Joining columns, variant B: firstName and lastName in one column::
Joining columns, variant B: firstName and lastName in one table column::
 
10 {
sql = SELECT '<td>', p.firstName, ' ', p.lastName, '</td><td>', p.country, '</td>' FROM Person AS p
......@@ -8242,7 +8254,7 @@ Joining columns, variant B: firstName and lastName in one column::
rend = </tr>
}
 
Joining columns, variant C: firstName and lastName in one column::
Joining columns, variant C: firstName and lastName in one table column. Notice ``fbeg``, ``fend` and ``fskipwrap``::
 
10 {
sql = SELECT '<td>', p.firstName, ' ', p.lastName, '</td>', p.country FROM Person AS p
......@@ -8255,6 +8267,18 @@ Joining columns, variant C: firstName and lastName in one column::
fskipwrap = 1,2,3,4,5
}
 
Joining columns, variant D: firstName and lastName in one table column. Notice ``fbeg``, ``fend` and ``fskipwrap``::
10 {
sql = SELECT CONCAT('<td>', p.firstName, ' ', p.lastName, '</td>') AS '_noWrap', p.country FROM Person AS p
head = <table class="table">
tail = </table>
rbeg = <tr>
rend = </tr>
fbeg = <td>
fend = </td>
}
Recent List
^^^^^^^^^^^
 
......
......@@ -1585,9 +1585,18 @@ const COLUMN_MAILTO = "mailto";
const COLUMN_SENDMAIL = "sendmail";
const COLUMN_VERTICAL = "vertical";
const COLUMN_NO_WRAP = "noWrap";
const COLUMN_HIDE = "hide";
const C_FULL = 'full';
const C_TITLE = 'title';
const C_NO_WRAP = 'noWrap';
const C_SPECIAL = 'special';
const C_HIDE = 'hide';
const COLUMN_WRAP_TOKEN = '+';
const COLUMN_STORE_USER = '=';
const FORM_NAME_FORM = 'form';
const FORM_NAME_FORM_ELEMENT = 'formElement';
const FORM_LOG_MODE = '_formLogMode'; // Variable to call the form in debug mode.
......
......@@ -536,9 +536,16 @@ class Report {
if ('' != ($str = ($this->frArray[$fullLevel . "." . TOKEN_FSKIPWRAP]) ?? '')) {
$str = str_replace(' ', '', $str);
$fSkipWrap = explode(',', $str);
// Decrement all values to start counting with 0.
foreach ($fSkipWrap as $key => $value) {
$fSkipWrap[$key] = $value - 1;
}
$fSkipWrap = array_flip($fSkipWrap);
}
$newKeys = $this->splitColumnNames($keys, $fSkipWrap);
//---------------------------------
// Process each row of result set
$columnValueSeparator = "";
......@@ -566,7 +573,7 @@ class Report {
//-----------------------------
// COLUMNS: Collect all columns
$contentLevel .= $this->collectRow($row, $keys, $fullLevel, $rowIndex, $fSkipWrap);
$contentLevel .= $this->collectRow($row, $newKeys, $fullLevel, $rowIndex);
// REND
$contentLevel .= $this->variables->doVariables($this->frArray[$fullLevel . "." . TOKEN_REND]);
......@@ -597,6 +604,8 @@ class Report {
if (!empty($sql)) {
$result = $this->db->sql($sql, ROW_KEYS, array(), '', $keys, $stat);
$newKeys = $this->splitColumnNames($keys);
$this->variables->resultArray[$fullLevel . ".line."][LINE_ALT_TOTAL] = $rowTotal;
$this->variables->resultArray[$fullLevel . ".line."][LINE_ALT_COUNT] = is_array($result) ? 1 : 0;
$this->variables->resultArray[$fullLevel . ".line."][LINE_ALT_INSERT_ID] = $stat[DB_INSERT_ID] ?? 0;
......@@ -604,7 +613,7 @@ class Report {
if (is_array($result)) {
foreach ($result as $row) {
$rowIndex = 0;
$contentLevel .= $this->collectRow($row, $keys, $fullLevel, $rowIndex, array());
$contentLevel .= $this->collectRow($row, $newKeys, $fullLevel, $rowIndex);
}
}
}
......@@ -666,6 +675,65 @@ class Report {
return $content;
}
/**
* Called with an array of column names.
* Each column name can be splitted in multiple string by '|': [s1[|s2[|s3]]]
* Each s1|s2|s3 can be: {title}, _{special colum name}, _hide, _noWrap, _={title}, _+{tag}, _<{tag1}><{tag2}>
*
* Return an Array: newKeys[idx][C_FULL|C_TITLE|C_NO_WRAP|C_HIDE]
*
* @param array $keys
* @param array $fSkipWrap
* @return array
*/
private function splitColumnNames(array $keys, array $fSkipWrap = array()) {
// Split Keynames in title / specialColumnName / fSkipWrap / hide
$ii = 0;
$newKeys = array();
foreach ($keys as $key) {
$newKeys[$ii][C_FULL] = $key;
$arr = explode(PARAM_DELIMITER, $key);
foreach ($arr as $kk) {
if (($kk[0] ?? '') == TOKEN_COLUMN_CTRL) {
switch ($kk) {
case TOKEN_COLUMN_CTRL . COLUMN_NO_WRAP:
$newKeys[$ii][C_NO_WRAP] = 1;
break;
case TOKEN_COLUMN_CTRL . COLUMN_HIDE:
$newKeys[$ii][C_HIDE] = 1;
break;
default:
$newKeys[$ii][C_SPECIAL] = $kk;
break;
}
} else {
$newKeys[$ii][C_TITLE] = $kk;
}
}
// Explicit given fSkipWrap
if (isset($fSkipWrap[$ii])) {
$newKeys[$ii][C_NO_WRAP] = $fSkipWrap[$ii];
}
// Fallback, if no dedicated title is given.
if (!isset($newKeys[$ii][C_TITLE])) {
// If no title is given, check if there is a specialColumnName (backward compatibility)
$newKeys[$ii][C_TITLE] = isset($newKeys[$ii][C_SPECIAL]) ? $newKeys[$ii][C_SPECIAL] : $newKeys[$ii][C_FULL];
}
if (($newKeys[$ii][C_TITLE][0] ?? '') == TOKEN_COLUMN_CTRL) {
$newKeys[$ii][C_TITLE] = substr($newKeys[$ii][C_TITLE], 1);
}
$ii++;
}
return $newKeys;
}
/**
* Render given Twig template with content from $result
*
......@@ -774,18 +842,20 @@ class Report {
* @param string $full_level Recent position to work on.
* @param string $rowIndex Index of recent row in resultset.
*
* @param array $fSkipWrap
* @return string Collected content of all printable columns
* @throws \CodeException
* @throws \DbException
* @throws \DownloadException
* @throws \UserFormException
* @throws \UserReportException
* @throws \PhpOffice\PhpSpreadsheet\Exception
* @throws \PhpOffice\PhpSpreadsheet\Reader\Exception
* @throws \PhpOffice\PhpSpreadsheet\Writer\Exception
* @throws \Twig\Error\LoaderError
* @throws \Twig\Error\RuntimeError
* @throws \Twig\Error\SyntaxError
* @throws \UserFormException
* @throws \UserReportException
*/
private function collectRow(array $row, array $keys, $full_level, $rowIndex, array $fSkipWrap) {
private function collectRow(array $row, array $keys, $full_level, $rowIndex) {
$content = "";
$assoc = array();
......@@ -795,13 +865,13 @@ class Report {
// Debugging
$this->store->setVar(SYSTEM_REPORT_COLUMN_INDEX, $ii + 1, STORE_SYSTEM);
$this->store->setVar(SYSTEM_REPORT_COLUMN_NAME, $keys[$ii], STORE_SYSTEM);
$this->store->setVar(SYSTEM_REPORT_COLUMN_NAME, $keys[$ii][C_FULL], STORE_SYSTEM);
$this->store->setVar(SYSTEM_REPORT_COLUMN_VALUE, $row[$ii], STORE_SYSTEM);
$flagOutput = false;
$renderedColumn = $this->renderColumn($ii, $keys[$ii], $row[$ii], $full_level, $rowIndex, $flagOutput);
$keyAssoc = OnString::stripFirstCharIf(TOKEN_COLUMN_CTRL, $keys[$ii]);
$keyAssoc = OnString::stripFirstCharIf(TOKEN_COLUMN_CTRL, $keys[$ii][C_TITLE]);
$keyAssoc = OnString::stripFirstCharIf(COLUMN_STORE_USER, $keyAssoc);
if ($keyAssoc != '') {
$assoc[$keyAssoc] = $row[$ii];
......@@ -811,14 +881,14 @@ class Report {
if ($flagOutput) {
//prints
if (!isset($fSkipWrap[$ii + 1])) {
if (!isset($keys[$ii][C_NO_WRAP])) {
$content .= $this->variables->doVariables($fsep);
$content .= $this->variables->doVariables($this->frArray[$full_level . "." . TOKEN_FBEG]);
}
$content .= $renderedColumn;
if (!isset($fSkipWrap[$ii + 1])) {
if (!isset($keys[$ii][C_NO_WRAP])) {
$content .= $this->variables->doVariables($this->frArray[$full_level . "." . TOKEN_FEND]);
}
......@@ -835,7 +905,7 @@ class Report {
* Renders column depending of column name (if name is a reserved column name)
*
* @param string $columnIndex
* @param string $columnName
* @param array $columnCtrl
* @param string $columnValue
* @param string $full_level
* @param string $rowIndex
......@@ -845,30 +915,30 @@ class Report {
* @throws \CodeException
* @throws \DbException
* @throws \DownloadException
* @throws \UserFormException
* @throws \UserReportException
* @throws \PhpOffice\PhpSpreadsheet\Exception
* @throws \PhpOffice\PhpSpreadsheet\Reader\Exception
* @throws \PhpOffice\PhpSpreadsheet\Writer\Exception
* @throws \Twig\Error\LoaderError
* @throws \Twig\Error\RuntimeError
* @throws \Twig\Error\SyntaxError
* @throws \UserFormException
* @throws \UserReportException
*/
private function renderColumn($columnIndex, $columnName, $columnValue, $full_level, $rowIndex, &$flagOutput) {
private function renderColumn($columnIndex, array $columnCtrl, $columnValue, $full_level, $rowIndex, &$flagOutput) {
$content = "";
$flagControl = false;
$flagOutput = true;
// Special column name: '_...'? Empty column names are allowed: check with isset
if (isset($columnName[0]) && $columnName[0] === TOKEN_COLUMN_CTRL) {
if (($columnCtrl[C_SPECIAL][0] ?? '') === TOKEN_COLUMN_CTRL) {
$flagControl = true;
$columnName = substr($columnName, 1);
$columnName = substr($columnCtrl[C_SPECIAL], 1);
// Special column name and hide output: '__...'? (double '_' at the beginning)
if (isset($columnName[0]) && $columnName[0] === TOKEN_COLUMN_CTRL) {
// Special column name and hide output
if (isset($columnCtrl[C_HIDE])) {
$flagOutput = false;
$columnName = substr($columnName, 1);
}
//TODO: reserved names,not starting with '_' will be still accepted - stop this!
switch ($columnName) {
case COLUMN_LINK:
$content .= $this->link->renderLink($columnValue);
......@@ -1114,7 +1184,7 @@ class Report {
default :
$flagOutput = false;
$token = isset($columnName[0]) ? $columnName[0] : '';
$token = ($columnName[0] ?? '');
switch ($token) {
case COLUMN_WRAP_TOKEN:
if (isset($columnName[1])) {
......@@ -1134,13 +1204,14 @@ class Report {
}
break;
}
} else {
// No special Columnname: just add the column value.
// No special column name: just add the column value.
$content .= $columnValue;
}
// Always save column values, even if they are hidden.
$this->variables->resultArray[$full_level . "."][$columnName] = ($content == '' && $flagControl) ? $columnValue : $content;
$this->variables->resultArray[$full_level . "."][$columnCtrl[C_TITLE]] = ($content == '' && $flagControl) ? $columnValue : $content;
return $content;
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment