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) ...@@ -785,7 +785,9 @@ QFQ Keywords (Bodytext)
+-------------------+---------------------------------------------------------------------------------+ +-------------------+---------------------------------------------------------------------------------+
| <level>.fsep | Separator token between fields (=columns) | | <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 | | <level>.shead | Static start token for whole <level>, independent if records are selected |
| | Shown before `head`. | | | Shown before `head`. |
...@@ -1621,7 +1623,7 @@ Get Parameter ...@@ -1621,7 +1623,7 @@ Get Parameter
Post 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 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`). required, the encoding can be disabled per FormElement: `encode=none` (default is `specialchar`).
   
...@@ -6057,7 +6059,7 @@ Special column names ...@@ -6057,7 +6059,7 @@ Special column names
Twig: respect that the 'special column name'-columns are rendered before Twig becomes active. The recommended 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*. 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.:: One exception are columns, whose name starts with '_'. E.g.::
   
...@@ -6068,6 +6070,16 @@ 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. content will be hidden.
* The fourth column (alias name 'link') uses a QFQ special column name. Here, only in this example, it has no * The fourth column (alias name 'link') uses a QFQ special column name. Here, only in this example, it has no
further meaning. 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: Summary:
   
...@@ -8206,7 +8218,7 @@ Best practice *recommendation* for using parameter - see `access-column-values`_ ...@@ -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 { 10 {
sql = SELECT p.firstName, p.lastName, p.country FROM Person AS p sql = SELECT p.firstName, p.lastName, p.country FROM Person AS p
...@@ -8218,9 +8230,9 @@ Create HTML tables:: ...@@ -8218,9 +8230,9 @@ Create HTML tables::
fend = </td> 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 { 10 {
sql = SELECT CONCAT(p.firstName, ' ', p.lastName), p.country FROM Person AS p 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:: ...@@ -8232,7 +8244,7 @@ Joining columns, variant A: firstName and lastName in one column::
fend = </td> fend = </td>
} }
   
Joining columns, variant B: firstName and lastName in one column:: Joining columns, variant B: firstName and lastName in one table column::
   
10 { 10 {
sql = SELECT '<td>', p.firstName, ' ', p.lastName, '</td><td>', p.country, '</td>' FROM Person AS p 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:: ...@@ -8242,7 +8254,7 @@ Joining columns, variant B: firstName and lastName in one column::
rend = </tr> 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 { 10 {
sql = SELECT '<td>', p.firstName, ' ', p.lastName, '</td>', p.country FROM Person AS p 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:: ...@@ -8255,6 +8267,18 @@ Joining columns, variant C: firstName and lastName in one column::
fskipwrap = 1,2,3,4,5 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 Recent List
^^^^^^^^^^^ ^^^^^^^^^^^
   
......
...@@ -1585,9 +1585,18 @@ const COLUMN_MAILTO = "mailto"; ...@@ -1585,9 +1585,18 @@ const COLUMN_MAILTO = "mailto";
const COLUMN_SENDMAIL = "sendmail"; const COLUMN_SENDMAIL = "sendmail";
const COLUMN_VERTICAL = "vertical"; 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_WRAP_TOKEN = '+';
const COLUMN_STORE_USER = '='; const COLUMN_STORE_USER = '=';
const FORM_NAME_FORM = 'form'; const FORM_NAME_FORM = 'form';
const FORM_NAME_FORM_ELEMENT = 'formElement'; const FORM_NAME_FORM_ELEMENT = 'formElement';
const FORM_LOG_MODE = '_formLogMode'; // Variable to call the form in debug mode. const FORM_LOG_MODE = '_formLogMode'; // Variable to call the form in debug mode.
......
...@@ -536,9 +536,16 @@ class Report { ...@@ -536,9 +536,16 @@ class Report {
if ('' != ($str = ($this->frArray[$fullLevel . "." . TOKEN_FSKIPWRAP]) ?? '')) { if ('' != ($str = ($this->frArray[$fullLevel . "." . TOKEN_FSKIPWRAP]) ?? '')) {
$str = str_replace(' ', '', $str); $str = str_replace(' ', '', $str);
$fSkipWrap = explode(',', $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); $fSkipWrap = array_flip($fSkipWrap);
} }
$newKeys = $this->splitColumnNames($keys, $fSkipWrap);
//--------------------------------- //---------------------------------
// Process each row of result set // Process each row of result set
$columnValueSeparator = ""; $columnValueSeparator = "";
...@@ -566,7 +573,7 @@ class Report { ...@@ -566,7 +573,7 @@ class Report {
//----------------------------- //-----------------------------
// COLUMNS: Collect all columns // COLUMNS: Collect all columns
$contentLevel .= $this->collectRow($row, $keys, $fullLevel, $rowIndex, $fSkipWrap); $contentLevel .= $this->collectRow($row, $newKeys, $fullLevel, $rowIndex);
// REND // REND
$contentLevel .= $this->variables->doVariables($this->frArray[$fullLevel . "." . TOKEN_REND]); $contentLevel .= $this->variables->doVariables($this->frArray[$fullLevel . "." . TOKEN_REND]);
...@@ -597,6 +604,8 @@ class Report { ...@@ -597,6 +604,8 @@ class Report {
if (!empty($sql)) { if (!empty($sql)) {
$result = $this->db->sql($sql, ROW_KEYS, array(), '', $keys, $stat); $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_TOTAL] = $rowTotal;
$this->variables->resultArray[$fullLevel . ".line."][LINE_ALT_COUNT] = is_array($result) ? 1 : 0; $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; $this->variables->resultArray[$fullLevel . ".line."][LINE_ALT_INSERT_ID] = $stat[DB_INSERT_ID] ?? 0;
...@@ -604,7 +613,7 @@ class Report { ...@@ -604,7 +613,7 @@ class Report {
if (is_array($result)) { if (is_array($result)) {
foreach ($result as $row) { foreach ($result as $row) {
$rowIndex = 0; $rowIndex = 0;
$contentLevel .= $this->collectRow($row, $keys, $fullLevel, $rowIndex, array()); $contentLevel .= $this->collectRow($row, $newKeys, $fullLevel, $rowIndex);
} }
} }
} }
...@@ -666,6 +675,65 @@ class Report { ...@@ -666,6 +675,65 @@ class Report {
return $content; 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 * Render given Twig template with content from $result
* *
...@@ -774,18 +842,20 @@ class Report { ...@@ -774,18 +842,20 @@ class Report {
* @param string $full_level Recent position to work on. * @param string $full_level Recent position to work on.
* @param string $rowIndex Index of recent row in resultset. * @param string $rowIndex Index of recent row in resultset.
* *
* @param array $fSkipWrap
* @return string Collected content of all printable columns * @return string Collected content of all printable columns
* @throws \CodeException * @throws \CodeException
* @throws \DbException * @throws \DbException
* @throws \DownloadException * @throws \DownloadException
* @throws \UserFormException
* @throws \UserReportException
* @throws \PhpOffice\PhpSpreadsheet\Exception * @throws \PhpOffice\PhpSpreadsheet\Exception
* @throws \PhpOffice\PhpSpreadsheet\Reader\Exception * @throws \PhpOffice\PhpSpreadsheet\Reader\Exception
* @throws \PhpOffice\PhpSpreadsheet\Writer\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 = ""; $content = "";
$assoc = array(); $assoc = array();
...@@ -795,13 +865,13 @@ class Report { ...@@ -795,13 +865,13 @@ class Report {
// Debugging // Debugging
$this->store->setVar(SYSTEM_REPORT_COLUMN_INDEX, $ii + 1, STORE_SYSTEM); $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); $this->store->setVar(SYSTEM_REPORT_COLUMN_VALUE, $row[$ii], STORE_SYSTEM);
$flagOutput = false; $flagOutput = false;
$renderedColumn = $this->renderColumn($ii, $keys[$ii], $row[$ii], $full_level, $rowIndex, $flagOutput); $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); $keyAssoc = OnString::stripFirstCharIf(COLUMN_STORE_USER, $keyAssoc);
if ($keyAssoc != '') { if ($keyAssoc != '') {
$assoc[$keyAssoc] = $row[$ii]; $assoc[$keyAssoc] = $row[$ii];
...@@ -811,14 +881,14 @@ class Report { ...@@ -811,14 +881,14 @@ class Report {
if ($flagOutput) { if ($flagOutput) {
//prints //prints
if (!isset($fSkipWrap[$ii + 1])) { if (!isset($keys[$ii][C_NO_WRAP])) {
$content .= $this->variables->doVariables($fsep); $content .= $this->variables->doVariables($fsep);
$content .= $this->variables->doVariables($this->frArray[$full_level . "." . TOKEN_FBEG]); $content .= $this->variables->doVariables($this->frArray[$full_level . "." . TOKEN_FBEG]);
} }
$content .= $renderedColumn; $content .= $renderedColumn;
if (!isset($fSkipWrap[$ii + 1])) { if (!isset($keys[$ii][C_NO_WRAP])) {
$content .= $this->variables->doVariables($this->frArray[$full_level . "." . TOKEN_FEND]); $content .= $this->variables->doVariables($this->frArray[$full_level . "." . TOKEN_FEND]);
} }
...@@ -835,7 +905,7 @@ class Report { ...@@ -835,7 +905,7 @@ class Report {
* Renders column depending of column name (if name is a reserved column name) * Renders column depending of column name (if name is a reserved column name)
* *
* @param string $columnIndex * @param string $columnIndex
* @param string $columnName * @param array $columnCtrl
* @param string $columnValue * @param string $columnValue
* @param string $full_level * @param string $full_level
* @param string $rowIndex * @param string $rowIndex
...@@ -845,30 +915,30 @@ class Report { ...@@ -845,30 +915,30 @@ class Report {
* @throws \CodeException * @throws \CodeException
* @throws \DbException * @throws \DbException
* @throws \DownloadException * @throws \DownloadException
* @throws \UserFormException
* @throws \UserReportException
* @throws \PhpOffice\PhpSpreadsheet\Exception * @throws \PhpOffice\PhpSpreadsheet\Exception
* @throws \PhpOffice\PhpSpreadsheet\Reader\Exception * @throws \PhpOffice\PhpSpreadsheet\Reader\Exception
* @throws \PhpOffice\PhpSpreadsheet\Writer\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 = ""; $content = "";
$flagControl = false; $flagControl = false;
$flagOutput = true; $flagOutput = true;
// Special column name: '_...'? Empty column names are allowed: check with isset // 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; $flagControl = true;
$columnName = substr($columnName, 1); $columnName = substr($columnCtrl[C_SPECIAL], 1);
// Special column name and hide output: '__...'? (double '_' at the beginning) // Special column name and hide output
if (isset($columnName[0]) && $columnName[0] === TOKEN_COLUMN_CTRL) { if (isset($columnCtrl[C_HIDE])) {
$flagOutput = false; $flagOutput = false;
$columnName = substr($columnName, 1);
} }
//TODO: reserved names,not starting with '_' will be still accepted - stop this!
switch ($columnName) { switch ($columnName) {
case COLUMN_LINK: case COLUMN_LINK:
$content .= $this->link->renderLink($columnValue); $content .= $this->link->renderLink($columnValue);
...@@ -1114,7 +1184,7 @@ class Report { ...@@ -1114,7 +1184,7 @@ class Report {
default : default :
$flagOutput = false; $flagOutput = false;
$token = isset($columnName[0]) ? $columnName[0] : ''; $token = ($columnName[0] ?? '');
switch ($token) { switch ($token) {
case COLUMN_WRAP_TOKEN: case COLUMN_WRAP_TOKEN:
if (isset($columnName[1])) { if (isset($columnName[1])) {
...@@ -1134,13 +1204,14 @@ class Report { ...@@ -1134,13 +1204,14 @@ class Report {
} }
break; break;
} }
} else { } else {
// No special Columnname: just add the column value. // No special column name: just add the column value.
$content .= $columnValue; $content .= $columnValue;
} }
// Always save column values, even if they are hidden. // 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; return $content;
} }
......
Supports Markdown
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