modelfactory.class.inc.php 53 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075
  1. <?php
  2. // Copyright (C) 2011 Combodo SARL
  3. //
  4. /**
  5. * ModelFactory: in-memory manipulation of the XML MetaModel
  6. *
  7. * @author Erwan Taloc <erwan.taloc@combodo.com>
  8. * @author Romain Quetiez <romain.quetiez@combodo.com>
  9. * @author Denis Flaven <denis.flaven@combodo.com>
  10. * @license Combodo Private
  11. */
  12. require_once(APPROOT.'setup/moduleinstaller.class.inc.php');
  13. /**
  14. * ModelFactoryItem: an item managed by the ModuleFactory
  15. * @package ModelFactory
  16. */
  17. abstract class MFItem
  18. {
  19. public function __construct()
  20. {
  21. }
  22. /**
  23. * List the source files for this item
  24. */
  25. public function ListSources()
  26. {
  27. }
  28. /**
  29. * List the rights/restrictions for this item
  30. */
  31. public function ListRights()
  32. {
  33. }
  34. }
  35. /**
  36. * ModelFactoryModule: the representation of a Module (i.e. element that can be selected during the setup)
  37. * @package ModelFactory
  38. */
  39. class MFModule extends MFItem
  40. {
  41. protected $sId;
  42. protected $sName;
  43. protected $sVersion;
  44. protected $sRootDir;
  45. protected $sLabel;
  46. protected $aDataModels;
  47. public function __construct($sId, $sRootDir, $sLabel)
  48. {
  49. $this->sId = $sId;
  50. list($this->sName, $this->sVersion) = ModuleDiscovery::GetModuleName($sId);
  51. if (strlen($this->sVersion) == 0)
  52. {
  53. $this->sVersion = '1.0.0';
  54. }
  55. $this->sRootDir = $sRootDir;
  56. $this->sLabel = $sLabel;
  57. $this->aDataModels = array();
  58. // Scan the module's root directory to find the datamodel(*).xml files
  59. if ($hDir = opendir($sRootDir))
  60. {
  61. // This is the correct way to loop over the directory. (according to the documentation)
  62. while (($sFile = readdir($hDir)) !== false)
  63. {
  64. if (preg_match('/^datamodel(.*)\.xml$/i', $sFile, $aMatches))
  65. {
  66. $this->aDataModels[] = $this->sRootDir.'/'.$aMatches[0];
  67. }
  68. }
  69. closedir($hDir);
  70. }
  71. }
  72. public function GetId()
  73. {
  74. return $this->sId;
  75. }
  76. public function GetName()
  77. {
  78. return $this->sName;
  79. }
  80. public function GetVersion()
  81. {
  82. return $this->sVersion;
  83. }
  84. public function GetLabel()
  85. {
  86. return $this->sLabel;
  87. }
  88. public function GetRootDir()
  89. {
  90. return $this->sRootDir;
  91. }
  92. public function GetModuleDir()
  93. {
  94. return basename($this->sRootDir);
  95. }
  96. public function GetDataModelFiles()
  97. {
  98. return $this->aDataModels;
  99. }
  100. /**
  101. * List all classes in this module
  102. */
  103. public function ListClasses()
  104. {
  105. return array();
  106. }
  107. }
  108. /**
  109. * ModelFactoryClass: the representation of a Class (i.e. a PHP class)
  110. * @package ModelFactory
  111. */
  112. class MFClass extends MFItem
  113. {
  114. protected $oNode;
  115. protected $oProperties;
  116. protected $oFields;
  117. protected $oMethods;
  118. protected $sName;
  119. public function __construct(MFElement $oNode)
  120. {
  121. parent::__construct();
  122. $this->oNode = $oNode;
  123. $this->sName = $oNode->getAttribute('id');
  124. $this->oProperties = $this->oNode->GetOptionalElement('properties');
  125. $this->oFields = $this->oNode->GetOptionalElement('fields');
  126. $this->oMethods = $this->oNode->GetOptionalElement('methods');
  127. }
  128. public static function CreateClass(ModelFactory $oFactory, $sClassName, $sModuleName)
  129. {
  130. $oNode = $oFactory->CreateElement('class');
  131. $oNode->setAttribute('id', $sClassName);
  132. $oNode->setAttribute('_created_in', $sModuleName);
  133. $oDoc = $oNode->ownerDocument;
  134. $oProperties = $oDoc->createElement('properties');
  135. $oNode->appendChild($oProperties);
  136. $oFields = $oDoc->createElement('fields');
  137. $oNode->appendChild($oFields);
  138. $oMethods = $oDoc->createElement('methods');
  139. $oNode->appendChild($oMethods);
  140. return new MFClass($oNode);
  141. }
  142. public function AddToFactory(ModelFactory $oFactory)
  143. {
  144. $oFactory->AddClass($this->oNode, $this->GetModuleName());
  145. }
  146. public function Delete()
  147. {
  148. $this->oNode->Delete();
  149. }
  150. public function GetName()
  151. {
  152. return $this->sName;
  153. }
  154. public function GetModuleName()
  155. {
  156. return $this->oNode->getAttribute('_created_in');
  157. }
  158. public function GetParentName()
  159. {
  160. return $this->oNode->GetChildText('parent', '');
  161. }
  162. public function IsRelation()
  163. {
  164. return $this->GetProperty('is_link', false);
  165. }
  166. public function GetProperty($sCode, $defaultValue)
  167. {
  168. $value = $defaultValue;
  169. if ($this->oProperties != null)
  170. {
  171. $value = $this->oProperties->GetChildText($sCode, $defaultValue);
  172. }
  173. return $value;
  174. }
  175. public function SetParentName($sNewParent)
  176. {
  177. $oNewNode = $this->oNode->ownerDocument->createElement('parent', $sNewParent);
  178. $oParentElement = $this->oProperties->GetOptionalElement('parent');
  179. if ($oParentElement == null)
  180. {
  181. // The specified property does not exist, let's create it
  182. $this->oNode->AddChildNode($oNewNode);
  183. }
  184. else
  185. {
  186. // The property already exists, let's replace/redefine its whole content
  187. $oParentElement->RedefineChildNode($oNewNode);
  188. }
  189. }
  190. public function SetProperty($sCode, $value)
  191. {
  192. if ($this->oProperties == null)
  193. {
  194. throw(new Exception("MFClass: Error: cannot set the property '$sCode' on the class '{$this->sName}', the class has no 'properties' in the DOM"));
  195. }
  196. $oNewNode = $this->oNode->ownerDocument->createElement($sCode, $value);
  197. $oProperty = $this->oProperties->GetOptionalElement($sCode);
  198. if ($oProperty == null)
  199. {
  200. // The specified property does not exist, let's create it
  201. $this->oProperties->AddChildNode($oNewNode);
  202. }
  203. else
  204. {
  205. // The property already exists, let's replace/redefine its whole content
  206. $oProperty->RedefineChildNode($oNewNode);
  207. }
  208. }
  209. /**
  210. * List all fields of this class
  211. */
  212. public function ListFields($bIncludeInheritedFields = false)
  213. {
  214. $aFields = array();
  215. if ($bIncludeInheritedFields)
  216. {
  217. // Recurse to get the parent fields
  218. $oParentClass = $this->GetParentClass();
  219. if ($oParentClass != null)
  220. {
  221. $aFields = $oParentClass->ListFields(true);
  222. }
  223. }
  224. if ($this->oFields)
  225. {
  226. $oList = $this->oFields->ListActiveChildNodes('field');
  227. foreach($oList as $oNode)
  228. {
  229. $sCode = $oNode->getAttribute('id');
  230. $aFields[$sCode] = new MFField($oNode);
  231. }
  232. }
  233. return $aFields;
  234. }
  235. /**
  236. * Get the given field from the class
  237. */
  238. public function GetField($sFieldCode, $bIncludeInheritedFields = false)
  239. {
  240. $aFields = $this->ListFields($bIncludeInheritedFields);
  241. $oField = null;
  242. if (array_key_exists($sFieldCode, $aFields))
  243. {
  244. $oField = $aFields[$sFieldCode];
  245. }
  246. return $oField;
  247. }
  248. /**
  249. * List all methods of this class
  250. */
  251. public function ListMethods()
  252. {
  253. return array();
  254. }
  255. /**
  256. * Whether or not the class has a lifecycle
  257. * @return bool
  258. */
  259. public function HasLifeCycle()
  260. {
  261. return true; //TODO implement
  262. }
  263. /**
  264. * Returns the code of the attribute used to store the lifecycle state
  265. * @return string
  266. */
  267. public function GetLifeCycleAttCode()
  268. {
  269. if ($this->HasLifeCycle())
  270. {
  271. }
  272. return '';
  273. }
  274. /**
  275. * List all states of this class
  276. */
  277. public function ListStates()
  278. {
  279. return array();
  280. }
  281. /**
  282. * List all relations of this class
  283. */
  284. public function ListRelations()
  285. {
  286. return array();
  287. }
  288. /**
  289. * List all transitions of this class
  290. */
  291. public function ListTransitions()
  292. {
  293. return array();
  294. }
  295. public function GetParentClass()
  296. {
  297. $oParentClass = null;
  298. $sParentName = $this->GetParentName();
  299. $oParent = $this->oNode->parentNode;
  300. if (($sParentName != '') && ($oParent == null))
  301. {
  302. // No parent node. Maybe the class is not yet inserted into the DOM
  303. // Try to find the parent class by its name ??
  304. $oList = $this->oNode->ownerDocument->GetNodeById('/itop_design/classes//class', $this->GetParentName());
  305. if ($oList->length == 1)
  306. {
  307. $oParentClass = $oList->item(0);
  308. }
  309. else if($oList->length == 0)
  310. {
  311. throw(new Exception("MFClass: Error: invalid parent class '$sParentName' for the class '{$this->sName}'."));
  312. }
  313. else
  314. {
  315. throw(new Exception("MFClass: Error: ambiguous parent class '$sParentName' for the class '{$this->sName}'. There are actually '.$oList->length.' classes named '$sParentName' in the DOM."));
  316. }
  317. }
  318. else
  319. {
  320. if ($oParent->tagName != 'class')
  321. {
  322. // We are up to the top of the classes tree, so no parent
  323. }
  324. else
  325. {
  326. $oParentClass = new MFClass($oParent);
  327. }
  328. }
  329. return $oParentClass;
  330. }
  331. public function GetChildClasses()
  332. {
  333. $aChildClasses = array();
  334. $this->oNode->ListActiveChildNodes('class');
  335. foreach($oChildClasses as $oClassNode)
  336. {
  337. $aChildClasses[] = new MFClass($oClassNode);
  338. }
  339. return $aChildClasses;
  340. }
  341. }
  342. /**
  343. * ModelFactoryField: the representation of a Field (i.e. a attribute of a class)
  344. * @package ModelFactory
  345. */
  346. class MFField extends MFItem
  347. {
  348. protected $oNode;
  349. protected $sName;
  350. public function __construct(MFElement $oNode)
  351. {
  352. parent::__construct();
  353. $this->oNode = $oNode;
  354. $this->sName = $oNode->getAttribute('id');
  355. }
  356. static public function CreateField($oFactory, $sFieldId, $sType)
  357. {
  358. $oNode = $oFactory->CreateElement('field');
  359. $oNode->setAttribute('id', $sClassName);
  360. $oNode->setAttribute('xsi:type', $sType);
  361. return new MFField($oNode);
  362. }
  363. public function Delete()
  364. {
  365. $this->oNode->Delete();
  366. }
  367. public function GetName()
  368. {
  369. return $this->sName;
  370. }
  371. public function GetType()
  372. {
  373. return $this->oNode->getAttribute('xsi:type');
  374. }
  375. public function GetSourceClassName()
  376. {
  377. if ( $this->oNode->parentNode && $this->oNode->parentNode->parentNode)
  378. {
  379. return $this->oNode->parentNode->parentNode->getAttribute('id');
  380. }
  381. return '';
  382. }
  383. public function IsRelation()
  384. {
  385. $bResult = false;
  386. switch($this->oNode->getAttribute('xsi:type'))
  387. {
  388. case 'AttributeLinkedSet':
  389. case 'AttributeLinkedSetIndirect':
  390. case 'AttributeHierarchicalKey':
  391. case 'AttributeExternalKey':
  392. case 'AttributeExternalField':
  393. $bResult = true;
  394. }
  395. return $bResult;
  396. }
  397. public function GetProperty($sCode, $defaultValue = '')
  398. {
  399. $value = $this->oNode->GetChildText($sCode, $defaultValue);
  400. return $value;
  401. }
  402. public function GetArrayProperty($sCode, $defaultValue ='')
  403. {
  404. return array(); // NOT YET IMPLEMENTED
  405. }
  406. public function GetTargetClass()
  407. {
  408. $sTargetClass = '';
  409. if ($this->GetType() == 'AttributeLinkedSetIndirect')
  410. {
  411. $sLinkedClass = $this->GetProperty('linked_class', '');
  412. $oLinkedClassNode = $this->oNode->ownerDocument->GetNodeById('/itop_design/classes//class', $sLinkedClass)->item(0);
  413. if ($oLinkedClassNode)
  414. {
  415. $oLinkedClass = new MFClass($oLinkedClassNode);
  416. $aLinkedFields = $oLinkedClass->ListFields();
  417. $oExtKeyToRemoteField = $aLinkedFields[$this->GetProperty('ext_key_to_remote', '')];
  418. $sTargetClass = $oExtKeyToRemoteField->GetProperty('target_class');
  419. }
  420. }
  421. return $sTargetClass;
  422. }
  423. public function SetProperty($sCode, $value)
  424. {
  425. $oProperty = $this->oNode->GetOptionalElement($sCode);
  426. if ($oProperty == null)
  427. {
  428. // The specified property does not exist, let's create it
  429. $oNewNode = $this->oNode->ownerDocument->createElement($sCode, $value);
  430. $this->oNode->appendChild($oNewNode); //TODO use priv_AppendXXXX to cope with the _alertation flags
  431. }
  432. else
  433. {
  434. // The property already exists, let's replace/redefine its whole content
  435. $oProperty->DeleteChildren();
  436. $oNewNode = $this->oNode->ownerDocument->createTextNode($value);
  437. $oProperty->appendChild($oNewNode); //TODO use priv_Replace/redefineXXXX to cope with the _alertation flags
  438. }
  439. }
  440. }
  441. /**
  442. * ModelFactoryMethod: the representation of a Method (i.e. a method of a class)
  443. * @package ModelFactory
  444. */
  445. class MFMethod extends MFItem
  446. {
  447. }
  448. /**
  449. * ModelFactoryState: the representation of a state in the life cycle of the class
  450. * @package ModelFactory
  451. */
  452. class MFState extends MFItem
  453. {
  454. }
  455. /**
  456. * ModelFactoryRelation: the representation of a n:n relationship between two classes
  457. * @package ModelFactory
  458. */
  459. class MFRelation extends MFItem
  460. {
  461. }
  462. /**
  463. * ModelFactoryTransition: the representation of a transition between two states in the life cycle of the class
  464. * @package ModelFactory
  465. */
  466. class MFTransition extends MFItem
  467. {
  468. }
  469. /**
  470. * ModelFactory: the class that manages the in-memory representation of the XML MetaModel
  471. * @package ModelFactory
  472. */
  473. class ModelFactory
  474. {
  475. protected $sRootDir;
  476. protected $oDOMDocument;
  477. protected $oRoot;
  478. protected $oClasses;
  479. protected $oMenus;
  480. static protected $aLoadedClasses;
  481. static protected $aLoadedMenus;
  482. static protected $aWellKnownParents = array('DBObject', 'CMDBObject','cmdbAbstractObject');
  483. // static protected $aWellKnownMenus = array('DataAdministration', 'Catalogs', 'ConfigManagement', 'Contact', 'ConfigManagementCI', 'ConfigManagement:Shortcuts', 'ServiceManagement');
  484. static protected $aLoadedModules;
  485. static protected $aLoadErrors;
  486. public function __construct($sRootDir)
  487. {
  488. $this->sRootDir = $sRootDir;
  489. $this->oDOMDocument = new MFDocument();
  490. $this->oRoot = $this->oDOMDocument->CreateElement('itop_design');
  491. $this->oDOMDocument->AppendChild($this->oRoot);
  492. $this->oClasses = $this->oDOMDocument->CreateElement('classes');
  493. $this->oRoot->AppendChild($this->oClasses);
  494. foreach (self::$aWellKnownParents as $sWellKnownParent)
  495. {
  496. $oWKClass = $this->oDOMDocument->CreateElement('class');
  497. $oWKClass->setAttribute('id', $sWellKnownParent);
  498. $this->oClasses->AppendChild($oWKClass);
  499. }
  500. $this->oMenus = $this->oDOMDocument->CreateElement('menus');
  501. $this->oRoot->AppendChild($this->oMenus);
  502. // foreach (self::$aWellKnownMenus as $sWellKnownMenu)
  503. // {
  504. // $oWKMenu = $this->oDOMDocument->CreateElement('menu');
  505. // $oWKMenu->setAttribute('id', $sWellKnownMenu);
  506. // $this->oMenus->AppendChild($oWKMenu);
  507. // }
  508. self::$aLoadedClasses = array();
  509. self::$aLoadedMenus = array();
  510. self::$aLoadedModules = array();
  511. self::$aLoadErrors = array();
  512. libxml_use_internal_errors(true);
  513. }
  514. public function Dump($oNode = null)
  515. {
  516. if (is_null($oNode))
  517. {
  518. $oNode = $this->oRoot;
  519. }
  520. $oNode->Dump();
  521. }
  522. /**
  523. * To progressively replace LoadModule
  524. * @param xxx xxx
  525. */
  526. public function LoadDelta(DOMDocument $oDeltaDoc, $oSourceNode, $oTargetParentNode)
  527. {
  528. if (!$oSourceNode instanceof DOMElement) return;
  529. //echo "Load $oSourceNode->tagName::".$oSourceNode->getAttribute('id')." (".$oSourceNode->getAttribute('_delta').")<br/>\n";
  530. $oTarget = $this->oDOMDocument;
  531. if (($oSourceNode->tagName == 'class') && ($oSourceNode->parentNode->tagName == 'classes'))
  532. {
  533. if ($oSourceNode->getAttribute('_delta') == 'define')
  534. {
  535. // This tag is organized in hierarchy: determine the real parent node (as a subnode of the current node)
  536. $sParentId = $oSourceNode->GetChildText('parent');
  537. $oTargetParentNode = $oTarget->GetNodeById('/itop_design/classes//class', $sParentId)->item(0);
  538. if (!$oTargetParentNode)
  539. {
  540. echo "Dumping target doc - looking for '$sPath'<br/>\n";
  541. $this->oDOMDocument->firstChild->Dump();
  542. throw new Exception("XML datamodel loader: could not find parent node for $oSourceNode->tagName/".$oSourceNode->getAttribute('id')." with parent id $sParentId");
  543. }
  544. }
  545. else
  546. {
  547. $oTargetNode = $oTarget->GetNodeById('/itop_design/classes//class', $oSourceNode->getAttribute('id'))->item(0);
  548. if (!$oTargetNode)
  549. {
  550. echo "Dumping target doc - looking for '$sPath'<br/>\n";
  551. $this->oDOMDocument->firstChild->Dump();
  552. throw new Exception("XML datamodel loader: could not find node for $oSourceNode->tagName/".$oSourceNode->getAttribute('id'));
  553. }
  554. else
  555. {
  556. $oTargetParentNode = $oTargetNode->parentNode;
  557. }
  558. }
  559. }
  560. switch ($oSourceNode->getAttribute('_delta'))
  561. {
  562. case 'must_exist':
  563. case 'merge':
  564. case '':
  565. $bMustExist = ($oSourceNode->getAttribute('_delta') == 'must_exist');
  566. $sSearchId = $oSourceNode->hasAttribute('_rename_from') ? $oSourceNode->getAttribute('_rename_from') : $oSourceNode->getAttribute('id');
  567. $oTargetNode = $oSourceNode->MergeInto($oTargetParentNode, $sSearchId, $bMustExist);
  568. foreach($oSourceNode->childNodes as $oSourceChild)
  569. {
  570. // Continue deeper
  571. $this->LoadDelta($oDeltaDoc, $oSourceChild, $oTargetNode);
  572. }
  573. break;
  574. case 'define':
  575. // New node - copy child nodes as well
  576. $oTargetNode = $oTarget->ImportNode($oSourceNode, true);
  577. $oTargetParentNode->AddChildNode($oTargetNode);
  578. break;
  579. case 'redefine':
  580. // Replace the existing node by the given node - copy child nodes as well
  581. $oTargetNode = $oTarget->ImportNode($oSourceNode, true);
  582. $oTargetParentNode->RedefineChildNode($oTargetNode);
  583. break;
  584. case 'delete':
  585. $oTargetNode = $oTargetParentNode->FindExistingChildNode($oSourceNode);
  586. $oTargetNode->Delete();
  587. break;
  588. }
  589. if ($oSourceNode->hasAttribute('_rename_from'))
  590. {
  591. $oTargetNode->Rename($oSourceNode->getAttribute('id'));
  592. }
  593. if ($oTargetNode->hasAttribute('_delta'))
  594. {
  595. $oTargetNode->removeAttribute('_delta');
  596. }
  597. }
  598. /**
  599. * Loads the definitions corresponding to the given Module
  600. * @param MFModule $oModule
  601. */
  602. public function LoadModule(MFModule $oModule)
  603. {
  604. $aDataModels = $oModule->GetDataModelFiles();
  605. $sModuleName = $oModule->GetName();
  606. $aClasses = array();
  607. self::$aLoadedModules[] = $oModule;
  608. foreach($aDataModels as $sXmlFile)
  609. {
  610. $oDocument = new MFDocument();
  611. libxml_clear_errors();
  612. $oDocument->load($sXmlFile);
  613. //$bValidated = $oDocument->schemaValidate(APPROOT.'setup/itop_design.xsd');
  614. $aErrors = libxml_get_errors();
  615. if (count($aErrors) > 0)
  616. {
  617. self::$aLoadErrors[$sModuleName] = $aErrors;
  618. return;
  619. }
  620. $oXPath = new DOMXPath($oDocument);
  621. $oNodeList = $oXPath->query('//class');
  622. foreach($oNodeList as $oNode)
  623. {
  624. $oNode->SetAttribute('_created_in', $sModuleName);
  625. }
  626. $oDeltaRoot = $oDocument->childNodes->item(0);
  627. $this->LoadDelta($oDocument, $oDeltaRoot, $this->oDOMDocument);
  628. }
  629. }
  630. /**
  631. * XML load errors (XML format and validation)
  632. */
  633. function HasLoadErrors()
  634. {
  635. return (count(self::$aLoadErrors) > 0);
  636. }
  637. function GetLoadErrors()
  638. {
  639. return self::$aLoadErrors;
  640. }
  641. function GetLoadedModules($bExcludeWorkspace = true)
  642. {
  643. if ($bExcludeWorkspace)
  644. {
  645. $aModules = array();
  646. foreach(self::$aLoadedModules as $oModule)
  647. {
  648. if (!$oModule instanceof MFWorkspace)
  649. {
  650. $aModules[] = $oModule;
  651. }
  652. }
  653. }
  654. else
  655. {
  656. $aModules = self::$aLoadedModules;
  657. }
  658. return $aModules;
  659. }
  660. function GetModule($sModuleName)
  661. {
  662. foreach(self::$aLoadedModules as $oModule)
  663. {
  664. if ($oModule->GetName() == $sModuleName) return $oModule;
  665. }
  666. return null;
  667. }
  668. public function CreateElement($sTagName, $sValue = '')
  669. {
  670. return $this->oDOMDocument->createElement($sTagName, $sValue);
  671. }
  672. /**
  673. * Check if the class specified by the given node already exists in the loaded DOM
  674. * @param DOMNode $oClassNode The node corresponding to the class to load
  675. * @throws Exception
  676. * @return bool True if the class exists, false otherwise
  677. */
  678. protected function ClassExists(DOMNode $oClassNode)
  679. {
  680. assert(false);
  681. if ($oClassNode->hasAttribute('id'))
  682. {
  683. $sClassName = $oClassNode->GetAttribute('id');
  684. }
  685. else
  686. {
  687. throw new Exception('ModelFactory::AddClass: Cannot add a class with no name');
  688. }
  689. return (array_key_exists($sClassName, self::$aLoadedClasses));
  690. }
  691. /**
  692. * Check if the class specified by the given name already exists in the loaded DOM
  693. * @param string $sClassName The node corresponding to the class to load
  694. * @throws Exception
  695. * @return bool True if the class exists, false otherwise
  696. */
  697. protected function ClassNameExists($sClassName, $bFlattenLayers = true)
  698. {
  699. return !is_null($this->GetClass($sClassName, $bFlattenLayers));
  700. }
  701. /**
  702. * Add the given class to the DOM
  703. * @param DOMNode $oClassNode
  704. * @param string $sModuleName The name of the module in which this class is declared
  705. * @throws Exception
  706. */
  707. public function AddClass(DOMNode $oClassNode, $sModuleName)
  708. {
  709. if ($oClassNode->hasAttribute('id'))
  710. {
  711. $sClassName = $oClassNode->GetAttribute('id');
  712. }
  713. else
  714. {
  715. throw new Exception('ModelFactory::AddClass: Cannot add a class with no name');
  716. }
  717. if ($this->ClassNameExists($oClassNode->getAttribute('id')))
  718. {
  719. throw new Exception("ModelFactory::AddClass: Cannot add the already existing class $sClassName");
  720. }
  721. $sParentClass = $oClassNode->GetChildText('parent', '');
  722. $oParentNode = $this->GetClass($sParentClass);
  723. if ($oParentNode == null)
  724. {
  725. throw new Exception("ModelFactory::AddClass: Cannot find the parent class of '$sClassName': '$sParentClass'");
  726. }
  727. else
  728. {
  729. if ($sModuleName != '')
  730. {
  731. $oClassNode->SetAttribute('_created_in', $sModuleName);
  732. }
  733. $oParentNode->AddChildNode($this->oDOMDocument->importNode($oClassNode, true));
  734. }
  735. }
  736. /**
  737. * Remove a class from the DOM
  738. * @param string $sClass
  739. * @throws Exception
  740. */
  741. public function RemoveClass($sClass)
  742. {
  743. $oClassNode = $this->GetClass($sClass);
  744. if ($oClassNode == null)
  745. {
  746. throw new Exception("ModelFactory::RemoveClass: Cannot remove the non existing class $sClass");
  747. }
  748. // Note: the child classes are removed entirely
  749. $oClassNode->Delete();
  750. }
  751. /**
  752. * Modify a class within the DOM
  753. * @param string $sMenuId
  754. * @param DOMNode $oMenuNode
  755. * @throws Exception
  756. */
  757. public function AlterClass($sClassName, DOMNode $oClassNode)
  758. {
  759. $sOriginalName = $sClassName;
  760. if ($this->ClassNameExists($sClassName))
  761. {
  762. $oDestNode = self::$aLoadedClasses[$sClassName];
  763. }
  764. else
  765. {
  766. $sOriginalName = $oClassNode->getAttribute('_original_name');
  767. if ($this->ClassNameExists($sOriginalName))
  768. {
  769. // Class was renamed !
  770. $oDestNode = self::$aLoadedClasses[$sOriginalName];
  771. }
  772. else
  773. {
  774. throw new Exception("ModelFactory::AddClass: Cannot alter the non-existing class $sClassName / $sOriginalName");
  775. }
  776. }
  777. $this->_priv_AlterNode($oDestNode, $oClassNode);
  778. $sClassName = $oDestNode->getAttribute('id');
  779. if ($sOriginalName != $sClassName)
  780. {
  781. unset(self::$aLoadedClasses[$sOriginalName]);
  782. self::$aLoadedClasses[$sClassName] = $oDestNode;
  783. }
  784. $this->_priv_SetFlag($oDestNode, 'modified');
  785. }
  786. /**
  787. * Add the given menu to the DOM
  788. * @param DOMNode $oMenuNode
  789. * @param string $sModuleName The name of the module in which this class is declared
  790. * @throws Exception
  791. */
  792. public function AddMenu($oMenuNode, $sModuleName)
  793. {
  794. $sMenuId = $oMenuNode->GetAttribute('id');
  795. self::$aLoadedMenus[$sMenuId] = $this->oDOMDocument->ImportNode($oMenuNode, true /* bDeep */);
  796. self::$aLoadedMenus[$sMenuId]->SetAttribute('_operation', 'added');
  797. if ($sModuleName != '')
  798. {
  799. self::$aLoadedMenus[$sMenuId]->SetAttribute('_created_in', $sModuleName);
  800. }
  801. $this->oMenus->AppendChild(self::$aLoadedMenus[$sMenuId]);
  802. }
  803. /**
  804. * Remove a menu from the DOM
  805. * @param string $sMenuId
  806. * @throws Exception
  807. */
  808. public function RemoveMenu($sMenuId)
  809. {
  810. $oMenuNode = self::$aLoadedMenus[$sClass];
  811. if ($oMenuNode->getAttribute('_operation') == 'added')
  812. {
  813. $oMenuNode->parentNode->RemoveChild($oMenuNode);
  814. unset(self::$aLoadedMenus[$sMenuId]);
  815. }
  816. else
  817. {
  818. self::$aLoadedMenus[$sMenuId]->SetAttribute('_operation', 'removed');
  819. }
  820. }
  821. /**
  822. * Modify a menu within the DOM
  823. * @param string $sMenuId
  824. * @param DOMNode $oMenuNode
  825. * @throws Exception
  826. */
  827. public function AlterMenu($sMenuId, DOMNode $oMenuNode)
  828. {
  829. // Todo - implement... do we have to handle menu renaming ???
  830. }
  831. public function GetClassXMLTemplate($sName, $sIcon)
  832. {
  833. $sHeader = '<?xml version="1.0" encoding="utf-8"?'.'>';
  834. return
  835. <<<EOF
  836. $sHeader
  837. <class id="$sName">
  838. <comment/>
  839. <properties>
  840. </properties>
  841. <naming format=""><attributes/></naming>
  842. <reconciliation><attributes/></reconciliation>
  843. <display_template/>
  844. <icon>$sIcon</icon>
  845. </properties>
  846. <fields/>
  847. <lifecycle/>
  848. <methods/>
  849. <presentation>
  850. <details><items/></details>
  851. <search><items/></search>
  852. <list><items/></list>
  853. </presentation>
  854. </class>
  855. EOF
  856. ;
  857. }
  858. /**
  859. * List all classes from the DOM, for a given module
  860. * @param string $sModuleNale
  861. * @param bool $bFlattenLayers
  862. * @throws Exception
  863. */
  864. public function ListClasses($sModuleName, $bFlattenLayers = true)
  865. {
  866. $sXPath = "/itop_design/classes//class[@_created_in='$sModuleName']";
  867. if ($bFlattenLayers)
  868. {
  869. $sXPath = "/itop_design/classes//class[@_created_in='$sModuleName' and (not(@_alteration) or @_alteration!='removed')]";
  870. }
  871. return $this->GetNodes($sXPath);
  872. }
  873. /**
  874. * List all classes from the DOM, for a given module
  875. * @param string $sModuleNale
  876. * @param bool $bFlattenLayers
  877. * @throws Exception
  878. */
  879. public function ListAllClasses($bFlattenLayers = true)
  880. {
  881. $sXPath = "/itop_design/classes//class";
  882. if ($bFlattenLayers)
  883. {
  884. $sXPath = "/itop_design/classes//class[not(@_alteration) or @_alteration!='removed']";
  885. }
  886. return $this->GetNodes($sXPath);
  887. }
  888. public function GetClass($sClassName, $bFlattenLayers = true)
  889. {
  890. $oClassNode = $this->GetNodes("/itop_design/classes//class[@id='$sClassName']")->item(0);
  891. if ($oClassNode == null)
  892. {
  893. return null;
  894. }
  895. elseif ($bFlattenLayers)
  896. {
  897. $sOperation = $oClassNode->getAttribute('_alteration');
  898. if ($sOperation == 'removed')
  899. {
  900. $oClassNode = null;
  901. }
  902. }
  903. return $oClassNode;
  904. }
  905. public function GetChildClasses($oClassNode, $bFlattenLayers = true)
  906. {
  907. $sXPath = "class";
  908. if ($bFlattenLayers)
  909. {
  910. $sXPath = "class[(@_operation!='removed')]";
  911. }
  912. return $this->GetNodes($sXPath, $oClassNode);
  913. }
  914. public function GetField($sClassName, $sAttCode, $bFlattenLayers = true)
  915. {
  916. if (!$this->ClassNameExists($sClassName))
  917. {
  918. return null;
  919. }
  920. $oClassNode = self::$aLoadedClasses[$sClassName];
  921. if ($bFlattenLayers)
  922. {
  923. $sOperation = $oClassNode->getAttribute('_operation');
  924. if ($sOperation == 'removed')
  925. {
  926. $oClassNode = null;
  927. }
  928. }
  929. $sXPath = "fields/field[@id='$sAttCode']";
  930. if ($bFlattenLayers)
  931. {
  932. $sXPath = "fields/field[(@id='$sAttCode' and (not(@_operation) or @_operation!='removed'))]";
  933. }
  934. $oFieldNode = $this->GetNodes($sXPath, $oClassNode)->item(0);
  935. if (($oFieldNode == null) && ($sParentClass = $oClassNode->GetChildText('parent')))
  936. {
  937. return $this->GetField($sParentClass, $sAttCode, $bFlattenLayers);
  938. }
  939. return $oFieldNode;
  940. }
  941. /**
  942. * List all classes from the DOM
  943. * @param bool $bFlattenLayers
  944. * @throws Exception
  945. */
  946. public function ListFields(DOMNode $oClassNode, $bFlattenLayers = true)
  947. {
  948. $sXPath = "fields/field";
  949. if ($bFlattenLayers)
  950. {
  951. $sXPath = "fields/field[not(@_alteration) or @_alteration!='removed']";
  952. }
  953. return $this->GetNodes($sXPath, $oClassNode);
  954. }
  955. public function AddField(DOMNode $oClassNode, $sFieldCode, $sFieldType, $sSQL, $defaultValue, $bIsNullAllowed, $aExtraParams)
  956. {
  957. $oNewField = $this->oDOMDocument->createElement('field');
  958. $oNewField->setAttribute('id', $sFieldCode);
  959. $this->_priv_AlterField($oNewField, $sFieldType, $sSQL, $defaultValue, $bIsNullAllowed, $aExtraParams);
  960. $oFields = $oClassNode->getElementsByTagName('fields')->item(0);
  961. $oFields->AppendChild($oNewField);
  962. $this->_priv_SetFlag($oNewField, 'added');
  963. }
  964. public function RemoveField(DOMNode $oClassNode, $sFieldCode)
  965. {
  966. $sXPath = "fields/field[@id='$sFieldCode']";
  967. $oFieldNodes = $this->GetNodes($sXPath, $oClassNode);
  968. if (is_object($oFieldNodes) && (is_object($oFieldNodes->item(0))))
  969. {
  970. $oFieldNode = $oFieldNodes->item(0);
  971. $sOpCode = $oFieldNode->getAttribute('_operation');
  972. if ($oFieldNode->getAttribute('_operation') == 'added')
  973. {
  974. $oFieldNode->parentNode->removeChild($oFieldNode);
  975. }
  976. else
  977. {
  978. $this->_priv_SetFlag($oFieldNode, 'removed');
  979. }
  980. }
  981. }
  982. public function AlterField(DOMNode $oClassNode, $sFieldCode, $sFieldType, $sSQL, $defaultValue, $bIsNullAllowed, $aExtraParams)
  983. {
  984. $sXPath = "fields/field[@id='$sFieldCode']";
  985. $oFieldNodes = $this->GetNodes($sXPath, $oClassNode);
  986. if (is_object($oFieldNodes) && (is_object($oFieldNodes->item(0))))
  987. {
  988. $oFieldNode = $oFieldNodes->item(0);
  989. //@@TODO: if the field was 'added' => then let it as 'added'
  990. $sOpCode = $oFieldNode->getAttribute('_operation');
  991. switch($sOpCode)
  992. {
  993. case 'added':
  994. case 'modified':
  995. // added or modified, let it as it is
  996. break;
  997. default:
  998. $this->_priv_SetFlag($oFieldNodes->item(0), 'modified');
  999. }
  1000. $this->_priv_AlterField($oFieldNodes->item(0), $sFieldType, $sSQL, $defaultValue, $bIsNullAllowed, $aExtraParams);
  1001. }
  1002. }
  1003. protected function _priv_AlterField(DOMNode $oFieldNode, $sFieldType, $sSQL, $defaultValue, $bIsNullAllowed, $aExtraParams)
  1004. {
  1005. switch($sFieldType)
  1006. {
  1007. case 'Blob':
  1008. case 'Boolean':
  1009. case 'CaseLog':
  1010. case 'Deadline':
  1011. case 'Duration':
  1012. case 'EmailAddress':
  1013. case 'EncryptedString':
  1014. case 'HTML':
  1015. case 'IPAddress':
  1016. case 'LongText':
  1017. case 'OQL':
  1018. case 'OneWayPassword':
  1019. case 'Password':
  1020. case 'Percentage':
  1021. case 'String':
  1022. case 'Text':
  1023. case 'Text':
  1024. case 'TemplateHTML':
  1025. case 'TemplateString':
  1026. case 'TemplateText':
  1027. case 'URL':
  1028. case 'Date':
  1029. case 'DateTime':
  1030. case 'Decimal':
  1031. case 'Integer':
  1032. break;
  1033. case 'ExternalKey':
  1034. $this->_priv_AddFieldAttribute($oFieldNode, 'target_class', $aExtraParams);
  1035. // Fall through
  1036. case 'HierarchicalKey':
  1037. $this->_priv_AddFieldAttribute($oFieldNode, 'on_target_delete', $aExtraParams);
  1038. $this->_priv_AddFieldAttribute($oFieldNode, 'filter', $aExtraParams);
  1039. break;
  1040. case 'ExternalField':
  1041. $this->_priv_AddFieldAttribute($oFieldNode, 'extkey_attcode', $aExtraParams);
  1042. $this->_priv_AddFieldAttribute($oFieldNode, 'target_attcode', $aExtraParams);
  1043. break;
  1044. case 'Enum':
  1045. $this->_priv_SetFieldValues($oFieldNode, $aExtraParams);
  1046. break;
  1047. case 'LinkedSetIndirect':
  1048. $this->_priv_AddFieldAttribute($oFieldNode, 'ext_key_to_remote', $aExtraParams);
  1049. // Fall through
  1050. case 'LinkedSet':
  1051. $this->_priv_AddFieldAttribute($oFieldNode, 'linked_class', $aExtraParams);
  1052. $this->_priv_AddFieldAttribute($oFieldNode, 'ext_key_to_me', $aExtraParams);
  1053. $this->_priv_AddFieldAttribute($oFieldNode, 'count_min', $aExtraParams);
  1054. $this->_priv_AddFieldAttribute($oFieldNode, 'count_max', $aExtraParams);
  1055. break;
  1056. default:
  1057. throw(new Exception('Unsupported type of field: '.$sFieldType));
  1058. }
  1059. $this->_priv_SetFieldDependencies($oFieldNode, $aExtraParams);
  1060. $oFieldNode->setAttribute('type', $sFieldType);
  1061. $oFieldNode->setAttribute('sql', $sSQL);
  1062. $oFieldNode->setAttribute('default_value', $defaultValue);
  1063. $oFieldNode->setAttribute('is_null_alllowed', $bIsNullAllowed ? 'true' : 'false');
  1064. }
  1065. protected function _priv_AddFieldAttribute(DOMNode $oFieldNode, $sAttributeCode, $aExtraParams, $bMandatory = false)
  1066. {
  1067. $value = array_key_exists($sAttributeCode, $aExtraParams) ? $aExtraParams[$sAttributeCode] : '';
  1068. if (($value == '') && (!$bMandatory)) return;
  1069. $oFieldNode->setAttribute($sAttributeCode, $value);
  1070. }
  1071. protected function _priv_SetFieldDependencies($oFieldNode, $aExtraParams)
  1072. {
  1073. $aDeps = array_key_exists('dependencies', $aExtraParams) ? $aExtraParams['dependencies'] : '';
  1074. $oDependencies = $oFieldNode->getElementsByTagName('dependencies')->item(0);
  1075. // No dependencies before, and no dependencies to add, exit
  1076. if (($oDependencies == null) && ($aDeps == '')) return;
  1077. // Remove the previous dependencies
  1078. $oFieldNode->removeChild($oDependencies);
  1079. // If no dependencies, exit
  1080. if ($aDeps == '') return;
  1081. // Build the new list of dependencies
  1082. $oDependencies = $this->oDOMDocument->createElement('dependencies');
  1083. foreach($aDeps as $sAttCode)
  1084. {
  1085. $oDep = $this->oDOMDocument->createElement('attribute');
  1086. $oDep->setAttribute('id', $sAttCode);
  1087. $oDependencies->addChild($oDep);
  1088. }
  1089. $oFieldNode->addChild($oDependencies);
  1090. }
  1091. protected function _priv_SetFieldValues($oFieldNode, $aExtraParams)
  1092. {
  1093. $aVals = array_key_exists('values', $aExtraParams) ? $aExtraParams['values'] : '';
  1094. $oValues = $oFieldNode->getElementsByTagName('values')->item(0);
  1095. // No dependencies before, and no dependencies to add, exit
  1096. if (($oValues == null) && ($aVals == '')) return;
  1097. // Remove the previous dependencies
  1098. $oFieldNode->removeChild($oValues);
  1099. // If no dependencies, exit
  1100. if ($aVals == '') return;
  1101. // Build the new list of dependencies
  1102. $oValues = $this->oDOMDocument->createElement('values');
  1103. foreach($aVals as $sValue)
  1104. {
  1105. $oVal = $this->oDOMDocument->createElement('value', $sValue);
  1106. $oValues->appendChild($oVal);
  1107. }
  1108. $oFieldNode->appendChild($oValues);
  1109. }
  1110. public function SetPresentation(DOMNode $oClassNode, $sPresentationCode, $aPresentation)
  1111. {
  1112. $oPresentation = $oClassNode->getElementsByTagName('presentation')->item(0);
  1113. if (!is_object($oPresentation))
  1114. {
  1115. $oPresentation = $this->oDOMDocument->createElement('presentation');
  1116. $oClassNode->appendChild($oPresentation);
  1117. }
  1118. $oZlist = $oPresentation->getElementsByTagName($sPresentationCode)->item(0);
  1119. if (is_object($oZlist))
  1120. {
  1121. // Remove the previous Zlist
  1122. $oPresentation->removeChild($oZlist);
  1123. }
  1124. // Create the ZList anew
  1125. $oZlist = $this->oDOMDocument->createElement($sPresentationCode);
  1126. $oPresentation->appendChild($oZlist);
  1127. $this->AddZListItem($oZlist, $aPresentation);
  1128. $this->_priv_SetFlag($oZlist, 'replaced');
  1129. }
  1130. protected function AddZListItem($oXMLNode, $value)
  1131. {
  1132. if (is_array($value))
  1133. {
  1134. $oXmlItems = $this->oDOMDocument->CreateElement('items');
  1135. $oXMLNode->appendChild($oXmlItems);
  1136. foreach($value as $key => $item)
  1137. {
  1138. $oXmlItem = $this->oDOMDocument->CreateElement('item');
  1139. $oXmlItems->appendChild($oXmlItem);
  1140. if (is_string($key))
  1141. {
  1142. $oXmlItem->SetAttribute('key', $key);
  1143. }
  1144. $this->AddZListItem($oXmlItem, $item);
  1145. }
  1146. }
  1147. else
  1148. {
  1149. $oXmlText = $this->oDOMDocument->CreateTextNode((string) $value);
  1150. $oXMLNode->appendChild($oXmlText);
  1151. }
  1152. }
  1153. /**
  1154. * List all transitions from a given state
  1155. * @param DOMNode $oStateNode The state
  1156. * @param bool $bFlattenLayers
  1157. * @throws Exception
  1158. */
  1159. public function ListTransitions(DOMNode $oStateNode, $bFlattenLayers = true)
  1160. {
  1161. $sXPath = "transitions/transition";
  1162. if ($bFlattenLayers)
  1163. {
  1164. //$sXPath = "transitions/transition[@_operation!='removed']";
  1165. }
  1166. return $this->GetNodes($sXPath, $oStateNode);
  1167. }
  1168. /**
  1169. * List all states of a given class
  1170. * @param DOMNode $oClassNode The class
  1171. * @param bool $bFlattenLayers
  1172. * @throws Exception
  1173. */
  1174. public function ListStates(DOMNode $oClassNode, $bFlattenLayers = true)
  1175. {
  1176. $sXPath = "lifecycle/states/state";
  1177. if ($bFlattenLayers)
  1178. {
  1179. //$sXPath = "lifecycle/states/state[@_operation!='removed']";
  1180. }
  1181. return $this->GetNodes($sXPath, $oClassNode);
  1182. }
  1183. /**
  1184. * List Zlists from the DOM for a given class
  1185. * @param bool $bFlattenLayers
  1186. * @throws Exception
  1187. */
  1188. public function ListZLists(DOMNode $oClassNode, $bFlattenLayers = true)
  1189. {
  1190. // Not yet implemented !!!
  1191. return array();
  1192. }
  1193. /**
  1194. * List all menus from the DOM, for a given module
  1195. * @param string $sModuleName
  1196. * @param bool $bFlattenLayers
  1197. * @throws Exception
  1198. */
  1199. public function ListMenus($sModuleName, $bFlattenLayers = true)
  1200. {
  1201. $sXPath = "//menu[@_created_in='$sModuleName']";
  1202. if ($bFlattenLayers)
  1203. {
  1204. $sXPath = "//menu[@_created_in='$sModuleName' and (not(@_alteration) or @_alteration!='removed')]";
  1205. }
  1206. return $this->GetNodes($sXPath, $this->oMenus);
  1207. }
  1208. /**
  1209. * Get a menu, given its is id
  1210. * @param string $sModuleName
  1211. * @param bool $bFlattenLayers
  1212. * @throws Exception
  1213. */
  1214. public function GetMenu($sMenuId, $bFlattenLayers = true)
  1215. {
  1216. if (!array_key_exists($sMenuId, self::$aLoadedMenus))
  1217. {
  1218. return null;
  1219. }
  1220. $oMenuNode = self::$aLoadedMenus[$sMenuId];
  1221. if ($bFlattenLayers)
  1222. {
  1223. $sOperation = $oMenuNode->getAttribute('_operation');
  1224. if ($sOperation == 'removed')
  1225. {
  1226. $oMenuNode = null;
  1227. }
  1228. }
  1229. return $oMenuNode;
  1230. }
  1231. public function ApplyChanges()
  1232. {
  1233. $oNodes = $this->ListChanges();
  1234. foreach($oNodes as $oNode)
  1235. {
  1236. $sOperation = $oNode->GetAttribute('_alteration');
  1237. switch($sOperation)
  1238. {
  1239. case 'added':
  1240. case 'replaced':
  1241. // marked as added or modified, just reset the flag
  1242. $oNode->removeAttribute('_alteration');
  1243. break;
  1244. case 'removed':
  1245. // marked as deleted, let's remove the node from the tree
  1246. $oNode->parentNode->removeChild($oNode);
  1247. // TODO!!!!!!!
  1248. //unset(self::$aLoadedClasses[$sClass]);
  1249. break;
  1250. }
  1251. if ($oNode->hasAttribute('_old_id'))
  1252. {
  1253. $oNode->removeAttribute('_old_id');
  1254. }
  1255. }
  1256. }
  1257. public function ListChanges()
  1258. {
  1259. return $this->GetNodes('//*[@_alteration or @_old_id]');
  1260. }
  1261. /**
  1262. * Create path for the delta
  1263. * @param DOMDocument oTargetDoc Where to attach the top of the hierarchy
  1264. * @param MFElement oNode The node to import with its path
  1265. */
  1266. protected function ImportNodeAndPathDelta($oTargetDoc, $oNode)
  1267. {
  1268. // Preliminary: skip the parent if this node is organized hierarchically into the DOM
  1269. // The criteria to detect a hierarchy is: same tag + have an id
  1270. $oParent = $oNode->parentNode;
  1271. while (($oParent instanceof DOMElement) && ($oParent->tagName == $oNode->tagName) && $oParent->hasAttribute('id'))
  1272. {
  1273. $oParent = $oParent->parentNode;
  1274. }
  1275. // Recursively create the path for the parent
  1276. if ($oParent instanceof DOMElement)
  1277. {
  1278. $oParentClone = $this->ImportNodeAndPathDelta($oTargetDoc, $oParent);
  1279. }
  1280. else
  1281. {
  1282. // We've reached the top let's add the node into the root recipient
  1283. $oParentClone = $oTargetDoc;
  1284. }
  1285. // Look for the node into the parent node
  1286. // Note: this is an identified weakness of the algorithm,
  1287. // because for each node modified, and each node of its path
  1288. // we will have to lookup for the existing entry
  1289. // Anyhow, this loop is quite quick to execute because in the delta
  1290. // the number of nodes is limited
  1291. $oNodeClone = null;
  1292. foreach ($oParentClone->childNodes as $oChild)
  1293. {
  1294. if (($oChild instanceof DOMElement) && ($oChild->tagName == $oNode->tagName))
  1295. {
  1296. if (!$oNode->hasAttribute('id') || ($oNode->getAttribute('id') == $oChild->getAttribute('id')))
  1297. {
  1298. $oNodeClone = $oChild;
  1299. break;
  1300. }
  1301. }
  1302. }
  1303. if (!$oNodeClone)
  1304. {
  1305. $sAlteration = $oNode->getAttribute('_alteration');
  1306. $bCopyContents = ($sAlteration == 'replaced') || ($sAlteration == 'added');
  1307. $oNodeClone = $oTargetDoc->importNode($oNode->cloneNode($bCopyContents), $bCopyContents);
  1308. $oNodeClone->removeAttribute('_alteration');
  1309. if ($oNodeClone->hasAttribute('_old_id'))
  1310. {
  1311. $oNodeClone->setAttribute('_rename_from', $oNodeClone->getAttribute('_old_id'));
  1312. $oNodeClone->removeAttribute('_old_id');
  1313. }
  1314. switch ($sAlteration)
  1315. {
  1316. case '':
  1317. if ($oNodeClone->hasAttribute('id'))
  1318. {
  1319. $oNodeClone->setAttribute('_delta', 'must_exist');
  1320. }
  1321. break;
  1322. case 'added':
  1323. $oNodeClone->setAttribute('_delta', 'define');
  1324. break;
  1325. case 'replaced':
  1326. $oNodeClone->setAttribute('_delta', 'redefine');
  1327. break;
  1328. case 'removed':
  1329. $oNodeClone->setAttribute('_delta', 'delete');
  1330. break;
  1331. }
  1332. $oParentClone->appendChild($oNodeClone);
  1333. }
  1334. return $oNodeClone;
  1335. }
  1336. /**
  1337. * Get the text/XML version of the delta
  1338. */
  1339. public function GetDelta()
  1340. {
  1341. $oDelta = new MFDocument();
  1342. foreach($this->ListChanges() as $oAlteredNode)
  1343. {
  1344. $this->ImportNodeAndPathDelta($oDelta, $oAlteredNode);
  1345. }
  1346. return $oDelta->saveXML();
  1347. }
  1348. /**
  1349. * Searches on disk in the root directory for module description files
  1350. * and returns an array of MFModules
  1351. * @return array Array of MFModules
  1352. */
  1353. public function FindModules($sSubDirectory = '')
  1354. {
  1355. $aAvailableModules = ModuleDiscovery::GetAvailableModules($this->sRootDir, $sSubDirectory);
  1356. $aResult = array();
  1357. foreach($aAvailableModules as $sId => $aModule)
  1358. {
  1359. $aResult[] = new MFModule($sId, $aModule['root_dir'], $aModule['label']);
  1360. }
  1361. return $aResult;
  1362. }
  1363. public function TestAlteration()
  1364. {
  1365. if (false)
  1366. {
  1367. echo "<h4>Extrait des données chargées</h4>\n";
  1368. $oRoot = $this->GetNodes("//class[@id='Contact']")->item(0);
  1369. $oRoot->Dump();
  1370. return;
  1371. }
  1372. $sHeader = '<?xml version="1.0" encoding="utf-8"?'.'>';
  1373. $sOriginalXML =
  1374. <<<EOF
  1375. $sHeader
  1376. <itop_design>
  1377. <a id="first a">
  1378. <b>Text</b>
  1379. <c id="1">
  1380. <d>D1</d>
  1381. <d>D2</d>
  1382. </c>
  1383. </a>
  1384. <a id="second a">
  1385. <parent>first a</parent>
  1386. </a>
  1387. <a id="third a">
  1388. <parent>first a</parent>
  1389. <x>blah</x>
  1390. </a>
  1391. </itop_design>
  1392. EOF;
  1393. $this->oDOMDocument = new MFDocument();
  1394. $this->oDOMDocument->loadXML($sOriginalXML);
  1395. echo "<h4>Données d'origine</h4>\n";
  1396. $oRoot = $this->GetNodes('//itop_design')->item(0);
  1397. $oRoot->Dump();
  1398. $oNode = $oRoot->GetNodes('a/b')->item(0);
  1399. $oNew = $this->oDOMDocument->CreateElement('b', 'New text');
  1400. $oNode->parentNode->RedefineChildNode($oNew);
  1401. $oNode = $oRoot->GetNodes('a/c')->item(0);
  1402. $oNew = $this->oDOMDocument->CreateElement('c');
  1403. $oNew->setAttribute('id', '1');
  1404. $oNew->appendChild($this->oDOMDocument->CreateElement('d', 'x'));
  1405. $oNew->appendChild($this->oDOMDocument->CreateElement('d', 'y'));
  1406. $oNew->appendChild($this->oDOMDocument->CreateElement('d', 'z'));
  1407. $oNode->parentNode->RedefineChildNode($oNew);
  1408. $oNode = $oRoot->GetNodes("//a[@id='second a']")->item(0);
  1409. $oNode->Rename('el secundo A');
  1410. $oNew = $this->oDOMDocument->CreateElement('e', 'Something new here');
  1411. $oNode->AddChildNode($oNew);
  1412. $oNew = $this->oDOMDocument->CreateElement('a');
  1413. $oNew->setAttribute('id', 'new a');
  1414. $oNew->appendChild($this->oDOMDocument->CreateElement('parent', 'el secundo A'));
  1415. $oNew->appendChild($this->oDOMDocument->CreateElement('f', 'Welcome to the newcomer'));
  1416. $oNode->AddChildNode($oNew);
  1417. $oNode = $oRoot->GetNodes("//a[@id='third a']")->item(0);
  1418. $oNode->Delete();
  1419. echo "<h4>Après modifications (avec les APIs de ModelFactory)</h4>\n";
  1420. $oRoot->Dump();
  1421. echo "<h4>Delta calculé</h4>\n";
  1422. $sDeltaXML = $this->GetDelta();
  1423. echo "<pre>\n";
  1424. echo htmlentities($sDeltaXML);
  1425. echo "</pre>\n";
  1426. echo "<h4>Réitération: on recharge le modèle épuré</h4>\n";
  1427. $this->oDOMDocument = new MFDocument();
  1428. $this->oDOMDocument->loadXML($sOriginalXML);
  1429. $oRoot = $this->GetNodes('//itop_design')->item(0);
  1430. $oRoot->Dump();
  1431. echo "<h4>On lui applique le delta calculé vu ci-dessus, et on obtient...</h4>\n";
  1432. $oDeltaDoc = new MFDocument();
  1433. $oDeltaDoc->loadXML($sDeltaXML);
  1434. $oDeltaDoc->Dump();
  1435. $this->oDOMDocument->Dump();
  1436. $oDeltaRoot = $oDeltaDoc->childNodes->item(0);
  1437. $this->LoadDelta($oDeltaDoc, $oDeltaRoot, $this->oDOMDocument);
  1438. $oRoot->Dump();
  1439. } // TEST !
  1440. /**
  1441. * Extracts some nodes from the DOM
  1442. * @param string $sXPath A XPath expression
  1443. * @return DOMNodeList
  1444. */
  1445. protected function GetNodes($sXPath, $oContextNode = null)
  1446. {
  1447. return $this->oDOMDocument->GetNodes($sXPath, $oContextNode);
  1448. }
  1449. }
  1450. /**
  1451. * MFElement: helper to read/change the DOM
  1452. * @package ModelFactory
  1453. */
  1454. class MFElement extends DOMElement
  1455. {
  1456. /**
  1457. * Extracts some nodes from the DOM
  1458. * @param string $sXPath A XPath expression
  1459. * @return DOMNodeList
  1460. */
  1461. public function GetNodes($sXPath)
  1462. {
  1463. return $this->ownerDocument->GetNodes($sXPath, $this);
  1464. }
  1465. /**
  1466. * For debugging purposes
  1467. */
  1468. public function Dump()
  1469. {
  1470. echo "<pre>\n";
  1471. echo htmlentities($this->ownerDocument->saveXML($this));
  1472. echo "</pre>\n";
  1473. }
  1474. /**
  1475. * Returns the node directly under the given node
  1476. */
  1477. public function GetUniqueElement($sTagName, $bMustExist = true)
  1478. {
  1479. $oNode = null;
  1480. foreach($this->childNodes as $oChildNode)
  1481. {
  1482. if ($oChildNode->nodeName == $sTagName)
  1483. {
  1484. $oNode = $oChildNode;
  1485. break;
  1486. }
  1487. }
  1488. if ($bMustExist && is_null($oNode))
  1489. {
  1490. throw new DOMFormatException('Missing unique tag: '.$sTagName);
  1491. }
  1492. return $oNode;
  1493. }
  1494. /**
  1495. * Returns the node directly under the current node, or null if missing
  1496. */
  1497. public function GetOptionalElement($sTagName)
  1498. {
  1499. return $this->GetUniqueElement($sTagName, false);
  1500. }
  1501. /**
  1502. * Returns the TEXT of the current node (possibly from several subnodes)
  1503. */
  1504. public function GetText($sDefault = null)
  1505. {
  1506. $sText = null;
  1507. foreach($this->childNodes as $oChildNode)
  1508. {
  1509. if ($oChildNode instanceof DOMCharacterData) // Base class of DOMText and DOMCdataSection
  1510. {
  1511. if (is_null($sText)) $sText = '';
  1512. $sText .= $oChildNode->wholeText;
  1513. }
  1514. }
  1515. if (is_null($sText))
  1516. {
  1517. return $sDefault;
  1518. }
  1519. else
  1520. {
  1521. return $sText;
  1522. }
  1523. }
  1524. /**
  1525. * Get the TEXT value from the child node
  1526. */
  1527. public function GetChildText($sTagName, $sDefault = null)
  1528. {
  1529. $sRet = $sDefault;
  1530. if ($oChild = $this->GetOptionalElement($sTagName))
  1531. {
  1532. $sRet = $oChild->GetText($sDefault);
  1533. }
  1534. return $sRet;
  1535. }
  1536. /**
  1537. * Assumes the current node to be either a text or
  1538. * <items>
  1539. * <item [key]="..."]>value<item>
  1540. * <item [key]="..."]>value<item>
  1541. * </items>
  1542. * where value can be the either a text or an array of items... recursively
  1543. * Returns a PHP array
  1544. */
  1545. public function GetNodeAsArrayOfItems()
  1546. {
  1547. $oItems = $this->GetOptionalElement('items');
  1548. if ($oItems)
  1549. {
  1550. $res = array();
  1551. foreach($oItems->childNodes as $oItem)
  1552. {
  1553. // When an attribute is missing
  1554. if ($oItem->hasAttribute('id'))
  1555. {
  1556. $key = $oItem->getAttribute('id');
  1557. $res[$key] = $oItem->GetNodeAsArrayOfItems();
  1558. }
  1559. else
  1560. {
  1561. $res[] = $oItem->GetNodeAsArrayOfItems();
  1562. }
  1563. }
  1564. }
  1565. else
  1566. {
  1567. $res = $this->GetText();
  1568. }
  1569. return $res;
  1570. }
  1571. /**
  1572. * Helper to remove child nodes
  1573. */
  1574. public function DeleteChildren()
  1575. {
  1576. while (isset($this->firstChild))
  1577. {
  1578. if ($this->firstChild instanceof MFElement)
  1579. {
  1580. $this->firstChild->DeleteChildren();
  1581. }
  1582. $this->removeChild($this->firstChild);
  1583. }
  1584. }
  1585. /**
  1586. * Find the child node matching the given node
  1587. * @param MFElement $oRefNode The node to search for
  1588. * @param bool $sSearchId substitutes to the value of the 'id' attribute
  1589. */
  1590. public function FindExistingChildNode(MFElement $oRefNode, $sSearchId = null)
  1591. {
  1592. return self::FindNode($this, $oRefNode, $sSearchId);
  1593. }
  1594. /**
  1595. * Find the child node matching the given node
  1596. * @param DOMNode $oParent The node to look into (could be DOMDocument, DOMElement...)
  1597. * @param MFElement $oRefNode The node to search for
  1598. * @param bool $sSearchId substitutes to the value of the 'id' attribute
  1599. */
  1600. public static function FindNode(DOMNode $oParent, MFElement $oRefNode, $sSearchId = null)
  1601. {
  1602. $oRes = null;
  1603. if ($oRefNode->hasAttribute('id'))
  1604. {
  1605. // Find the first element having the same tag name and id
  1606. if (!$sSearchId)
  1607. {
  1608. $sSearchId = $oRefNode->getAttribute('id');
  1609. }
  1610. foreach($oParent->childNodes as $oChildNode)
  1611. {
  1612. if (($oChildNode instanceof DOMElement) && ($oChildNode->tagName == $oRefNode->tagName))
  1613. {
  1614. if ($oChildNode->hasAttribute('id') && ($oChildNode->getAttribute('id') == $sSearchId))
  1615. {
  1616. $oRes = $oChildNode;
  1617. break;
  1618. }
  1619. }
  1620. }
  1621. }
  1622. else
  1623. {
  1624. // Get the first one having the same tag name (ignore others)
  1625. foreach($oParent->childNodes as $oChildNode)
  1626. {
  1627. if (($oChildNode instanceof DOMElement) && ($oChildNode->tagName == $oRefNode->tagName))
  1628. {
  1629. $oRes = $oChildNode;
  1630. break;
  1631. }
  1632. }
  1633. }
  1634. return $oRes;
  1635. }
  1636. public function ListActiveChildNodes($sTagName)
  1637. {
  1638. $sXPath = $sTagName."[not(@_alteration) or @_alteration!='removed']";
  1639. return $this->GetNodes($sXPath);
  1640. }
  1641. /**
  1642. * Add a node and set the flags that will be used to compute the delta
  1643. * @param MFElement $oNode The node (including all subnodes) to add
  1644. */
  1645. public function AddChildNode(MFElement $oNode)
  1646. {
  1647. $sFlag = null;
  1648. $oExisting = $this->FindExistingChildNode($oNode);
  1649. if ($oExisting)
  1650. {
  1651. if ($oExisting->getAttribute('_alteration') != 'removed')
  1652. {
  1653. throw new Exception("Attempting to add a node that already exists: $oNode->tagName (id: ".$oNode->getAttribute('id')."");
  1654. }
  1655. $sFlag = 'replaced';
  1656. $oExisting->ReplaceWith($oNode);
  1657. }
  1658. else
  1659. {
  1660. $this->appendChild($oNode);
  1661. $sFlag = 'added';
  1662. // Iterate through the parents: reset the flag if any of them has a flag set
  1663. for($oParent = $oNode ; $oParent instanceof MFElement ; $oParent = $oParent->parentNode)
  1664. {
  1665. if ($oParent->getAttribute('_alteration') != '')
  1666. {
  1667. $sFlag = null;
  1668. break;
  1669. }
  1670. }
  1671. }
  1672. if ($sFlag)
  1673. {
  1674. $oNode->setAttribute('_alteration', $sFlag);
  1675. }
  1676. }
  1677. /**
  1678. * Modify a node and set the flags that will be used to compute the delta
  1679. * @param MFElement $oNode The node (including all subnodes) to set
  1680. */
  1681. public function RedefineChildNode(MFElement $oNode)
  1682. {
  1683. $oExisting = $this->FindExistingChildNode($oNode);
  1684. if (!$oExisting)
  1685. {
  1686. throw new Exception("Attempting to modify a non existing node: $oNode->tagName (id: ".$oNode->getAttribute('id').")");
  1687. }
  1688. if ($oExisting->getAttribute('_alteration') == 'removed')
  1689. {
  1690. throw new Exception("Attempting to modify a deleted node: $oNode->tagName (id: ".$oNode->getAttribute('id')."");
  1691. }
  1692. $oExisting->ReplaceWith($oNode);
  1693. if ($oNode->getAttribute('_alteration') != '')
  1694. {
  1695. // added or modified: leave the flag unchanged
  1696. $sFlag = null;
  1697. }
  1698. else
  1699. {
  1700. $sFlag = 'replaced';
  1701. // Iterate through the parents: reset the flag if any of them has a flag set
  1702. for($oParent = $oNode ; $oParent instanceof MFElement ; $oParent = $oParent->parentNode)
  1703. {
  1704. if ($oParent->getAttribute('_alteration') != '')
  1705. {
  1706. $sFlag = null;
  1707. break;
  1708. }
  1709. }
  1710. }
  1711. if ($sFlag)
  1712. {
  1713. $oNode->setAttribute('_alteration', $sFlag);
  1714. }
  1715. }
  1716. /**
  1717. * Replaces a node by another one, making sure that recursive nodes are preserved
  1718. * @param MFElement $oNewNode The replacement
  1719. */
  1720. protected function ReplaceWith($oNewNode)
  1721. {
  1722. // Move the classes from the old node into the new one
  1723. foreach($this->GetNodes('class') as $oChild)
  1724. {
  1725. $oNewNode->appendChild($oChild);
  1726. }
  1727. $oParentNode = $this->parentNode;
  1728. $oParentNode->replaceChild($oNewNode, $this);
  1729. }
  1730. /**
  1731. * Remove a node and set the flags that will be used to compute the delta
  1732. */
  1733. public function Delete()
  1734. {
  1735. $oParent = $this->parentNode;
  1736. switch ($this->getAttribute('_alteration'))
  1737. {
  1738. case 'replaced':
  1739. $sFlag = 'removed';
  1740. break;
  1741. case 'added':
  1742. $sFlag = null;
  1743. break;
  1744. case 'removed':
  1745. throw new Exception("Attempting to remove a deleted node: $this->tagName (id: ".$this->getAttribute('id')."");
  1746. default:
  1747. $sFlag = 'removed';
  1748. // Iterate through the parents: reset the flag if any of them has a flag set
  1749. for($oParent = $this ; $oParent instanceof MFElement ; $oParent = $oParent->parentNode)
  1750. {
  1751. if ($oParent->getAttribute('_alteration') != '')
  1752. {
  1753. $sFlag = null;
  1754. break;
  1755. }
  1756. }
  1757. }
  1758. if ($sFlag)
  1759. {
  1760. $this->setAttribute('_alteration', $sFlag);
  1761. $this->DeleteChildren();
  1762. }
  1763. else
  1764. {
  1765. // Remove the node entirely
  1766. $oParent->removeChild($this);
  1767. }
  1768. }
  1769. /**
  1770. * Merge the current node into the given container
  1771. *
  1772. * @param DOMNode $oContainer An element or a document
  1773. * @param string $sSearchId The id to consider (could be blank)
  1774. * @param bool $bMustExist Throw an exception if the node must already be found (and not marked as deleted!)
  1775. */
  1776. public function MergeInto($oContainer, $sSearchId, $bMustExist)
  1777. {
  1778. $oTargetNode = $oContainer->FindExistingChildNode($this, $sSearchId);
  1779. if ($oTargetNode)
  1780. {
  1781. if ($oTargetNode->getAttribute('_alteration') == 'removed')
  1782. {
  1783. if ($bMustExist)
  1784. {
  1785. throw new Exception("XML datamodel loader: found mandatory node $this->tagName/$sSearchId marked as deleted in $oContainer->tagName");
  1786. }
  1787. $oTargetNode = $oContainer->ownerDocument->ImportNode($this, false);
  1788. $oContainer->AddChildNode($oTargetNode);
  1789. }
  1790. }
  1791. else
  1792. {
  1793. if ($bMustExist)
  1794. {
  1795. echo "Dumping parent node<br/>\n";
  1796. $oContainer->Dump();
  1797. throw new Exception("XML datamodel loader: could not find $this->tagName/$sSearchId in $oContainer->tagName");
  1798. }
  1799. $oTargetNode = $oContainer->ownerDocument->ImportNode($this, false);
  1800. $oContainer->AddChildNode($oTargetNode);
  1801. }
  1802. return $oTargetNode;
  1803. }
  1804. /**
  1805. * Renames a node and set the flags that will be used to compute the delta
  1806. * @param String $sNewId The new id
  1807. */
  1808. public function Rename($sId)
  1809. {
  1810. $this->setAttribute('_old_id', $this->getAttribute('id'));
  1811. $this->setAttribute('id', $sId);
  1812. }
  1813. }
  1814. /**
  1815. * MFDocument - formating rules for XML input/output
  1816. * @package ModelFactory
  1817. */
  1818. class MFDocument extends DOMDocument
  1819. {
  1820. public function __construct()
  1821. {
  1822. parent::__construct('1.0', 'UTF-8');
  1823. $this->registerNodeClass('DOMElement', 'MFElement');
  1824. $this->formatOutput = true; // indent (must by loaded with option LIBXML_NOBLANKS)
  1825. $this->preserveWhiteSpace = true; // otherwise the formatOutput option would have no effect
  1826. }
  1827. /**
  1828. * Overload of the standard API
  1829. */
  1830. public function load($filename, $options = 0)
  1831. {
  1832. parent::load($filename, LIBXML_NOBLANKS);
  1833. }
  1834. /**
  1835. * Overload of the standard API
  1836. */
  1837. public function loadXML($source, $options = 0)
  1838. {
  1839. parent::loadXML($source, LIBXML_NOBLANKS);
  1840. }
  1841. /**
  1842. * For debugging purposes
  1843. */
  1844. public function Dump()
  1845. {
  1846. echo "<pre>\n";
  1847. echo htmlentities($this->saveXML());
  1848. echo "</pre>\n";
  1849. }
  1850. /**
  1851. * Find the child node matching the given node
  1852. * @param MFElement $oRefNode The node to search for
  1853. * @param bool $sSearchId substitutes to the value of the 'id' attribute
  1854. */
  1855. public function FindExistingChildNode(MFElement $oRefNode, $sSearchId = null)
  1856. {
  1857. return MFElement::FindNode($this, $oRefNode, $sSearchId);
  1858. }
  1859. /**
  1860. * Extracts some nodes from the DOM
  1861. * @param string $sXPath A XPath expression
  1862. * @return DOMNodeList
  1863. */
  1864. public function GetNodes($sXPath, $oContextNode = null)
  1865. {
  1866. $oXPath = new DOMXPath($this);
  1867. if (is_null($oContextNode))
  1868. {
  1869. return $oXPath->query($sXPath);
  1870. }
  1871. else
  1872. {
  1873. return $oXPath->query($sXPath, $oContextNode);
  1874. }
  1875. }
  1876. public function GetNodeById($sXPath, $sId, $oContextNode = null)
  1877. {
  1878. $oXPath = new DOMXPath($this);
  1879. $sXPath .= "[@id='$sId' and(not(@_alteration) or @_alteration!='removed')]";
  1880. if (is_null($oContextNode))
  1881. {
  1882. return $oXPath->query($sXPath);
  1883. }
  1884. else
  1885. {
  1886. return $oXPath->query($sXPath, $oContextNode);
  1887. }
  1888. }
  1889. }