Entity.php
<?php
namespace Doc;
class Entity {
protected $node;
protected $table;
protected $lookup;
protected $initData;
protected $loop;
protected $autocreate;
protected $doc;
protected $options;
protected $new = false;
protected $propAttribute = 'prop';
protected $printIfNull = false;
protected $hideIfNull = false;
protected $isEditMode = false;
public function __construct($node,$schemaDoc,$options=[]){
$this->node = $node;
$this->table = $node->table??null;
$this->options = $options;
$new = $this->boolAttribute('new');
$lookupStr = $options['lookup'] ?? $node->lookup ?? '';
$this->new = $lookupStr=='new'||$lookupStr=='new;';
$this->lookup = $this->decodeDataString($lookupStr);
$this->lookupStr = $lookupStr;
$initDataStr = $options['initData'] ?? $node->getAttribute('data-init') ?? '';
$this->initData = $this->decodeDataString($initDataStr) ?? [];
$this->initDataStr = $initDataStr;
$this->loop = $this->boolAttribute('loop');
$this->autocreate = $this->boolAttribute('autocreate');
$this->printIfNull = $this->boolAttribute('printifnull');
$this->hideIfNull = $this->boolAttribute('hideifnull');
$this->doc = $schemaDoc->doc;
$this->schemaDoc = $schemaDoc;
$this->prepareNodeWithOptions($node,$options);
}
protected function prepareNodeWithOptions($node,$opt){
$overrides = ['lookup'=>'lookup','initData'=>'data-init','formName'=>'form'];
foreach ($overrides as $key=>$attrName){
if ($opt[$key]??null!=null)$node->setAttribute($attrName, $opt[$key]);
}
}
public function fillSelf($removeAPIData){
if ($this->table==null)exit;
$binds = [];
$whereStr = $this->encodeWhereData($this->lookup,$binds);
$rows = \RDB::find($this->table,$whereStr,$binds);
if ($this->new){
return;
} else if (!$this->loop&&count($rows)>1){
throw new \Exception(" add 'loop' attribute, since there are multiple rows for table '{$this->table}' or refine the 'lookup' attribute");
} else if (!$this->autocreate&&$this->printIfNull&&count($rows)==0){
return;
} else if (count($rows)==0&&$this->hideIfNull){
$this->node->parentNode->removeChild($this->node);
return;
} else if (!$this->autocreate&&count($rows)==0){
throw new \Exception("No entry in '{$this->table}' for lookup '{$this->lookupStr}'");
}
$this->tryAutocreate($rows);
$nodes = [];
$parent = $this->node->parentNode;
$nextSib = $this->node->nextSibling;
$parent->removeChild($this->node);
$entries[] = ['node'=>$this->node,'bean'=>reset($rows)];
for ($i=1;$i<count($rows);$i++){
$entries[] = ['node'=>$this->node->cloneNode(true),'bean'=>next($rows)];
}
foreach ($entries as $entry){
$node = $entry['node'];
$bean = $entry['bean'];
$this->doc->importNode($node,TRUE);
$parent->insertBefore($node,$nextSib);
$q = new \DOMXpath($this->doc);
$props = $q->evaluate('.//*[@'.$this->propAttribute.']',$node);
$this->setProps($node,$props,$bean);
if ($removeAPIData){
$this->cleanEntityAttributes($node);
$this->cleanPropsAttributes($props);
}
}
}
protected function cleanEntityAttributes($node){
$list = ['table', 'printifnull', 'view', 'form', 'hideifnull', 'loop', 'autocreate', 'lookup','data-init','options'];
foreach ($list as $attr){
$node->removeAttribute($attr);
}
}
protected function cleanPropsAttributes($props){
$list = ['prop','format'];
foreach ($props as $p){
foreach ($list as $attr){
$p->removeAttribute($attr);
}
}
}
protected function setProps($node,$propNodes,$bean){
$edit = TRUE;
foreach ($propNodes as $prop){
$name = $prop->getAttribute($this->propAttribute);
$value = $bean->$name;
$tag = strtolower($prop->tagName);
if ($value==null)continue;
if ($tag=='a'){
$value = $this->fullUrl($value);
$prop->href = $value;
} else {
$this->setNodeValue($prop,$value);
}
}
if ($edit&&!$this->new){
$node->lookup = "id:".$bean->id;
}
}
public function enableEditMode(){
$xPath = new \DOMXpath($this->doc);
$entities = $xPath->query('//*[@table]');
$inputs = [];
foreach ($entities as $node) {
$class = $node->getAttribute('class');
$list = explode(' ',$class);
$list = array_combine($list,$list);
$list['SchemaEditable'] = 'SchemaEditable';
$node->setAttribute('class',implode(' ',$list));
$viewName = $node->getAttribute('view');
if ($viewName!=null){
$formName = $viewName .= 'Form';
if (!$node->hasAttribute('view'))$node->setAttribute('view',$viewName);
if (!$node->hasAttribute('form'))$node->setAttribute('form',$formName);
}
}
}
protected function tryAutocreate(array &$rows){
if (count($rows)==0&&$this->autocreate){
$bean = \RDB::dispense($this->table);
$rows[] = $bean;
$q = new \DOMXpath($this->doc);
$props = $q->evaluate('.//*[@'.$this->propAttribute.']',$this->node);
foreach ($props as $node){
$tag = strtolower($node->tagName);
$propName = $node->getAttribute($this->propAttribute);
if ($tag=='img'){
$bean->$propName = $node->getAttribute('src');
} else {
$bean->$propName = $node->innerHTML;
}
}
$initData = array_merge($this->lookup,$this->initData);
foreach ($initData as $key=>$value){
$bean->$key=$value;
}
\RDB::store($bean);
}
}
public function nodeToHtml($node){
$outerHTML = $node->ownerDocument->saveXML($node);
return $outerHTML;
}
protected function boolAttribute($attr){
$result = $this->node->hasAttribute($attr)&&strtolower($this->node->$attr!='false') ? true : false;
return $result;
}
protected function getNodeValue($node){
return $node->innerHTML;
}
protected function setNodeValue($node,$newValue){
$tagName = strtolower($node->tagName);
$format = $node->getAttribute('format');
if ($tagName=='input'){
$this->setFormInputValue($node,$newValue);
return;
} else if ($tagName=='img'){
$newValue = $this->fullUrl($newValue);
$node->src = $newValue;
return;
} else if ($tagName=='object'){
$newValue = $this->fullUrl($newValue);
$node->data = $newValue;
} else if ($format=='md'){
$converter = new \League\CommonMark\CommonMarkConverter([
'html_input' => 'strip',
'allow_unsafe_links' => false,
]);
$newValue = $converter->convertToHtml($newValue);
}
$node->innerHTML = $newValue;
}
protected function encodeWhereData($lookupData, &$bindsArray=[],&$insertData=[]){
$qs = [];
foreach ($lookupData as $key=>$value){
if ($value===null)continue;
$qs[] = $key.' = :'.$key;
$bindsArray[':'.$key] = $value;
$insertData[$key] = $value;
}
$where = implode(' AND ',$qs);
return $where;
}
protected function decodeDataString($dataStr){
return \Doc::decodeDataString($dataStr);
}
protected function nodeFromHTML($html,$hideXmlErrors=true){
libxml_use_internal_errors($hideXmlErrors);
$domDoc = new \DomDocument();
$domDoc->registerNodeClass('DOMElement', 'JSLikeHTMLElement');
$domDoc->loadXML($html);
libxml_use_internal_errors(false);
$node = $this->doc->childNodes[1]->childNodes[0]->childNodes[0];
$node->parentNode->removeChild($node);
return $node;
}
protected function cloneNode($node,$doc){
$nd=$doc->createElement($node->nodeName);
foreach($node->attributes as $value)
$nd->setAttribute($value->nodeName,$value->value);
if(!$node->childNodes)
return $nd;
foreach($node->childNodes as $child) {
if($child->nodeName=="#text")
$nd->appendChild($doc->createTextNode($child->nodeValue));
else
$nd->appendChild($this->cloneNode($child,$doc));
}
return $nd;
}
protected function fullUrl($value){
return \Doc::fullUrl($value);
}
}