Commit b31fb9eb authored by Carsten  Rose's avatar Carsten Rose
Browse files

Dynamic Update implemented

load.php: implemented
FillStoreForm.php: implemented
Store.php: phpunit test complains about 'store already filled'. Option set to explicitly allow rewrite.
AbstractBuildForm.php: Added new mode 'FORM_UPDATE'. Elements additionaly create json code. 'data-load' attribute will be added to form elements, if 'dynamicUpdate=yes'
  elements(): added call by reference parameter $json, to return the generated json code.
BodyTextParse.php: added 'r =' as a new 'start new line' indicator. This was necessary at least for phpunit tests to run.
BuildFormBootstrap.php: buildPill() passes json data structure.
BuildFormPlain, BuildFormTable.php: doSubrecords()  passes json data structure.
Constants.php: New FORM_UPDATE, SQL_FORM_ELEMENT_SIMPLE_ALL_CONTAINER, ERROR_FORM_NOT_FOUND, API_FORM_UPDATE
Evaluate.php: Exception text enhanced.
QuickFormQuery.php: FillStoreForm.php included. Automatic detection of FORM_LOAD and FORM_SAVE removed. Instead the mode are given explicitly. mode=FORM_UPDATE implemented.
Save.php: added TODOs in code.
formEditor.sql: reformat code. Add 'FormElement.dynamicUpdate'. 'FormElemente.checkType': 'number' replaced by 'digit'. Added 'alnumx', 'digit'. Form 'form', 'formElement': output of 'title' replaced by 'name' - outputting 'title' confuses the user (tries to show records which do fit to the formEditor) and might produce recursion in evaluation (did not understand why, but happens). FormEditor: implemented 'dynamicUpdate', escpecially the 'type' select list will be adjusted dynamically.
parent e6d18f0a
......@@ -6,3 +6,85 @@
* Time: 6:17 PM
*/
namespace qfq;
use qfq;
require_once(__DIR__ . '/../qfq/QuickFormQuery.php');
require_once(__DIR__ . '/../qfq/store/Store.php');
require_once(__DIR__ . '/../qfq/Constants.php');
/**
* Return JSON encoded answer
*
* status: success|error
* message: <message>
* redirect: client|url|no
* redirect-url: <url>
* field-name: <field name>
* field-message: <message>
* form-data: [ fieldname1 => value1, fieldname2 => value2, ... ]
* form-control: [ fieldname1 => status1, fieldname2 => status2, ... ] status: show|hide, enabled|disabled, readonly|readwrite
*
* Description:
*
* Save successfull. Button 'close', 'new'. Form.forward: 'auto'. Client logic decide to redirect or not. Show message if no redirect.
* status = 'success'
* message = <message>
* redirect = 'client'
*
* Save successfull. Button 'close': Form.forward: 'page'. Client redirect to url.
* status = 'success'
* message = <message>
* redirect = 'url'
* redirect-url = <URL>
*
* Save failed: Button: any. Show message and set 'alert' on _optional_ specified form element. Bring 'pill' of specified form element to front.
* status = 'error'
* message = <message>
* redirect = 'no'
* Optional:
* field-name = <field name>
* field-message = <message appearing as tooltip (or similar) near the form element>
*/
$answer = array();
$answer[API_REDIRECT] = API_ANSWER_REDIRECT_NO;
$answer[API_STATUS] = API_ANSWER_STATUS_ERROR;
$answer[API_MESSAGE] = '';
try {
$qfq = new \qfq\QuickFormQuery(['bodytext' => ""]);
$data = $qfq->updateForm();
// $answer[API_REDIRECT] = $qfq->getForwardMode($answer[API_REDIRECT_URL]);
$answer[API_STATUS] = API_ANSWER_STATUS_SUCCESS;
$answer[API_MESSAGE] = 'load: success';
$answer[API_FORM_UPDATE] = $data;
} catch (qfq\UserException $e) {
$answer[API_MESSAGE] = $e->formatMessage();
$val = Store::getVar(SYSTEM_FORM_ELEMENT, STORE_SYSTEM);
if ($val !== false)
$answer[API_FIELD_NAME] = $val;
$val = Store::getVar(SYSTEM_FORM_ELEMENT_MESSAGE, STORE_SYSTEM);
if ($val !== false)
$answer[API_FIELD_MESSAGE] = $val;
} catch (qfq\CodeException $e) {
$answer[API_MESSAGE] = $e->formatMessage();
} catch (qfq\DbException $e) {
$answer[API_MESSAGE] = $e->formatMessage();
} catch (\Exception $e) {
$answer[API_MESSAGE] = "Generic Exception: " . $e->getMessage();
}
header("Content-Type: application/json");
echo json_encode($answer);
......@@ -111,14 +111,25 @@ abstract class AbstractBuildForm {
/**
* Builds complete form. Depending of Formspecification, the layout will be 'plain' / 'table' / 'bootstrap'.
*
* @return string The whole form as HTML
* @param $mode
* @return string|array $mode=LOAD_FORM: The whole form as HTML, $mode=FORM_UPDATE: array of all formElement.dynamicUpdate-yes values/states
* @throws CodeException
* @throws DbException
* @throws \qfq\UserException
*/
public function process() {
public function process($mode) {
$htmlHead = '';
$htmlTail = '';
$htmlSubrecords = '';
$htmlElements = '';
$json = array();
// <form>
$html = $this->head();
if ($mode === FORM_LOAD) {
$htmlHead = $this->head();
$htmlTail = $this->tail();
$htmlSubrecords = $this->doSubrecords();
}
$filter = $this->getProcessFilter();
......@@ -127,18 +138,17 @@ abstract class AbstractBuildForm {
$parentRecords = $this->db->sql($this->formSpec['multiSql']);
foreach ($parentRecords as $row) {
$this->store->setVarArray($row, STORE_PARENT_RECORD, true);
$html .= $this->elements($row['_id'], $filter);
$jsonTmp = array();
$htmlElements = $this->elements($row['_id'], $filter, 0, $jsonTmp);
$json[] = $jsonTmp;
}
} else {
$html .= $this->elements($this->store->getVar(SIP_RECORD_ID, STORE_SIP), $filter);
$htmlElements = $this->elements($this->store->getVar(SIP_RECORD_ID, STORE_SIP), $filter, 0, $json);
}
// </form>
$html .= $this->tail();
$html .= $this->doSubrecords();
return $html;
return ($mode === FORM_LOAD) ? $htmlHead . $htmlElements . $htmlTail . $htmlSubrecords : $json;
}
/**
......@@ -286,6 +296,10 @@ abstract class AbstractBuildForm {
}
abstract public function tail();
abstract public function doSubrecords();
abstract public function getProcessFilter();
/**
......@@ -299,7 +313,7 @@ abstract class AbstractBuildForm {
* @throws DbException
* @throws \qfq\UserException
*/
public function elements($recordId, $filter = FORM_ELEMENTS_NATIVE, $feIdContainer = 0) {
public function elements($recordId, $filter = FORM_ELEMENTS_NATIVE, $feIdContainer = 0, &$json) {
$html = '';
// get current data record
......@@ -310,8 +324,9 @@ abstract class AbstractBuildForm {
// Iterate over all FormElements
foreach ($this->feSpecNative as $fe) {
if (($filter === FORM_ELEMENTS_NATIVE && $fe['type'] === 'subrecord') ||
($filter === FORM_ELEMENTS_SUBRECORD && $fe['type'] !== 'subrecord')
if (($filter === FORM_ELEMENTS_NATIVE && $fe['type'] === 'subrecord')
|| ($filter === FORM_ELEMENTS_SUBRECORD && $fe['type'] !== 'subrecord')
// || ($filter === FORM_ELEMENTS_DYNAMIC_UPDATE && $fe['dynamicUpdate'] === 'no')
) {
continue; // skip this FE
}
......@@ -326,25 +341,42 @@ abstract class AbstractBuildForm {
$formElement = $evaluate->parseArray($fe, $debugStack);
// Get default value
$value = $formElement['value'] === '' ? $this->store->getVar($formElement['name']) : $value = $formElement['value'];
$value = ($formElement['value'] === '') ? $this->store->getVar($formElement['name']) : $formElement['value'];
$htmlFormElementId = HelperFormElement::buildFormElementId($formElement['name'], $recordId);
// Construct Marshaller Name
// Construct Marshaller Name: buildElement
$buildElementFunctionName = 'build' . $this->buildElementFunctionName[$formElement['type']];
$jsonElement = array();
// Render pure element
$elementHtml = $this->$buildElementFunctionName($formElement, $htmlFormElementId, $value, $debugStack);
$elementHtml = $this->$buildElementFunctionName($formElement, $htmlFormElementId, $value, $jsonElement);
$fake0 = $fe['dynamicUpdate'];
$fake1 = $formElement['dynamicUpdate'];
// container elements do not have dynamicUpdate='yes'. Instead they deliver nested elements.
if ($formElement['class'] == 'container') {
if (count($jsonElement) > 0) {
$json = array_merge($json, $jsonElement);
}
} else {
// for non container elements: just add the current json status
if ($fe['dynamicUpdate'] == 'yes') {
$json[] = $jsonElement;
}
}
// debugStack as Tooltip
if ($this->showDebugInfo && count($debugStack) > 0) {
$elementHtml = Support::appendTooltip($elementHtml, implode("\n", OnArray::htmlentitiesOnArray($debugStack)));
}
// Construct Marshaller Name
// Construct Marshaller Name: buildRow
$buildRowName = 'buildRow' . $this->buildRowName[$formElement['type']];
$html .= $this->$buildRowName($formElement, $elementHtml);
// break;
}
// Log / Debug: Last FormElement has been processed.
......@@ -353,10 +385,6 @@ abstract class AbstractBuildForm {
return $html;
}
abstract public function tail();
abstract public function doSubrecords();
/**
* Takes the current SIP ('form' and additional parameter), set SIP_RECORD_ID=0 and create a new 'NewRecordUrl'.
*
......@@ -414,23 +442,10 @@ abstract class AbstractBuildForm {
* @return string
* @throws UserException
*/
public function buildInput(array $formElement, $htmlFormElementId, $value) {
public function buildInput(array $formElement, $htmlFormElementId, $value, &$json) {
$textarea = '';
$attribute = $this->getAttribute('name', $htmlFormElementId);
$htmlTag = '<input';
// MIN( $formElement['maxLength'], tabledefinition)
$maxLength = $this->getColumnSize($formElement['name']);
if ($maxLength !== false) {
if (is_numeric($formElement['maxLength'])) {
if ($formElement['maxLength'] > $maxLength) {
$formElement['maxLength'] = $maxLength;
}
} else {
$formElement['maxLength'] = $maxLength;
}
}
// Check for input type 'textarea'
$colsRows = explode(',', $formElement['size'], 2);
......@@ -443,26 +458,54 @@ abstract class AbstractBuildForm {
$textarea = htmlentities($value) . '</textarea>';
} else {
$htmlTag = '<input';
$this->adjustMaxLength($formElement);
// <input>
if ($formElement['maxLength'] > 0) {
$value = substr($value, 0, $formElement['maxLength']);
// crop string only if it's not empty (substr returns false on empty strings)
if ($value !== '')
$value = substr($value, 0, $formElement['maxLength']);
// 'maxLength' needs an upper 'L': naming convention for DB tables!
$attribute .= $this->getAttributeList($formElement, ['type', 'size', 'maxLength']);
$attribute .= $this->getAttribute('value', htmlentities($value), false);
}
}
// 'maxLength' needs an upper 'L': naming convention for DB tables!
$attribute .= $this->getAttributeList($formElement, ['autocomplete', 'autofocus', 'placeholder']);
$attribute .= $this->getAttribute('data-load', ($formElement['dynamicUpdate'] === 'yes') ? 'data-load' : '');
$attribute .= $this->getAttribute('title', $formElement['tooltip']);
$attribute .= $this->getInputCheckPattern($formElement['checkType'], $formElement['checkPattern']);
$attribute .= $this->getAttributeMode($formElement);
$json = $this->getJsonElementUpdate($htmlFormElementId, $value, $formElement['mode']);
return "$htmlTag $attribute>$textarea";
}
/**
* @param array $formElement
*/
private function adjustMaxLength(array &$formElement) {
// MIN( $formElement['maxLength'], tabledefinition)
$maxLength = $this->getColumnSize($formElement['name']);
if ($maxLength !== false) {
if (is_numeric($formElement['maxLength'])) {
if ($formElement['maxLength'] > $maxLength) {
$formElement['maxLength'] = $maxLength;
}
} else {
$formElement['maxLength'] = $maxLength;
}
}
}
/**
* Get column spec from tabledefinition and parse size of it. If nothing defined, return false.
*
......@@ -509,10 +552,10 @@ abstract class AbstractBuildForm {
* ------- ----------------------- -------------------------------------------------------------------------------
* min|max <min value>|<max value> min="%s"|max="%s"
* pattern <regexp> pattern="%s"
* number - pattern="^[0-9]*$"
* digit - pattern="^[0-9]*$"
* email - pattern="^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,4})$"
*
* For 'min|max' and 'pattern' the 'data' will be injected in the attribute string vai '%s'.
* For 'min|max' and 'pattern' the 'data' will be injected in the attribute string via '%s'.
*
* @param $type
* @param $data
......@@ -539,7 +582,6 @@ abstract class AbstractBuildForm {
return $attribute;
}
/**
* Set corresponding html attributes readonly/required/disabled, based on $formElement['mode'].
*
......@@ -573,6 +615,23 @@ abstract class AbstractBuildForm {
return $attribute;
}
/**
* @param $htmlFormElementId
* @param string|array $value
* @param $mode
* @return array
*/
private function getJsonElementUpdate($htmlFormElementId, $value, $mode) {
$json = array();
$json['form-element'] = $htmlFormElementId;
$json['value'] = $value;
$json['disabled'] = ($mode === 'disabled');
$json['readonly'] = ($mode === 'readonly');
return $json;
}
/**
* Builds HTML 'checkbox' element.
*
......@@ -588,7 +647,7 @@ abstract class AbstractBuildForm {
* @return string
* @throws UserException
*/
public function buildCheckbox(array $formElement, $htmlFormElementId, $value) {
public function buildCheckbox(array $formElement, $htmlFormElementId, $value, &$json) {
$itemKey = array();
$itemValue = array();
......@@ -622,6 +681,7 @@ abstract class AbstractBuildForm {
throw new UserException('checkBoxMode: \'' . $formElement['checkBoxMode'] . '\' is unknown.', ERROR_CHECKBOXMODE_UNKNOWN);
}
$json = $this->getJsonElementUpdate($htmlFormElementId, $value, $formElement['mode']);
// return Support::wrapTag('<div class="checkbox">', $html, true);
return $html;
}
......@@ -692,10 +752,10 @@ abstract class AbstractBuildForm {
$itemKey[] = '';
}
if(isset($formElement['emptyHide']) ) {
if(isset($itemValue['']))
if (isset($formElement['emptyHide'])) {
if (isset($itemValue['']))
unset($itemValue['']);
if(isset($itemKey['']))
if (isset($itemKey['']))
unset($itemKey['']);
}
......@@ -792,6 +852,8 @@ abstract class AbstractBuildForm {
$attribute .= $this->getAttribute('name', $htmlFormElementId);
$attribute .= $this->getAttribute('value', $formElement['checked'], false);
$attribute .= $this->getAttribute('data-load', ($formElement['dynamicUpdate'] === 'yes') ? 'data-load' : '');
if ($formElement['checked'] === $value) {
$attribute .= $this->getAttribute('checked', 'checked');
}
......@@ -841,10 +903,11 @@ abstract class AbstractBuildForm {
$values = explode(',', $value);
$attributeBase .= $this->getAttribute('name', $htmlFormElementId);
$attributeBase .= $this->getAttribute('data-load', ($formElement['dynamicUpdate'] === 'yes') ? 'data-load' : '');
$html = $this->buildNativeHidden($htmlFormElementId, '');
$orientation = ($formElement[CHECKBOX_ORIENTATION] === 'vertical') ? '' : 'checkbox-inline';
$orientation = (isset($formElement[CHECKBOX_ORIENTATION]) && $formElement[CHECKBOX_ORIENTATION] === 'vertical') ? '' : 'checkbox-inline';
$flagFirst = true;
for ($ii = 0, $jj = 1; $ii < count($itemKey); $ii++, $jj++) {
......@@ -869,13 +932,13 @@ abstract class AbstractBuildForm {
$htmlCheckbox .= $itemValue[$ii];
$htmlCheckbox = Support::wrapTag("<label class='$orientation'>", $htmlCheckbox, true);
if ($formElement[CHECKBOX_ORIENTATION] === 'vertical')
if (isset($formElement[CHECKBOX_ORIENTATION]) && $formElement[CHECKBOX_ORIENTATION] === 'vertical')
$htmlCheckbox = Support::wrapTag("<div class='checkbox'>", $htmlCheckbox, true);
$html .= $htmlCheckbox;
}
if ($formElement[CHECKBOX_ORIENTATION] !== 'vertical')
if (isset($formElement[CHECKBOX_ORIENTATION]) && $formElement[CHECKBOX_ORIENTATION] !== 'vertical')
$html = Support::wrapTag("<div class='checkbox'>", $html, true);
return $html;
......@@ -892,7 +955,7 @@ abstract class AbstractBuildForm {
* @param $value
* @return string
*/
public function buildHidden(array $formElement, $htmlFormElementId, $value) {
public function buildHidden(array $formElement, $htmlFormElementId, $value, &$json) {
$this->store->setVar($htmlFormElementId, $value, STORE_SIP, false);
}
......@@ -912,7 +975,7 @@ abstract class AbstractBuildForm {
* @return string
* @throws UserException
*/
public function buildRadio(array $formElement, $htmlFormElementId, $value) {
public function buildRadio(array $formElement, $htmlFormElementId, $value, &$json) {
$itemKey = array();
$itemValue = array();
......@@ -922,6 +985,7 @@ abstract class AbstractBuildForm {
$attributeBase = $this->getAttributeMode($formElement);
$attributeBase .= $this->getAttribute('name', $htmlFormElementId);
$attributeBase .= $this->getAttribute('type', $formElement['type']);
$attributeBase .= $this->getAttribute('data-load', ($formElement['dynamicUpdate'] === 'yes') ? 'data-load' : '');
$jj = 0;
$flagFirst = true;
......@@ -956,6 +1020,9 @@ abstract class AbstractBuildForm {
$html .= '<br>';
}
}
$json = $this->getJsonElementUpdate($htmlFormElementId, $value, $formElement['mode']);
return $html;
}
......@@ -967,7 +1034,7 @@ abstract class AbstractBuildForm {
* @param $value
* @return mixed
*/
public function buildSelect(array $formElement, $htmlFormElementId, $value) {
public function buildSelect(array $formElement, $htmlFormElementId, $value, &$json) {
$itemKey = array();
$itemValue = array();
......@@ -977,6 +1044,7 @@ abstract class AbstractBuildForm {
$attribute = $this->getAttributeMode($formElement);
$attribute .= $this->getAttribute('name', $htmlFormElementId);
$attribute .= $this->getAttributeList($formElement, ['autofocus']);
$attribute .= $this->getAttribute('data-load', ($formElement['dynamicUpdate'] === 'yes') ? 'data-load' : '');
if (isset($formElement['size']) && $formElement['size'] > 1) {
$attribute .= $this->getAttribute('size', $formElement['size']);
......@@ -984,19 +1052,29 @@ abstract class AbstractBuildForm {
}
$option = '';
$selected = 'selected';
$firstSelect = true;
$jsonValues = array();
for ($ii = 0; $ii < count($itemValue); $ii++) {
$option .= '<option ';
$option .= $this->getAttribute('value', $itemKey[$ii]);
if ($itemKey[$ii] === $value) {
$option .= $selected;
$selected = '';
$jsonValues[] = [
'value' => $itemKey[$ii],
'text' => $itemValue[$ii],
'selected' => ($itemKey[$ii] === $value && $firstSelect)
];
if ($itemKey[$ii] === $value && $firstSelect) {
$option .= 'selected';
$firstSelect = false;
}
$option .= '>' . $itemValue[$ii] . '</option>';
}
$json = $this->getJsonElementUpdate($htmlFormElementId, $jsonValues, $formElement['mode']);
return '<select ' . $attribute . '>' . $option . '</select>';
}
......@@ -1010,7 +1088,7 @@ abstract class AbstractBuildForm {
* @return string
* @throws UserException
*/
public function buildSubrecord(array $formElement, $htmlFormElementId, $value) {
public function buildSubrecord(array $formElement, $htmlFormElementId, $value, &$json) {
$rcText = false;
$nameColumnId = 'id';
$targetTableName = '';
......@@ -1308,7 +1386,6 @@ abstract class AbstractBuildForm {
$sip = $this->store->getSipInstance();
// return $sip->queryStringToSip($queryString, RETURN_URL, API_DIR . '/delete.php');
return $sip->queryStringToSip($queryString, $mode, API_DIR . '/delete.php');
}
......@@ -1321,12 +1398,15 @@ abstract class AbstractBuildForm {
* @return string
* @throws UserException
*/
public function buildFile(array $formElement, $htmlFormElementId, $value) {
public function buildFile(array $formElement, $htmlFormElementId, $value, &$json) {
$attribute = $this->getAttributeMode($formElement);
$attribute .= $this->getAttribute('type', 'file');
$attribute .= $this->getAttribute('name', $htmlFormElementId);
$attribute .= $this->getAttributeList($formElement, ['autofocus', 'accept']);
$attribute .= $this->getAttribute('data-load', ($formElement['dynamicUpdate'] === 'yes') ? 'data-load' : '');
$json = $this->getJsonElementUpdate($htmlFormElementId, $value, $formElement['mode']);
return '<input ' . $attribute . '>';
}
......@@ -1365,7 +1445,7 @@ abstract class AbstractBuildForm {
* @param $value
* @return mixed
*/
public function buildNote(array $formElement, $htmlFormElementId, $value) {
public function buildNote(array $formElement, $htmlFormElementId, $value, &$json) {
return $value;
}
......@@ -1377,7 +1457,7 @@ abstract class AbstractBuildForm {
* @param $value
* @return mixed
*/
public function buildPill(array $formElement, $htmlFormElementId, $value) {
public function buildPill(array $formElement, $htmlFormElementId, $value, &$json) {
return $value;
}
......@@ -1389,13 +1469,19 @@ abstract class AbstractBuildForm {
* @param $value
* @return mixed
*/
public function buildFieldset(array $formElement, $htmlFormElementId, $value) {
public function buildFieldset(array $formElement, $htmlFormElementId, $value, &$json) {
$attribute = '';
// save parent processed FE's
$tmpStore = $this->feSpecNative;
$attribute .= $this->getAttribute('name', $htmlFormElementId);
$attribute .= $this->getAttribute('data-load', ($formElement['dynamicUpdate'] === 'yes') ? 'data-load' : '');
// <fieldset>
$html = '<fieldset ' . $this->getAttribute('name', $htmlFormElementId) . '>';
$html = '<fieldset ' . $attribute . '>';
if ($formElement['label'] !== '') {
$html .= '<legend>' . $formElement['label'] . '</legend>';
}
......@@ -1406,7 +1492,7 @@ abstract class AbstractBuildForm {
$sql = SQL_FORM_ELEMENT_SPECIFIC_CONTAINER;
$this->feSpecNative = $this->db->sql($sql, ROW_REGULAR, ['yes', $this->formSpec["id"], 'native,container', $formElement['id']]);
HelperFormElement::explodeParameterInArrayElements($this->feSpecNative);
$html .= $this->elements($this->store->getVar(SIP_RECORD_ID, STORE_SIP), FORM_ELEMENTS_NATIVE_SUBRECORD);
$html .= $this->elements($this->store->getVar(SIP_RECORD_ID, STORE_SIP), FORM_ELEMENTS_NATIVE_SUBRECORD, 0, $json);
$html .= $this->wrap[WRAP_SETUP_IN_FIELDSET][WRAP_SETUP_END];
......@@ -1416,6 +1502,8 @@ abstract class AbstractBuildForm {
// restore parent processed FE's
$this->feSpecNative = $tmpStore;
$json = $this->getJsonElementUpdate($htmlFormElementId, $value, $formElement['mode']);
return $html;
}
......
......@@ -61,7 +61,7 @@ class BodytextParser {
$full = '';
foreach ($bodytextArray as $row) {
// Valid 'new line' starts indicators: form, <level>, <level.sublevel>, <level>.<keyword>, {, <level> {, }
if ((1 === preg_match('/^\s*(\d*(\.)?)*\s*(head|althead|tail|sql|rbeg|rend|renr|rsep|fbeg|fend|fsep|form) *=/', $row))
if ((1 === preg_match('/^\s*(\d*(\.)?)*\s*(head|althead|tail|sql|rbeg|rend|renr|rsep|fbeg|fend|fsep|form|debugShowStack|r) *=/', $row))
|| (1 === preg_match('/^\s*(\d*(\.)?)*\s*({|})\s*/', $row))
|| (1 === preg_match('/^\s*(\d+(\.)?)+/', $row))
) {
......@@ -74,6 +74,7 @@ class BodytextParser {
$full = $row;
} else {