modelverse.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807
  1. from sccd.runtime.statecharts_core import Event
  2. import sccd.runtime.socket2event as socket2event
  3. import modelverse_SCCD as modelverse_SCCD
  4. import time
  5. import threading
  6. import sys
  7. if sys.version_info[0] < 3:
  8. from urllib2 import urlopen as urlopen
  9. from urllib2 import Request as Request
  10. from urllib import urlencode as urlencode
  11. else:
  12. from urllib.request import urlopen as urlopen
  13. from urllib.request import Request as Request
  14. from urllib.parse import urlencode as urlencode
  15. # Exceptions
  16. class ModelverseException(Exception):
  17. pass
  18. class SuperclassAttribute(ModelverseException):
  19. pass
  20. class CallbackOnEmptySignature(ModelverseException):
  21. pass
  22. class NotAModel(ModelverseException):
  23. pass
  24. class ManualActivityRequiresIO(ModelverseException):
  25. pass
  26. class UserNotInGroup(ModelverseException):
  27. pass
  28. class IncorrectFormat(ModelverseException):
  29. pass
  30. class SignatureMismatch(ModelverseException):
  31. pass
  32. class EmptySignature(SignatureMismatch):
  33. pass
  34. class SourceModelNotBound(SignatureMismatch):
  35. pass
  36. class TargetModelNotBound(SignatureMismatch):
  37. pass
  38. class DifferingModelsForKey(SignatureMismatch):
  39. pass
  40. class TypeMismatch(SignatureMismatch):
  41. pass
  42. class UnknownError(ModelverseException):
  43. pass
  44. class UnknownM3(ModelverseException):
  45. pass
  46. class UnknownIdentifier(ModelverseException):
  47. pass
  48. class CompilationError(ModelverseException):
  49. pass
  50. class NotAnActivity(ModelverseException):
  51. pass
  52. class NotAProcess(ModelverseException):
  53. pass
  54. class NotAValidProcess(ModelverseException):
  55. pass
  56. class UnknownAttribute(UnknownIdentifier):
  57. pass
  58. class UnknownElement(UnknownIdentifier):
  59. pass
  60. class UnknownModel(UnknownIdentifier):
  61. pass
  62. class UnknownLocation(UnknownIdentifier):
  63. pass
  64. class UnknownGroup(UnknownIdentifier):
  65. pass
  66. class UnknownUser(UnknownIdentifier):
  67. pass
  68. class ConnectionError(ModelverseException):
  69. pass
  70. class ExistsError(ModelverseException):
  71. pass
  72. class AttributeExists(ExistsError):
  73. pass
  74. class ElementExists(ExistsError):
  75. pass
  76. class LocationExists(ExistsError):
  77. pass
  78. class FolderExists(ExistsError):
  79. pass
  80. class GroupExists(ExistsError):
  81. pass
  82. class UserExists(ExistsError):
  83. pass
  84. class PermissionDenied(ModelverseException):
  85. pass
  86. class ReadPermissionDenied(PermissionDenied):
  87. pass
  88. class WritePermissionDenied(PermissionDenied):
  89. pass
  90. class ExecutePermissionDenied(PermissionDenied):
  91. pass
  92. class UserPermissionDenied(PermissionDenied):
  93. pass
  94. class GroupPermissionDenied(PermissionDenied):
  95. pass
  96. class AdminPermissionDenied(PermissionDenied):
  97. pass
  98. class InterfaceMismatch(ModelverseException):
  99. pass
  100. class UnknownMetamodellingHierarchy(ModelverseException):
  101. pass
  102. class NotAnAssociation(ModelverseException):
  103. pass
  104. def run_controller():
  105. try:
  106. controller.start()
  107. finally:
  108. controller.stop()
  109. def _next_ID():
  110. global ID
  111. ID += 1
  112. return ID
  113. def __run_new_modelverse(address, username, password, callback, model):
  114. init(address)
  115. login(username, password)
  116. callback(model)
  117. exit_save(model)
  118. disconnect()
  119. def __run_new_modelverse_activity(address, username, password, taskname, pipe, callback):
  120. init(address, taskname=taskname)
  121. controller.username = username
  122. controller.password = password
  123. t = OUTPUT()
  124. if t == "OP":
  125. model = OUTPUT()
  126. if callback is not None:
  127. __invoke(callback, model)
  128. controller.addInput(Event("data_input", "action_in", [None]))
  129. time.sleep(2)
  130. elif t == "SC":
  131. while 1:
  132. empty = True
  133. # Fetch output from the MV
  134. response = responses.fetch(0)
  135. if response is not None:
  136. if response.name == "data_output":
  137. # Got output of MV, so forward to SCCD
  138. if pipe is not None:
  139. pipe.send(("input", response.parameters))
  140. elif response.name == "result":
  141. # Finished execution, so continue and return result
  142. if pipe is not None:
  143. pipe.send(("terminate", []))
  144. pipe.close()
  145. return
  146. else:
  147. raise Exception("Unknown data from MV to SC: " + str(response))
  148. empty = False
  149. # Fetch output from the SC
  150. if pipe is not None and pipe.poll():
  151. response = pipe.recv()
  152. if response.name == "output":
  153. controller.addInput(Event("data_input", "action_in", [response.parameters]))
  154. else:
  155. raise Exception("Unknown data from SC to MV: " + str(response))
  156. empty = False
  157. if empty:
  158. time.sleep(0.05)
  159. def __invoke(callback, model):
  160. import multiprocessing
  161. p = multiprocessing.Process(target=__run_new_modelverse, args=[controller.address, controller.username, controller.password, callback, model])
  162. p.start()
  163. p.join()
  164. def _process_SC(statechart, port_sc, taskname):
  165. import multiprocessing
  166. p2c_pipe, c2p_pipe = multiprocessing.Pipe()
  167. p = multiprocessing.Process(target=__run_new_modelverse_activity, args=[controller.address, controller.username, controller.password, taskname, c2p_pipe, None])
  168. p.start()
  169. while 1:
  170. empty = True
  171. if p2c_pipe.poll():
  172. response = p2c_pipe.recv()
  173. statechart[0].addInput(Event(response[0], statechart[1], response[1]))
  174. if response[0] == "terminate":
  175. p2c_pipe.close()
  176. break
  177. empty = False
  178. response = port_sc.fetch(0)
  179. if response is not None:
  180. p2c_pipe.send(response)
  181. empty = False
  182. if empty:
  183. time.sleep(0.05)
  184. p.join()
  185. def _process_OP(callback, taskname):
  186. import multiprocessing
  187. p = multiprocessing.Process(target=__run_new_modelverse_activity, args=[controller.address, controller.username, controller.password, taskname, None, callback])
  188. p.start()
  189. p.join()
  190. def INPUT(action, parameters):
  191. controller.addInput(Event("action", "action_in", [action, _next_ID(), parameters]))
  192. def OUTPUT():
  193. while 1:
  194. response = responses.fetch(-1)
  195. if response.name == "result":
  196. return response.parameters[1]
  197. elif response.name == "exception":
  198. try:
  199. raise eval(response.parameters[1])(*response.parameters[2:])
  200. except NameError:
  201. raise UnknownError(response.parameters[1:])
  202. def init(address_param="127.0.0.1:8001", timeout=60.0, taskname=None):
  203. global controller
  204. global ID
  205. global responses
  206. controller = modelverse_SCCD.Controller(taskname)
  207. socket2event.boot_translation_service(controller)
  208. ID = 0
  209. thrd = threading.Thread(target=run_controller)
  210. thrd.daemon = True
  211. thrd.start()
  212. responses = controller.addOutputListener("action_out")
  213. controller.addOutputListener("ready").fetch(-1)
  214. INPUT("init", [address_param, timeout])
  215. controller.address = address_param
  216. OUTPUT()
  217. return
  218. def login(username, password):
  219. if username == None:
  220. import uuid
  221. username = str(uuid.uuid4())
  222. if password == None:
  223. import uuid
  224. password = str(uuid.uuid4())
  225. controller.username = username
  226. controller.password = password
  227. INPUT("login", [username, password])
  228. return OUTPUT()
  229. def model_list(location):
  230. INPUT("model_list", [location])
  231. return OUTPUT()
  232. def user_password(username, password):
  233. INPUT("user_password", [username, password])
  234. return OUTPUT()
  235. def model_add(model_name, metamodel_name, model_code=""):
  236. INPUT("model_add", [model_name, metamodel_name, model_code])
  237. return OUTPUT()
  238. def model_move(source_name, target_name):
  239. INPUT("model_move", [source_name, target_name])
  240. return OUTPUT()
  241. def model_delete(model_name):
  242. INPUT("model_delete", [model_name])
  243. return OUTPUT()
  244. def model_list_full(location):
  245. INPUT("model_list_full", [location])
  246. return OUTPUT()
  247. def verify(model_name, metamodel_name, conformance_function=""):
  248. INPUT("verify", [model_name, metamodel_name, conformance_function])
  249. return OUTPUT()
  250. def model_overwrite(model_name, new_model):
  251. INPUT("model_overwrite", [model_name, new_model])
  252. return OUTPUT()
  253. def disconnect():
  254. INPUT("disconnect", [])
  255. return OUTPUT()
  256. def user_logout():
  257. INPUT("user_logout", [])
  258. return OUTPUT()
  259. def model_render(model_name, mapper_name, rendered_name):
  260. INPUT("model_render", [model_name, mapper_name, rendered_name])
  261. return OUTPUT()
  262. def transformation_between(sources, targets):
  263. INPUT("transformation_between", [sources, targets])
  264. return OUTPUT()
  265. def transformation_add_MT(source_metamodels, target_metamodels, operation_name, code, callback=None):
  266. if len(source_metamodels) + len(target_metamodels) == 0:
  267. if callback is not None:
  268. raise CallbackOnEmptySignature()
  269. INPUT("transformation_add_MT", [source_metamodels, target_metamodels, operation_name, code, True])
  270. model = OUTPUT()
  271. if model is None:
  272. return OUTPUT()
  273. else:
  274. if callback is not None:
  275. __invoke(callback, model)
  276. controller.addInput(Event("data_input", "action_in", [None]))
  277. return OUTPUT()
  278. def transformation_add_AL(source_metamodels, target_metamodels, operation_name, code, callback=None):
  279. if len(source_metamodels) + len(target_metamodels) == 0:
  280. if callback is not None:
  281. raise CallbackOnEmptySignature()
  282. INPUT("transformation_add_AL", [source_metamodels, target_metamodels, operation_name, code, True])
  283. model = OUTPUT()
  284. if model is None:
  285. return OUTPUT()
  286. else:
  287. if callback is not None:
  288. __invoke(callback, model)
  289. controller.addInput(Event("data_input", "action_in", [None]))
  290. return OUTPUT()
  291. def transformation_add_MANUAL(source_metamodels, target_metamodels, operation_name, callback=None):
  292. if len(source_metamodels) + len(target_metamodels) == 0:
  293. if callback is not None:
  294. raise CallbackOnEmptySignature()
  295. INPUT("transformation_add_MANUAL", [source_metamodels, target_metamodels, operation_name, True])
  296. model = OUTPUT()
  297. if model is None:
  298. return None
  299. else:
  300. if callback is not None:
  301. __invoke(callback, model)
  302. controller.addInput(Event("data_input", "action_in", [None]))
  303. return OUTPUT()
  304. def __transformation_execute(operation_name, input_models_dict, output_models_dict, statechart, tracability_model, fetch_output):
  305. if statechart is not None:
  306. port_sc = statechart[0].addOutputListener(statechart[2])
  307. INPUT("transformation_execute", [operation_name, input_models_dict, output_models_dict, tracability_model, fetch_output])
  308. taskname = OUTPUT()
  309. if statechart is not None:
  310. threading.Thread(target=_process_SC, args=[statechart, port_sc, taskname]).start()
  311. return OUTPUT()
  312. def transformation_execute_MT(operation_name, input_models_dict, output_models_dict, statechart=None, traceability_model="", fetch_output=True):
  313. return __transformation_execute(operation_name, input_models_dict, output_models_dict, statechart, traceability_model, fetch_output)
  314. def transformation_execute_AL(operation_name, input_models_dict, output_models_dict, statechart=None, traceability_model="", fetch_output=True):
  315. return __transformation_execute(operation_name, input_models_dict, output_models_dict, statechart, traceability_model, fetch_output)
  316. def transformation_execute_MANUAL(operation_name, input_models_dict, output_models_dict, callback=None, traceability_model=""):
  317. INPUT("transformation_execute", [operation_name, input_models_dict, output_models_dict, traceability_model])
  318. taskname = OUTPUT()
  319. _process_OP(callback, taskname)
  320. return OUTPUT()
  321. def transformation_signature(operation_name):
  322. INPUT("transformation_signature", [operation_name])
  323. return OUTPUT()
  324. def process_signature(process_name):
  325. INPUT("process_signature", [process_name])
  326. return OUTPUT()
  327. def permission_modify(model_name, permissions):
  328. INPUT("permission_modify", [model_name, permissions])
  329. return OUTPUT()
  330. def permission_owner(model_name, permission):
  331. INPUT("permission_owner", [model_name, permission])
  332. return OUTPUT()
  333. def permission_group(model_name, group):
  334. INPUT("permission_group", [model_name, group])
  335. return OUTPUT()
  336. def group_create(group_name):
  337. INPUT("group_create", [group_name])
  338. return OUTPUT()
  339. def group_delete(group_name):
  340. INPUT("group_delete", [group_name])
  341. return OUTPUT()
  342. def group_owner_add(group_name, user_name):
  343. INPUT("group_owner_add", [group_name, user_name])
  344. return OUTPUT()
  345. def group_owner_delete(group_name, user_name):
  346. INPUT("group_owner_delete", [group_name, user_name])
  347. return OUTPUT()
  348. def group_join(group_name, user_name):
  349. INPUT("group_join", [group_name, user_name])
  350. return OUTPUT()
  351. def group_kick(group_name, user_name):
  352. INPUT("group_kick", [group_name, user_name])
  353. return OUTPUT()
  354. def group_list():
  355. INPUT("group_list", [])
  356. return OUTPUT()
  357. def admin_promote(user_name):
  358. INPUT("admin_promote", [user_name])
  359. return OUTPUT()
  360. def admin_demote(user_name):
  361. INPUT("admin_demote", [user_name])
  362. return OUTPUT()
  363. def conformance_delete(model_name, metamodel_name, type_mapping_name):
  364. INPUT("conformance_delete", [model_name, metamodel_name, type_mapping_name])
  365. return OUTPUT()
  366. def conformance_add(model_name, metamodel_name):
  367. INPUT("conformance_add", [model_name, metamodel_name])
  368. return OUTPUT()
  369. def folder_create(folder_name):
  370. INPUT("folder_create", [folder_name])
  371. return OUTPUT()
  372. def model_types(model_name):
  373. INPUT("model_types", [model_name])
  374. return OUTPUT()
  375. def alter_context(model_name, metamodel_name):
  376. INPUT("alter_context", [model_name, metamodel_name])
  377. def reset_context():
  378. INPUT("reset_context", [])
  379. def element_list(model_name):
  380. INPUT("element_list", [model_name])
  381. return OUTPUT()
  382. def element_list_nice(model_name):
  383. INPUT("element_list_nice", [model_name])
  384. return OUTPUT()
  385. def types(model_name):
  386. INPUT("types", [model_name])
  387. return OUTPUT()
  388. def read_info(model_name, ID):
  389. INPUT("read_info", [model_name, ID])
  390. return OUTPUT()
  391. def read_attrs(model_name, ID):
  392. INPUT("read_attrs", [model_name, ID])
  393. return OUTPUT()
  394. def instantiate(model_name, typename, edge=None, ID=""):
  395. INPUT("instantiate", [model_name, typename, edge, ID])
  396. return OUTPUT()
  397. def delete_element(model_name, ID):
  398. INPUT("delete_element", [model_name, ID])
  399. return OUTPUT()
  400. def attr_assign(model_name, ID, attr, value):
  401. INPUT("attr_assign", [model_name, ID, attr, value])
  402. return OUTPUT()
  403. def attr_assign_code(model_name, ID, attr, code):
  404. INPUT("attr_assign_code", [model_name, ID, attr, code])
  405. return OUTPUT()
  406. def attr_delete(model_name, ID, attr):
  407. INPUT("attr_delete", [model_name, ID, attr])
  408. return OUTPUT()
  409. def AL_text(code_location):
  410. INPUT("AL_text", [code_location])
  411. return OUTPUT()
  412. def read_outgoing(model_name, ID, typename):
  413. INPUT("read_outgoing", [model_name, ID, typename])
  414. return OUTPUT()
  415. def read_incoming(model_name, ID, typename):
  416. INPUT("read_incoming", [model_name, ID, typename])
  417. return OUTPUT()
  418. def read_association_source(model_name, ID):
  419. INPUT("read_association_source", [model_name, ID])
  420. return OUTPUT()
  421. def read_association_destination(model_name, ID):
  422. INPUT("read_association_destination", [model_name, ID])
  423. return OUTPUT()
  424. def connections_between(model_name, source, target):
  425. INPUT("connections_between", [model_name, source, target])
  426. return OUTPUT()
  427. def define_attribute(model_name, node, attr_name, attr_type):
  428. INPUT("define_attribute", [model_name, node, attr_name, attr_type])
  429. return OUTPUT()
  430. def undefine_attribute(model_name, node, attr_name):
  431. INPUT("undefine_attribute", [model_name, node, attr_name])
  432. return OUTPUT()
  433. def attribute_optional(model_name, node, attr_name, optionality):
  434. INPUT("attr_optional", [model_name, node, attr_name, optionality])
  435. return OUTPUT()
  436. def attribute_name(model_name, node, attr_name, new_name):
  437. INPUT("attr_name", [model_name, node, attr_name, new_name])
  438. return OUTPUT()
  439. def attribute_type(model_name, node, attr_name, new_type):
  440. INPUT("attr_type", [model_name, node, attr_name, new_type])
  441. return OUTPUT()
  442. def read_defined_attrs(model_name, node):
  443. INPUT("read_defined_attrs", [model_name, node])
  444. return OUTPUT()
  445. def all_instances(model_name, type_name):
  446. INPUT("all_instances", [model_name, type_name])
  447. return OUTPUT()
  448. def read_permissions(model_name):
  449. INPUT("read_permissions", [model_name])
  450. return OUTPUT()
  451. def process_execute(process_name, model_mapping, callbacks={}):
  452. # for all callbacks to SCs, start up the output port already
  453. sc_ports = {}
  454. for k, v in callbacks.items():
  455. if isinstance(v, (tuple, list)):
  456. # Is a statechart, so register already
  457. sc_ports[k] = v[0].addOutputListener(v[2])
  458. INPUT("process_execute", [process_name, model_mapping])
  459. while 1:
  460. result = OUTPUT()
  461. if result == "Success":
  462. # Finished
  463. return None
  464. else:
  465. taskname, operation = result
  466. if (operation in callbacks):
  467. data = callbacks[operation]
  468. if isinstance(data, (tuple, list)):
  469. # Statechart, so consider like that
  470. threading.Thread(target=_process_SC, args=[data, sc_ports[operation], taskname]).start()
  471. else:
  472. # Assume function
  473. threading.Thread(target=_process_OP, args=[data, taskname]).start()
  474. else:
  475. # Assume empty function
  476. threading.Thread(target=_process_OP, args=[None, taskname]).start()
  477. def get_taskname():
  478. """Fetch the taskname of the current connection."""
  479. return controller.taskname
  480. def exit_save(model_name):
  481. INPUT("exit_save", [model_name])
  482. return OUTPUT()
  483. """ Some hardcoded functions... Way easier to express them with code than with statecharts!"""
  484. import json
  485. import urllib
  486. try:
  487. import urllib2
  488. except ImportError:
  489. import urllib as urllib2
  490. def service_register(name, function):
  491. """Register a function as a service with a specific name."""
  492. INPUT("service_register", [name, function])
  493. port = OUTPUT()
  494. return port
  495. def service_stop():
  496. """Stop the currently executing process."""
  497. INPUT("service_stop", [])
  498. return OUTPUT()
  499. def service_get(port):
  500. """Get the values on the specified port."""
  501. data = urlencode({"op": "get_output", "taskname": port}).encode()
  502. val = json.loads(urlopen(Request("http://%s" % controller.address, data), timeout=99999).read().decode('utf-8'))
  503. return val
  504. def service_set(port, value):
  505. """Set a value on a specified port."""
  506. if isinstance(value, type([])):
  507. value = json.dumps(value)
  508. data = urlencode({"op": "set_input", "data": value, "taskname": port}).encode()
  509. urlopen(Request("http://%s" % controller.address, data), timeout=99999).read()
  510. else:
  511. value = json.dumps(value)
  512. data = urlencode({"op": "set_input", "value": value, "taskname": port}).encode()
  513. urlopen(Request("http://%s" % controller.address, data), timeout=99999).read()
  514. def service_poll(port):
  515. """Checks whether or not the Modelverse side has any input ready to be processed."""
  516. raise NotImplementedError()
  517. def show(model_name):
  518. data_list = INPUT("element_list_nice", [model_name])
  519. result = OUTPUT()
  520. import uuid
  521. INPUT("model_types", [model_name])
  522. is_scd = len([i for i in OUTPUT() if i[0] == "formalisms/SimpleClassDiagrams"]) > 0
  523. edges = []
  524. texts = []
  525. rectangles = []
  526. defined_attributes = {}
  527. names = {}
  528. todo_edges = []
  529. locations = {}
  530. text_skip = 15
  531. past_x = 3
  532. past_y = 3
  533. max_x = 0
  534. max_y = 0
  535. spacing = 50
  536. if is_scd:
  537. for elem in result:
  538. if elem["__type"] == "AttributeLink":
  539. defined_attributes.setdefault(elem["__source"], []).append((elem["name"], elem["__target"], True if elem["optional"] == True else False))
  540. if "name" in elem:
  541. names[elem["__id"]] = elem["name"]
  542. for elem in list(result):
  543. is_edge = False
  544. attrs = {}
  545. for key, value in elem.items():
  546. if key == "__id":
  547. element_id = value
  548. elif key == "__source":
  549. element_source = value
  550. is_edge = True
  551. elif key == "__target":
  552. element_target = value
  553. is_edge = True
  554. elif key == "__type":
  555. element_type = value
  556. else:
  557. attrs[key] = value
  558. max_text = 0
  559. if is_edge:
  560. edge_edge = len([x for x in result if (x["__id"] == element_source or x["__id"] == element_target) and "__source" in x]) > 0
  561. else:
  562. edge_edge = False
  563. if is_edge:
  564. if not (elem["__type"] == "AttributeLink" and is_scd):
  565. todo_edges.append({"source": element_source, "target": element_target, "source_x": 0 if edge_edge else 100, "source_y": 0 if edge_edge else 30, "target_x": 100, "target_y": 30})
  566. else:
  567. # Add the group
  568. x = past_x
  569. y = past_y
  570. locations[element_id] = (x, y)
  571. # Add the text elements
  572. # First the header
  573. texts.append({"text": "%s : %s" % (element_id, element_type), "x": x + 10, "y": y + text_skip})
  574. max_text = max(len(texts[-1]["text"]), max_text)
  575. # Then the attributes
  576. text_counter = 1
  577. for name, attr_type, optional in defined_attributes.get(elem["__id"], []):
  578. text = "%s : %s" % (name, names.get(attr_type, "(%s)" % attr_type))
  579. if optional:
  580. text = text.replace(" : ", " ?: ")
  581. texts.append({"text": text, "x": x + 10, "y": y + text_skip * 1.25 + text_counter * text_skip})
  582. max_text = max(len(text), max_text)
  583. text_counter += 1
  584. if text_counter > 1:
  585. text_counter += 1
  586. y_loc_edge = y + text_skip + text_counter * text_skip
  587. for key, value in attrs.items():
  588. if isinstance(value, dict):
  589. if value["AL"] == "":
  590. text = "(%s)" % key
  591. else:
  592. text = "%s = ^%s" % (key, value['AL'])
  593. else:
  594. text = ("%s = %s" % (key, json.dumps(value))) if value is not None else ("(%s)" % key)
  595. texts.append({"text": text, "x": x + 10, "y": y + text_skip * 2.5 + text_counter * text_skip})
  596. max_text = max(len(text), max_text)
  597. text_counter += 1
  598. # Add the rectangle
  599. rectangles.append({"x": x, "y": y, "width": max_text * 9 + 10, "height": text_skip * 3 + text_counter * text_skip})
  600. # Add the line
  601. edges.append({"sx": x, "sy": y_loc_edge, "tx": x + max_text * 9 + 10, "ty": y_loc_edge})
  602. edges.append({"sx": x, "sy": y + 20, "tx": x + max_text * 9 + 10, "ty": y + 20})
  603. past_x = x + max_text * 9 + spacing
  604. if past_x > 1000:
  605. max_x = max(max_x, past_x)
  606. past_x = 3
  607. past_y = max_y + spacing
  608. max_x = max(max_x, past_x)
  609. max_y = max(max_y, past_y + text_skip * 3 + text_counter * text_skip)
  610. data_content = '<svg width="%s" height="%s">' % (max_x + 3, max_y + 3)
  611. data_content += '<marker orient="auto" refY="0.0" refX="0.0" id="triangle" style="overflow:visible;"><path id="path941" d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1" transform="scale(0.8) rotate(180) translate(12.5,0)"/></marker>'
  612. for edge in todo_edges:
  613. sx = locations[edge["source"]][0] + edge["source_x"]
  614. sy = locations[edge["source"]][1] + edge["source_y"]
  615. tx = locations[edge["target"]][0] + edge["target_x"]
  616. ty = locations[edge["target"]][1] + edge["target_y"]
  617. midx = (tx - sx) / 2 + sx
  618. midy = (ty - sy) / 2 + sy
  619. data_content += '<line x1="%s" y1="%s" x2="%s" y2="%s" stroke="black" style="marker-end:url(#triangle)"/>' % (sx, sy, midx, midy)
  620. data_content += '<line x1="%s" y1="%s" x2="%s" y2="%s" stroke="black"/>' % (midx, midy, tx, ty)
  621. for rec in rectangles:
  622. data_content += '<rect x="%s" y="%s" width="%s" height="%s" fill="white" stroke="black"/>' % (rec["x"], rec["y"], rec["width"], rec["height"])
  623. for edge in edges:
  624. data_content += '<line x1="%s" y1="%s" x2="%s" y2="%s" stroke="black"/>' % (edge["sx"], edge["sy"], edge["tx"], edge["ty"])
  625. for text in texts:
  626. data_content += '<text x="%s" y="%s" fill="black">%s</text>' % (text["x"], text["y"], text["text"])
  627. data_content += '</svg>'
  628. return data_content