From ef87fa39c37bc23ff6d2c506d5c8d26a4ea0c98d Mon Sep 17 00:00:00 2001 From: Rokas Puzonas Date: Sun, 26 Feb 2023 11:52:41 +0200 Subject: [PATCH] parse magic draw project files --- .gitignore | 2 + Cargo.lock | 986 +++++++++++++++++++++++ Cargo.toml | 17 + LICENSE | 20 + index.html | 8 + index.scss | 35 + src/magicdraw_parser/ddl_parser.rs | 141 ++++ src/magicdraw_parser/mod.rs | 263 ++++++ src/magicdraw_parser/sql_types_parser.rs | 169 ++++ src/magicdraw_parser/uml_model_parser.rs | 294 +++++++ src/magicdraw_parser/utils.rs | 140 ++++ src/main.rs | 29 + 12 files changed, 2104 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 LICENSE create mode 100644 index.html create mode 100644 index.scss create mode 100644 src/magicdraw_parser/ddl_parser.rs create mode 100644 src/magicdraw_parser/mod.rs create mode 100644 src/magicdraw_parser/sql_types_parser.rs create mode 100644 src/magicdraw_parser/uml_model_parser.rs create mode 100644 src/magicdraw_parser/utils.rs create mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..77fdd3e --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +dist diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..d6c6ace --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,986 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aho-corasick" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800" + +[[package]] +name = "anymap2" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d301b3b94cb4b2f23d7917810addbbaff90738e0ca2be692bd027e70d7e0330c" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "boolinator" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfa8873f51c92e232f9bac4065cddef41b714152812bfc5f7672ba16d6ef8cd9" + +[[package]] +name = "bumpalo" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "flate2" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "form_urlencoded" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13e2792b0ff0340399d58445b88fd9770e3489eff258a4cbc1523418f12abf84" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608" + +[[package]] +name = "futures-io" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531" + +[[package]] +name = "futures-macro" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364" + +[[package]] +name = "futures-task" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366" + +[[package]] +name = "futures-util" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "gloo" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a4bef6b277b3ab073253d4bca60761240cf8d6998f4bd142211957b69a61b20" +dependencies = [ + "gloo-console", + "gloo-dialogs", + "gloo-events", + "gloo-file", + "gloo-history", + "gloo-net", + "gloo-render", + "gloo-storage", + "gloo-timers", + "gloo-utils", + "gloo-worker", +] + +[[package]] +name = "gloo-console" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b7ce3c05debe147233596904981848862b068862e9ec3e34be446077190d3f" +dependencies = [ + "gloo-utils", + "js-sys", + "serde", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-dialogs" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67062364ac72d27f08445a46cab428188e2e224ec9e37efdba48ae8c289002e6" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-events" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68b107f8abed8105e4182de63845afcc7b69c098b7852a813ea7462a320992fc" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-file" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d5564e570a38b43d78bdc063374a0c3098c4f0d64005b12f9bbe87e869b6d7" +dependencies = [ + "gloo-events", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-history" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd451019e0b7a2b8a7a7b23e74916601abf1135c54664e57ff71dcc26dfcdeb7" +dependencies = [ + "gloo-events", + "gloo-utils", + "serde", + "serde-wasm-bindgen", + "serde_urlencoded", + "thiserror", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-net" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9902a044653b26b99f7e3693a42f171312d9be8b26b5697bd1e43ad1f8a35e10" +dependencies = [ + "futures-channel", + "futures-core", + "futures-sink", + "gloo-utils", + "js-sys", + "pin-project", + "serde", + "serde_json", + "thiserror", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "gloo-render" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd9306aef67cfd4449823aadcd14e3958e0800aa2183955a309112a84ec7764" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-storage" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d6ab60bf5dbfd6f0ed1f7843da31b41010515c745735c970e821945ca91e480" +dependencies = [ + "gloo-utils", + "js-sys", + "serde", + "serde_json", + "thiserror", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-timers" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "gloo-utils" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8e8fc851e9c7b9852508bc6e3f690f452f474417e8545ec9857b7f7377036b5" +dependencies = [ + "js-sys", + "serde", + "serde_json", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-worker" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13471584da78061a28306d1359dd0178d8d6fc1c7c80e5e35d27260346e0516a" +dependencies = [ + "anymap2", + "bincode", + "gloo-console", + "gloo-utils", + "js-sys", + "serde", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + +[[package]] +name = "implicit-clone" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40fc102e70475c320b185cd18c1e48bba2d7210b63970a4d581ef903e4368ef7" +dependencies = [ + "indexmap", +] + +[[package]] +name = "indexmap" +version = "1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "itoa" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" + +[[package]] +name = "js-sys" +version = "0.3.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy-regex" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a505da2f89befd87ab425d252795f0f285e100b43e7d22d29528df3d9a576793" +dependencies = [ + "lazy-regex-proc_macros", + "once_cell", + "regex", +] + +[[package]] +name = "lazy-regex-proc_macros" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8edfc11b8f56ce85e207e62ea21557cfa09bb24a8f6b04ae181b086ff8611c22" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "syn", +] + +[[package]] +name = "libc" +version = "0.2.139" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "magic-sql-gen" +version = "0.1.0" +dependencies = [ + "anyhow", + "lazy-regex", + "thiserror", + "xml-rs", + "yew", + "zip", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "miniz_oxide" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" +dependencies = [ + "adler", +] + +[[package]] +name = "num_cpus" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" + +[[package]] +name = "percent-encoding" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" + +[[package]] +name = "pin-project" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pinned" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a829027bd95e54cfe13e3e258a1ae7b645960553fb82b75ff852c29688ee595b" +dependencies = [ + "futures", + "rustversion", + "thiserror", +] + +[[package]] +name = "prettyplease" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e97e3215779627f01ee256d2fad52f3d95e8e1c11e9fc6fd08f7cd455d5d5c78" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prokio" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03b55e106e5791fa5a13abd13c85d6127312e8e09098059ca2bc9b03ca4cf488" +dependencies = [ + "futures", + "gloo", + "num_cpus", + "once_cell", + "pin-project", + "pinned", + "tokio", + "tokio-stream", + "wasm-bindgen-futures", +] + +[[package]] +name = "quote" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" + +[[package]] +name = "rustversion" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5583e89e108996506031660fe09baa5011b9dd0341b89029313006d1fb508d70" + +[[package]] +name = "ryu" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" + +[[package]] +name = "serde" +version = "1.0.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-wasm-bindgen" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b4c031cd0d9014307d82b8abf653c0290fbdaeb4c02d00c63cf52f728628bf" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + +[[package]] +name = "serde_derive" +version = "1.0.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cad406b69c91885b5107daf2c29572f6c8cdb3c66826821e286c533490c0bc76" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "slab" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +dependencies = [ + "autocfg", +] + +[[package]] +name = "syn" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8e00990ebabbe4c14c08aca901caed183ecd5c09562a12c824bb53d3c3fd3af" +dependencies = [ + "autocfg", + "pin-project-lite", + "windows-sys", +] + +[[package]] +name = "tokio-stream" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fb52b74f05dbf495a8fba459fdc331812b96aa086d9eb78101fa0d4569c3313" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "unicode-ident" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasm-bindgen" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" + +[[package]] +name = "web-sys" +version = "0.3.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" + +[[package]] +name = "xml-rs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" + +[[package]] +name = "yew" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dbecfe44343b70cc2932c3eb445425969ae21754a8ab3a0966981c1cf7af1cc" +dependencies = [ + "console_error_panic_hook", + "futures", + "gloo", + "implicit-clone", + "indexmap", + "js-sys", + "prokio", + "rustversion", + "serde", + "slab", + "thiserror", + "tokio", + "tracing", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "yew-macro", +] + +[[package]] +name = "yew-macro" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b64c253c1d401f1ea868ca9988db63958cfa15a69f739101f338d6f05eea8301" +dependencies = [ + "boolinator", + "once_cell", + "prettyplease", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zip" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0445d0fbc924bb93539b4316c11afb121ea39296f99a3c4c9edad09e3658cdef" +dependencies = [ + "byteorder", + "crc32fast", + "crossbeam-utils", + "flate2", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..42d68e1 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "magic-sql-gen" +version = "0.1.0" +edition = "2021" +license = "MIT" + +[dependencies] +xml-rs = "0.8.4" +yew = { version="0.20", features=["csr"] } +anyhow = "1.0.69" +thiserror = "1.0.38" +lazy-regex = "2.4.1" + +[dependencies.zip] +version = "0.6.4" +default-features = false +features=["deflate"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..721841b --- /dev/null +++ b/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) +Copyright © 2023 Rokas Puzonas + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the “Software”), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/index.html b/index.html new file mode 100644 index 0000000..4d13cf1 --- /dev/null +++ b/index.html @@ -0,0 +1,8 @@ + + + + + Trunk Template + + + diff --git a/index.scss b/index.scss new file mode 100644 index 0000000..710545f --- /dev/null +++ b/index.scss @@ -0,0 +1,35 @@ +html, +body { + height: 100%; + margin: 0; +} + +body { + align-items: center; + display: flex; + justify-content: center; + + background: linear-gradient(to bottom right, #444444, #009a5b); + font-size: 1.5rem; +} + +main { + color: #fff6d5; + font-family: sans-serif; + text-align: center; +} + +.logo { + height: 20em; +} + +.heart:after { + content: "❤️"; + + font-size: 1.75em; +} + +h1 + .subtitle { + display: block; + margin-top: -1em; +} diff --git a/src/magicdraw_parser/ddl_parser.rs b/src/magicdraw_parser/ddl_parser.rs new file mode 100644 index 0000000..36a2bcc --- /dev/null +++ b/src/magicdraw_parser/ddl_parser.rs @@ -0,0 +1,141 @@ +use std::io::{Read, Seek}; + +use xml::attribute::OwnedAttribute; +use xml::name::OwnedName; +use xml::{EventReader, reader::XmlEvent}; +use zip::ZipArchive; +use anyhow::{Result, Context, Ok}; + +use crate::magicdraw_parser::utils::get_attribute; + +use super::utils::{check_name, check_attribute, MyEventReader, parse_element}; + +#[derive(Debug)] +pub struct DDLClass { + pub class_id: String, + pub property_ids: Vec +} + +#[derive(Debug)] +pub struct DDLScript { + pub script_id: String, + pub classess: Vec +} + +#[derive(Debug)] +pub struct DDLProject { + pub model_id: String, + pub scripts: Vec +} + +fn get_id_from_href(attrs: &[OwnedAttribute]) -> Option { + let href = get_attribute(attrs, None, "href").ok()?; + let parts = href.split_once("#")?; + Some(parts.1.to_string()) +} + +fn parse_class(parser: &mut MyEventReader, attrs: &[OwnedAttribute]) -> Result { + let mut property_ids = vec![]; + let mut class_id = None; + + fn is_model_element(name: &OwnedName, attributes: &[OwnedAttribute]) -> bool { + check_name(name, None, "modelElement") && check_attribute(&attributes, Some("xsi"), "type", "uml:Class") + } + + fn is_property_element(name: &OwnedName, attributes: &[OwnedAttribute]) -> bool { + check_name(name, None, "modelElement") && check_attribute(&attributes, Some("xsi"), "type", "uml:Property") + } + + parse_element(parser, &mut |p, name, attrs| { + if is_model_element(&name, &attrs) && class_id.is_none() { + class_id = get_id_from_href(&attrs); + } else if is_property_element(&name, &attrs) { + property_ids.push(get_id_from_href(&attrs).context("Property id not found")?); + } + Ok(()) + })?; + + Ok(DDLClass { + class_id: class_id.context("Missing class id")?, + property_ids + }) +} + +fn parse_script(parser: &mut MyEventReader, attrs: &[OwnedAttribute]) -> Result { + let mut classess = vec![]; + let mut script_id = None; + + fn is_model_element(name: &OwnedName, attributes: &[OwnedAttribute]) -> bool { + check_name(name, None, "modelElement") && check_attribute(&attributes, Some("xsi"), "type", "uml:Component") + } + + fn is_class_element(name: &OwnedName, attributes: &[OwnedAttribute]) -> bool { + check_name(name, None, "objects") && check_attribute(&attributes, Some("xsi"), "type", "md.ce.rt.objects:RTClassObject") + } + + parse_element(parser, &mut |p, name, attrs| { + if is_model_element(&name, &attrs) && script_id.is_none() { + script_id = get_id_from_href(&attrs); + } else if is_class_element(&name, &attrs) { + classess.push(parse_class(p, &attrs)?); + } + Ok(()) + })?; + + Ok(DDLScript { + script_id: script_id.context("Missing script id")?, + classess + }) +} + +fn parse_project(parser: &mut MyEventReader, attrs: &[OwnedAttribute]) -> Result { + let mut scripts = vec![]; + let mut model_id = None; + + fn is_model_element(name: &OwnedName, attributes: &[OwnedAttribute]) -> bool { + check_name(name, None, "modelElement") && check_attribute(&attributes, Some("xsi"), "type", "uml:Model") + } + + fn is_component_element(name: &OwnedName, attributes: &[OwnedAttribute]) -> bool { + check_name(name, None, "objects") && check_attribute(&attributes, Some("xsi"), "type", "md.ce.rt.objects:RTComponent") + } + + parse_element(parser, &mut |p, name, attrs| { + if is_model_element(&name, &attrs) && model_id.is_none() { + model_id = get_id_from_href(&attrs); + } else if is_component_element(&name, &attrs) { + scripts.push(parse_script(p, &attrs)?); + } + Ok(()) + })?; + + Ok(DDLProject { + model_id: model_id.context("Missing model id")?, + scripts + }) +} + +pub fn parse_ddl_scripts(project: &mut ZipArchive) -> Result> { + let mut ddl_scripts = vec![]; + + let file = project.by_name("personal-com.nomagic.magicdraw.ce.dmn.personaldmncodeengineering")?; + let mut parser: MyEventReader<_> = EventReader::new(file).into(); + + fn is_project_element(name: &OwnedName, attributes: &[OwnedAttribute]) -> bool { + check_name(name, None, "contents") && check_attribute(&attributes, Some("xsi"), "type", "md.ce.ddl.rt.objects:DDLProjectObject") + } + + loop { + match parser.next()? { + XmlEvent::StartElement { name, attributes, .. } => { + if is_project_element(&name, &attributes) { + ddl_scripts.push(parse_project(&mut parser, &attributes)?); + } + }, + XmlEvent::EndDocument => { break; }, + _ => {} + } + } + + Ok(ddl_scripts) +} diff --git a/src/magicdraw_parser/mod.rs b/src/magicdraw_parser/mod.rs new file mode 100644 index 0000000..08c7548 --- /dev/null +++ b/src/magicdraw_parser/mod.rs @@ -0,0 +1,263 @@ +#![allow(dead_code)] +#![allow(unused_variables)] + +mod utils; +mod uml_model_parser; +mod ddl_parser; +mod sql_types_parser; + +use std::{io::{Read, Seek}, collections::HashSet}; +use anyhow::{Result, Context}; +use lazy_regex::regex_captures; +use zip::ZipArchive; + +use crate::unwrap_opt_continue; + +use self::{uml_model_parser::{parse_uml_model, UMLModel, UMLClass, UMLModifier, UMLNullableModifier, UMLPrimaryKeyModifier, UMLTypeModifier, UMLForeignKeyModifier}, ddl_parser::parse_ddl_scripts, sql_types_parser::{parse_sql_types, SQLTypeName}}; + +#[derive(Debug)] +pub enum SQLType { + Int, + Decimal, + Date, + Float, + Bool, + Char(u8), + Varchar(u16), +} + +#[derive(Debug)] +pub enum SQLCheckConstraint { + OneOf(Vec), + Freeform(String) +} + +#[derive(Debug)] +pub struct SQLColumn { + name: String, + sql_type: SQLType, + primary_key: bool, + nullable: bool, + foreign_key: Option<(String, String)>, + check_constraint: Option +} + +#[derive(Debug)] +pub struct SQLTable { + name: String, + columns: Vec, +} + +#[derive(Debug)] +pub struct SQLTableCollection { + tables: Vec +} + +fn find_class_by_id<'a>(models: &'a [UMLModel], id: &str) -> Option<&'a UMLClass> { + for model in models { + for package in &model.packages { + if let Some(class) = package.classess.iter().find(|t| t.id.eq(id)) { + return Some(class); + } + } + } + None +} + +fn is_nullabe(modifiers: &[UMLModifier], property: &str) -> bool { + for modifier in modifiers { + if let UMLModifier::Nullable(UMLNullableModifier { property_id, nullable }) = modifier { + if property_id.eq(property) { + return *nullable; + } + } + } + false +} + +fn is_primary_key(modifiers: &[UMLModifier], property: &str) -> bool { + for modifier in modifiers { + if let UMLModifier::PirmaryKey(UMLPrimaryKeyModifier { property_id }) = modifier { + if property_id.eq(property) { + return true + } + } + } + false +} + +fn get_type_modifier<'a>(modifiers: &'a [UMLModifier], property: &str) -> Option<&'a str> { + for modifier in modifiers { + if let UMLModifier::Type(UMLTypeModifier { property_id, modifier }) = modifier { + if property_id.eq(property) { + return Some(modifier) + } + } + } + None +} + +fn get_foreign_key_constraint<'a>(modifiers: &'a [UMLModifier], from_id: &str) -> Option<&'a str> { + for modifier in modifiers { + if let UMLModifier::ForeignKey(UMLForeignKeyModifier { from_property_id, to_property_id }) = modifier { + if from_property_id.eq(from_id) { + return Some(&to_property_id) + } + } + } + None +} + +fn get_foreign_key(modifiers: &[UMLModifier], classess: &[&UMLClass], property: &str) -> Result> { + let to_id = get_foreign_key_constraint(modifiers, property); + if to_id.is_none() { + return Ok(None) + } + let to_id = to_id.unwrap(); + + for class in classess { + for property in &class.properties { + if property.id.eq(to_id) { + let property_name = property.name.clone().context("Missing property name")?; + let class_name = class.name.clone().context("Missing class name")?; + return Ok(Some((class_name, property_name))); + } + } + } + + Ok(None) +} + +fn parse_check_constraint(str: &str) -> SQLCheckConstraint { + fn try_parse_one_of(str: &str) -> Option { + let (_, inner) = regex_captures!(r#"^in \((.+)\)$"#, str)?; + let mut variants = vec![]; + for part in inner.split(", ") { + let (_, variant) = regex_captures!(r#"^'(.+)'$"#, part)?; + variants.push(variant.to_string()); + } + + Some(SQLCheckConstraint::OneOf(variants)) + } + + try_parse_one_of(str) + .unwrap_or(SQLCheckConstraint::Freeform(str.to_string())) +} + +// TODO: Refactor this function, less nesting would be good +fn get_sql_check_constraint<'a>(models: &'a [UMLModel], property_name: &str) -> Option { + for model in models { + for package in &model.packages { + for class in &package.classess { + for constraint in &class.constraints { + let prop_name = unwrap_opt_continue!(&constraint.property_name); + let body = unwrap_opt_continue!(&constraint.body); + + if prop_name.eq(property_name) && constraint.body.is_some() { + return Some(parse_check_constraint(body)); + } + } + } + } + } + None +} + +fn get_sql_type(modifiers: &[UMLModifier], type_name: SQLTypeName, property: &str) -> Result { + Ok(match type_name { + SQLTypeName::Int => SQLType::Int, + SQLTypeName::Date => SQLType::Date, + SQLTypeName::Float => SQLType::Float, + SQLTypeName::Bool => SQLType::Bool, + SQLTypeName::Decimal => SQLType::Decimal, + SQLTypeName::Char => { + if let Some(type_modifier) = get_type_modifier(modifiers, property) { + let (_, size) = regex_captures!(r#"^\((\d+)\)$"#, type_modifier) + .context("Type modifier doesn't match format")?; + SQLType::Char(size.parse()?) + } else { + // TODO: Add better error message to say which table is missing type modifier + // For now just pick a defautl arbitrarily + SQLType::Char(31) + } + }, + SQLTypeName::Varchar => { + if let Some(type_modifier) = get_type_modifier(modifiers, property) { + let (_, size) = regex_captures!(r#"^\((\d+)\)$"#, type_modifier) + .context("Type modifier doesn't match format")?; + SQLType::Varchar(size.parse()?) + } else { + // TODO: Add better error message to say which table is missing type modifier + // For now just pick a defautl arbitrarily + SQLType::Varchar(255) + } + }, + }) +} + +fn get_used_types<'a>(models: &'a [UMLModel]) -> HashSet<&'a String> { + models.iter() + .flat_map(|model| &model.packages) + .flat_map(|package| &package.classess) + .flat_map(|class| &class.properties) + .filter_map(|property| property.type_href.as_ref()) + .collect::>() +} + +pub fn parse_project(project_file: R) -> Result> { + let mut zip = ZipArchive::new(project_file).unwrap(); + + let (models, modifiers) = parse_uml_model(&mut zip)?; + let ddl_scripts = parse_ddl_scripts(&mut zip)?; + let sql_type_names = parse_sql_types(&mut zip, &get_used_types(&models))?; + + let mut collections = vec![]; + for ddl_project in ddl_scripts { + for ddl_script in ddl_project.scripts { + let mut tables = vec![]; + + let model_properties = ddl_script.classess.iter() + .flat_map(|class| class.property_ids.iter().map(|prop| (&class.class_id, prop))) + .collect::>(); + + let mut model_classess = vec![]; + for ddl_class in &ddl_script.classess { + let model_class = find_class_by_id(&models, &ddl_class.class_id).context("UML class not found")?; + model_classess.push(model_class); + } + + for (ddl_class, model_class) in ddl_script.classess.iter().zip(&model_classess) { + let name = model_class.name.clone().context("UML class name not found")?; + + let mut columns = vec![]; + for property_id in &ddl_class.property_ids { + let property = model_class.properties.iter().find(|p| p.id.eq(property_id)).context("Property not found")?; + let prop_name = unwrap_opt_continue!(&property.name).clone(); + + let type_href = unwrap_opt_continue!(&property.type_href); + let type_name = sql_type_names.get(type_href).context("Proerty type name conversion not found")?; + + let check_constraint = get_sql_check_constraint(&models, &prop_name); + let foreign_key = get_foreign_key(&modifiers, &model_classess, property_id)?; + + columns.push(SQLColumn { + name: prop_name, + sql_type: get_sql_type(&modifiers, *type_name, property_id)?, + primary_key: is_primary_key(&modifiers, property_id), + nullable: is_nullabe(&modifiers, property_id), + foreign_key, + check_constraint, + }) + } + + tables.push(SQLTable { + name, + columns + }) + } + collections.push(SQLTableCollection { tables }) + } + } + + Ok(collections) +} diff --git a/src/magicdraw_parser/sql_types_parser.rs b/src/magicdraw_parser/sql_types_parser.rs new file mode 100644 index 0000000..62bd784 --- /dev/null +++ b/src/magicdraw_parser/sql_types_parser.rs @@ -0,0 +1,169 @@ +use std::{io::{Read, Seek}, collections::{HashMap, HashSet}}; + +use xml::{EventReader, reader::XmlEvent, attribute::OwnedAttribute, name::OwnedName}; +use zip::ZipArchive; +use anyhow::{Result, Context, bail}; + +use crate::unwrap_opt_continue; + +use super::utils::{MyEventReader, check_name, parse_element, get_attribute, check_attribute}; + +#[derive(Debug)] +struct UsedPackage { + share_point_id: String, + name: String, + needed_types: Vec +} + +#[derive(Debug, Clone, Copy)] +pub enum SQLTypeName { + Int, + Decimal, + Date, + Float, + Bool, + Char, + Varchar +} + +fn get_used_project_name(attrs: &[OwnedAttribute]) -> Option<&str> { + let project_uri = get_attribute(&attrs, None, "usedProjectURI").ok()?; + project_uri.split("/").last() +} + +fn parse_used_package(parser: &mut MyEventReader, attrs: &[OwnedAttribute], needed_types: &[&str]) -> Result { + let mut share_point_id = None; + let project_uri = get_attribute(&attrs, None, "usedProjectURI")?; + let name = project_uri.split("/").last().unwrap(); + + parse_element(parser, &mut |p, name, attrs| { + if share_point_id.is_none() && check_name(&name, None, "mountPoints") { + share_point_id = get_attribute(&attrs, None, "sharePointID").ok().map(str::to_string); + } + Ok(()) + })?; + + Ok(UsedPackage { + name: name.to_string(), + share_point_id: share_point_id.context("Share point id not found")?, + needed_types: needed_types.iter().map(|s| s.to_string()).collect() + }) +} + +fn list_used_packages(file: R, needed_types: &HashSet<&String>) -> Result> { + let mut packages = vec![]; + + let mut needed_types_per_package = HashMap::new(); + for needed_type in needed_types.iter() { + let (package_name, type_id) = unwrap_opt_continue!(needed_type.split_once("#")); + let ids = needed_types_per_package.entry(package_name).or_insert(vec![]); + ids.push(type_id); + } + + let mut parser: MyEventReader<_> = EventReader::new(file).into(); + loop { + match parser.next()? { + XmlEvent::StartElement { name, attributes, .. } => { + if check_name(&name, None, "projectUsages") { + let project_name = unwrap_opt_continue!(get_used_project_name(&attributes)); + if let Some(needed_types_for_package) = needed_types_per_package.get(&project_name) { + packages.push(parse_used_package(&mut parser, &attributes, needed_types_for_package)?); + } + } + }, + XmlEvent::EndDocument => { break; }, + _ => {} + } + } + + Ok(packages) +} + +fn is_umodel_snapshot_file(filename: &str) -> bool { + filename.ends_with("_resource_com$dnomagic$dmagicdraw$duml_umodel$dshared_umodel$dsnapshot") +} + +fn parse_type_name(str: &str) -> Result { + use SQLTypeName::*; + Ok(match str { + "decimal" => Decimal, + "char" => Char, + "varchar" => Varchar, + "float" => Float, + "Integer" | "integer" | "int" => Int, + "date" => Date, + "Boolean" => Bool, + _ => bail!("Unknown SQL type: '{}'", str) + }) +} + +fn parse_types_package(parser: &mut MyEventReader) -> Result> { + let mut types = vec![]; + + fn is_primitive_type_element(name: &OwnedName, attrs: &[OwnedAttribute]) -> bool { + check_name(&name, None, "packagedElement") && check_attribute(&attrs, Some("xsi"), "type", "uml:PrimitiveType") + } + + parse_element(parser, &mut |p, name, attrs| { + if is_primitive_type_element(&name, &attrs) { + let type_name = get_attribute(&attrs, None, "name")?; + if !type_name.eq("StructuredExpression") { + types.push(( + get_attribute(&attrs, Some("xmi"), "id")?.to_string(), + parse_type_name(type_name)? + )); + } + } + Ok(()) + })?; + + Ok(types) +} + +fn parse_primitive_types(reader: R, used_packages: &[UsedPackage]) -> Result> { + let mut types = vec![]; + + let mut parser: MyEventReader<_> = EventReader::new(reader).into(); + loop { + match parser.next()? { + XmlEvent::StartElement { name, attributes, .. } => { + if check_name(&name, Some("uml"), "Package") { + if let Some(id) = get_attribute(&attributes, None, "ID").ok() { + if let Some(package) = used_packages.iter().find(|p| p.share_point_id.eq(id)) { + let package_types = parse_types_package(&mut parser)? + .into_iter() + .filter(|t| package.needed_types.contains(&t.0)) + .map(|(id, type_name)| (format!("{}#{}", package.name, id), type_name)); + types.extend(package_types); + } + } + } + }, + XmlEvent::EndDocument => { break; }, + _ => {} + } + } + + Ok(types) +} + +pub fn parse_sql_types(project: &mut ZipArchive, needed_types: &HashSet<&String>) -> Result> { + let mut type_names = HashMap::new(); + + let meta_model_file = project.by_name("com.nomagic.ci.metamodel.project")?; + let used_packages = list_used_packages(meta_model_file, needed_types)?; + + let snapshot_files = project.file_names() + .filter(|f| is_umodel_snapshot_file(f)) + .map(|f| f.to_string()) + .collect::>(); + + for filename in &snapshot_files { + let f = project.by_name(filename).unwrap(); + for (id, type_name) in parse_primitive_types(f, &used_packages)? { + type_names.insert(id, type_name); + } + } + + Ok(type_names) +} diff --git a/src/magicdraw_parser/uml_model_parser.rs b/src/magicdraw_parser/uml_model_parser.rs new file mode 100644 index 0000000..42bbb96 --- /dev/null +++ b/src/magicdraw_parser/uml_model_parser.rs @@ -0,0 +1,294 @@ +use std::io::{Read, Seek}; + +use xml::{EventReader, reader::XmlEvent, attribute::OwnedAttribute, name::OwnedName}; +use zip::ZipArchive; +use anyhow::{Result, Context}; + +use crate::{unwrap_err_continue, unwrap_opt_continue}; + +use super::utils::{check_name, parse_element, get_attribute, check_attribute, MyEventReader, ParseProjectError, get_element_characters}; + +#[derive(Debug)] +pub struct UMLProperty { + pub id: String, + pub name: Option, + pub is_id: bool, + pub type_href: Option +} + +// TODO: Make this an enum? Because from what I have seen there were only 2 cases, +// * Constraint applied to property +// * Constraint applied to class +#[derive(Debug)] +pub struct UMLConstraint { + pub id: String, + pub class_id: Option, + pub property_id: Option, + pub property_name: Option, + pub body: Option, +} + +#[derive(Debug)] +pub struct UMLClass { + pub id: String, + pub name: Option, + pub properties: Vec, + pub constraints: Vec +} + +#[derive(Debug)] +pub struct UMLPackage { + pub id: String, + pub name: Option, + pub classess: Vec +} + +#[derive(Debug)] +pub struct UMLModel { + pub id: String, + pub name: String, + pub packages: Vec +} + +#[derive(Debug)] +pub struct UMLPrimaryKeyModifier { + pub property_id: String +} + +#[derive(Debug)] +pub struct UMLNullableModifier { + pub property_id: String, + pub nullable: bool +} + +#[derive(Debug)] +pub struct UMLForeignKeyModifier { + pub from_property_id: String, + pub to_property_id: String, +} + +#[derive(Debug)] +pub struct UMLUniqueModifier { + pub property_id: String +} + +#[derive(Debug)] +pub struct UMLTypeModifier { + pub property_id: String, + pub modifier: String +} + +#[derive(Debug)] +pub enum UMLModifier { + Unique(UMLUniqueModifier), + PirmaryKey(UMLPrimaryKeyModifier), + Nullable(UMLNullableModifier), + ForeignKey(UMLForeignKeyModifier), + Type(UMLTypeModifier) +} + +fn parse_property(parser: &mut MyEventReader, attrs: &[OwnedAttribute]) -> Result { + let id = get_attribute(attrs, Some("xmi"), "id")?.into(); + let name = get_attribute(attrs, None, "name").ok().map(str::to_string); + let is_id = get_attribute(attrs, None, "isID").unwrap_or("false").eq("true"); + let mut type_href = None; + + parse_element(parser, &mut |p, name, attrs| { + if check_name(&name, None, "type") && type_href.is_none() { + if let Ok(value) = get_attribute(&attrs, None, "href") { + type_href = Some(value.to_string()); + } + } + Ok(()) + })?; + + Ok(UMLProperty { + id, + name, + is_id, + type_href + }) +} + +fn parse_constraint(parser: &mut MyEventReader, attrs: &[OwnedAttribute]) -> Result> { + let id = get_attribute(attrs, Some("xmi"), "id")?.into(); + let mut constrainted_element_id = None; + let mut language = None; + let mut body = None; + + parse_element(parser, &mut |p, name, attrs| { + if check_name(&name, None, "constrainedElement") && constrainted_element_id.is_none() { + constrainted_element_id = get_attribute(&attrs, Some("xmi"), "idref").ok().map(str::to_string); + } else if check_name(&name, None, "body") && body.is_none() { + let contents = get_element_characters(p)?; + if contents.len() > 0 { + body = Some(contents); + } + } else if check_name(&name, None, "language") && language.is_none() { + language = Some(get_element_characters(p)?); + } + Ok(()) + })?; + + if language.eq(&Some("SQL".into())) && body.is_some() { + if let Some((prop_name, check_body)) = body.unwrap().split_once(" in ") { + return Ok(Some(UMLConstraint { + id, + class_id: Some(constrainted_element_id.context("Missing class id")?), + body: Some(format!("in {}", check_body)), + property_id: None, + property_name: Some(prop_name.into()) + })) + } + } + + return Ok(Some(UMLConstraint { + id, + property_id: Some(constrainted_element_id.context("Missing property id")?), + body: None, + class_id: None, + property_name: None + })) +} + +fn parse_class(parser: &mut MyEventReader, attrs: &[OwnedAttribute]) -> Result { + let mut properties = vec![]; + let mut consraints = vec![]; + let id = get_attribute(attrs, Some("xmi"), "id")?.into(); + let name = get_attribute(attrs, None, "name").ok().map(str::to_string); + + fn is_property_element(name: &OwnedName, attrs: &[OwnedAttribute]) -> bool { + check_name(name, None, "ownedAttribute") && check_attribute(&attrs, Some("xmi"), "type", "uml:Property") + } + + fn is_constraint_element(name: &OwnedName, attrs: &[OwnedAttribute]) -> bool { + check_name(name, None, "ownedRule") && check_attribute(&attrs, Some("xmi"), "type", "uml:Constraint") + } + + parse_element(parser, &mut |p, name, attrs| { + if is_property_element(&name, &attrs) { + properties.push(parse_property(p, &attrs)?); + } else if is_constraint_element(&name, &attrs) { + if let Some(constraint) = parse_constraint(p, &attrs)? { + consraints.push(constraint); + } + } + Ok(()) + })?; + + Ok(UMLClass { + id, + name, + properties, + constraints: consraints, + }) +} + +fn parse_package(parser: &mut MyEventReader, attrs: &[OwnedAttribute]) -> Result { + let mut classess = vec![]; + let id = get_attribute(attrs, Some("xmi"), "id")?.into(); + let name = get_attribute(attrs, None, "name").ok().map(str::to_string); + + fn is_class_element(name: &OwnedName, attrs: &[OwnedAttribute]) -> bool { + check_name(name, None, "packagedElement") && check_attribute(&attrs, Some("xmi"), "type", "uml:Class") + } + + parse_element(parser, &mut |p, name, attrs| { + if is_class_element(&name, &attrs) { + classess.push(parse_class(p, &attrs)?); + } + Ok(()) + })?; + + Ok(UMLPackage { + id, + name, + classess + }) +} + +fn parse_model(parser: &mut MyEventReader, attrs: &[OwnedAttribute]) -> Result { + let mut packages = vec![]; + let id = get_attribute(attrs, Some("xmi"), "id")?.into(); + let name = get_attribute(attrs, None, "name")?.into(); + + fn is_package_element(name: &OwnedName, attrs: &[OwnedAttribute]) -> bool { + check_name(name, None, "packagedElement") && check_attribute(&attrs, Some("xmi"), "type", "uml:Package") + } + + parse_element(parser, &mut |p, name, attrs| { + if is_package_element(&name, &attrs) { + packages.push(parse_package(p, &attrs)?); + } + Ok(()) + })?; + + Ok(UMLModel { + id, + name, + packages + }) +} + +fn find_constraint_by_id<'a>(models: &'a [UMLModel], id: &str) -> Option<&'a UMLConstraint> { + for model in models { + for package in &model.packages { + for class in &package.classess { + for constraint in &class.constraints { + if constraint.id.eq(id) { + return Some(constraint); + } + } + } + } + } + + None +} + +pub fn parse_uml_model(project: &mut ZipArchive) -> Result<(Vec, Vec)> { + let mut models = vec![]; + let mut modifiers = vec![]; + + let file = project.by_name("com.nomagic.magicdraw.uml_model.model")?; + let mut parser: MyEventReader<_> = EventReader::new(file).into(); + + loop { + match parser.next()? { + XmlEvent::StartElement { name, attributes, .. } => { + if check_name(&name, Some("uml"), "Model") { + models.push(parse_model(&mut parser, &attributes)?); + + } else if check_name(&name, Some("SQLProfile"), "PrimaryKey") { + let constraint_id = unwrap_err_continue!(get_attribute(&attributes, None, "base_Constraint")); + let constraint = unwrap_opt_continue!(find_constraint_by_id(&models, constraint_id)); + let property_id = unwrap_opt_continue!(&constraint.property_id).clone(); + modifiers.push(UMLModifier::PirmaryKey(UMLPrimaryKeyModifier { property_id })); + + } else if check_name(&name, Some("SQLProfile"), "PKMember") { + let property_id = unwrap_err_continue!(get_attribute(&attributes, None, "base_Property")).to_string(); + modifiers.push(UMLModifier::PirmaryKey(UMLPrimaryKeyModifier { property_id })); + + } else if check_name(&name, Some("SQLProfile"), "Column") { + let property_id = unwrap_err_continue!(get_attribute(&attributes, None, "base_Property")).to_string(); + let nullable = unwrap_err_continue!(get_attribute(&attributes, None, "nullable")).eq("true"); + modifiers.push(UMLModifier::Nullable(UMLNullableModifier { property_id, nullable })); + + } else if check_name(&name, Some("MagicDraw_Profile"), "typeModifier") { + let property_id = unwrap_err_continue!(get_attribute(&attributes, None, "base_Element")).into(); + let modifier = unwrap_err_continue!(get_attribute(&attributes, None, "typeModifier")).into(); + modifiers.push(UMLModifier::Type(UMLTypeModifier { property_id, modifier })); + + } else if check_name(&name, Some("SQLProfile"), "FK") { + let from_property_id = unwrap_err_continue!(get_attribute(&attributes, None, "members")).into(); + let to_property_id = unwrap_err_continue!(get_attribute(&attributes, None, "referencedMembers")).into(); + modifiers.push(UMLModifier::ForeignKey(UMLForeignKeyModifier { from_property_id, to_property_id })); + } + }, + XmlEvent::EndDocument => { break; }, + _ => {} + } + } + + Ok((models, modifiers)) +} diff --git a/src/magicdraw_parser/utils.rs b/src/magicdraw_parser/utils.rs new file mode 100644 index 0000000..58e8c21 --- /dev/null +++ b/src/magicdraw_parser/utils.rs @@ -0,0 +1,140 @@ +use std::io::Read; + +use xml::{EventReader, reader::XmlEvent, name::OwnedName, attribute::OwnedAttribute}; +use anyhow::Result; +use thiserror::Error; + +pub struct MyEventReader { + depth: u32, + event_reader: EventReader +} + +impl MyEventReader { + pub fn next(&mut self) -> Result { + let event = self.event_reader.next()?; + if let XmlEvent::StartElement { .. } = event { + self.depth += 1; + } else if let XmlEvent::EndElement { .. } = event { + self.depth -= 1; + } + Ok(event) + } + + #[inline(always)] + pub fn depth(&self) -> u32 { + self.depth + } +} + +impl From> for MyEventReader { + fn from(event_reader: EventReader) -> Self { + MyEventReader { + depth: 0, + event_reader + } + } +} + +#[derive(Error, Debug, PartialEq)] +pub enum ParseProjectError { + #[error("XML attribute '{}' not found", format_name_from_parts(.0, .1))] + AttributeNotFound(Option, String), + + #[error("Unexpected end of XML document")] + EndOfDocument +} + +fn format_name_from_parts(prefix: &Option, local_name: &str) -> String { + if let Some(prefix) = &prefix { + format!("{}:{}", prefix, local_name) + } else { + local_name.into() + } +} + +pub fn format_name(name: &OwnedName) -> String { + format_name_from_parts(&name.prefix, &name.local_name) +} + +pub fn check_name(name: &OwnedName, prefix: Option<&str>, local_name: &str) -> bool { + name.local_name.eq(local_name) && name.prefix_ref().eq(&prefix) +} + +pub fn get_attribute<'a>(attributes: &'a [OwnedAttribute], prefix: Option<&str>, name: &str) -> Result<&'a str, ParseProjectError> { + Ok(attributes.iter() + .find(|attr| check_name(&attr.name, prefix, name)) + .map(|attr| &attr.value[..]) + .ok_or_else( + || ParseProjectError::AttributeNotFound(prefix.map(|s| s.to_owned()), name.to_owned()), + )?) +} + +#[inline(always)] +pub fn check_attribute(attributes: &[OwnedAttribute], prefix: Option<&str>, name: &str, expected_value: &str) -> bool { + if let Ok(attr) = get_attribute(attributes, prefix, name) { + return attr.eq(expected_value); + } + false +} + +pub fn get_element_characters(parser: &mut MyEventReader) -> Result { + let mut parts = vec![]; + + loop { + match parser.next()? { + XmlEvent::Characters(text) => { + parts.push(text); + }, + XmlEvent::EndElement { name } => { + break; + }, + _ => {} + } + } + + Ok(parts.join(" ")) +} + +pub fn parse_element(parser: &mut MyEventReader, process_element: &mut F) -> Result<()> + where F: FnMut(&mut MyEventReader, OwnedName, Vec) -> Result<()> +{ + + let starting_depth = parser.depth(); + loop { + match parser.next()? { + XmlEvent::StartElement { name, attributes, .. } => { + process_element(parser, name, attributes)?; + if parser.depth() == starting_depth-1 { break; } + } + XmlEvent::EndElement { name } => { + if parser.depth() == starting_depth-1 { break; } + }, + XmlEvent::EndDocument => { + Err(ParseProjectError::EndOfDocument)? + }, + _ => {} + } + } + + return Ok(()); +} + +#[macro_export] +macro_rules! unwrap_err_continue { + ($res:expr) => { + match $res { + Ok(val) => val, + Err(e) => { continue; } + } + }; +} + +#[macro_export] +macro_rules! unwrap_opt_continue { + ($res:expr) => { + match $res { + Some(val) => val, + None => { continue; } + } + }; +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..d06bdb6 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,29 @@ +use magicdraw_parser::parse_project; +use yew::prelude::*; +use std::fs::File; +use anyhow::Result; + +mod magicdraw_parser; + +// TODO: Make this work with enumation lookup tables + +#[function_component] +fn App() -> Html { + html! { +
+ +

{ "Hello World!" }

+ { "from Yew with " } +
+ } +} + +fn main() -> Result<()> { + let f = File::open("example.mdzip").unwrap(); + + let collections = parse_project(f)?; + dbg!(collections); + + // yew::Renderer::::new().render(); + Ok(()) +}