open($filePath)) throw new Exception("Failed to open ant schema file '{$fileName}'", 500); return self::xmlReadToArray($z); } public static function xmlReadToArray($z) { while ($z->read()) { if (XMLReader::ELEMENT !== $z->nodeType) continue; return self::xmlReadRecurse($z); } } public static function xmlReadRecurse($z) { $node = [ $z->name, [], [] ];// name, attrs, childrens $depth = $z->depth; $isEmpty = $z->isEmptyElement; if ($z->hasAttributes) { while ($z->moveToNextAttribute()) { $node[1][$z->name] = $z->value; } } if ($isEmpty) { $node[2] = null; return $node; } // switch ($z->name) { // case 'xsd:complexType': $node[1]['name'] = $z->getAttribute('name'); break; // case 'xsd:simpleType': $node[1]['name'] = $z->getAttribute('name'); break; // case 'xsd:restriction': $node[1]['base'] = $z->getAttribute('base'); break; // case 'xsd:enumeration': $node[1]['value'] = $z->getAttribute('value'); break; // case 'xsd:element': $node[1] = [ // 'type' => $z->getAttribute('type') // ]; break; // case 'xsd:complexContent': break; // case 'xsd:sequence': break; // case 'xsd:attribute': $node[1]['base'] = $z->getAttribute('base'); break; // case 'xsd:extension': $node[1]['base'] = $z->getAttribute('base'); break; // default: UI::alert('warning', "TODO: read attributes from: d({$z->depth}) name($z->name)"); // } while ($z->read() && $z->depth > $depth) { if (XMLReader::ELEMENT !== $z->nodeType) continue; // if ($z->depth == $depth + 2) $node[2][] = self::xmlReadRecurse($z); // else $node[2][] = "d({$z->depth}) name($z->name)"; } return $node; } public static function printXmlFromArray($xml) { $xmlWriter = new Core_XmlWriter(); $xmlWriter->openUri('php://output'); $xmlWriter->setIndent(true); $xmlWriter->startDocument('1.0','UTF-8'); $xmlWriter->h($xml[0], $xml[1], $xml[2]); $xmlWriter->endDocument(); } public static function findTargetNamespace($docArray) { return V::get('targetNamespace', '', $docArray[1]); } public static function findSimpleTypeNode($docArray, $name) { foreach ($docArray[2] as $child) { if ('simpleType' === self::getTagName($child[0])) { if ($name === $child[1]['name']) { return $child; } } } } public static function findTargetNamespacePrefix($docArray) { $tns = self::findTargetNamespace($docArray); if (!$tns) throw new Exception("targetNamespace not defined"); foreach ($docArray[1] as $attr => $val) { if ('xmlns:' !== substr($attr, 0, 6)) continue; if ($tns === $val) return substr($attr, 6); } throw new Exception("Missing targetNamespace xmlns:..."); } public static function findElementName($docArray, $nodeArray) { if (!empty($nodeArray[1]['name'])) return $nodeArray[1]['name']; if (!empty($nodeArray[1]['ref'])) return $nodeArray[1]['ref']; throw new Exception("Missing xsd:element name"); } public static function findElementType($docArray, $nodeArray) { $fieldName = self::findElementName($docArray, $nodeArray); if (!empty($nodeArray[1]['type'])) { // TODO:TYPE_RECURCE: if local ns prefix then find correct typeName? // TODO:TYPE_RECURCE:the same for restrictions $type = $nodeArray[1]['type']; DBG::log(['is_xs:' => ('xs:' === substr($type, 0, 3)), 'is_xsd:'=>('xsd:' === substr($type, 0, 4))], 'array', "DBG: findElementType field({$fieldName}) type({$type})"); if ($fixedType = self::tryConvertXsdTypeToXsdPrefix($type)) { return $fixedType; } list($prefix, $name) = explode(':', $type); if ($prefix === self::findTargetNamespacePrefix($docArray)) { $simpleTypeNode = self::findSimpleTypeNode($docArray, $name); DBG::log($simpleTypeNode, 'array', "\$simpleTypeNode \$fieldName='{$fieldName}' type='{$type}'"); if (!empty($simpleTypeNode[1]['type'])) { throw new Exception("TODO: findElementType node/@type => 'xsd:simpleType/@type' = '{$simpleTypeNode[1]['type']}'"); } if (!empty($simpleTypeNode[2][0]) && self::isXsdTag($simpleTypeNode[2][0][0], 'restriction')) { $restrictionNode = $simpleTypeNode[2][0]; if (empty($restrictionNode[1]['base'])) throw new Exception("Missing xsd:restriction/@base (node/@type => xsd:simpleType/[@type='{$simpleTypeNode[1]['type']}']/xsd:restriction')"); $type = $restrictionNode[1]['base']; DBG::log($type, 'array', "findElementType \$fieldName='{$fieldName}' type='{$nodeArray[1]['type']}' => type='{$type}'"); // check restrictions - if has enumeration then return 'p5:enum' $isEnum = false; foreach ($restrictionNode[2] as $restr) { if ('enumeration' === self::getTagName($restr[0])) { $isEnum = true; break; } } if ($isEnum) return 'p5:enum'; // TODO: recurse with limit if ('xs:' === substr($type, 0, 3)) return "xsd:" . substr($type, 3); if ('xsd:' === substr($type, 0, 4)) return $type; list($prefix, $name) = explode(':', $type); $simpleTypeNode = self::findSimpleTypeNode($docArray, $name); DBG::log($simpleTypeNode, 'array', "\$simpleTypeNode \$fieldName='{$fieldName}' ... type='{$type}'"); if (!empty($simpleTypeNode[1]['type'])) { throw new Exception("TODO: findElementType node/@type => 'xsd:simpleType/@type' = '{$simpleTypeNode[1]['type']}'"); } if (!empty($simpleTypeNode[2][0]) && self::isXsdTag($simpleTypeNode[2][0][0], 'restriction')) { $restrictionNode = $simpleTypeNode[2][0]; if (empty($restrictionNode[1]['base'])) throw new Exception("Missing xsd:restriction/@base (node/@type => xsd:simpleType/[@type='{$simpleTypeNode[1]['type']}']/xsd:restriction')"); $type = $restrictionNode[1]['base']; DBG::log($type, 'array', "findElementType \$fieldName='{$fieldName}' ... type='{$type}'"); return self::convertXsdTypeToXsdPrefix($type); } // TODO: throw... } // TODO: throw... // 0 => 'xsd:simpleType', // 1 => [ // 'name' => 'PROCES_INIT_Simple', // ], // 2 => [ // 0 => [ // 0 => 'xsd:restriction', // 1 => [ // 'base' => 'default_db__x3A__CRM_PROCES:TYPE_Simple', // ], // 2 => [ // 0 => [ // 0 => 'xsd:enumeration', // 1 => [ // 'value' => 'PROCES_INIT', // ], // 2 => NULL, } else { return $nodeArray[1]['type']; } } if (!empty($nodeArray[1]['ref'])) return 'ref:' . $nodeArray[1]['ref']; if (empty($nodeArray[2])) throw new Exception("Missing xsd:element childrens - cannot find type"); // TODO: find in loop (xsd:annotation may accure) if (empty($nodeArray[2][0][0]) || !self::isXsdTag($nodeArray[2][0][0], 'simpleType')) throw new Exception("Missing 'xsd:simpleType' for field '{$fieldName}'"); if (empty($nodeArray[2][0][2][0]) || !self::isXsdTag($nodeArray[2][0][2][0][0], 'restriction')) throw new Exception("Missing 'xsd:restriction' for field '{$fieldName}'"); if (empty($nodeArray[2][0][2][0][1]['base'])) throw new Exception("Missing 'xsd:restriction/@base' for field '{$fieldName}'"); return self::convertXsdTypeToXsdPrefix($nodeArray[2][0][2][0][1]['base']); } public static function tryConvertXsdTypeToXsdPrefix($type) { try { $type = self::convertXsdTypeToXsdPrefix($type); return $type; } catch (Exception $e) { DBG::log($e); } return false; } public static function convertXsdTypeToXsdPrefix($type) { // TODO: validate if type is supported in object engine and gui // TODO: prefix p5 list($prefix, $name) = explode(':', $type); $prefix = ('xs' === $prefix) ? 'xsd' : $prefix; if ('xsd' === $prefix && 'int' === $name) return 'xsd:integer'; if ('xsd' === $prefix && 'NCName' === $name) return 'xsd:string'; // TODO: restriction? if ('xsd' === $prefix && 'NMTOKEN' === $name) return 'xsd:string'; // TODO: restriction? if ('xsd' === $prefix) return implode(":", [ $prefix, $name ]); // if ('xs:' === substr($type, 0, 3)) return "xsd:" . substr($type, 3); // if ('xsd:' === substr($type, 0, 4)) return $type; throw new Exception("Not implemented type '{$type}'"); } public static function findElementRestrictions($docArray, $nodeArray) { $restrictions = []; $fieldName = self::findElementName($docArray, $nodeArray); if (!empty($nodeArray[1]['nillable']) && 'true' === $nodeArray[1]['nillable']) $restrictions['nillable'] = true; if (!empty($nodeArray[2])) { foreach ($nodeArray[2] as $c) { switch (XML::getTagName($c[0])) { case 'annotation': break; // skip xsd:element/xsd:annotation @see findElementAppInfo case 'simpleType': { // xsd:element/xsd:simpleType if (empty($c[2][0]) || 'restriction' != XML::getTagName($c[2][0][0])) throw new Exception("Missing 'xsd:restriction' for field '{$fieldName}'"); if (empty($c[2][0][1]['base'])) throw new Exception("Missing 'xsd:restriction/@base' for field '{$fieldName}'"); // xsd:element/xsd:simpleType/xsd:restriction/xsd:string foreach ($c[2][0][2] as $tagRestriction) { // xsd:string/xsd:maxLength $val = $tagRestriction[1]['value']; if ('enumeration' == XML::getTagName($tagRestriction[0])) { $restrictions['enumeration'][$val] = $val; } else { $restrictions[XML::getTagName($tagRestriction[0])] = $val; } } } break; default: { DBG::log($c, 'array', "Not imeplemented element child '{$c[0]}'"); } } } } if (empty($nodeArray[2]) && !empty($nodeArray[1]['type'])) { list($prefix, $name) = explode(':', $nodeArray[1]['type']); if ($prefix === XML::findTargetNamespacePrefix($docArray)) { $simpleTypeNode = self::findSimpleTypeNode($docArray, $name); return XML::findSimpleTypeRestrictions($docArray, $simpleTypeNode); } } return $restrictions; } public static function findSimpleTypeRestrictions($docArray, $nodeArray) { // $nodeArray must be 'simpleType' === XML::getTagName($nodeArray[0]) DBG::log($nodeArray, 'array', "TODO: findElementRestrictions \$nodeArray"); $restrictions = []; if (empty($nodeArray[2][0]) || 'restriction' != XML::getTagName($nodeArray[2][0][0])) throw new Exception("Missing 'xsd:restriction' for field '{$fieldName}'"); if (empty($nodeArray[2][0][1]['base'])) throw new Exception("Missing 'xsd:restriction/@base' for field '{$fieldName}'"); // xsd:element/xsd:simpleType/xsd:restriction/xsd:string foreach ($nodeArray[2][0][2] as $tagRestriction) { // xsd:string/xsd:maxLength $val = $tagRestriction[1]['value']; if ('enumeration' == XML::getTagName($tagRestriction[0])) { $restrictions['enumeration'][$val] = $val; } else { $restrictions[XML::getTagName($tagRestriction[0])] = $val; } } return $restrictions; } public static function findElementAppInfo($docArray, $nodeArray) { $appInfo = []; $fieldName = self::findElementName($docArray, $nodeArray); if (!empty($nodeArray[2])) { foreach ($nodeArray[2] as $c) { switch (XML::getTagName($c[0])) { case 'annotation': { // skip xsd:element/xsd:annotation DBG::log($c, 'array', "xsd:annotation/xsd:appinfo '{$fieldName}'"); // // // // $prefix = 'system_cache__appinfo'; foreach ($c[2] as $cc) { if ('appinfo' == XML::getTagName($cc[0])) { foreach ($cc[2] as $appTag) { $appInfo[XML::getTagName($appTag[0])] = XML::readAppInfoRecurse($docArray, $appTag); } } } } break; case 'simpleType': break; // skip xsd:element/xsd:simpleType @see findElementRestrictions default: { DBG::log($c, 'array', "Not imeplemented element child '{$c[0]}'"); } } } } DBG::log($fieldName, 'array', "findElementAppInfo fieldName='{$fieldName}'"); return $appInfo; } public static function getTagName($xsdName) { return (false !== ($pos = strpos($xsdName, ':'))) ? substr($xsdName, $pos + 1) : $xsdName; } public static function isXsdTag($xsdName, $expectedTagName) { list($xsdPrefix, $tagName) = explode(':', $xsdName); switch ($xsdPrefix) { case 'xs': case 'xsd': return ($tagName === $expectedTagName); } return false; } public static function readAppInfoRecurse($docArray, $nodeArray) { $appInfo = []; if (!empty($nodeArray[1])) foreach ($nodeArray[1] as $attrName => $attrVal) { $appInfo['@' . XML::getTagName($attrName)] = $attrVal; } if (!empty($nodeArray[2])) foreach ($nodeArray[2] as $appTag) { $appInfo[XML::getTagName($appTag[0])] = XML::readAppInfoRecurse($docArray, $appTag); } // TODO: text nodes return $appInfo; } public static function findFieldsFromSequence($docArray, $nodeArray) { if (!self::isXsdTag($nodeArray[0], 'sequence')) throw new Exception("Error Parsing Schema - expected 'sequence'"); $fields = []; foreach ($nodeArray[2] as $f) { if (!self::isXsdTag($f[0], 'element')) { DBG::log($n, 'array', "Schema xsd parse error - Not implemented node type '{$f[0]}'"); continue; } $fieldName = XML::findElementName($docArray, $f); // V::get('name', '', $f[1]); if (!$fieldName) throw new Exception("Error Parsing Schema - expected 'element[@name]'"); if ('__' === substr($fieldName, 0, 2)) continue; if (!V::get('type', '', $f[1]) && !V::get('ref', '', $f[1]) && empty($f[2])) { UI::alert('danger', "Skipping not implemented field structure '{$fieldName}'"); DBG::log($f, 'array', "Skipping not implemented field structure '{$fieldName}'"); continue; } $fields[$fieldName] = [ 'type' => XML::findElementType($docArray, $f), 'minOccurs' => V::get('minOccurs', 0, $f[1], 'int'), 'maxOccurs' => V::get('maxOccurs', '1', $f[1]), 'restrictions' => XML::findElementRestrictions($docArray, $f), 'appInfo' => XML::findElementAppInfo($docArray, $f), ]; } return $fields; } public static function findFieldsFromComplexContent($docArray, $nodeArray) { // xsd:complexType / xsd:complexContent / xsd:restriction [ @base = "default_db__x3A__CRM_PROCES:CRM_PROCES" ] // xsd:complexType / xsd:complexContent / xsd:extension [ @base = "default_db__x3A__CRM_PROCES:CRM_PROCES" ] switch (XML::getTagName($nodeArray[2][0][0])) { case 'extension': return XML::findFieldsFromExtension($docArray, $nodeArray[2][0]); case 'restriction': return XML::findFieldsFromRestriction($docArray, $nodeArray[2][0]); } // TODO:? $xsdType['extensionBase'] = V::get('base', '', $nodeArray[1]); // TODO:? $xsdType['restrictionBase'] = V::get('base', '', $nodeArray[1]); } public static function findFieldsFromExtension($docArray, $nodeArray) { $fields = []; if (!self::isXsdTag($nodeArray[2][0][0], 'sequence')) throw new Exception("Error Parsing Schema - expected 'complexType/complexContent/extension/sequence'"); foreach ($nodeArray[2][0][2] as $f) { if (!self::isXsdTag($f[0], 'element')) { DBG::log($n, 'array', "Schema xsd parse error - Not implemented node type '{$f[0]}'"); continue; } $fieldName = XML::findElementName($docArray, $f); // V::get('name', '', $f[1]); if (!$fieldName) throw new Exception("Error Parsing Schema - expected 'element[@name]'"); if ('__' === substr($fieldName, 0, 2)) continue; if (!V::get('type', '', $f[1]) && !V::get('ref', '', $f[1]) && empty($f[2])) { UI::alert('danger', "Skipping not implemented field structure '{$fieldName}'"); continue; } $fields[$fieldName] = [ 'type' => XML::findElementType($docArray, $f), 'minOccurs' => V::get('minOccurs', 0, $f[1], 'int'), 'maxOccurs' => V::get('maxOccurs', '1', $f[1]), 'restrictions' => XML::findElementRestrictions($docArray, $f), 'appInfo' => XML::findElementAppInfo($docArray, $f), ]; } return $fields; } public static function findFieldsFromRestriction($docArray, $nodeArray) { $fields = []; if (!self::isXsdTag($nodeArray[2][0][0], 'sequence')) throw new Exception("Error Parsing Schema - expected 'complexType/complexContent/restriction/sequence'"); foreach ($nodeArray[2][0][2] as $f) { if (!self::isXsdTag($f[0], 'element')) { DBG::log($n, 'array', "Schema xsd parse error - Not implemented node type '{$f[0]}'"); continue; } $fieldName = XML::findElementName($docArray, $f); // V::get('name', '', $f[1]); if (!$fieldName) throw new Exception("Error Parsing Schema - expected 'element[@name]'"); if ('__' === substr($fieldName, 0, 2)) continue; if (!V::get('type', '', $f[1]) && !V::get('ref', '', $f[1]) && empty($f[2])) { UI::alert('danger', "Skipping not implemented field structure '{$fieldName}'"); continue; } $fields[$fieldName] = [ 'type' => XML::findElementType($docArray, $f), 'minOccurs' => V::get('minOccurs', 0, $f[1], 'int'), 'maxOccurs' => V::get('maxOccurs', '1', $f[1]), 'restrictions' => XML::findElementRestrictions($docArray, $f), 'appInfo' => XML::findElementAppInfo($docArray, $f), ]; } return $fields; } public static function getXsdTypeFromXsdSchema($xsdFilePath, $namespace, $name) { $schema = XML::readXmlFileToArray($xsdFilePath); if (empty($schema)) throw new Exception("Missing schema file for '{$namespace}'"); $xsdType = [ // find xsd:element with @name = $name 'nsPrefix' => null, 'name' => null, 'nsUri' => null, 'primaryKey' => null, 'targetNsUri' => V::get('targetNamespace', '', $schema[1]) ]; if (!$xsdType['targetNsUri']) throw new Exception("Missing schema target namespace declaration '{$name}'"); foreach ($schema[2] as $n) { if (!XML::isXsdTag($n[0], 'element')) continue; if ($name != V::get('name', '', $n[1])) continue; list($xsdType['nsPrefix'], $xsdType['name']) = explode(':', V::get('type', '', $n[1])); } if (!$xsdType['nsPrefix'] || !$xsdType['name']) throw new Exception("Missing schema root element name = '{$name}'"); $xsdType['nsUri'] = V::get("xmlns:{$xsdType['nsPrefix']}", '', $schema[1]);// find xmlns:default_objects = "https://biuro.biall-net.pl/wfs/default_objects" if (!$xsdType['nsUri']) throw new Exception("Missing schema root element namespace declaration = '{$name}'"); if ($xsdType['nsUri'] != $xsdType['targetNsUri']) throw new Exception("TODO: type ns is not the same as targetNamespace '{$name}'");// TODO foreach ($schema[2] as $n) { if (!XML::isXsdTag($n[0], 'complexType')) continue; if ($xsdType['name'] != V::get('name', '', $n[1])) continue; DBG::log($n, 'array', "find system_cache__appinfo:primaryKey from complexType"); if (array_key_exists('system_cache__appinfo:primaryKey', $n[1])) { $xsdType['primaryKey'] = V::get('system_cache__appinfo:primaryKey', '', $n[1]); } // complexType/sequence/element // complexType/complexContent/extension[base=...]/sequence/element switch ($n[2][0][0]) { case 'xs:sequence': case 'xsd:sequence': $xsdType['struct'] = XML::findFieldsFromSequence($schema, $n[2][0]); break; case 'xs:complexContent': case 'xsd:complexContent': $xsdType['struct'] = XML::findFieldsFromComplexContent($schema, $n[2][0]); break; case 'xs:annotation': case 'xsd:annotation': { switch ($n[2][1][0]) { case 'xs:sequence': case 'xsd:sequence': $xsdType['struct'] = XML::findFieldsFromSequence($schema, $n[2][1]); break; case 'xs:complexContent': case 'xsd:complexContent': $xsdType['struct'] = XML::findFieldsFromComplexContent($schema, $n[2][1]); break; } } break; } // if ($n[2][0][0] != 'xsd:complexContent') throw new Exception("Error Parsing Schema - expected 'complexType/complexContent'"); } if (empty($xsdType['primaryKey'])) { foreach ($xsdType['struct'] as $fieldName => $field) { if ('ID' === strtoupper($fieldName)) { $xsdType['primaryKey'] = $fieldName; break; } } } if (empty($xsdType['primaryKey'])) throw new Exception("Missing primaryKey for schema '{$namespace}'"); return $xsdType; } }