XML.php 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553
  1. <?php
  2. Lib::loadClass('Core_XmlWriter');
  3. // $zxo = new XMLDiff\Memory;// @require xmldiff pecl package installed
  4. // TODO:TYPE_RECURCE: if type has local prefix - choose:
  5. // - find recurse typeName and restrictions
  6. // - find types and save in SystemObjectField table (if simpleType)
  7. // - ? find types and save in SystemObject table + SystemObjectField table (if complexType)
  8. class XML {
  9. public static function xmlToArray($xml) {
  10. $z = new XMLReader();
  11. if (!$z) throw new HttpException("Class XMLReader not installed", 500);
  12. if (!$z->xml($xml)) throw new Exception("Failed to parse xml", 500);
  13. return self::xmlReadToArray($z);
  14. }
  15. public static function readXmlFileToArray($filePath) {
  16. $z = new XMLReader();
  17. if (!$z) throw new HttpException("Class XMLReader not installed", 500);
  18. $fileName = basename($filePath);
  19. if (!$z->open($filePath)) throw new Exception("Failed to open ant schema file '{$fileName}'", 500);
  20. return self::xmlReadToArray($z);
  21. }
  22. public static function xmlReadToArray($z) {
  23. while ($z->read()) {
  24. if (XMLReader::ELEMENT !== $z->nodeType) continue;
  25. return self::xmlReadRecurse($z);
  26. }
  27. }
  28. public static function xmlReadRecurse($z) {
  29. $node = [ $z->name, [], [] ];// name, attrs, childrens
  30. $depth = $z->depth;
  31. $isEmpty = $z->isEmptyElement;
  32. if ($z->hasAttributes) {
  33. while ($z->moveToNextAttribute()) {
  34. $node[1][$z->name] = $z->value;
  35. }
  36. }
  37. if ($isEmpty) {
  38. $node[2] = null;
  39. return $node;
  40. }
  41. // switch ($z->name) {
  42. // case 'xsd:complexType': $node[1]['name'] = $z->getAttribute('name'); break;
  43. // case 'xsd:simpleType': $node[1]['name'] = $z->getAttribute('name'); break;
  44. // case 'xsd:restriction': $node[1]['base'] = $z->getAttribute('base'); break;
  45. // case 'xsd:enumeration': $node[1]['value'] = $z->getAttribute('value'); break;
  46. // case 'xsd:element': $node[1] = [
  47. // 'type' => $z->getAttribute('type')
  48. // ]; break;
  49. // case 'xsd:complexContent': break;
  50. // case 'xsd:sequence': break;
  51. // case 'xsd:attribute': $node[1]['base'] = $z->getAttribute('base'); break;
  52. // case 'xsd:extension': $node[1]['base'] = $z->getAttribute('base'); break;
  53. // default: UI::alert('warning', "TODO: read attributes from: d({$z->depth}) name($z->name)");
  54. // }
  55. while ($z->read() && $z->depth > $depth) {
  56. switch ($z->nodeType) {
  57. case XMLReader::CDATA: $node[2][] = (string)$z->value; break;
  58. case XMLReader::TEXT: $node[2][] = (string)$z->value; break;
  59. case XMLReader::ELEMENT: $node[2][] = self::xmlReadRecurse($z); break;
  60. }
  61. }
  62. return $node;
  63. }
  64. public static function printXmlFromArray($xml) {
  65. $xmlWriter = new Core_XmlWriter();
  66. $xmlWriter->openUri('php://output');
  67. $xmlWriter->setIndent(true);
  68. $xmlWriter->startDocument('1.0','UTF-8');
  69. $xmlWriter->h($xml[0], $xml[1], $xml[2]);
  70. $xmlWriter->endDocument();
  71. }
  72. public static function findTargetNamespace($docArray) {
  73. return V::get('targetNamespace', '', $docArray[1]);
  74. }
  75. public static function findSimpleTypeNode($docArray, $name) {
  76. foreach ($docArray[2] as $child) {
  77. if ('simpleType' === self::getTagName($child[0])) {
  78. if ($name === $child[1]['name']) {
  79. return $child;
  80. }
  81. }
  82. }
  83. }
  84. public static function findComplexTypeNode($docArray, $name) {
  85. foreach ($docArray[2] as $child) {
  86. if ('complexType' === self::getTagName($child[0])) {
  87. if ($name === $child[1]['name']) {
  88. return $child;
  89. }
  90. }
  91. }
  92. }
  93. public static function findTargetNamespacePrefix($docArray) {
  94. $tns = self::findTargetNamespace($docArray);
  95. if (!$tns) throw new Exception("targetNamespace not defined");
  96. foreach ($docArray[1] as $attr => $val) {
  97. if ('xmlns:' !== substr($attr, 0, 6)) continue;
  98. if ($tns === $val) return substr($attr, 6);
  99. }
  100. throw new Exception("Missing targetNamespace xmlns:...");
  101. }
  102. public static function findElementName($docArray, $nodeArray) {
  103. if (!empty($nodeArray[1]['name'])) return $nodeArray[1]['name'];
  104. if (!empty($nodeArray[1]['ref'])) return $nodeArray[1]['ref'];
  105. throw new Exception("Missing xsd:element name");
  106. }
  107. public static function findElementType($docArray, $nodeArray) {
  108. $fieldName = self::findElementName($docArray, $nodeArray);
  109. if (!empty($nodeArray[1]['type'])) {
  110. // TODO:TYPE_RECURCE: if local ns prefix then find correct typeName?
  111. // TODO:TYPE_RECURCE:the same for restrictions
  112. $type = $nodeArray[1]['type'];
  113. DBG::log(['is_xs:' => ('xs:' === substr($type, 0, 3)), 'is_xsd:'=>('xsd:' === substr($type, 0, 4))], 'array', "DBG: findElementType field({$fieldName}) type({$type})");
  114. if ($fixedType = self::tryConvertXsdTypeToXsdPrefix($type)) {
  115. return $fixedType;
  116. }
  117. list($prefix, $name) = explode(':', $type);
  118. if ($prefix === self::findTargetNamespacePrefix($docArray)) {
  119. $simpleTypeNode = self::findSimpleTypeNode($docArray, $name);
  120. DBG::log($simpleTypeNode, 'array', "\$simpleTypeNode \$fieldName='{$fieldName}' type='{$type}'");
  121. if (!empty($simpleTypeNode[1]['type'])) {
  122. throw new Exception("TODO: findElementType node/@type => 'xsd:simpleType/@type' = '{$simpleTypeNode[1]['type']}'");
  123. }
  124. if (!empty($simpleTypeNode[2][0]) && self::isXsdTag($simpleTypeNode[2][0][0], 'restriction')) {
  125. $restrictionNode = $simpleTypeNode[2][0];
  126. if (empty($restrictionNode[1]['base'])) throw new Exception("Missing xsd:restriction/@base (node/@type => xsd:simpleType/[@type='{$simpleTypeNode[1]['type']}']/xsd:restriction')");
  127. $type = $restrictionNode[1]['base'];
  128. DBG::log($type, 'array', "findElementType \$fieldName='{$fieldName}' type='{$nodeArray[1]['type']}' => type='{$type}'");
  129. // check restrictions - if has enumeration then return 'p5:enum'
  130. $isEnum = false;
  131. if (!empty($restrictionNode[2])) foreach ($restrictionNode[2] as $restr) {
  132. if ('enumeration' === self::getTagName($restr[0])) {
  133. $isEnum = true;
  134. break;
  135. }
  136. }
  137. if ($isEnum) return 'p5:enum';
  138. // TODO: recurse with limit
  139. if ($fixedType = self::tryConvertXsdTypeToXsdPrefix($type)) {
  140. return $fixedType;
  141. }
  142. list($prefix, $name) = explode(':', $type);
  143. $simpleTypeNode = self::findSimpleTypeNode($docArray, $name);
  144. DBG::log($simpleTypeNode, 'array', "\$simpleTypeNode \$fieldName='{$fieldName}' ... type='{$type}'");
  145. if (!empty($simpleTypeNode[1]['type'])) {
  146. throw new Exception("TODO: findElementType node/@type => 'xsd:simpleType/@type' = '{$simpleTypeNode[1]['type']}'");
  147. }
  148. if (!empty($simpleTypeNode[2][0]) && self::isXsdTag($simpleTypeNode[2][0][0], 'restriction')) {
  149. $restrictionNode = $simpleTypeNode[2][0];
  150. if (empty($restrictionNode[1]['base'])) throw new Exception("Missing xsd:restriction/@base (node/@type => xsd:simpleType/[@type='{$simpleTypeNode[1]['type']}']/xsd:restriction')");
  151. $type = $restrictionNode[1]['base'];
  152. DBG::log($type, 'array', "findElementType \$fieldName='{$fieldName}' ... type='{$type}'");
  153. return self::convertXsdTypeToXsdPrefix($type);
  154. }
  155. // TODO: throw...
  156. }
  157. // TODO: throw...
  158. // 0 => 'xsd:simpleType',
  159. // 1 => [
  160. // 'name' => 'PROCES_INIT_Simple',
  161. // ],
  162. // 2 => [
  163. // 0 => [
  164. // 0 => 'xsd:restriction',
  165. // 1 => [
  166. // 'base' => 'default_db__x3A__CRM_PROCES:TYPE_Simple',
  167. // ],
  168. // 2 => [
  169. // 0 => [
  170. // 0 => 'xsd:enumeration',
  171. // 1 => [
  172. // 'value' => 'PROCES_INIT',
  173. // ],
  174. // 2 => NULL,
  175. } else {
  176. return $nodeArray[1]['type'];
  177. }
  178. }
  179. if (!empty($nodeArray[1]['ref'])) return 'ref:' . $nodeArray[1]['ref'];
  180. if (empty($nodeArray[2])) throw new Exception("Missing xsd:element childrens - cannot find type");
  181. // TODO: find in loop (xsd:annotation may accure)
  182. if (empty($nodeArray[2][0][0]) || !self::isXsdTag($nodeArray[2][0][0], 'simpleType')) throw new Exception("Missing 'xsd:simpleType' for field '{$fieldName}'");
  183. 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}'");
  184. if (empty($nodeArray[2][0][2][0][1]['base'])) throw new Exception("Missing 'xsd:restriction/@base' for field '{$fieldName}'");
  185. $type = $nodeArray[2][0][2][0][1]['base'];
  186. if ($fixedType = self::tryConvertXsdTypeToXsdPrefix($type)) {
  187. return $fixedType;
  188. }
  189. DBG::log($nodeArray[2][0], 'array', "TODO:findElementType: local type with restriction field({$fieldName}) type({$type})");
  190. // TODO: element local type with restriction
  191. // <xsd:element name="TYPE">
  192. // <xsd:simpleType>
  193. // <xsd:restriction base="default_db__x3A__CRM_PROCES:TYPE_Simple">
  194. // <xsd:enumeration value="PROCES_BENEFIT_INFO"/>
  195. // </xsd:restriction>
  196. // </xsd:simpleType>
  197. // </xsd:element>
  198. return $type;
  199. }
  200. public static function tryConvertXsdTypeToXsdPrefix($type) {
  201. try {
  202. $type = self::convertXsdTypeToXsdPrefix($type);
  203. return $type;
  204. } catch (Exception $e) {
  205. DBG::log($e);
  206. }
  207. return false;
  208. }
  209. public static function convertXsdTypeToXsdPrefix($type) {
  210. // TODO: validate if type is supported in object engine and gui
  211. // TODO: prefix p5
  212. list($prefix, $name) = explode(':', $type);
  213. $prefix = ('xs' === $prefix) ? 'xsd' : $prefix;
  214. if ('xsd' === $prefix && 'int' === $name) return 'xsd:integer';
  215. if ('xsd' === $prefix && 'NCName' === $name) return 'xsd:string'; // TODO: restriction?
  216. if ('xsd' === $prefix && 'NMTOKEN' === $name) return 'xsd:string'; // TODO: restriction?
  217. if ('xsd' === $prefix) return implode(":", [ $prefix, $name ]);
  218. if ('p5' === $prefix) return implode(":", [ $prefix, $name ]);
  219. // if ('xs:' === substr($type, 0, 3)) return "xsd:" . substr($type, 3);
  220. // if ('xsd:' === substr($type, 0, 4)) return $type;
  221. throw new Exception("Not implemented type '{$type}'");
  222. }
  223. public static function findElementRestrictions($docArray, $nodeArray) {
  224. $restrictions = [];
  225. $fieldName = self::findElementName($docArray, $nodeArray);
  226. $childNodes = !empty($nodeArray[2]) ? $nodeArray[2] : null; // definition in child nodes
  227. $typeAlias = !empty($nodeArray[1]['type']) ? $nodeArray[1]['type'] : null; // definition alias - search simpleType with name = Type
  228. DBG::log([
  229. 'has $childNodes' => !empty($childNodes),
  230. 'has $typeAlias' => !empty($typeAlias),
  231. '$typeAlias' => $typeAlias,
  232. '$nodeArray' => $nodeArray
  233. ], 'array', "DBG: findElementRestrictions field({$fieldName})");
  234. if (!empty($nodeArray[1]['nillable']) && 'true' === $nodeArray[1]['nillable']) $restrictions['nillable'] = true;
  235. if ($typeAlias) {
  236. list($prefix, $name) = explode(':', $typeAlias);
  237. if ($prefix === XML::findTargetNamespacePrefix($docArray)) {
  238. $simpleTypeNode = self::findSimpleTypeNode($docArray, $name);
  239. return XML::parseSimpleTypeRestrictions($docArray, $simpleTypeNode, $fieldName);
  240. }
  241. }
  242. if ($childNodes) {
  243. foreach ($childNodes as $c) {
  244. switch (XML::getTagName($c[0])) {
  245. case 'annotation': break; // skip xsd:element/xsd:annotation @see findElementAppInfo
  246. case 'simpleType': $restrictions = XML::parseSimpleTypeRestrictions($docArray, $c, $fieldName);
  247. default: {
  248. DBG::log($c, 'array', "Not imeplemented element child '{$c[0]}'");
  249. }
  250. }
  251. }
  252. }
  253. return $restrictions;
  254. }
  255. public static function parseSimpleTypeRestrictions($docArray, $simpleTypeNode, $fieldName) { // $simpleTypeNode must be 'simpleType' === XML::getTagName($nodeArray[0]), expected 'xsd:restriction' child
  256. DBG::log($simpleTypeNode, 'array', "DBG: parseSimpleTypeRestrictions \$simpleTypeNode for field '{$fieldName}'");
  257. if (empty($simpleTypeNode[2])) return []; // Missing childrens in simpleType definition
  258. $restrictionNode = null;
  259. foreach ($simpleTypeNode[2] as $c) {
  260. if ('restriction' == XML::getTagName($c[0])) {
  261. $restrictionNode = $c;
  262. break;
  263. }
  264. }
  265. DBG::log($restrictionNode, 'array', "DBG: parseSimpleTypeRestrictions \$restrictionNode for field '{$fieldName}'");
  266. if (empty($restrictionNode)) return []; // Missing xsd:restriction in simpleType definition
  267. $restrictions = [];
  268. if (empty($restrictionNode[1]['base'])) throw new Exception("Missing 'xsd:restriction/@base' for field '{$fieldName}'");
  269. // xsd:simpleType/xsd:restriction/xsd:string
  270. if (!empty($restrictionNode[2])) foreach ($restrictionNode[2] as $tagRestriction) {
  271. // xsd:string/xsd:maxLength
  272. $val = $tagRestriction[1]['value'];
  273. if ('enumeration' == XML::getTagName($tagRestriction[0])) {
  274. $restrictions['enumeration'][$val] = $val;
  275. } else {
  276. $restrictions[ XML::getTagName($tagRestriction[0]) ] = $val;
  277. }
  278. }
  279. return $restrictions;
  280. }
  281. public static function findElementAppInfo($docArray, $nodeArray) {
  282. $appInfo = [];
  283. $fieldName = self::findElementName($docArray, $nodeArray);
  284. if (!empty($nodeArray[2])) {
  285. foreach ($nodeArray[2] as $c) {
  286. switch (XML::getTagName($c[0])) {
  287. case 'annotation': { // skip xsd:element/xsd:annotation
  288. DBG::log($c, 'array', "xsd:annotation/xsd:appinfo '{$fieldName}'");
  289. // <xsd:annotation>
  290. // <xsd:appinfo>
  291. // <system_cache__appinfo:flat_relation_cache>
  292. // <system_cache__appinfo:source system_cache__appinfo:name="ID"
  293. // system_cache__appinfo:xpath="default_db__x3A__CRM_WSKAZNIK:CRM_WSKAZNIK/ID_PROCES"/>
  294. $prefix = 'system_cache__appinfo';
  295. foreach ($c[2] as $cc) {
  296. if ('appinfo' == XML::getTagName($cc[0])) {
  297. foreach ($cc[2] as $appTag) {
  298. $appInfo[ XML::getTagName($appTag[0]) ] = XML::readAppInfoRecurse($appTag);
  299. }
  300. }
  301. }
  302. DBG::log($appInfo, 'array', "xsd:annotation/xsd:appinfo '{$fieldName}' \$appInfo");
  303. // <xs:annotation>
  304. // <xs:appinfo>
  305. // <system_cache__appinfo:flat_relation_cache system_cache__appinfo:backref_evaluate="true">
  306. // <system_cache__appinfo:source system_cache__appinfo:name="krs" system_cache__appinfo:xpath="default_db__x3A__BI_audit_KRS:BI_audit_KRS/krs" system_cache__appinfo:ref_engine="view"/>
  307. } break;
  308. case 'simpleType': break; // skip xsd:element/xsd:simpleType @see findElementRestrictions
  309. default: {
  310. DBG::log($c, 'array', "Not imeplemented element child '{$c[0]}'");
  311. }
  312. }
  313. }
  314. }
  315. DBG::log($fieldName, 'array', "findElementAppInfo fieldName='{$fieldName}'");
  316. return $appInfo;
  317. }
  318. public static function getTagName($xsdName) {
  319. return (false !== ($pos = strpos($xsdName, ':')))
  320. ? substr($xsdName, $pos + 1)
  321. : $xsdName;
  322. }
  323. public static function isXsdTag($xsdName, $expectedTagName) {
  324. list($xsdPrefix, $tagName) = explode(':', $xsdName);
  325. switch ($xsdPrefix) {
  326. case 'xs':
  327. case 'xsd': return ($tagName === $expectedTagName);
  328. }
  329. return false;
  330. }
  331. static function isXsdNodeAnnotation($xsdNode) { return self::isXsdTag($xsdNode[0], 'annotation'); }
  332. static function isXsdNodeAppInfo($xsdNode) { return self::isXsdTag($xsdNode[0], 'appinfo'); }
  333. public static function readAppInfoRecurse($nodeArray) {
  334. $appInfo = [];
  335. if (!empty($nodeArray[1])) foreach ($nodeArray[1] as $attrName => $attrVal) {
  336. $appInfo['@' . XML::getTagName($attrName)] = $attrVal;
  337. }
  338. if (!empty($nodeArray[2])) foreach ($nodeArray[2] as $appTag) {
  339. $appInfo[ XML::getTagName($appTag[0]) ] = XML::readAppInfoRecurse($appTag);
  340. }
  341. // TODO: text nodes
  342. return $appInfo;
  343. }
  344. public static function findFieldsFromSequence($docArray, $nodeArray) {
  345. if (!self::isXsdTag($nodeArray[0], 'sequence')) throw new Exception("Error Parsing Schema - expected 'sequence'");
  346. $fields = [];
  347. foreach ($nodeArray[2] as $f) {
  348. if (!self::isXsdTag($f[0], 'element')) {
  349. DBG::log($n, 'array', "Schema xsd parse error - Not implemented node type '{$f[0]}'");
  350. continue;
  351. }
  352. $fieldName = XML::findElementName($docArray, $f); // V::get('name', '', $f[1]);
  353. if (!$fieldName) throw new Exception("Error Parsing Schema - expected 'element[@name]'");
  354. if ('__' === substr($fieldName, 0, 2)) continue;
  355. if (!V::get('type', '', $f[1]) && !V::get('ref', '', $f[1]) && empty($f[2])) {
  356. UI::alert('danger', "Skipping not implemented field structure '{$fieldName}'");
  357. DBG::log($f, 'array', "Skipping not implemented field structure '{$fieldName}'");
  358. continue;
  359. }
  360. $fields[$fieldName] = [
  361. 'type' => XML::findElementType($docArray, $f),
  362. 'minOccurs' => V::get('minOccurs', 0, $f[1], 'int'),
  363. 'maxOccurs' => V::get('maxOccurs', '1', $f[1]),
  364. 'restrictions' => XML::findElementRestrictions($docArray, $f),
  365. 'appInfo' => XML::findElementAppInfo($docArray, $f),
  366. ];
  367. }
  368. return $fields;
  369. }
  370. public static function findFieldsFromComplexContent($docArray, $nodeArray) {
  371. // xsd:complexType / xsd:complexContent / xsd:restriction [ @base = "default_db__x3A__CRM_PROCES:CRM_PROCES" ]
  372. // xsd:complexType / xsd:complexContent / xsd:extension [ @base = "default_db__x3A__CRM_PROCES:CRM_PROCES" ]
  373. switch (XML::getTagName($nodeArray[2][0][0])) {
  374. case 'extension': return XML::findFieldsFromExtension($docArray, $nodeArray[2][0]);
  375. case 'restriction': return XML::findFieldsFromRestriction($docArray, $nodeArray[2][0]);
  376. }
  377. // TODO:? $xsdType['extensionBase'] = V::get('base', '', $nodeArray[1]);
  378. // TODO:? $xsdType['restrictionBase'] = V::get('base', '', $nodeArray[1]);
  379. }
  380. public static function findFieldsFromExtension($docArray, $nodeArray) {
  381. $fields = [];
  382. if (empty($nodeArray[2])) { // empty extension tag - no childrens
  383. // [0] => xs:extension
  384. // [1] => Array:
  385. // [base] => default_db__x3A__NEURO_MATRIX:Pain2
  386. // [2] => Array: <-- empty!
  387. if (empty($nodeArray[1]['base'])) throw new Exception("Error Parsing Schema - missing base attribute when empty extension");
  388. list($prefix, $name) = explode(':', $nodeArray[1]['base']);
  389. if ($prefix !== XML::findTargetNamespacePrefix($docArray)) throw new Exception("Error Parsing Schema - extension base not in local namespace");
  390. $foundComplexTypeNode = XML::findComplexTypeNode($docArray, $name);
  391. if (!$foundComplexTypeNode) throw new Exception("Error Parsing Schema - cannot find extension base in local namespace");
  392. switch ($foundComplexTypeNode[2][0][0]) {
  393. case 'xs:sequence':
  394. case 'xsd:sequence': return XML::findFieldsFromSequence($docArray, $foundComplexTypeNode[2][0]); break;
  395. case 'xs:complexContent':
  396. case 'xsd:complexContent': return XML::findFieldsFromComplexContent($docArray, $foundComplexTypeNode[2][0]); break;
  397. case 'xs:annotation':
  398. case 'xsd:annotation': {
  399. switch ($foundComplexTypeNode[2][1][0]) {
  400. case 'xs:sequence':
  401. case 'xsd:sequence': return XML::findFieldsFromSequence($docArray, $foundComplexTypeNode[2][1]); break;
  402. case 'xs:complexContent':
  403. case 'xsd:complexContent': return XML::findFieldsFromComplexContent($docArray, $foundComplexTypeNode[2][1]); break;
  404. }
  405. } break;
  406. }
  407. }
  408. if (!self::isXsdTag($nodeArray[2][0][0], 'sequence')) throw new Exception("Error Parsing Schema - expected 'complexType/complexContent/extension/sequence'");
  409. foreach ($nodeArray[2][0][2] as $f) {
  410. if (!self::isXsdTag($f[0], 'element')) {
  411. DBG::log($n, 'array', "Schema xsd parse error - Not implemented node type '{$f[0]}'");
  412. continue;
  413. }
  414. $fieldName = XML::findElementName($docArray, $f); // V::get('name', '', $f[1]);
  415. if (!$fieldName) throw new Exception("Error Parsing Schema - expected 'element[@name]'");
  416. if ('__' === substr($fieldName, 0, 2)) continue;
  417. if (!V::get('type', '', $f[1]) && !V::get('ref', '', $f[1]) && empty($f[2])) {
  418. UI::alert('danger', "Skipping not implemented field structure '{$fieldName}'");
  419. continue;
  420. }
  421. $fields[$fieldName] = [
  422. 'type' => XML::findElementType($docArray, $f),
  423. 'minOccurs' => V::get('minOccurs', 0, $f[1], 'int'),
  424. 'maxOccurs' => V::get('maxOccurs', '1', $f[1]),
  425. 'restrictions' => XML::findElementRestrictions($docArray, $f),
  426. 'appInfo' => XML::findElementAppInfo($docArray, $f),
  427. ];
  428. }
  429. return $fields;
  430. }
  431. public static function findFieldsFromRestriction($docArray, $nodeArray) {
  432. $fields = [];
  433. if (!self::isXsdTag($nodeArray[2][0][0], 'sequence')) throw new Exception("Error Parsing Schema - expected 'complexType/complexContent/restriction/sequence'");
  434. foreach ($nodeArray[2][0][2] as $f) {
  435. if (!self::isXsdTag($f[0], 'element')) {
  436. DBG::log($n, 'array', "Schema xsd parse error - Not implemented node type '{$f[0]}'");
  437. continue;
  438. }
  439. $fieldName = XML::findElementName($docArray, $f); // V::get('name', '', $f[1]);
  440. if (!$fieldName) throw new Exception("Error Parsing Schema - expected 'element[@name]'");
  441. if ('__' === substr($fieldName, 0, 2)) continue;
  442. if (!V::get('type', '', $f[1]) && !V::get('ref', '', $f[1]) && empty($f[2])) {
  443. UI::alert('danger', "Skipping not implemented field structure '{$fieldName}'");
  444. continue;
  445. }
  446. $fields[$fieldName] = [
  447. 'type' => XML::findElementType($docArray, $f),
  448. 'minOccurs' => V::get('minOccurs', 0, $f[1], 'int'),
  449. 'maxOccurs' => V::get('maxOccurs', '1', $f[1]),
  450. 'restrictions' => XML::findElementRestrictions($docArray, $f),
  451. 'appInfo' => XML::findElementAppInfo($docArray, $f),
  452. ];
  453. }
  454. return $fields;
  455. }
  456. public static function getXsdTypeFromXsdSchema($xsdFilePath, $namespace, $name) {
  457. $schema = XML::readXmlFileToArray($xsdFilePath);
  458. if (empty($schema)) throw new Exception("Missing schema file for '{$namespace}'");
  459. $xsdType = [ // find xsd:element with @name = $name
  460. 'nsPrefix' => null,
  461. 'name' => null,
  462. 'nsUri' => null,
  463. 'primaryKey' => null,
  464. 'targetNsUri' => V::get('targetNamespace', '', $schema[1])
  465. ];
  466. if (!$xsdType['targetNsUri']) throw new Exception("Missing schema target namespace declaration '{$name}'");
  467. foreach ($schema[2] as $n) {
  468. if (!XML::isXsdTag($n[0], 'element')) continue;
  469. if ($name != V::get('name', '', $n[1])) continue;
  470. list($xsdType['nsPrefix'], $xsdType['name']) = explode(':', V::get('type', '', $n[1]));
  471. }
  472. if (!$xsdType['nsPrefix'] || !$xsdType['name']) throw new Exception("Missing schema root element name = '{$name}'");
  473. $xsdType['nsUri'] = V::get("xmlns:{$xsdType['nsPrefix']}", '', $schema[1]);// find xmlns:default_objects = "https://biuro.biall-net.pl/wfs/default_objects"
  474. if (!$xsdType['nsUri']) throw new Exception("Missing schema root element namespace declaration = '{$name}'");
  475. if ($xsdType['nsUri'] != $xsdType['targetNsUri']) throw new Exception("TODO: type ns is not the same as targetNamespace '{$name}'");// TODO
  476. $foundComplexTypes = array_filter($schema[2], function ($n) use ($xsdType) {
  477. return (XML::isXsdTag($n[0], 'complexType') && $xsdType['name'] === V::get('name', '', $n[1]));
  478. });
  479. if (empty($foundComplexTypes)) throw new Exception("Missing complexType node with name='{$xsdType['name']}'");
  480. $nodeComplexType = reset($foundComplexTypes);
  481. DBG::log($nodeComplexType, 'array', "found main complexType node");
  482. if (array_key_exists('system_cache__appinfo:primaryKey', $nodeComplexType[1])) {
  483. $xsdType['primaryKey'] = V::get('system_cache__appinfo:primaryKey', '', $nodeComplexType[1]);
  484. }
  485. $listAnnotations = array_filter($nodeComplexType[2], [ self, 'isXsdNodeAnnotation' ]);
  486. if (!empty($listAnnotations)) {
  487. // DBG::nicePrint($listAnnotations, "\$listAnnotations");
  488. $nodeAnnotation = reset($listAnnotations);
  489. $listAppInfo = array_filter($nodeAnnotation[2], [ self, 'isXsdNodeAppInfo' ]);
  490. if (!empty($listAppInfo)) {
  491. // DBG::nicePrint($listAppInfo, "\$listAppInfo");
  492. $nodeAppInfo = reset($listAppInfo);
  493. $xsdType['appInfo'] = XML::readAppInfoRecurse($nodeAppInfo);
  494. }
  495. }
  496. // complexType/sequence/element
  497. // complexType/complexContent/extension[base=...]/sequence/element
  498. switch ($nodeComplexType[2][0][0]) { // TODO: convert to loop
  499. case 'xs:sequence':
  500. case 'xsd:sequence': $xsdType['struct'] = XML::findFieldsFromSequence($schema, $nodeComplexType[2][0]); break;
  501. case 'xs:complexContent':
  502. case 'xsd:complexContent': $xsdType['struct'] = XML::findFieldsFromComplexContent($schema, $nodeComplexType[2][0]); break;
  503. case 'xs:annotation':
  504. case 'xsd:annotation': {
  505. switch ($nodeComplexType[2][1][0]) {
  506. case 'xs:sequence':
  507. case 'xsd:sequence': $xsdType['struct'] = XML::findFieldsFromSequence($schema, $nodeComplexType[2][1]); break;
  508. case 'xs:complexContent':
  509. case 'xsd:complexContent': $xsdType['struct'] = XML::findFieldsFromComplexContent($schema, $nodeComplexType[2][1]); break;
  510. }
  511. } break;
  512. }
  513. // if ($n[2][0][0] != 'xsd:complexContent') throw new Exception("Error Parsing Schema - expected 'complexType/complexContent'");
  514. if (empty($xsdType['primaryKey'])) {
  515. foreach ($xsdType['struct'] as $fieldName => $field) {
  516. if ('ID' === strtoupper($fieldName)) {
  517. $xsdType['primaryKey'] = $fieldName;
  518. break;
  519. }
  520. }
  521. }
  522. if (empty($xsdType['primaryKey'])) throw new Exception("Missing primaryKey for schema '{$namespace}'");
  523. return $xsdType;
  524. }
  525. }