mirror of
https://github.com/EGroupware/egroupware.git
synced 2024-12-22 14:41:29 +01:00
base DTD (and RNG) generation now on components.json
enhanced with Shoelace attributes instead of custom-elements.json
This commit is contained in:
parent
cf96aa5d98
commit
a69b47f397
@ -4,14 +4,14 @@
|
||||
* eTemplate2 XML schema
|
||||
* 1. we read the legacy etemplate2.dtd converted by PHPStorm (Tools > XML Actions > Convert Schema) to Relax NG with an XML parser
|
||||
* 2. remove the obsolete widgets
|
||||
* 3. add new widgets from custom-elements.json file generated by our documentation build
|
||||
* 3. add new widgets from components.json file generated by our documentation build (and enhanced with attributes inherited from Shoelace )
|
||||
* 4. apply overwrites below
|
||||
* 5. output it again as new eTemplate2 Relax NG schema
|
||||
* 6. convert it again via PHPStorm (Tools > XML Actions > Convert Schema) to etemplate2.0.dtd referenced in our xet files
|
||||
* (until we figure out how to use RELAX NG direct)
|
||||
*
|
||||
* Open problems:
|
||||
* - custom-elements.json has no information about element hierarchy: not all elements are allowed be contained by an arbitrary element
|
||||
* - components.json has no information about element hierarchy: not all elements are allowed be contained by an arbitrary element
|
||||
* - Relax NG can define attribute types, need to check how with match our internal types to the xet attributes
|
||||
*
|
||||
* @link https://en.wikipedia.org/wiki/RELAX_NG RELAX NG (REgular LAnguage for XML Next Generation)
|
||||
@ -19,11 +19,11 @@
|
||||
* @link https://github.com/EGroupware/etemplate/blob/6767672516524444847207d50b21ba59ff7f1540/js/widget_browser.js old widget-browser dtd generation
|
||||
* @link https://www.jetbrains.com/help/phpstorm/validating-web-content-files.html JetBrains DTD, XML-Schema or RelaxNG support
|
||||
*/
|
||||
if (!file_exists($file=__DIR__."/dist/custom-elements.json"))
|
||||
if (!file_exists($file=__DIR__."/etemplate2/_data/components.json"))
|
||||
{
|
||||
die("Missing '$file file!");
|
||||
}
|
||||
if (!($data=json_decode(file_get_contents($file), true)))
|
||||
if (!($components=json_decode(file_get_contents($file), true)))
|
||||
{
|
||||
die("Could not load '$file'!");
|
||||
}
|
||||
@ -41,47 +41,23 @@ $widgets_choice = getByName($grammar, 'Widgets')->choice;
|
||||
$overwrites = [
|
||||
// RE to remove no longer used legacy widgets not matching "et2-<legacy-name>"
|
||||
'.remove' => '/^(button|dropdown_button|int|float|menu|select|taglist|tree|passwd|date|time|ajax_select|vfs-(select|path))/',
|
||||
'*' => [ // all widgets
|
||||
'*' => [ // all widgets, DOM attributes are NOT reported
|
||||
'.attrs' => [
|
||||
'id' => 'string', // commented out with some reasoning in Et2Widget
|
||||
//'data' => null, // ToDo: not sure, but AFAIK this is no attribute, but somehow listed in each widget
|
||||
'width' => 'string',
|
||||
'height' => 'string',
|
||||
'span' => 'string', // actually int|"all"
|
||||
'slot' => 'string',
|
||||
'height' => 'string',
|
||||
'slot' => 'string', // would be nice, if we could list parent slots ...
|
||||
'style' => 'string',
|
||||
'span' => "'all' | '2' | '3' | '4'", // eT2 grid span
|
||||
],
|
||||
],
|
||||
'Et2InputWidget' => [
|
||||
'.attrs' => [
|
||||
'tabindex' => 'int',
|
||||
'value' => 'string',
|
||||
'tabindex' => 'int', // not reported, probably because DOM attributeq
|
||||
],
|
||||
],
|
||||
'Et2Textbox' => [
|
||||
'.attrs' => [
|
||||
'placeholder' => 'string',
|
||||
'maxlength' => 'int',
|
||||
'size' => 'int',
|
||||
'type' => 'string',
|
||||
],
|
||||
],
|
||||
'et2-textbox' => [
|
||||
'.children' => ['.quantity' => 'optional', 'et2-image'],
|
||||
],
|
||||
'Et2InvokerMixin' => 'Et2TextBox',
|
||||
'et2-description' => [
|
||||
'.attrs' => [
|
||||
'for' => 'string',
|
||||
],
|
||||
],
|
||||
'et2-textarea' => [
|
||||
'.attrs' => [
|
||||
'maxlength' => 'int',
|
||||
'rows' => 'int',
|
||||
'resizeRatio' => 'number', // is this correct
|
||||
'size' => 'int',
|
||||
'placeholder' => 'string',
|
||||
],
|
||||
'et2-textbox' => [
|
||||
'.children' => ['.quantity' => 'optional', 'et2-image'],
|
||||
],
|
||||
'et2-date' => [
|
||||
'.attrs' => [
|
||||
@ -100,7 +76,7 @@ $overwrites = [
|
||||
],
|
||||
'Et2Box' => [ // inherited by et2-(v|h)box too
|
||||
'.attrs' => [
|
||||
'overflow' => 'string',
|
||||
'overflow' => 'string', // DOM attributes
|
||||
],
|
||||
],
|
||||
'et2-tabbox' => [
|
||||
@ -117,9 +93,6 @@ $overwrites = [
|
||||
'et2-tab-panel' => null,
|
||||
'et2-details' => [
|
||||
'.children' => 'Widgets',
|
||||
'.attrs' => [
|
||||
'summary' => 'string',
|
||||
],
|
||||
],
|
||||
'et2-split' => [
|
||||
'.children' => 'Widgets',
|
||||
@ -130,35 +103,35 @@ $overwrites = [
|
||||
],
|
||||
],
|
||||
'et2-nextmatch-header-custom' => [
|
||||
'.attrs' => [
|
||||
'emptyLabel' => 'string',
|
||||
],
|
||||
],
|
||||
'Et2Button' => [
|
||||
'.attrs' => [
|
||||
'image' => 'string',
|
||||
'noSubmit' => 'boolean',
|
||||
'hideOnReadonly' => 'boolean',
|
||||
],
|
||||
],
|
||||
'Et2ButtonIcon' => 'Et2Button', // no inheritance from Et2Button, but Et2ButtonMixin, which is not recognised
|
||||
'.attrs' => [
|
||||
'emptyLabel' => 'string',
|
||||
],
|
||||
],
|
||||
'Et2Button' => [
|
||||
'.attrs' => [
|
||||
'image' => 'string',
|
||||
'noSubmit' => 'boolean',
|
||||
'hideOnReadonly' => 'boolean',
|
||||
],
|
||||
],
|
||||
'Et2ButtonIcon' => 'Et2Button', // no inheritance from Et2Button, but Et2ButtonMixin, which is not recognised
|
||||
'Et2ButtonScroll' => 'Et2Button',
|
||||
'Et2Select' => [
|
||||
'.attrs' => [
|
||||
'rows' => 'int',
|
||||
'tabindex' => 'int',
|
||||
'allowFreeEntries' => 'boolean',
|
||||
],
|
||||
],
|
||||
'Et2Select' => [
|
||||
'.attrs' => [
|
||||
'rows' => 'int',
|
||||
'tabindex' => 'int',
|
||||
'allowFreeEntries' => 'boolean',
|
||||
],
|
||||
],
|
||||
'et2-select' => [
|
||||
'.children' => ['.quantity' => 'zeroOrMore', 'option'],
|
||||
],
|
||||
'et2-email' => [
|
||||
'.attrs' => [
|
||||
'onTagClick' => 'function',
|
||||
'multiple' => 'boolean',
|
||||
],
|
||||
],
|
||||
'et2-email' => [
|
||||
'.attrs' => [
|
||||
'onTagClick' => 'function',
|
||||
'multiple' => 'boolean',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
@ -245,84 +218,69 @@ foreach($missing_legacy_attributes as $attribute => $widgets)
|
||||
|
||||
// build a hashed version of all classes, members and attributes to e.g. find ancestors
|
||||
$classes = [];
|
||||
foreach($data['modules'] as $module)
|
||||
foreach($components as $component)
|
||||
{
|
||||
foreach ($module['declarations'] as $declaration)
|
||||
{
|
||||
if ($declaration['kind'] === 'class')
|
||||
{
|
||||
foreach (['members', 'attributes'] as $collection)
|
||||
{
|
||||
foreach ($declaration[$collection] ?? [] as $key => $element)
|
||||
{
|
||||
$declaration[$collection][$element['name']] = $element;
|
||||
unset($declaration[$collection][$key]);
|
||||
}
|
||||
}
|
||||
$classes[$declaration['name']] = $declaration;
|
||||
}
|
||||
}
|
||||
foreach (['members', 'attributes', 'properties'] as $collection)
|
||||
{
|
||||
foreach ($component[$collection] ?? [] as $key => $element)
|
||||
{
|
||||
if (!empty($element['name']))
|
||||
{
|
||||
$component[$collection][$element['name']] = $element;
|
||||
}
|
||||
unset($component[$collection][$key]);
|
||||
}
|
||||
}
|
||||
$classes[$component['name']] = $component;
|
||||
}
|
||||
|
||||
// iterate of custom-elements to define in the schema
|
||||
foreach($data['modules'] as $module)
|
||||
foreach($components as $component)
|
||||
{
|
||||
foreach($module['exports'] ?? [] as $export)
|
||||
{
|
||||
// some widgets use: customElements.defines(<tag>, <class> as any, ...) --> use previous export
|
||||
if (!empty($export['declaration']['name']) && $export['declaration']['name'] === "anonymous_0")
|
||||
{
|
||||
$export['declaration']['name'] = $last_export;
|
||||
}
|
||||
$last_export = $export['name'];
|
||||
if (empty($component['tagName']) ||
|
||||
preg_match('/_(ro|mobile)$/', $component['tagName']) ||
|
||||
array_key_exists($component['tagName'], $overwrites) && !isset($overwrites[$component['tagName']]))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// ignore / skip none-widgets and some of the widgets e.g. r/o versions
|
||||
if ($export['kind'] !== 'custom-element-definition' ||
|
||||
preg_match('/_(ro|mobile)$/', $export['name']) ||
|
||||
array_key_exists($export['name'], $overwrites) && !isset($overwrites[$export['name']]))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// add the element
|
||||
$define = $grammar->addChild('define');
|
||||
$define->addAttribute('name', $component['tagName']);
|
||||
$element = $define->addChild('element');
|
||||
$element->addAttribute('name', $component['tagName']);
|
||||
$attrs = $element->addChild('ref');
|
||||
$attrs->addAttribute('name', 'attlist.'.$component['tagName']);
|
||||
// add to widgets
|
||||
$widgets_choice->addChild('ref')->addAttribute('name', $component['tagName']);
|
||||
|
||||
// add the element
|
||||
$define = $grammar->addChild('define');
|
||||
$define->addAttribute('name', $export['name']);
|
||||
$element = $define->addChild('element');
|
||||
$element->addAttribute('name', $export['name']);
|
||||
$attrs = $element->addChild('ref');
|
||||
$attrs->addAttribute('name', 'attlist.'.$export['name']);
|
||||
// add to widgets
|
||||
$widgets_choice->addChild('ref')->addAttribute('name', $export['name']);
|
||||
// add the element-attributes
|
||||
$attrs = $grammar->addChild('define');
|
||||
$attrs->addAttribute('name', 'attlist.'.$component['tagName']);
|
||||
$attrs->addAttribute('combine', 'interleave');
|
||||
attributes($component, $attrs);
|
||||
|
||||
// add the element-attributes
|
||||
$attrs = $grammar->addChild('define');
|
||||
$attrs->addAttribute('name', 'attlist.'.$export['name']);
|
||||
$attrs->addAttribute('combine', 'interleave');
|
||||
if (empty($classes[$export['declaration']['name']]['tagName'])) $classes[$export['declaration']['name']]['tagName'] = $export['name'];
|
||||
attributes($classes[$export['declaration']['name']], $attrs);
|
||||
// add or disallow children depending on overwrites (not available from the TS sources)
|
||||
// ToDo: this ignores the use in slots!
|
||||
if (empty($overwrites[$component['tagName']]['.children']))
|
||||
{
|
||||
// don't allow children
|
||||
$element->addChild('empty');
|
||||
}
|
||||
else
|
||||
{
|
||||
$children = (array)$overwrites[$component['tagName']]['.children'];
|
||||
$list = $element->addChild($children['.quantity'] ?? 'oneOrMore'); // zeroOrMore for e.g. empty boxes?
|
||||
unset($children['.quantity']);
|
||||
// add allowed children
|
||||
foreach($children as $child)
|
||||
{
|
||||
$list->addChild('ref')->addAttribute('name', $child);
|
||||
}
|
||||
}
|
||||
|
||||
// add or disallow children depending on overwrites (not available from the TS sources)
|
||||
// ToDo: this ignores the use in slots!
|
||||
if (empty($overwrites[$export['name']]['.children']))
|
||||
{
|
||||
// don't allow children
|
||||
$element->addChild('empty');
|
||||
}
|
||||
else
|
||||
{
|
||||
$children = (array)$overwrites[$export['name']]['.children'];
|
||||
$list = $element->addChild($children['.quantity'] ?? 'oneOrMore'); // zeroOrMore for e.g. empty boxes?
|
||||
unset($children['.quantity']);
|
||||
// add allowed children
|
||||
foreach($children as $child)
|
||||
{
|
||||
$list->addChild('ref')->addAttribute('name', $child);
|
||||
}
|
||||
}
|
||||
|
||||
// remove corresponding legacy widget
|
||||
removeWidget(str_replace('et2-', '', $export['name']));
|
||||
}
|
||||
// remove corresponding legacy widget
|
||||
removeWidget(str_replace('et2-', '', $component['tagName']));
|
||||
}
|
||||
|
||||
$remove = [];
|
||||
@ -446,11 +404,22 @@ function overwriteAttributes(array& $element, string $name=null)
|
||||
{
|
||||
if (isset($type))
|
||||
{
|
||||
$element['attributes'][$attr] = ['name' => $attr, 'type' => ['text' => $type]];
|
||||
// only add it, if not already there
|
||||
if (!array_filter($element['attributes']??[], static function($attribute) use ($attr)
|
||||
{
|
||||
return isset($attribute) && $attribute['name'] === $attr;
|
||||
}))
|
||||
{
|
||||
$element['attributes'][] = ['name' => $attr, 'type' => ['text' => $type]];
|
||||
}
|
||||
}
|
||||
else
|
||||
// remove attribute set to NULL in overwrites
|
||||
elseif (isset($element['attributes']))
|
||||
{
|
||||
unset($element['attributes'][$attr]);
|
||||
$element['attributes'] = array_filter($element['attributes'], static function($attribute) use($attr)
|
||||
{
|
||||
return isset($attribute) && $attribute['name'] !== $attr;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -458,34 +427,37 @@ function overwriteAttributes(array& $element, string $name=null)
|
||||
/**
|
||||
* Generate attribute list for an element
|
||||
*
|
||||
* @param array $class class defining the element
|
||||
* @param array $component class defining the element
|
||||
* @param SimpleXMLElement|null $attrs attribute list element: <define name="attlist.<element>" combine="interleave"/>
|
||||
* @return string[]|void
|
||||
*/
|
||||
function attributes(array $class, ?SimpleXMLElement $attrs=null)
|
||||
function attributes(array $component, ?SimpleXMLElement $attrs=null)
|
||||
{
|
||||
overwriteAttributes($class, '*');
|
||||
overwriteAttributes($class);
|
||||
overwriteAttributes($component, '*');
|
||||
overwriteAttributes($component);
|
||||
// also apply overwrites of own class, direct parent and mixins
|
||||
foreach(getAncestors($class) as $parent)
|
||||
foreach(getAncestors($component) as $parent)
|
||||
{
|
||||
if ($parent && !empty($parent['name']) && preg_match('/^Et2/', $parent['name'])) // can also be Lit or Sl*
|
||||
{
|
||||
overwriteAttributes($class, $parent['name']);
|
||||
overwriteAttributes($component, $parent['name']);
|
||||
}
|
||||
}
|
||||
$attributes = array_filter($class['attributes'] ?? [], static function ($attr)
|
||||
$attributes = array_filter($component['attributes'] ?? [], static function ($attr)
|
||||
{
|
||||
return ($attr['name'] ?? null) && $attr['name'][0] !== '_'; // ignore attributes with empty name or name starting with underscore
|
||||
});
|
||||
usort($attributes, static function ($a, $b) {
|
||||
return strcasecmp($a['name'], $b['name']);
|
||||
});
|
||||
|
||||
if (!isset($attrs))
|
||||
{
|
||||
return array_map(static function($attr) use ($class)
|
||||
return array_map(static function($attr) use ($component)
|
||||
{
|
||||
return $attr['name'].'('.($attr['type']['text']??'any').
|
||||
(isset($attr['fieldName']) && isset($class['members'][$attr['fieldName']]['default']) ?
|
||||
':'.$class['members'][$attr['fieldName']]['default'] : '').')';
|
||||
(isset($attr['fieldName']) && isset($component['members'][$attr['fieldName']]['default']) ?
|
||||
':'.$component['members'][$attr['fieldName']]['default'] : '').')';
|
||||
}, $attributes);
|
||||
}
|
||||
foreach($attributes as $attr)
|
||||
@ -494,9 +466,9 @@ function attributes(array $class, ?SimpleXMLElement $attrs=null)
|
||||
$optional = $attrs->addChild('optional');
|
||||
$attribute = $optional->addChild('attribute');
|
||||
$attribute->addAttribute('name', $attr['name']);
|
||||
if (isset($attr['fieldName']) && isset($class['members'][$attr['fieldName']]['default']))
|
||||
if (isset($attr['fieldName']) && isset($component['members'][$attr['fieldName']]['default']))
|
||||
{
|
||||
$default = $class['members'][$attr['fieldName']]['default'];
|
||||
$default = $component['members'][$attr['fieldName']]['default'];
|
||||
if (in_array($default[0], ['"', "'"]) && $default[0] === substr($default, -1))
|
||||
{
|
||||
$default = substr($default, 1, -1);
|
||||
@ -516,7 +488,19 @@ function attributes(array $class, ?SimpleXMLElement $attrs=null)
|
||||
// not understood by DTD :(
|
||||
//$choice->addChild('text'); // as we allow "@<attr>" or "$cont[name]"
|
||||
break;
|
||||
case 'any':
|
||||
break;
|
||||
// todo: other types are understood by RELAX NG, but not by DTD
|
||||
default: // distinct values: 'a' | 'b' | 'c'
|
||||
if (isset($attr['type']['text']) && $attr['type']['text'][0] === "'" && substr($attr['type']['text'], -1) === "'")
|
||||
{
|
||||
$choice = $attribute->addChild('choice');
|
||||
foreach(preg_split("/'\s*\|\s*'/", substr($attr['type']['text'], 1, -1)) as $part)
|
||||
{
|
||||
$choice->addChild('value', $part);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user