forked from Plugin-JF-Onkostar/osc-variant
Compare commits
116 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f3bbef5d22 | |||
| ef8f6ab1b5 | |||
| a1e5133516 | |||
| 13bcd74f6c | |||
| 6fdbe06106 | |||
| 31c162e977 | |||
| 2413e4b9b8 | |||
| c9ecb8e944 | |||
| 774b57d78e | |||
| a52eb9742e | |||
| 4c76504000 | |||
| 4676a63c69 | |||
| e250e330a5 | |||
| 349b571e09 | |||
| 90423b5b4e | |||
| d45ded939e | |||
| 7ab5523f3c | |||
| f02ea9b065 | |||
| a08abd7688 | |||
| bf7b8cd9ba | |||
| 821b30e452 | |||
| 6a0a356120 | |||
| 352f5e23fc | |||
| 7b13251d34 | |||
| 6da1c48c28 | |||
| e2d5eedd02 | |||
| a55db66e57 | |||
| dce2a5cdda | |||
| 93981f7709 | |||
| b6694b9e53 | |||
| 0e690cbb85 | |||
| 0d6525c398 | |||
| df643b5e60 | |||
| 1746026af8 | |||
| 7cdfe0068f | |||
| 1f5ec80cc6 | |||
| f851e9c424 | |||
| 7ef1638b58 | |||
| 4745a75f2e | |||
| f089ad0b32 | |||
| 3948cdd697 | |||
| 5a04571a16 | |||
| f5dc366488 | |||
| 8c11c6d891 | |||
| 8a23d5b71b | |||
| 5d293555f8 | |||
| f1c8a002fa | |||
| 86894632b6 | |||
| afb5a2e17a | |||
| f456e278cb | |||
| f28ab1afcd | |||
| ae6b8d48e5 | |||
| 1302963b56 | |||
| 6be11a8951 | |||
| 416204b30a | |||
| 86e988c50e | |||
| 76de86b685 | |||
| d0de76770f | |||
| 8cae6ce706 | |||
| 81a0c6204b | |||
| 21e31f35d1 | |||
| 108aae329c | |||
| 1f51d3e994 | |||
| c514005182 | |||
| a127133ba2 | |||
| e2d4d71063 | |||
| 629cc8aba9 | |||
| 298a142586 | |||
| 9b9a0b4622 | |||
| 9ffc0783ff | |||
| 771d99fa27 | |||
| eadf9326d0 | |||
| bf63d93efa | |||
| ce52f92a7f | |||
| b3054f971e | |||
| e27d31a8bf | |||
| d30c2991c0 | |||
| ce8dca1c10 | |||
| bfa7cc3c6b | |||
| 9256e242eb | |||
| e33b1a3a4c | |||
| 42cbb9ce7e | |||
| 8edd50feb4 | |||
| 31eda3efc9 | |||
| 54cea88486 | |||
| 4040c49521 | |||
| e0b16c16d4 | |||
| c07d4c8976 | |||
| 3ec51099c7 | |||
| 1e7a95bb09 | |||
| abdef90e90 | |||
| a2df2650ed | |||
| f947395c54 | |||
| d3d4ec2646 | |||
| f507893b4d | |||
| af4ec8898a | |||
| 7d6a6ee9b3 | |||
| a605018176 | |||
| a8851c5e4f | |||
| 0b0188bd30 | |||
| 1e553aad58 | |||
| f9c66cfdb1 | |||
| 103075ab78 | |||
| 37c8b47d1f | |||
| d83fa34adb | |||
| 5c91e4d10f | |||
| e84a39b7c4 | |||
| 376bfb2852 | |||
| ad35b99371 | |||
| 224a7dba27 | |||
| 62ab073cfa | |||
| b2adb3c5fe | |||
| a8911f86dd | |||
| b030ce6a53 | |||
| b04ee563f2 | |||
| 6e266feaf5 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,4 +1,5 @@
|
||||
.idea/*
|
||||
/target
|
||||
/completion
|
||||
|
||||
*.iml
|
||||
|
||||
831
Cargo.lock
generated
831
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
44
Cargo.toml
44
Cargo.toml
@@ -1,15 +1,37 @@
|
||||
[package]
|
||||
name = "osc-variant"
|
||||
version = "0.1.0"
|
||||
version = "0.7.0"
|
||||
edition = "2021"
|
||||
authors = ["Paul-Christian Volkmer <volkmer_p@ukw.de>"]
|
||||
description = "Anwendung zum Anpassen einer OSC-Datei an einen Standort"
|
||||
license = "MIT"
|
||||
readme = "README.md"
|
||||
|
||||
build = "build.rs"
|
||||
|
||||
[workspace]
|
||||
members = ["libs/deob"]
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4.3", features = ["std", "help", "usage", "derive", "error-context" ], default-features = false }
|
||||
clap = { version = "4.4", features = ["std", "help", "usage", "derive", "error-context"], default-features = false }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_yaml = "0.9"
|
||||
quick-xml = { version = "0.28", features = ["escape-html", "serialize"], default-features=false }
|
||||
xml-rs = "0.8"
|
||||
quick-xml = { version = "0.31", features = ["escape-html", "serialize"], default-features = false }
|
||||
console = "0.15"
|
||||
sha256 = "1.4"
|
||||
dialoguer = "0.11"
|
||||
indicatif = "0.17"
|
||||
|
||||
deob = { path = "./libs/deob", optional = true }
|
||||
zip = { version = "0.6", optional = true }
|
||||
|
||||
[features]
|
||||
# Requires env var OSB_KEY to be present at build time
|
||||
unzip-osb = ["dep:deob", "dep:zip"]
|
||||
|
||||
[build-dependencies]
|
||||
clap = { version = "4.4", features = ["std", "help", "usage", "derive", "error-context"], default-features = false }
|
||||
clap_complete = "4.4"
|
||||
|
||||
[profile.release]
|
||||
opt-level = "s"
|
||||
@@ -17,3 +39,17 @@ codegen-units = 1
|
||||
lto = "thin"
|
||||
strip = true
|
||||
panic = "abort"
|
||||
|
||||
[package.metadata.deb]
|
||||
copyright = "Copyright (c) 2023 Comprehensive Cancer Center Mainfranken"
|
||||
extended-description = "Anwendung zum Anpassen einer OSC-Datei an einen Standort."
|
||||
assets = [
|
||||
["target/release/osc-variant", "usr/bin/", "755"],
|
||||
["completion/osc-variant.bash", "etc/bash_completion.d/", "644"]
|
||||
]
|
||||
|
||||
[package.metadata.generate-rpm]
|
||||
assets = [
|
||||
{ source = "target/release/osc-variant", dest = "/usr/bin/", mode = "755" },
|
||||
{ source = "completion/osc-variant.bash", dest = "/etc/bash_completion.d/", mode = "644" }
|
||||
]
|
||||
|
||||
12
Makefile
12
Makefile
@@ -32,15 +32,19 @@ binary-all: win-binary-x86_64 linux-binary-x86_64
|
||||
|
||||
.PHONY: win-binary-x86_64
|
||||
win-binary-x86_64:
|
||||
cargo build --release --target=x86_64-pc-windows-gnu
|
||||
cargo build --release --target=x86_64-pc-windows-gnu --features unzip-osb
|
||||
|
||||
.PHONY: linux-binary-x86_64
|
||||
linux-binary-x86_64:
|
||||
cargo build --release --target=x86_64-unknown-linux-gnu
|
||||
cargo build --release --target=x86_64-unknown-linux-gnu --features unzip-osb
|
||||
|
||||
.PHONE: clean
|
||||
.PHONY: install
|
||||
install:
|
||||
cargo install --path .
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
cargo clean
|
||||
rm -rf osc-variant 2>/dev/null || true
|
||||
rm *_win64.zip 2>/dev/null || true
|
||||
rm *_linux.tar.gz 2>/dev/null || true
|
||||
rm *_linux.tar.gz 2>/dev/null || true
|
||||
|
||||
116
README.md
116
README.md
@@ -16,12 +16,67 @@ unvollständigen Ausgabedateien zu erzeugen.
|
||||
|
||||
### Beispiele
|
||||
|
||||
Die folgenden Unterbefehle sind verfügbar
|
||||
|
||||
#### Unterbefehl `sha256sum`
|
||||
|
||||
Das Berechnen der SHA256 Prüfsumme ist mit dem Unterbefehl `sha256sum` auch unter Windows einfach möglich
|
||||
und erzeugt eine Ausgabe analog dem Befehl auf Linux-Systemen:
|
||||
|
||||
```
|
||||
osc-variant sha256sum meine-beispieldatei.osc
|
||||
```
|
||||
|
||||
#### Unterbefehl `list`
|
||||
|
||||
Zum Auflisten der Inhalte einer Datei wird folgender Befehl verwendet:
|
||||
|
||||
```
|
||||
osc-variant list meine-beispieldatei.osc
|
||||
```
|
||||
|
||||
Mit der Option `--filter` kann die Ausgabe eingeschränkt werden.
|
||||
|
||||
#### Unterbefehl `tree`
|
||||
|
||||
Zum Auflisten der Inhalte mit allen Abhängigkeiten, z.B. Daten- und Merkmalskataloge und bei Formularen wird der Befehl
|
||||
`tree` verwendet:
|
||||
|
||||
```
|
||||
osc-variant tree meine-beispieldatei.osc
|
||||
```
|
||||
|
||||
Abhängigkeiten werden je nach Art gekennzeichnet:
|
||||
|
||||
* `+`: Datenkatalog
|
||||
* `-`: Merkmalskatalog
|
||||
* `>`: Formularverweis
|
||||
* `*`: Unterformular
|
||||
|
||||
Für Formularverweise und Unterformulare werden dabei die verwendeten Datenkataloge nicht erneut ausgegeben.
|
||||
|
||||
Achtung! Dies erzeugt eine sehr umfangreiche Ausgabe.
|
||||
|
||||
Mit der Option `--filter` kann auch hier die Ausgabe eingeschränkt werden.
|
||||
|
||||
#### Unterbefehl `diff`
|
||||
|
||||
Zum Vergleich zweier OSC-Dateien wird der Unterbefehl `diff` verwendet.
|
||||
Der optionale Parameter `--strict` vergleicht auch den Inhalt der OSC-Datei.
|
||||
Ohne diesen wird nur das Vorhandensein von Inhalten und die Revision verglichen.
|
||||
|
||||
```
|
||||
osc-variant diff meine-beispieldatei.osc andere-beispieldatei.osc
|
||||
```
|
||||
|
||||
bzw.
|
||||
|
||||
```
|
||||
osc-variant diff meine-beispieldatei.osc andere-beispieldatei.osc --strict
|
||||
```
|
||||
|
||||
#### Unterbefehl `modify`
|
||||
|
||||
Zum Anpassen des Inhalts einer Datei:
|
||||
|
||||
```
|
||||
@@ -29,21 +84,71 @@ osc-variant modify meine-beispieldatei.osc --profile ukw-profil.yml --output ukw
|
||||
```
|
||||
|
||||
Die Parameter `--profile` und `--output` sind optional.
|
||||
Mit dem ebenfalls optionalen Parameter `--interactve` oder `-i` können die Parameter zur kompakten Ausgabe, zum Sortieren
|
||||
und dem Entfernen von Inhalten der Systembibliothek interaktiv gesetzt werden.
|
||||
|
||||
Ohne Profildatei wird die Datei lediglich eingelesen, Leerzeichen am Ende eines XML-Tags entfernt und wieder ausgegeben.
|
||||
|
||||
Ohne eine Angabe der Ausgabedatei wird auf die Standardausgabe ausgegeben.
|
||||
|
||||
##### Enthaltene Profile
|
||||
|
||||
Die im Ordner [`examples/`](/examples) enthaltenen Profile für Standorte sind in der ausführbaren Anwendung enthalten
|
||||
und die Dateien müssen nicht explizit als Datei vorliegen:
|
||||
|
||||
* `--profile examples/dnpm-ukm.yml` => `--profile UKM`
|
||||
* `--profile examples/dnpm-ukw.yml` => `--profile UKW`
|
||||
* `--profile examples/dnpm-umg.yml` => `--profile UMG`
|
||||
|
||||
#### Unterbefehl `unzip-osb`
|
||||
|
||||
Ab Version 0.6.0 ist die Anwendung zudem in der Lage, die für eine Aktualisierung der OS-Bibliothek genutzten OSB-Dateien zu entpacken:
|
||||
|
||||
```
|
||||
osc-variant unzip-osb OSBIB-6.10.osb
|
||||
```
|
||||
|
||||
Dieser Befehl kennt die beiden optionalen Parameter
|
||||
|
||||
* `-d`: Optionale Angabe des Zielverzeichnisses. Wenn keine Angabe vorhanden ist, wird das aktuelle Verzeichnis verwendet.
|
||||
* `-p`/`--password`: Optionale Angabe des Passworts zum Entpacken der OSB-Datei.
|
||||
|
||||
#### Unterbefehl `check`
|
||||
|
||||
Der Unterbefehl `check` prüft eine OSC-Datei auf bekannte Probleme und gibt eine Liste mit erkannten Problemen aus.
|
||||
|
||||
Eine Liste mit bekannten Problemen wird mit `check --list` ausgegeben.
|
||||
|
||||
#### Kompakte Ausgabe
|
||||
|
||||
OSC-Dateien sind XML-Dateien. Diese Anwendung ermöglicht optional die Ausgabe als kompaktere XML-Datei ohne Zeilenumbrüche.
|
||||
Hierzu ist die Option `--compact` vorgesehen. Es können, je nach Datei, bis zu 30% eingespart werden.
|
||||
|
||||
#### Filter
|
||||
|
||||
Bei der Auflistung von Inhalten ist es möglich, die Anzeige für die Unterbefehle `list` und `tree` anhand des Namens zu filtern.
|
||||
Hierzu ist die Option `--filter=` vorgesehen.
|
||||
Wird diese angewendet, werden nur Inhalte angezeigt, deren Name die angegebene Zeichenkette beinhalten.
|
||||
|
||||
#### Sortierung
|
||||
|
||||
Bei der Auflistung der Inhalte, kann die Option `--sorted` dazu verwendet werden, die angezeigten Einträge alphabetisch zu sortieren.
|
||||
Die Sortierung erfolgt dabei nach Namen des Katalogs oder des Formulars.
|
||||
|
||||
Beim Modifizieren der Inhalte kann ebenfalls die Option `--sorted` dazu verwendet werden, die Einträge im Anschluss an die Modifikation
|
||||
nach Namen und für Formulare der Abhängigkeit von Formularverweisen und Unterformularen zu sortieren.
|
||||
|
||||
Formulare, die von anderen Formularen in einem Formularverweis oder als Unterformular verwendet werden, werden dabei weiter oben angeordnet,
|
||||
da Onkostar einen Formularimport sequenziell, ohne Berücksichtigung von Abhängigkeiten, durchführt.
|
||||
|
||||
Dies erlaubt eine konsistente Reihenfolge der Einträge, wodurch ein direkter Vergleich mit Vorversionen ermöglicht wird.
|
||||
|
||||
##### Entfernen von Inhalten der Systembibliothek bei Modifikation
|
||||
|
||||
Mit der die experimentelle Option `--strip` ist es möglich, die in der OSC-Datei enthaltenen und beim Import nicht genutzten Inhalte aus der Systembibliothek zu entfernen.
|
||||
|
||||
Hierbei werden alle Inhalte entfernt, die im Ordner "ONKOSTAR Bibliothek" enthalten sind, beim Import jedoch ignoriert werden.
|
||||
|
||||
## Profile
|
||||
|
||||
Zum Erstellen von Varianten einer OSC-Datei wird eine Profildatei im YAML-Format verwendet.
|
||||
@@ -53,6 +158,9 @@ In ihr sind die durchzuführenden Änderungen definiert. Eine Profildatei hat di
|
||||
```
|
||||
forms:
|
||||
- name: "ExampleForm"
|
||||
form_field:
|
||||
- name: "formularfeld"
|
||||
hide: true
|
||||
form_references:
|
||||
- name: "ref_first_mtb"
|
||||
referenced_data_form: "Formularverweis.Variante"
|
||||
@@ -79,6 +187,12 @@ und dabei die vorhandenen Angaben für den Formularverweis zu ersetzen.
|
||||
Die Angaben für `referenced_data_form`, `anzeige_auswahl`, `anzeige` und `scripts_code` sind optional.
|
||||
Wird keine Angabe gemacht, wird der bestehende Wert beibehalten.
|
||||
|
||||
Zudem wird im Formular "ExampleForm" das Formularfeld "formularfeld" ausgeblendet, indem der Filter auf "false" gesetzt wird.
|
||||
Dadurch wird das Formularfeld nie angezeigt.
|
||||
Ein zuvor bestehender Filter wird ersetzt.
|
||||
Weiterhin wird die Eigenschaft "Speichern" des Formularfelds auf "Immer speichern" gesetzt um sicherzustellen, dass zuvor
|
||||
enthaltene Daten weiterhin gespeichert bleiben und werden, auch wenn das Formularfeld nicht sichtbar ist.
|
||||
|
||||
**Achtung!** Diese Anwendung überprüft keine Scripts und verwendet angegebene Scripts als "valid" im resultierenden OSC-File.
|
||||
|
||||
Zudem kann die Menükategorie angepasst werden.
|
||||
@@ -88,4 +202,4 @@ Wird sie angeben, sind die Felder `name`, `position` und `column` verpflichtend.
|
||||
Es können beliebig viele Formulare mit beliebig vielen Änderungen zu Formularverweisen in einer Profildatei
|
||||
hinterlegt werden, jedoch ist mindestens eine Angabe zu einem Formularfeld erforderlich.
|
||||
|
||||
Beispiele für eine Profildatei sind unter [`examples/`](examples/) zu finden.
|
||||
Beispiele für eine Profildatei sind unter [`examples/`](/examples) zu finden.
|
||||
|
||||
44
build.rs
Normal file
44
build.rs
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2023 Comprehensive Cancer Center Mainfranken
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
use std::fs;
|
||||
use std::io::Error;
|
||||
|
||||
use clap_complete::generate_to;
|
||||
use clap_complete::Shell::Bash;
|
||||
|
||||
include!("src/cli.rs");
|
||||
|
||||
fn main() -> Result<(), Error> {
|
||||
let mut cmd = build_cli();
|
||||
|
||||
let package_name = std::env::var("CARGO_CRATE_NAME").unwrap_or("osc-variant".to_string());
|
||||
|
||||
fs::remove_dir_all("completion").unwrap_or_default();
|
||||
fs::create_dir("completion")?;
|
||||
|
||||
generate_to(Bash, &mut cmd, package_name.as_str(), "completion")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
27
examples/dnpm-master.yml
Normal file
27
examples/dnpm-master.yml
Normal file
@@ -0,0 +1,27 @@
|
||||
forms:
|
||||
- name: 'DNPM Klinik/Anamnese'
|
||||
form_references:
|
||||
- name: MTB
|
||||
referenced_data_form: 'OS.Tumorkonferenz'
|
||||
anzeige_auswahl: 'MTB vom {Datum}'
|
||||
- name: 'DNPM Therapieplan'
|
||||
form_references:
|
||||
- name: referstemtb
|
||||
referenced_data_form: 'OS.Tumorkonferenz'
|
||||
anzeige_auswahl: 'MTB vom {Datum}'
|
||||
- name: reftkhumangenber
|
||||
referenced_data_form: 'OS.Tumorkonferenz'
|
||||
anzeige_auswahl: 'MTB vom {Datum}'
|
||||
- name: reftkreevaluation
|
||||
referenced_data_form: 'OS.Tumorkonferenz'
|
||||
anzeige_auswahl: 'MTB vom {Datum}'
|
||||
- name: 'DNPM UF Einzelempfehlung'
|
||||
form_references:
|
||||
- name: mtb
|
||||
referenced_data_form: 'OS.Tumorkonferenz'
|
||||
anzeige_auswahl: 'MTB vom {Datum}'
|
||||
- name: 'DNPM UF Rebiopsie'
|
||||
form_references:
|
||||
- name: reftumorkonferenz
|
||||
referenced_data_form: 'OS.Tumorkonferenz'
|
||||
anzeige_auswahl: 'MTB vom {Datum}'
|
||||
@@ -3,4 +3,29 @@ forms:
|
||||
form_references:
|
||||
- name: MTB
|
||||
referenced_data_form: 'MR.MTB_Anmeldung'
|
||||
anzeige_auswahl: 'MTB Anmeldung vom {Anmeldedatum}'
|
||||
anzeige_auswahl: 'MTB Anmeldung vom {Anmeldedatum}'
|
||||
scripts_code: |
|
||||
setFieldValue('AnmeldedatumMTB', getFieldValue('MTB').MTBTermin);
|
||||
setFieldValue('WHOGrad', getFieldValue('MTB').WHOGrad);
|
||||
setFieldValue('Leitlinienstatus', getFieldValue('MTB').Leitlinienstatus);
|
||||
- name: 'DNPM Therapieplan'
|
||||
form_references:
|
||||
- name: referstemtb
|
||||
referenced_data_form: 'MR.MTB_Empfehlung'
|
||||
anzeige_auswahl: 'MTB vom {Datum}'
|
||||
- name: reftkhumangenber
|
||||
referenced_data_form: 'MR.MTB_Empfehlung'
|
||||
anzeige_auswahl: 'MTB vom {Datum}'
|
||||
- name: reftkreevaluation
|
||||
referenced_data_form: 'MR.MTB_Empfehlung'
|
||||
anzeige_auswahl: 'MTB vom {Datum}'
|
||||
- name: 'DNPM UF Einzelempfehlung'
|
||||
form_references:
|
||||
- name: mtb
|
||||
referenced_data_form: 'MR.MTB_Empfehlung'
|
||||
anzeige_auswahl: 'MTB vom {Datum}'
|
||||
- name: 'DNPM UF Rebiopsie'
|
||||
form_references:
|
||||
- name: reftumorkonferenz
|
||||
referenced_data_form: 'MR.MTB_Empfehlung'
|
||||
anzeige_auswahl: 'MTB vom {Datum}'
|
||||
7
libs/deob/Cargo.toml
Normal file
7
libs/deob/Cargo.toml
Normal file
@@ -0,0 +1,7 @@
|
||||
[package]
|
||||
name = "deob"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[build-dependencies]
|
||||
cc = "1.0"
|
||||
32
libs/deob/build.rs
Normal file
32
libs/deob/build.rs
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2023 Comprehensive Cancer Center Mainfranken
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
use std::io::Error;
|
||||
|
||||
fn main() -> Result<(), Error> {
|
||||
cc::Build::new().file("src/deob.c").compile("deob");
|
||||
println!("cargo:rerun-if-changed=src/deob.c");
|
||||
println!("cargo:rerun-if-changed=src/deob.h");
|
||||
Ok(())
|
||||
}
|
||||
37
libs/deob/src/deob.c
Normal file
37
libs/deob/src/deob.c
Normal file
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2023 Comprehensive Cancer Center Mainfranken
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "deob.h"
|
||||
|
||||
void deob(char * in) {
|
||||
const long long s[2] = { S0, S1 };
|
||||
char d[] = "OSTAR.password$OSB";
|
||||
for (size_t i = 0; i < DL; i++) d[i] = (CS)[i];
|
||||
size_t l = strlen(in) / 2;
|
||||
for (size_t i = 0; i < l; i++) {
|
||||
for (size_t j = 0; j < DL; j++) { DLT(0); DLT(1); }
|
||||
DLS(i);
|
||||
}
|
||||
INZ(l);
|
||||
}
|
||||
43
libs/deob/src/deob.h
Normal file
43
libs/deob/src/deob.h
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2023 Comprehensive Cancer Center Mainfranken
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef OSC_VARIANT_DEOB_H
|
||||
#define OSC_VARIANT_DEOB_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define S0 8373972096940928081
|
||||
#define S1 7378413942531504450
|
||||
#define CS (char*)&s
|
||||
#define DL sizeof(d) / sizeof(d[0]) - 2
|
||||
#define I2 i*2
|
||||
#define INZ(var) in[var] = 0
|
||||
#define DLS(idx) in[idx] = (d[DL+1]<<4)|d[DL]
|
||||
#define DLT(idx) d[DL+idx] = (in[I2+idx] == d[j]) ? (char)j : d[DL+idx]
|
||||
|
||||
void deob(char * in);
|
||||
|
||||
#endif //OSC_VARIANT_DEOB_H
|
||||
38
libs/deob/src/lib.rs
Normal file
38
libs/deob/src/lib.rs
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2023 Comprehensive Cancer Center Mainfranken
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
use std::ffi::{c_char, CStr, CString};
|
||||
|
||||
#[link(name = "deob")]
|
||||
extern "C" {
|
||||
fn deob(key: *const c_char);
|
||||
}
|
||||
|
||||
pub fn deobfuscate(s: &str) -> String {
|
||||
let key = CString::new(s).unwrap_or_default().into_raw();
|
||||
unsafe {
|
||||
deob(key);
|
||||
String::from_utf8_lossy(CStr::from_ptr(key).to_bytes()).to_string()
|
||||
}
|
||||
}
|
||||
30
osc-variant.wxs
Normal file
30
osc-variant.wxs
Normal file
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
|
||||
<Product Id="*"
|
||||
Language="1033"
|
||||
Manufacturer="CCC Mainfranken"
|
||||
Name="OSC-Variant"
|
||||
UpgradeCode="{83088581-5db3-49a2-8932-27da356818c7}"
|
||||
Version="0.6.0">
|
||||
|
||||
<Package InstallScope="perMachine" Compressed="yes" />
|
||||
|
||||
<MediaTemplate EmbedCab="yes" />
|
||||
|
||||
<Directory Id="TARGETDIR" Name="SourceDir">
|
||||
<Directory Id="ProgramFiles64Folder">
|
||||
<Directory Id="INSTALLFOLDER" Name="osc-variant">
|
||||
<Component Id="MainExecutable" Guid="*">
|
||||
<File Id="OscVariantExe" Name="osc-variant.exe" KeyPath="yes" Source="target/x86_64-pc-windows-gnu/release/osc-variant.exe" />
|
||||
<File Id="LicenseTxt" Name="LICENSE.txt" Source="LICENSE.txt" />
|
||||
</Component>
|
||||
</Directory>
|
||||
</Directory>
|
||||
</Directory>
|
||||
|
||||
<Feature Id="Complete">
|
||||
<ComponentRef Id="MainExecutable" />
|
||||
</Feature>
|
||||
|
||||
</Product>
|
||||
</Wix>
|
||||
245
src/checks/mod.rs
Normal file
245
src/checks/mod.rs
Normal file
@@ -0,0 +1,245 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2023 Comprehensive Cancer Center Mainfranken
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::path::Path;
|
||||
|
||||
use console::style;
|
||||
|
||||
#[cfg(feature = "unzip-osb")]
|
||||
pub mod osb;
|
||||
pub mod osc;
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub enum CheckNotice {
|
||||
/// This will result in Error if importing file and has a support code
|
||||
ErrorWithCode {
|
||||
code: String,
|
||||
description: String,
|
||||
line: Option<usize>,
|
||||
example: Option<String>,
|
||||
},
|
||||
/// This will result in Error if importing file
|
||||
Error {
|
||||
description: String,
|
||||
line: Option<usize>,
|
||||
},
|
||||
/// Other known issues
|
||||
Warning {
|
||||
description: String,
|
||||
line: Option<usize>,
|
||||
},
|
||||
/// Other known issues
|
||||
Info {
|
||||
description: String,
|
||||
line: Option<usize>,
|
||||
},
|
||||
/// Ok
|
||||
Ok(String),
|
||||
}
|
||||
|
||||
impl Display for CheckNotice {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
CheckNotice::ErrorWithCode {
|
||||
code,
|
||||
description,
|
||||
line,
|
||||
example,
|
||||
} => match line {
|
||||
Some(line) => write!(
|
||||
f,
|
||||
"{: <7} ({}) at Line {}: {}{}",
|
||||
style("ERROR").red().bold(),
|
||||
code,
|
||||
line,
|
||||
description,
|
||||
match example {
|
||||
Some(example) => format!("\n 🔥 '{}'", style(example).dim()),
|
||||
_ => String::new(),
|
||||
}
|
||||
),
|
||||
None => write!(
|
||||
f,
|
||||
"{: <7} ({}): {}{}",
|
||||
style("ERROR").red().bold(),
|
||||
code,
|
||||
description,
|
||||
match example {
|
||||
Some(example) => format!("\n 🔥 '{}'", style(example).dim()),
|
||||
_ => String::new(),
|
||||
}
|
||||
),
|
||||
},
|
||||
CheckNotice::Error { description, line } => match line {
|
||||
Some(line) => write!(
|
||||
f,
|
||||
"{: <7} at Line {}: {}",
|
||||
style("ERROR").red().bold(),
|
||||
line,
|
||||
description
|
||||
),
|
||||
None => write!(f, "{: <7} {}", style("ERROR").red().bold(), description),
|
||||
},
|
||||
CheckNotice::Warning { description, line } => match line {
|
||||
Some(line) => write!(
|
||||
f,
|
||||
"{: <7} at Line {}: {}",
|
||||
style("WARNING").yellow().bold(),
|
||||
line,
|
||||
description
|
||||
),
|
||||
None => write!(
|
||||
f,
|
||||
"{: <7} {}",
|
||||
style("WARNING").yellow().bold(),
|
||||
description
|
||||
),
|
||||
},
|
||||
CheckNotice::Info { description, line } => match line {
|
||||
Some(line) => write!(
|
||||
f,
|
||||
"{: <7} at Line {}: {}",
|
||||
style("INFO").blue().bold(),
|
||||
line,
|
||||
description
|
||||
),
|
||||
None => write!(f, "{: <7} {}", style("INFO").blue().bold(), description),
|
||||
},
|
||||
CheckNotice::Ok(msg) => write!(f, "{: <7} {}", style("OK").green(), msg),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Checkable {
|
||||
fn check(&self) -> Vec<CheckNotice>;
|
||||
}
|
||||
|
||||
pub trait Fixable {
|
||||
fn fix(&mut self) -> bool;
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
pub fn check_file(file: &Path, password: Option<String>) -> Result<Vec<CheckNotice>, CheckNotice> {
|
||||
match file.extension() {
|
||||
Some(ex) => match ex.to_str() {
|
||||
#[cfg(feature = "unzip-osb")]
|
||||
Some("osb") => match password {
|
||||
Some(password) => osb::check_file(file, password.as_str()),
|
||||
None => {
|
||||
use deob::deobfuscate;
|
||||
osb::check_file(file, deobfuscate(env!("OSB_KEY").trim()).as_str())
|
||||
}
|
||||
},
|
||||
Some("osc") => osc::check_file(file),
|
||||
_ => Err(CheckNotice::Error {
|
||||
description: "Keine prüfbare Datei".to_string(),
|
||||
line: None,
|
||||
}),
|
||||
},
|
||||
_ => Err(CheckNotice::Error {
|
||||
description: "Keine prüfbare Datei".to_string(),
|
||||
line: None,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn print_checks() {
|
||||
println!(
|
||||
"{}",
|
||||
style("Die folgenden Probleme sind bekannt\n")
|
||||
.yellow()
|
||||
.bold()
|
||||
);
|
||||
|
||||
struct Problem<'a> {
|
||||
code: &'a str,
|
||||
name: &'a str,
|
||||
description: &'a str,
|
||||
fixable: bool,
|
||||
}
|
||||
|
||||
impl<'a> Display for Problem<'a> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{} {} {}\n\n{}",
|
||||
style(self.code).bold(),
|
||||
style(self.name).underlined(),
|
||||
match self.fixable {
|
||||
true => style("(Behebbar)").green(),
|
||||
false => style("(Nicht behebbar)").red(),
|
||||
},
|
||||
self.description
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
vec![
|
||||
Problem {
|
||||
code: "2023-0001",
|
||||
name: "Unterformular mit Markierung 'hat Unterformulare'",
|
||||
description: " Aktuell gibt es keine Unterformulare in Unterformularen, daher\n \
|
||||
sollte dies nicht vorkommen.\n\n \
|
||||
Eine mögliche Ursache ist die Speicherung eines Unterformulars als Formular.",
|
||||
fixable: false,
|
||||
},
|
||||
Problem {
|
||||
code: "2023-0002",
|
||||
name: "Formular hat keine Angabe zum Prozedurdatum",
|
||||
description: " Formulare benötigen die Angabe des Prozedurdatums, anderenfalls\n \
|
||||
führt dies zu Problemen in Onkostar.\n\n \
|
||||
Unterformulare können ein Prozedurdatum haben, müssen es aber nicht.\n\n \
|
||||
Eine mögliche Ursache ist die Speicherung eines Formulars als Unterformular.",
|
||||
fixable: false,
|
||||
},
|
||||
Problem {
|
||||
code: "2023-0003",
|
||||
name: "Leerzeichen am Ende der Plausibilitätsregel-Bezeichnung (OSTARSUPP-13334)",
|
||||
description:
|
||||
" Treten Leerzeichen am Ende der Plausibilitätsregel-Bezeichnung auf,\n \
|
||||
führt dies zu Fehlern beim Import der OSC-Datei.\n\n \
|
||||
Das Problem wird beim Verwenden des Unterbefehls 'modify' automatisch\n \
|
||||
behoben und Leerzeichen entfernt.
|
||||
",
|
||||
fixable: true,
|
||||
},
|
||||
Problem {
|
||||
code: "2023-0004",
|
||||
name: "Verweis auf noch nicht definiertes Formular (OSTARSUPP-13212)",
|
||||
description: " Wenn ein Formular einen Verweis auf ein anderes Formular enthält,\n \
|
||||
das nicht vor diesem Formular in der OSC-Datei definiert ist, wird der\n \
|
||||
Formularverweis beim Import der OSC-Datei nicht übernommen.\n\n \
|
||||
Dies kann bei wechselseitiger Abhängigkeit zwischen zwei (Unter-)Formularen\n \
|
||||
auftreten.\n\n \
|
||||
In diesem Fall kann ein erneuter/zweiter Import helfen, da das Onkostar in\n \
|
||||
diesem Fall alle Formulare importiert hat und der Formularverweis dann \n \
|
||||
gespeichert werden kann.
|
||||
",
|
||||
fixable: false,
|
||||
},
|
||||
]
|
||||
.iter()
|
||||
.for_each(|problem| println!("{}\n", problem))
|
||||
}
|
||||
108
src/checks/osb.rs
Normal file
108
src/checks/osb.rs
Normal file
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2023 Comprehensive Cancer Center Mainfranken
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
use std::fs;
|
||||
use std::io::Read;
|
||||
use std::path::Path;
|
||||
|
||||
use indicatif::ProgressBar;
|
||||
|
||||
use crate::checks::{osc, CheckNotice};
|
||||
|
||||
#[cfg(feature = "unzip-osb")]
|
||||
pub fn check_file(file: &Path, password: &str) -> Result<Vec<CheckNotice>, CheckNotice> {
|
||||
let file = match fs::File::open(file) {
|
||||
Ok(file) => file,
|
||||
Err(err) => {
|
||||
return Err(CheckNotice::Error {
|
||||
description: format!("Kann Datei nicht lesen: {}", err),
|
||||
line: None,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
let mut archive = match zip::ZipArchive::new(file) {
|
||||
Ok(file) => file,
|
||||
Err(err) => {
|
||||
return Err(CheckNotice::Error {
|
||||
description: format!("Kann Datei nicht lesen: {}", err),
|
||||
line: None,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
let mut result = vec![];
|
||||
|
||||
let progress_bar = ProgressBar::new(archive.len() as u64);
|
||||
|
||||
for i in 0..archive.len() {
|
||||
progress_bar.inc(1);
|
||||
if let Ok(Ok(mut zip_file)) = archive.by_index_decrypt(i, password.as_bytes()) {
|
||||
if zip_file.is_file() && zip_file.name().ends_with(".osc") {
|
||||
let mut buf = String::new();
|
||||
let _ = zip_file.read_to_string(&mut buf);
|
||||
match osc::check(buf) {
|
||||
Ok(ref mut check_result) => {
|
||||
result.push(CheckNotice::Info {
|
||||
description: format!("Prüfe Eintrag '{}'", zip_file.name()),
|
||||
line: None,
|
||||
});
|
||||
if check_result.is_empty() {
|
||||
result.push(CheckNotice::Ok(format!(
|
||||
"Keine Probleme in '{}' erkannt",
|
||||
zip_file.name()
|
||||
)))
|
||||
}
|
||||
result.append(check_result)
|
||||
}
|
||||
Err(_) => result.push(CheckNotice::Warning {
|
||||
description: format!(
|
||||
"Überspringe Eintrag '{}': Inhalt kann nicht geprüft werden",
|
||||
zip_file.name(),
|
||||
),
|
||||
line: None,
|
||||
}),
|
||||
};
|
||||
continue;
|
||||
}
|
||||
if zip_file.is_file() {
|
||||
result.push(CheckNotice::Warning {
|
||||
description: format!(
|
||||
"Überspringe Eintrag '{}': Keine OSC-Datei",
|
||||
zip_file.name()
|
||||
),
|
||||
line: None,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
return Err(CheckNotice::Error {
|
||||
description: format!("Kann Datei nicht lesen"),
|
||||
line: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
progress_bar.finish_and_clear();
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
78
src/checks/osc.rs
Normal file
78
src/checks/osc.rs
Normal file
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2023 Comprehensive Cancer Center Mainfranken
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::checks::{CheckNotice, Checkable};
|
||||
use crate::model::onkostar_editor::OnkostarEditor;
|
||||
|
||||
pub fn check_file(file: &Path) -> Result<Vec<CheckNotice>, CheckNotice> {
|
||||
match fs::read_to_string(file) {
|
||||
Ok(content) => check(content),
|
||||
_ => Err(CheckNotice::Error {
|
||||
description: "Kann Datei nicht lesen".to_string(),
|
||||
line: None,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check(content: String) -> Result<Vec<CheckNotice>, CheckNotice> {
|
||||
let mut result = content
|
||||
.lines()
|
||||
.enumerate()
|
||||
.flat_map(|(line, content)| check_line(line, content.to_string()))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let inner_checks = &mut match OnkostarEditor::from_str(content.as_str()) {
|
||||
Ok(data) => data.check(),
|
||||
Err(err) => {
|
||||
return Err(CheckNotice::Error {
|
||||
description: format!("Interner Fehler: {}", err),
|
||||
line: None,
|
||||
})
|
||||
}
|
||||
};
|
||||
result.append(inner_checks);
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn check_line(line: usize, content: String) -> Vec<CheckNotice> {
|
||||
let mut result = vec![];
|
||||
|
||||
if content.contains(" </Bezeichnung>") {
|
||||
result.append(&mut vec![CheckNotice::ErrorWithCode {
|
||||
code: "2023-0003".to_string(),
|
||||
description:
|
||||
"Leerzeichen am Ende der Plausibilitätsregel-Bezeichnung (OSTARSUPP-13334)"
|
||||
.to_string(),
|
||||
line: Some(line),
|
||||
example: Some(content.trim().to_string()),
|
||||
}])
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
91
src/cli.rs
91
src/cli.rs
@@ -22,18 +22,28 @@
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
use clap::{Parser, Subcommand};
|
||||
use clap::{Command, CommandFactory, Parser, Subcommand};
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn build_cli() -> Command {
|
||||
Cli::command()
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
#[command(propagate_version = true, arg_required_else_help(true))]
|
||||
pub struct Cli {
|
||||
#[command(subcommand)]
|
||||
pub command: Command,
|
||||
pub cmd: SubCommand,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
pub enum Command {
|
||||
pub enum SubCommand {
|
||||
#[command(
|
||||
name = "sha256sum",
|
||||
about = "Berechne SHA256 Prüfsumme für die angegebene Datei"
|
||||
)]
|
||||
Sha256Sum { inputfile: String },
|
||||
#[command(about = "Zeigt alle enthaltenen Kataloge und Formulare mit Revision an.")]
|
||||
List {
|
||||
inputfile: String,
|
||||
@@ -42,6 +52,19 @@ pub enum Command {
|
||||
help = "Sortiere Kataloge und Formulare nach Name (Optional)"
|
||||
)]
|
||||
sorted: bool,
|
||||
#[arg(long = "filter", help = "Filtere Ausgabe nach Name (Optional)")]
|
||||
filter: Option<String>,
|
||||
},
|
||||
#[command(about = "Zeigt Kataloge und Formulare mit Revision und Abhängigkeiten an.")]
|
||||
Tree {
|
||||
inputfile: String,
|
||||
#[arg(
|
||||
long = "sorted",
|
||||
help = "Sortiere Kataloge und Formulare nach Name (Optional)"
|
||||
)]
|
||||
sorted: bool,
|
||||
#[arg(long = "filter", help = "Filtere Ausgabe nach Name (Optional)")]
|
||||
filter: Option<String>,
|
||||
},
|
||||
#[command(about = "Modifiziert die angegebene Datei anhand der Profildatei")]
|
||||
Modify {
|
||||
@@ -52,5 +75,67 @@ pub enum Command {
|
||||
outputfile: Option<String>,
|
||||
#[arg(long = "compact", help = "Kompakte Ausgabe, ohne Einrücken (Optional)")]
|
||||
compact: bool,
|
||||
#[arg(
|
||||
long = "sorted",
|
||||
alias = "x-sorted",
|
||||
help = "Sortiere Kataloge und Formulare nach Name und Abhängigkeiten (Optional)."
|
||||
)]
|
||||
sorted: bool,
|
||||
#[arg(
|
||||
long = "strip",
|
||||
alias = "x-strip",
|
||||
help = "Entferne Einträge aus der Systembibliothek die nicht importiert werden (Optional)."
|
||||
)]
|
||||
strip: bool,
|
||||
#[arg(
|
||||
short = 'i',
|
||||
long = "interactive",
|
||||
help = "Starte interaktiven Dialog zum Modifizieren von OSC-Dateien"
|
||||
)]
|
||||
interactive: bool,
|
||||
#[arg(
|
||||
long = "fix",
|
||||
help = "Erweiterte Problembehandlung und Reparatur der OSC-Datei"
|
||||
)]
|
||||
fix: bool,
|
||||
},
|
||||
#[command(about = "Vergleiche zwei Dateien anhand der Revision der enthaltenen Inhalte")]
|
||||
Diff {
|
||||
inputfile_a: String,
|
||||
inputfile_b: String,
|
||||
#[arg(long = "strict", help = "Strikter Vergleich des Inhalts")]
|
||||
strict: bool,
|
||||
},
|
||||
#[command(about = if cfg!(feature = "unzip-osb") { "Prüfe eine OSB- oder OSC-Datei auf bekannte Problemen" } else { "Prüfe eine OSC-Datei auf bekannte Problemen" })]
|
||||
Check {
|
||||
#[arg(help = "Die zu prüfende Datei", group = "check-file", required = true)]
|
||||
file: Option<String>,
|
||||
#[arg(
|
||||
short = 'p',
|
||||
long = "password",
|
||||
help = "Passwort der OSB-Datei (Optional - für OSB-Dateien)",
|
||||
requires = "check-file",
|
||||
hide = !cfg!(feature = "unzip-osb")
|
||||
)]
|
||||
password: Option<String>,
|
||||
#[arg(
|
||||
long = "list",
|
||||
help = "Prüfe nicht und zeige Liste mit Checks auf bekannte Problemen",
|
||||
conflicts_with = "check-file"
|
||||
)]
|
||||
list: bool,
|
||||
},
|
||||
#[cfg(feature = "unzip-osb")]
|
||||
#[command(about = "Entpackt eine OSB-Datei")]
|
||||
UnzipOsb {
|
||||
file: String,
|
||||
#[arg(
|
||||
short = 'p',
|
||||
long = "password",
|
||||
help = "Passwort der OSB-Datei (Optional)"
|
||||
)]
|
||||
password: Option<String>,
|
||||
#[arg(short = 'd', help = "Zielverzeichnis (Optional)")]
|
||||
dir: Option<String>,
|
||||
},
|
||||
}
|
||||
|
||||
173
src/main.rs
173
src/main.rs
@@ -28,19 +28,27 @@ use std::fs;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Write;
|
||||
use std::ops::Add;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::checks::{check_file, print_checks, CheckNotice};
|
||||
use clap::Parser;
|
||||
use console::style;
|
||||
use dialoguer::Confirm;
|
||||
use quick_xml::se::Serializer;
|
||||
use serde::Serialize;
|
||||
use sha256::digest;
|
||||
|
||||
use crate::cli::{Cli, Command};
|
||||
use crate::cli::{Cli, SubCommand};
|
||||
use crate::model::onkostar_editor::OnkostarEditor;
|
||||
use crate::profile::Profile;
|
||||
|
||||
mod checks;
|
||||
mod cli;
|
||||
mod model;
|
||||
mod profile;
|
||||
#[cfg(feature = "unzip-osb")]
|
||||
mod unzip_osb;
|
||||
|
||||
enum FileError {
|
||||
Reading(String, String),
|
||||
@@ -108,29 +116,99 @@ fn read_profile(filename: String) -> Result<Profile, FileError> {
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
let cli = Cli::parse();
|
||||
|
||||
match cli.command {
|
||||
Command::List { inputfile, sorted } => {
|
||||
match cli.cmd {
|
||||
SubCommand::List {
|
||||
inputfile,
|
||||
sorted,
|
||||
filter,
|
||||
} => {
|
||||
let mut data = read_inputfile(inputfile)?;
|
||||
if sorted {
|
||||
data.sorted()
|
||||
}
|
||||
if let Some(name) = filter {
|
||||
OnkostarEditor::print_list_filtered(&mut data, name.as_str());
|
||||
return Ok(());
|
||||
}
|
||||
data.print_list();
|
||||
}
|
||||
Command::Modify {
|
||||
SubCommand::Tree {
|
||||
inputfile,
|
||||
sorted,
|
||||
filter,
|
||||
} => {
|
||||
let mut data = read_inputfile(inputfile)?;
|
||||
if sorted {
|
||||
data.sorted()
|
||||
}
|
||||
if let Some(name) = filter {
|
||||
OnkostarEditor::print_tree_filtered(&mut data, name.as_str());
|
||||
return Ok(());
|
||||
}
|
||||
OnkostarEditor::print_tree(&data);
|
||||
}
|
||||
SubCommand::Modify {
|
||||
inputfile,
|
||||
profile,
|
||||
outputfile,
|
||||
compact,
|
||||
sorted,
|
||||
strip,
|
||||
interactive,
|
||||
fix,
|
||||
} => {
|
||||
let data = &mut read_inputfile(inputfile)?;
|
||||
|
||||
if let Some(profile) = profile {
|
||||
let profile = read_profile(profile.clone()).map_err(|_| {
|
||||
FileError::Reading(profile, "Kann Profildatei nicht lesen!".into())
|
||||
})?;
|
||||
let profile = if profile.contains(".") {
|
||||
read_profile(profile.clone()).map_err(|_| {
|
||||
FileError::Reading(profile, "Kann Profildatei nicht lesen!".into())
|
||||
})?
|
||||
} else {
|
||||
Profile::embedded_profile(profile.as_str())?
|
||||
};
|
||||
|
||||
data.apply_profile(&profile);
|
||||
}
|
||||
|
||||
let mut compact = compact;
|
||||
let mut sorted = sorted;
|
||||
let mut strip = strip;
|
||||
|
||||
if interactive {
|
||||
compact = Confirm::new()
|
||||
.with_prompt("Kompakte Ausgabe, ohne Einrücken?")
|
||||
.default(compact)
|
||||
.interact()
|
||||
.unwrap();
|
||||
|
||||
sorted = Confirm::new()
|
||||
.with_prompt("Sortiere Kataloge und Formulare nach Name und Abhängigkeiten?")
|
||||
.default(sorted)
|
||||
.interact()
|
||||
.unwrap();
|
||||
|
||||
strip = Confirm::new()
|
||||
.with_prompt(
|
||||
"Entferne Einträge aus der Systembibliothek die nicht importiert werden?",
|
||||
)
|
||||
.default(strip)
|
||||
.interact()
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
if fix {
|
||||
// No operation as of now
|
||||
}
|
||||
|
||||
if sorted {
|
||||
data.sorted();
|
||||
}
|
||||
|
||||
if strip {
|
||||
data.strip_system_library_content();
|
||||
}
|
||||
|
||||
let mut buf = String::new();
|
||||
|
||||
let mut serializer = Serializer::new(&mut buf);
|
||||
@@ -157,6 +235,87 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||
}
|
||||
}
|
||||
}
|
||||
SubCommand::Diff {
|
||||
inputfile_a,
|
||||
inputfile_b,
|
||||
strict,
|
||||
} => {
|
||||
println!(
|
||||
"Vergleiche Datei A ({}) mit Datei B ({})",
|
||||
style(&inputfile_a).yellow(),
|
||||
style(&inputfile_b).yellow()
|
||||
);
|
||||
|
||||
let data_a = &mut read_inputfile(inputfile_a)?;
|
||||
let data_b = &mut read_inputfile(inputfile_b)?;
|
||||
|
||||
data_a.print_diff(data_b, strict);
|
||||
}
|
||||
SubCommand::Sha256Sum { inputfile } => {
|
||||
match fs::read_to_string(inputfile.clone()) {
|
||||
Ok(content) => {
|
||||
println!(
|
||||
"{} {}",
|
||||
digest(content).as_str(),
|
||||
PathBuf::from(inputfile.clone())
|
||||
.canonicalize()
|
||||
.unwrap_or_default()
|
||||
.to_str()
|
||||
.unwrap_or_default()
|
||||
)
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("{}", FileError::Reading(inputfile, err.to_string()));
|
||||
}
|
||||
};
|
||||
}
|
||||
SubCommand::Check {
|
||||
file,
|
||||
list,
|
||||
password,
|
||||
} => {
|
||||
if list {
|
||||
print_checks();
|
||||
} else {
|
||||
match check_file(Path::new(file.unwrap_or_default().as_str()), password) {
|
||||
Ok(notices) => {
|
||||
println!(
|
||||
"Es wurden {} Probleme gefunden\n",
|
||||
notices
|
||||
.iter()
|
||||
.filter(|notice| match notice {
|
||||
CheckNotice::ErrorWithCode { .. }
|
||||
| CheckNotice::Error { .. } => true,
|
||||
_ => false,
|
||||
})
|
||||
.count()
|
||||
);
|
||||
notices
|
||||
.iter()
|
||||
.for_each(|check_notice| println!("{}", check_notice));
|
||||
}
|
||||
Err(err) => {
|
||||
println!("{}", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "unzip-osb")]
|
||||
SubCommand::UnzipOsb {
|
||||
file,
|
||||
password,
|
||||
dir,
|
||||
} => {
|
||||
use crate::unzip_osb::{unzip_osb, unzip_osb_using_password};
|
||||
match password {
|
||||
Some(password) => unzip_osb_using_password(
|
||||
file.as_str(),
|
||||
dir.unwrap_or_default().as_str(),
|
||||
password.as_str(),
|
||||
),
|
||||
None => unzip_osb(file.as_str(), dir.unwrap_or_default().as_str()),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -22,10 +22,14 @@
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
use std::collections::HashSet;
|
||||
|
||||
use console::style;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::model::{Listable, Ordner, Sortable};
|
||||
use crate::model::onkostar_editor::OnkostarEditor;
|
||||
use crate::model::requirements::{Requirement, Requires};
|
||||
use crate::model::{Ansichten, Comparable, FolderContent, Listable, Ordner, Sortable};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
@@ -54,12 +58,19 @@ pub struct DataCatalogue {
|
||||
entries: Entries,
|
||||
#[serde(rename = "Ordner")]
|
||||
ordner: Ordner,
|
||||
#[serde(rename = "Ansichten", default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
ansichten: Option<Ansichten>,
|
||||
}
|
||||
|
||||
impl Listable for DataCatalogue {
|
||||
fn to_listed_string(&self) -> String {
|
||||
format!(
|
||||
"Datenkatalog '{}' in Revision '{}'",
|
||||
"Datenkatalog ({}) '{}' in Revision '{}'",
|
||||
match self.is_system_library_content() {
|
||||
true => style("S").yellow(),
|
||||
_ => style("u"),
|
||||
},
|
||||
style(&self.name).yellow(),
|
||||
style(&self.revision).yellow()
|
||||
)
|
||||
@@ -70,6 +81,78 @@ impl Sortable for DataCatalogue {
|
||||
fn sorting_key(&self) -> String {
|
||||
self.name.clone()
|
||||
}
|
||||
|
||||
fn sorted(&mut self) -> &Self {
|
||||
self.entries
|
||||
.entry
|
||||
.sort_unstable_by_key(|item| item.sorting_key());
|
||||
self.entries.entry.iter_mut().for_each(|item| {
|
||||
item.sorted();
|
||||
});
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Comparable for DataCatalogue {
|
||||
fn get_name(&self) -> String {
|
||||
self.name.clone()
|
||||
}
|
||||
|
||||
fn get_revision(&self) -> u16 {
|
||||
self.revision
|
||||
}
|
||||
}
|
||||
|
||||
impl Requires for DataCatalogue {
|
||||
fn get_required_entries<'a>(&'a self, all: &'a OnkostarEditor) -> Vec<Requirement> {
|
||||
let mut result = self
|
||||
.entries
|
||||
.entry
|
||||
.iter()
|
||||
.filter(|&entry| entry.property_catalogue.is_some())
|
||||
.map(|entry| match &entry.property_catalogue {
|
||||
Some(entry) => entry.to_string(),
|
||||
_ => String::new(),
|
||||
})
|
||||
.collect::<HashSet<_>>()
|
||||
.into_iter()
|
||||
.map(|entry| match all.find_property_catalogue(entry.as_str()) {
|
||||
Some(contained) => Requirement::PropertyCatalogue(contained),
|
||||
None => Requirement::ExternalPropertyCatalogue(entry),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
result.sort_unstable_by_key(|item| item.sorting_key());
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn to_requirement_string<'a>(&'a self, all: &'a OnkostarEditor) -> String {
|
||||
format!(
|
||||
"{}\n{}",
|
||||
self.to_listed_string(),
|
||||
self.get_required_entries(all)
|
||||
.iter()
|
||||
.map(|entry| match entry {
|
||||
Requirement::PropertyCatalogue(_) => {
|
||||
Some(format!(" - {}\n", entry.to_string()))
|
||||
}
|
||||
Requirement::ExternalPropertyCatalogue(_) => {
|
||||
Some(format!(" - {}\n", entry.to_string()))
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.filter(Option::is_some)
|
||||
.flatten()
|
||||
.collect::<Vec<_>>()
|
||||
.join("")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl FolderContent for DataCatalogue {
|
||||
fn get_library_folder(&self) -> String {
|
||||
self.ordner.bibliothek.name.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
@@ -141,6 +224,23 @@ pub struct Entry {
|
||||
revision: u16,
|
||||
}
|
||||
|
||||
impl Sortable for Entry {
|
||||
fn sorting_key(&self) -> String {
|
||||
self.name.clone()
|
||||
}
|
||||
|
||||
fn sorted(&mut self) -> &Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
if let Some(ref mut use_) = self.use_ {
|
||||
use_.program_module
|
||||
.sort_unstable_by_key(|item| item.sorting_key())
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Use {
|
||||
@@ -156,3 +256,9 @@ pub struct ProgramModule {
|
||||
#[serde(rename = "@name")]
|
||||
name: String,
|
||||
}
|
||||
|
||||
impl Sortable for ProgramModule {
|
||||
fn sorting_key(&self) -> String {
|
||||
format!("{}-{}", self.program, self.name)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,12 +22,20 @@
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::HashSet;
|
||||
|
||||
use console::style;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::checks::CheckNotice::ErrorWithCode;
|
||||
use crate::checks::{CheckNotice, Checkable};
|
||||
use crate::model::onkostar_editor::OnkostarEditor;
|
||||
use crate::model::requirements::{Requirement, Requires};
|
||||
use crate::model::{
|
||||
apply_profile_to_form_entry, Ansichten, Entries, Filter, FormEntry, FormEntryContainer,
|
||||
Listable, MenuCategory, PlausibilityRules, Script, Sortable,
|
||||
apply_profile_to_form_entry, apply_profile_to_form_field, Ansichten, Comparable, Entries,
|
||||
Filter, FolderContent, FormEntry, FormEntryContainer, Kennzahlen, Listable, MenuCategory,
|
||||
PlausibilityRules, PunkteKategorien, RefEntries, Script, Sortable,
|
||||
};
|
||||
use crate::model::{Haeufigkeiten, Ordner};
|
||||
use crate::profile::Profile;
|
||||
@@ -74,18 +82,26 @@ pub struct DataForm {
|
||||
#[serde(rename = "EmailTemplate")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
mail_template: Option<String>,
|
||||
#[serde(rename = "ErkrankungText")]
|
||||
erkrankung_text: String,
|
||||
#[serde(rename = "ErkrankungText", default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
erkrankung_text: Option<String>,
|
||||
#[serde(rename = "ErkrankungTextLong")]
|
||||
erkrankung_text_long: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
erkrankung_text_long: Option<String>,
|
||||
#[serde(rename = "ErkrankungProzedurText")]
|
||||
erkrankung_prozedur_text: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
erkrankung_prozedur_text: Option<String>,
|
||||
#[serde(rename = "ErkrankungSummary")]
|
||||
erkrankung_summary: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
erkrankung_summary: Option<String>,
|
||||
#[serde(rename = "ErkrankungBigSummary")]
|
||||
erkrankung_big_summary: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
erkrankung_big_summary: Option<String>,
|
||||
#[serde(rename = "Kontext")]
|
||||
kontext: String,
|
||||
#[serde(rename = "Datenart")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
datenart: Option<String>,
|
||||
#[serde(rename = "TudokReadonly")]
|
||||
tudok_readonly: bool,
|
||||
#[serde(rename = "VitalstatusRelevant")]
|
||||
@@ -132,6 +148,9 @@ pub struct DataForm {
|
||||
guid: String,
|
||||
#[serde(rename = "Revision")]
|
||||
revision: u16,
|
||||
#[serde(rename = "VerknuepftGUID")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
verknuepft_guid: Option<String>,
|
||||
#[serde(rename = "SeitenzahlSichtbar")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
seitenanzahl_sichtbar: Option<bool>,
|
||||
@@ -142,12 +161,15 @@ pub struct DataForm {
|
||||
#[serde(rename = "Haeufigkeiten")]
|
||||
haeufigkeiten: Haeufigkeiten,
|
||||
#[serde(rename = "Kennzahlen")]
|
||||
kennzahlen: String,
|
||||
kennzahlen: Kennzahlen,
|
||||
#[serde(rename = "Ordner")]
|
||||
ordner: Ordner,
|
||||
#[serde(rename = "MenuCategory")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
menu_category: Option<MenuCategory>,
|
||||
#[serde(rename = "PunkteKategorien")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
punkte_kategorien: Option<PunkteKategorien>,
|
||||
#[serde(rename = "Ansichten")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
ansichten: Option<Ansichten>,
|
||||
@@ -165,6 +187,12 @@ impl FormEntryContainer for DataForm {
|
||||
apply_profile_to_form_entry(entry, form_reference)
|
||||
});
|
||||
|
||||
// Hide form field using filter set to "false" if requested
|
||||
profile_form
|
||||
.form_fields
|
||||
.iter()
|
||||
.for_each(|form_field| apply_profile_to_form_field(entry, form_field));
|
||||
|
||||
if let Some(menu_category) = &profile_form.menu_category {
|
||||
self.menu_category = Some(MenuCategory {
|
||||
name: menu_category.name.clone(),
|
||||
@@ -181,9 +209,25 @@ impl FormEntryContainer for DataForm {
|
||||
impl Listable for DataForm {
|
||||
fn to_listed_string(&self) -> String {
|
||||
format!(
|
||||
"Formular '{}' in Revision '{}'",
|
||||
"Formular ({}) '{}' in Revision '{}' {}",
|
||||
match self.is_system_library_content() {
|
||||
true => style("S").yellow(),
|
||||
_ => style("u"),
|
||||
},
|
||||
style(&self.name).yellow(),
|
||||
style(&self.revision).yellow()
|
||||
style(&self.revision).yellow(),
|
||||
if self
|
||||
.entries
|
||||
.entry
|
||||
.iter()
|
||||
.filter(|entry| entry.procedure_date_status != "none")
|
||||
.count()
|
||||
== 0
|
||||
{
|
||||
style("Formular hat keine Angabe zum Prozedurdatum!").red()
|
||||
} else {
|
||||
style("")
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -192,6 +236,177 @@ impl Sortable for DataForm {
|
||||
fn sorting_key(&self) -> String {
|
||||
self.name.clone()
|
||||
}
|
||||
|
||||
fn sorted(&mut self) -> &Self {
|
||||
self.data_catalogues.data_catalogue.sort_unstable();
|
||||
|
||||
self.entries
|
||||
.entry
|
||||
.sort_unstable_by_key(|item| item.sorting_key());
|
||||
|
||||
self.entries.entry.iter_mut().for_each(|item| {
|
||||
item.sorted();
|
||||
});
|
||||
|
||||
if let Some(ref mut plausibility_rule) = self.plausibility_rules.plausibility_rule {
|
||||
plausibility_rule.sort_unstable_by_key(|item| item.bezeichnung.clone());
|
||||
|
||||
plausibility_rule.iter_mut().for_each(|item| {
|
||||
if let Some(ref mut data_form_entry_names) = item.data_form_entries.entry_name {
|
||||
data_form_entry_names.sort_unstable();
|
||||
}
|
||||
});
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Comparable for DataForm {
|
||||
fn get_name(&self) -> String {
|
||||
self.name.clone()
|
||||
}
|
||||
|
||||
fn get_revision(&self) -> u16 {
|
||||
self.revision
|
||||
}
|
||||
|
||||
fn compare_by_requirement(a: &Self, b: &Self) -> Ordering {
|
||||
if a.get_name() == b.get_name()
|
||||
|| a.is_system_library_content()
|
||||
|| b.is_system_library_content()
|
||||
{
|
||||
return Ordering::Equal;
|
||||
}
|
||||
|
||||
if a.requires_form_reference(&b.get_name()) || a.requires_subform(&b.get_name()) {
|
||||
return Ordering::Greater;
|
||||
}
|
||||
|
||||
Ordering::Less
|
||||
}
|
||||
}
|
||||
|
||||
impl Requires for DataForm {
|
||||
fn requires_form_reference(&self, name: &str) -> bool {
|
||||
self.entries
|
||||
.entry
|
||||
.iter()
|
||||
.map(|item| {
|
||||
item.type_ == "formReference"
|
||||
&& match item.referenced_data_form.as_ref() {
|
||||
Some(refname) => refname == name,
|
||||
_ => false,
|
||||
}
|
||||
})
|
||||
.filter(|&it| it)
|
||||
.last()
|
||||
.unwrap_or_default()
|
||||
}
|
||||
fn requires_subform(&self, name: &str) -> bool {
|
||||
self.entries
|
||||
.entry
|
||||
.iter()
|
||||
.map(|item| {
|
||||
item.type_ == "subform"
|
||||
&& match item.referenced_data_form.as_ref() {
|
||||
Some(refname) => refname == name,
|
||||
_ => false,
|
||||
}
|
||||
})
|
||||
.filter(|&it| it)
|
||||
.last()
|
||||
.unwrap_or_default()
|
||||
}
|
||||
fn get_required_entries<'a>(&'a self, all: &'a OnkostarEditor) -> Vec<Requirement> {
|
||||
let mut result = self
|
||||
.data_catalogues
|
||||
.data_catalogue
|
||||
.iter()
|
||||
.collect::<HashSet<_>>()
|
||||
.into_iter()
|
||||
.map(|entry| match all.find_data_catalogue(entry.as_str()) {
|
||||
Some(contained) => Requirement::DataCatalogue(contained),
|
||||
None => Requirement::ExternalDataCatalogue(entry.to_string()),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
result.sort_unstable_by_key(|item| item.sorting_key());
|
||||
|
||||
let referenced_forms = &mut self
|
||||
.entries
|
||||
.entry
|
||||
.iter()
|
||||
.filter(|&entry| entry.get_type() == "formReference")
|
||||
.filter_map(|entry| match &entry.referenced_data_form {
|
||||
Some(name) => Some(name),
|
||||
None => None,
|
||||
})
|
||||
.collect::<HashSet<_>>()
|
||||
.into_iter()
|
||||
.map(|entry| match all.find_data_form(entry.as_str()) {
|
||||
Some(contained) => Requirement::DataFormReference(contained),
|
||||
None => match all.find_unterformular(entry.as_str()) {
|
||||
Some(contained) => Requirement::UnterformularReference(contained),
|
||||
None => Requirement::ExternalUnterformularReference(entry.to_string()),
|
||||
},
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
referenced_forms.sort_unstable_by_key(|item| item.sorting_key());
|
||||
result.append(referenced_forms);
|
||||
|
||||
let sub_forms = &mut self
|
||||
.entries
|
||||
.entry
|
||||
.iter()
|
||||
.filter(|&entry| entry.get_type() == "subform")
|
||||
.filter_map(|entry| match &entry.referenced_data_form {
|
||||
Some(name) => Some(name),
|
||||
None => None,
|
||||
})
|
||||
.collect::<HashSet<_>>()
|
||||
.into_iter()
|
||||
.map(|entry| match all.find_data_form(entry.as_str()) {
|
||||
Some(contained) => Requirement::DataFormSubform(contained),
|
||||
None => match all.find_unterformular(entry.as_str()) {
|
||||
Some(contained) => Requirement::UnterformularSubform(contained),
|
||||
None => Requirement::ExternalUnterformularSubform(entry.to_string()),
|
||||
},
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
sub_forms.sort_unstable_by_key(|item| item.sorting_key());
|
||||
result.append(sub_forms);
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl FolderContent for DataForm {
|
||||
fn get_library_folder(&self) -> String {
|
||||
self.ordner.bibliothek.name.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl Checkable for DataForm {
|
||||
fn check(&self) -> Vec<CheckNotice> {
|
||||
if self
|
||||
.entries
|
||||
.entry
|
||||
.iter()
|
||||
.filter(|entry| entry.procedure_date_status != "none")
|
||||
.count()
|
||||
== 0
|
||||
{
|
||||
return vec![ErrorWithCode {
|
||||
code: "2023-0002".to_string(),
|
||||
description: format!(
|
||||
"Formular '{}' hat keine Angabe zum Prozedurdatum",
|
||||
self.name
|
||||
),
|
||||
line: None,
|
||||
example: None,
|
||||
}];
|
||||
}
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
@@ -262,6 +477,9 @@ pub struct Entry {
|
||||
grafik_ausrichtung: Option<String>,
|
||||
#[serde(rename = "Mandatory")]
|
||||
mandatory: String,
|
||||
#[serde(rename = "Datenart", default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
datenart: Option<String>,
|
||||
#[serde(rename = "Filter")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
filter: Option<Filter>,
|
||||
@@ -281,6 +499,9 @@ pub struct Entry {
|
||||
#[serde(rename = "AnzeigeAuswahl")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
anzeige_auswahl: Option<String>,
|
||||
#[serde(rename = "Druckvorlage")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
druckvorlage: Option<String>,
|
||||
#[serde(rename = "VersionFrom")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
version_from: Option<String>,
|
||||
@@ -288,6 +509,9 @@ pub struct Entry {
|
||||
speichern: String,
|
||||
#[serde(rename = "LeerAusblenden")]
|
||||
leer_ausblenden: bool,
|
||||
#[serde(rename = "Inhalt")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
inhalt: Option<String>,
|
||||
#[serde(rename = "GeschlossenAnzeigen")]
|
||||
geschlossen_anzeigen: bool,
|
||||
#[serde(rename = "Min")]
|
||||
@@ -297,7 +521,8 @@ pub struct Entry {
|
||||
#[serde(rename = "InUebersichtAnzeigen")]
|
||||
in_uebersicht_anzeigen: bool,
|
||||
#[serde(rename = "Hinweis")]
|
||||
hinweis: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
hinweis: Option<String>,
|
||||
#[serde(rename = "Vorschlagskategorie")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
vorschlagskategorie: Option<String>,
|
||||
@@ -369,12 +594,21 @@ pub struct Entry {
|
||||
in_auswertung: bool,
|
||||
#[serde(rename = "InAuswertungGraph")]
|
||||
in_auswertung_graph: bool,
|
||||
#[serde(rename = "FragebogenItemNummer")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
fragebogen_item_nummer: Option<u8>,
|
||||
#[serde(rename = "Score")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
score: Option<String>,
|
||||
#[serde(rename = "AlignmentPatModul")]
|
||||
alignment_pat_modul: String,
|
||||
#[serde(rename = "DirectionPatModul")]
|
||||
direction_pat_modul: String,
|
||||
#[serde(rename = "SeitenumbruchPatModul")]
|
||||
seitenumbruch_pat_modul: bool,
|
||||
#[serde(rename = "Kontaktliste")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
kontaktliste: Option<String>,
|
||||
#[serde(rename = "MarkierungIgnorieren")]
|
||||
markierung_ignorieren: bool,
|
||||
#[serde(rename = "SucheArt")]
|
||||
@@ -421,6 +655,35 @@ impl FormEntry for Entry {
|
||||
valid: true,
|
||||
});
|
||||
}
|
||||
|
||||
fn hide(&mut self) {
|
||||
self.filter = Some(Filter {
|
||||
condition: "false".into(),
|
||||
valid: true,
|
||||
ref_entries: Some(RefEntries { ref_entry: None }),
|
||||
});
|
||||
self.speichern = "0".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Sortable for Entry {
|
||||
fn sorting_key(&self) -> String {
|
||||
self.name.clone()
|
||||
}
|
||||
|
||||
fn sorted(&mut self) -> &Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
if let Some(ref mut filter) = self.filter {
|
||||
if let Some(ref mut ref_entries) = filter.ref_entries {
|
||||
if let Some(ref mut ref_entry) = ref_entries.ref_entry {
|
||||
ref_entry.sort_unstable()
|
||||
}
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
|
||||
149
src/model/mod.rs
149
src/model/mod.rs
@@ -22,13 +22,21 @@
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
use crate::profile::{FormReference, Profile};
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::fmt::Debug;
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::model::requirements::Requires;
|
||||
use crate::profile::{FormField, FormReference, Profile};
|
||||
|
||||
pub mod data_catalogue;
|
||||
pub mod data_form;
|
||||
pub mod onkostar_editor;
|
||||
pub mod property_catalogue;
|
||||
pub mod requirements;
|
||||
pub mod unterformular;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
@@ -88,11 +96,15 @@ pub struct Ansicht {
|
||||
#[serde(rename = "Konfiguration")]
|
||||
konfiguration: String,
|
||||
#[serde(rename = "DataForm")]
|
||||
data_form: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
data_form: Option<String>,
|
||||
#[serde(rename = "DataCatalogue")]
|
||||
data_catalogue: String,
|
||||
#[serde(rename = "TypAuswahl")]
|
||||
typ_auswahl: String,
|
||||
#[serde(rename = "PersonenstammKontext", default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
personenstamm_kontext: Option<String>,
|
||||
#[serde(rename = "Suche")]
|
||||
suche: bool,
|
||||
#[serde(rename = "SID")]
|
||||
@@ -123,6 +135,66 @@ pub struct MenuCategory {
|
||||
column: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct PunkteKategorien {
|
||||
#[serde(rename = "PunkteKategorie", default)]
|
||||
punkte_kategorie: Vec<PunkteKategorie>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct PunkteKategorie {
|
||||
#[serde(rename = "Name")]
|
||||
name: String,
|
||||
#[serde(rename = "Beschreibung")]
|
||||
beschreibung: String,
|
||||
#[serde(rename = "MaxLeerwerte")]
|
||||
max_leerwerte: u16,
|
||||
#[serde(rename = "Berechnung")]
|
||||
berechnung: String,
|
||||
#[serde(rename = "Felder")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
felder: Option<Felder>,
|
||||
#[serde(rename = "Vergleichswerttabellen")]
|
||||
vergleichswerttabellen: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Felder {
|
||||
#[serde(rename = "Feld", default)]
|
||||
feld: Vec<Feld>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Feld {
|
||||
#[serde(rename = "DataFormEntryName")]
|
||||
data_form_entry_name: String,
|
||||
#[serde(rename = "ManuellePunkte")]
|
||||
manuelle_punkte: bool,
|
||||
#[serde(rename = "Werte")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
werte: Option<FeldWerte>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct FeldWerte {
|
||||
#[serde(rename = "Wert", default)]
|
||||
wert: Vec<FeldWert>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct FeldWert {
|
||||
#[serde(rename = "Wert")]
|
||||
wert: String,
|
||||
#[serde(rename = "Punkte")]
|
||||
punkte: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Filter {
|
||||
@@ -181,8 +253,12 @@ pub struct Haeufigkeit {
|
||||
taeglich_aktualisieren: bool,
|
||||
#[serde(rename = "Typ")]
|
||||
typ: String,
|
||||
#[serde(rename = "NichtBerechnen")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
nicht_berechnen: Option<String>,
|
||||
#[serde(rename = "TabellenName")]
|
||||
tabellen_name: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
tabellen_name: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
@@ -206,6 +282,32 @@ pub struct Ordner {
|
||||
parent_order: Option<Box<Ordner>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Kennzahlen {
|
||||
#[serde(rename = "Kennzahl", default)]
|
||||
kennzahl: Vec<Kennzahl>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Kennzahl {
|
||||
#[serde(rename = "Name")]
|
||||
name: String,
|
||||
#[serde(rename = "Nummer")]
|
||||
nummer: String,
|
||||
#[serde(rename = "Beschreibung")]
|
||||
beschreibung: String,
|
||||
#[serde(rename = "Notiz")]
|
||||
notiz: String,
|
||||
#[serde(rename = "Vorgabe")]
|
||||
vorgabe: String,
|
||||
#[serde(rename = "Haeufigkeitenzaehler")]
|
||||
haeufigkeitenzaehler: String,
|
||||
#[serde(rename = "Haeufigkeitennenner")]
|
||||
haeufigkeitennenner: String,
|
||||
}
|
||||
|
||||
fn apply_profile_to_form_entry<E>(entry: &mut E, form_reference: &FormReference)
|
||||
where
|
||||
E: FormEntry,
|
||||
@@ -226,6 +328,15 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_profile_to_form_field<E>(entry: &mut E, form_field: &FormField)
|
||||
where
|
||||
E: FormEntry,
|
||||
{
|
||||
if entry.get_name() == form_field.name && form_field.hide {
|
||||
entry.hide()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait FormEntryContainer {
|
||||
fn apply_profile(&mut self, profile: &Profile);
|
||||
}
|
||||
@@ -236,6 +347,29 @@ pub trait Listable {
|
||||
|
||||
pub trait Sortable {
|
||||
fn sorting_key(&self) -> String;
|
||||
|
||||
fn sorted(&mut self) -> &Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Comparable: Debug {
|
||||
fn get_name(&self) -> String;
|
||||
fn get_revision(&self) -> u16;
|
||||
fn get_hash(&self) -> String {
|
||||
let mut h = DefaultHasher::new();
|
||||
format!("{:?}", self).hash(&mut h);
|
||||
h.finish().to_string()
|
||||
}
|
||||
fn compare_by_requirement(_: &Self, _: &Self) -> Ordering
|
||||
where
|
||||
Self: Requires,
|
||||
{
|
||||
Ordering::Equal
|
||||
}
|
||||
}
|
||||
|
||||
pub trait FormEntry {
|
||||
@@ -245,4 +379,13 @@ pub trait FormEntry {
|
||||
fn update_anzeige(&mut self, value: String);
|
||||
fn update_anzeige_auswahl(&mut self, value: String);
|
||||
fn update_scripts_code(&mut self, value: String);
|
||||
fn hide(&mut self);
|
||||
}
|
||||
|
||||
pub trait FolderContent {
|
||||
fn get_library_folder(&self) -> String;
|
||||
|
||||
fn is_system_library_content(&self) -> bool {
|
||||
"ONKOSTAR Bibliothek" == self.get_library_folder()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* 2023 Comprehensive Cancer Center Mainfranken
|
||||
* Copyright (c) 2023 Comprehensive Cancer Center Mainfranken
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -22,16 +22,21 @@
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
use std::cmp::Ordering;
|
||||
use std::fmt::Debug;
|
||||
use std::str::FromStr;
|
||||
|
||||
use console::style;
|
||||
use quick_xml::de::from_str;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::checks::{CheckNotice, Checkable};
|
||||
use crate::model::data_catalogue::DataCatalogue;
|
||||
use crate::model::data_form::DataForm;
|
||||
use crate::model::property_catalogue::PropertyCatalogue;
|
||||
use crate::model::requirements::{Requirement, Requires};
|
||||
use crate::model::unterformular::Unterformular;
|
||||
use crate::model::{FormEntryContainer, Listable, Sortable};
|
||||
use crate::model::{Comparable, FolderContent, FormEntryContainer, Listable, Sortable};
|
||||
use crate::profile::Profile;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
@@ -44,13 +49,73 @@ pub struct OnkostarEditor {
|
||||
}
|
||||
|
||||
impl OnkostarEditor {
|
||||
pub fn find_property_catalogue<'a>(&'a self, name: &str) -> Option<&'a PropertyCatalogue> {
|
||||
match self
|
||||
.editor
|
||||
.property_catalogue
|
||||
.iter()
|
||||
.filter(|&item| item.get_name().eq_ignore_ascii_case(name))
|
||||
.nth(0)
|
||||
{
|
||||
Some(x) => Some(x),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find_data_catalogue<'a>(&'a self, name: &str) -> Option<&'a DataCatalogue> {
|
||||
match self
|
||||
.editor
|
||||
.data_catalogue
|
||||
.iter()
|
||||
.filter(|&item| item.get_name().eq_ignore_ascii_case(name))
|
||||
.nth(0)
|
||||
{
|
||||
Some(x) => Some(x),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find_data_form<'a>(&'a self, name: &str) -> Option<&'a DataForm> {
|
||||
match self
|
||||
.editor
|
||||
.data_form
|
||||
.iter()
|
||||
.filter(|&item| item.get_name().eq_ignore_ascii_case(name))
|
||||
.nth(0)
|
||||
{
|
||||
Some(x) => Some(x),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find_unterformular<'a>(&'a self, name: &str) -> Option<&'a Unterformular> {
|
||||
match self
|
||||
.editor
|
||||
.unterformular
|
||||
.iter()
|
||||
.filter(|&item| item.get_name().eq_ignore_ascii_case(name))
|
||||
.nth(0)
|
||||
{
|
||||
Some(x) => Some(x),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply_profile(&mut self, profile: &Profile) {
|
||||
self.editor.data_form.iter_mut().for_each(|data_form| {
|
||||
data_form.apply_profile(profile);
|
||||
});
|
||||
self.editor.unterformular.iter_mut().for_each(|data_form| {
|
||||
data_form.apply_profile(profile);
|
||||
})
|
||||
self.editor
|
||||
.data_form
|
||||
.iter_mut()
|
||||
.filter(|data_form| !data_form.is_system_library_content())
|
||||
.for_each(|data_form| {
|
||||
data_form.apply_profile(profile);
|
||||
});
|
||||
self.editor
|
||||
.unterformular
|
||||
.iter_mut()
|
||||
.filter(|data_form| !data_form.is_system_library_content())
|
||||
.for_each(|data_form| {
|
||||
data_form.apply_profile(profile);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn print_list(&self) {
|
||||
@@ -66,28 +131,271 @@ impl OnkostarEditor {
|
||||
Self::print_items("Unterformulare", &self.editor.unterformular);
|
||||
}
|
||||
|
||||
fn filter_by_name_contains(&mut self, name: &str) {
|
||||
self.editor
|
||||
.property_catalogue
|
||||
.retain(|e| e.get_name().contains(name));
|
||||
self.editor
|
||||
.data_catalogue
|
||||
.retain(|e| e.get_name().contains(name));
|
||||
self.editor
|
||||
.data_form
|
||||
.retain(|e| e.get_name().contains(name));
|
||||
self.editor
|
||||
.unterformular
|
||||
.retain(|e| e.get_name().contains(name));
|
||||
}
|
||||
pub fn print_list_filtered(&mut self, name: &str) {
|
||||
println!(
|
||||
"Die Datei wurde am {} mit {} in Version {} erstellt.\n\nFolgende Inhalte für '{}' sind gespeichert",
|
||||
style(&self.info_xml.datum_xml).yellow(),
|
||||
style(&self.info_xml.name).yellow(),
|
||||
style(&self.info_xml.version).yellow(),
|
||||
name
|
||||
);
|
||||
|
||||
self.filter_by_name_contains(name);
|
||||
|
||||
Self::print_items("Merkmalskataloge", &self.editor.property_catalogue);
|
||||
Self::print_items("Datenkataloge", &self.editor.data_catalogue);
|
||||
Self::print_items("Formulare", &self.editor.data_form);
|
||||
Self::print_items("Unterformulare", &self.editor.unterformular);
|
||||
}
|
||||
|
||||
fn print_items(title: &str, list: &[impl Listable]) {
|
||||
println!("\n{} {}", list.len(), style(title).underlined());
|
||||
print!("\n{} {}", list.len(), style(title).underlined());
|
||||
println!(
|
||||
" - Inhalte der Systembibliothek sind mit ({}), der Benutzerbibliothek mit (u) markiert",
|
||||
style("S").yellow()
|
||||
);
|
||||
list.iter()
|
||||
.for_each(|entry| println!("{}", entry.to_listed_string()));
|
||||
}
|
||||
|
||||
pub fn print_tree(&self) {
|
||||
println!(
|
||||
"Die Datei wurde am {} mit {} in Version {} erstellt.\n\nFolgende Inhalte sind gespeichert",
|
||||
style(&self.info_xml.datum_xml).yellow(),
|
||||
style(&self.info_xml.name).yellow(),
|
||||
style(&self.info_xml.version).yellow()
|
||||
);
|
||||
|
||||
Self::print_items("Merkmalskataloge", &self.editor.property_catalogue);
|
||||
self.print_items_tree("Datenkataloge", &self.editor.data_catalogue);
|
||||
self.print_items_tree("Formulare", &self.editor.data_form);
|
||||
self.print_items_tree("Unterformulare", &self.editor.unterformular);
|
||||
}
|
||||
|
||||
pub fn print_tree_filtered(&mut self, name: &str) {
|
||||
println!(
|
||||
"Die Datei wurde am {} mit {} in Version {} erstellt.\n\nFolgende Inhalte für '{}' sind gespeichert",
|
||||
style(&self.info_xml.datum_xml).yellow(),
|
||||
style(&self.info_xml.name).yellow(),
|
||||
style(&self.info_xml.version).yellow(),
|
||||
name
|
||||
);
|
||||
|
||||
self.filter_by_name_contains(name);
|
||||
|
||||
Self::print_items("Merkmalskataloge", &self.editor.property_catalogue);
|
||||
self.print_items_tree("Datenkataloge", &self.editor.data_catalogue);
|
||||
self.print_items_tree("Formulare", &self.editor.data_form);
|
||||
self.print_items_tree("Unterformulare", &self.editor.unterformular);
|
||||
}
|
||||
|
||||
fn print_items_tree(&self, title: &str, list: &[impl Requires]) {
|
||||
print!("\n{} {}", list.len(), style(title).underlined());
|
||||
println!(
|
||||
" - Inhalte der Systembibliothek sind mit ({}), der Benutzerbibliothek mit (u) markiert",
|
||||
style("S").yellow()
|
||||
);
|
||||
list.iter()
|
||||
.for_each(|entry| println!("{}", entry.to_requirement_string(self)));
|
||||
}
|
||||
|
||||
pub fn sorted(&mut self) {
|
||||
self.editor
|
||||
.property_catalogue
|
||||
.sort_unstable_by_key(|e| e.sorting_key());
|
||||
|
||||
self.editor.property_catalogue.iter_mut().for_each(|item| {
|
||||
item.sorted();
|
||||
});
|
||||
|
||||
self.editor
|
||||
.data_catalogue
|
||||
.sort_unstable_by_key(|e| e.sorting_key());
|
||||
|
||||
self.editor.data_catalogue.iter_mut().for_each(|item| {
|
||||
item.sorted();
|
||||
});
|
||||
|
||||
/*self.editor
|
||||
.data_form
|
||||
.sort_unstable_by_key(|e| e.sorting_key());*/
|
||||
|
||||
self.editor
|
||||
.data_form
|
||||
.sort_unstable_by_key(|e| e.sorting_key());
|
||||
.sort_unstable_by(DataForm::compare_by_requirement);
|
||||
|
||||
self.editor.data_form.iter_mut().for_each(|item| {
|
||||
item.sorted();
|
||||
});
|
||||
|
||||
/*self.editor
|
||||
.unterformular
|
||||
.sort_unstable_by_key(|e| e.sorting_key());*/
|
||||
|
||||
self.editor
|
||||
.unterformular
|
||||
.sort_unstable_by_key(|e| e.sorting_key());
|
||||
.sort_unstable_by(Unterformular::compare_by_requirement);
|
||||
|
||||
self.editor.unterformular.iter_mut().for_each(|item| {
|
||||
item.sorted();
|
||||
});
|
||||
}
|
||||
|
||||
pub fn strip_system_library_content(&mut self) {
|
||||
self.editor
|
||||
.property_catalogue
|
||||
.retain(|e| !e.is_system_library_content());
|
||||
|
||||
self.editor
|
||||
.data_catalogue
|
||||
.retain(|e| !e.is_system_library_content());
|
||||
|
||||
self.editor
|
||||
.data_form
|
||||
.retain(|e| !e.is_system_library_content());
|
||||
|
||||
self.editor
|
||||
.unterformular
|
||||
.retain(|e| !e.is_system_library_content());
|
||||
}
|
||||
|
||||
pub fn print_diff(&mut self, other: &mut Self, strict: bool) {
|
||||
println!();
|
||||
|
||||
println!(
|
||||
"Datei A wurde am {} mit {} in Version {} erstellt.",
|
||||
style(&self.info_xml.datum_xml).yellow(),
|
||||
style(&self.info_xml.name).yellow(),
|
||||
style(&self.info_xml.version).yellow()
|
||||
);
|
||||
|
||||
println!(
|
||||
"Datei B wurde am {} mit {} in Version {} erstellt.",
|
||||
style(&other.info_xml.datum_xml).yellow(),
|
||||
style(&other.info_xml.name).yellow(),
|
||||
style(&other.info_xml.version).yellow()
|
||||
);
|
||||
|
||||
self.sorted();
|
||||
other.sorted();
|
||||
|
||||
Self::print_item_diff(
|
||||
"Merkmalskataloge",
|
||||
&self.editor.property_catalogue,
|
||||
&other.editor.property_catalogue,
|
||||
strict,
|
||||
);
|
||||
Self::print_item_diff(
|
||||
"Datenkataloge",
|
||||
&self.editor.data_catalogue,
|
||||
&other.editor.data_catalogue,
|
||||
strict,
|
||||
);
|
||||
Self::print_item_diff(
|
||||
"Formulare",
|
||||
&self.editor.data_form,
|
||||
&other.editor.data_form,
|
||||
strict,
|
||||
);
|
||||
Self::print_item_diff(
|
||||
"Unterformulare",
|
||||
&self.editor.unterformular,
|
||||
&other.editor.unterformular,
|
||||
strict,
|
||||
);
|
||||
}
|
||||
|
||||
fn print_item_diff(
|
||||
title: &str,
|
||||
list_a: &[impl Comparable],
|
||||
list_b: &[impl Comparable],
|
||||
strict: bool,
|
||||
) {
|
||||
println!("\n{}", style(title).underlined());
|
||||
|
||||
let mut has_diff = false;
|
||||
|
||||
let names_a = list_a
|
||||
.iter()
|
||||
.map(|entry| entry.get_name())
|
||||
.collect::<Vec<_>>();
|
||||
let names_b = list_b
|
||||
.iter()
|
||||
.map(|entry| entry.get_name())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
names_b.iter().for_each(|entry| {
|
||||
if !names_a.contains(entry) {
|
||||
println!("{}: {}", entry, style("Nicht in Datei A enthalten!").red());
|
||||
has_diff = true;
|
||||
}
|
||||
});
|
||||
|
||||
names_a.iter().for_each(|entry| {
|
||||
if !names_b.contains(entry) {
|
||||
println!("{}: {}", entry, style("Nicht in Datei B enthalten!").red());
|
||||
has_diff = true;
|
||||
}
|
||||
});
|
||||
|
||||
list_a.iter().for_each(|entry_a| {
|
||||
list_b.iter().for_each(|entry_b| {
|
||||
if entry_a.get_name() == entry_b.get_name() {
|
||||
match entry_a.get_revision().cmp(&entry_b.get_revision()) {
|
||||
Ordering::Less => {
|
||||
println!(
|
||||
"{}: {} (Revision {} < Revision {})",
|
||||
entry_a.get_name(),
|
||||
style("Neuer in Datei B").yellow(),
|
||||
style(entry_a.get_revision()).blue(),
|
||||
style(entry_b.get_revision()).green()
|
||||
);
|
||||
has_diff = true;
|
||||
}
|
||||
Ordering::Greater => {
|
||||
println!(
|
||||
"{}: {} (Revision {} > Revision {})",
|
||||
entry_a.get_name(),
|
||||
style("Neuer in Datei A").yellow(),
|
||||
style(entry_a.get_revision()).green(),
|
||||
style(entry_b.get_revision()).blue()
|
||||
);
|
||||
has_diff = true;
|
||||
}
|
||||
_ => {
|
||||
if strict && entry_a.get_hash() != entry_b.get_hash() {
|
||||
println!(
|
||||
"{}: {} (z.B. GUID oder Reihenfolge von Unterelementen)",
|
||||
entry_a.get_name(),
|
||||
style("Inhaltlich verschieden").yellow()
|
||||
);
|
||||
has_diff = true;
|
||||
} else if strict {
|
||||
println!("{}: {}", entry_a.get_name(), style("Identisch").green())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if !has_diff {
|
||||
println!("Keine Unterschiede")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,6 +410,89 @@ impl FromStr for OnkostarEditor {
|
||||
}
|
||||
}
|
||||
|
||||
impl Checkable for OnkostarEditor {
|
||||
fn check(&self) -> Vec<CheckNotice> {
|
||||
// Inner form checks
|
||||
|
||||
let mut result = self
|
||||
.editor
|
||||
.data_form
|
||||
.iter()
|
||||
.flat_map(|entity| entity.check())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let other = &mut self
|
||||
.editor
|
||||
.unterformular
|
||||
.iter()
|
||||
.flat_map(|entity| entity.check())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
result.append(other);
|
||||
|
||||
// Check requirements
|
||||
|
||||
let mut requirement_checked_forms = vec![];
|
||||
|
||||
fn requirement_error(
|
||||
form: &impl Comparable,
|
||||
item: &impl Comparable,
|
||||
t: &str,
|
||||
) -> CheckNotice {
|
||||
CheckNotice::ErrorWithCode {
|
||||
code: "2023-0004".to_string(),
|
||||
description: format!(
|
||||
"'{}' hat einen Verweis auf zuvor nicht definiertes {t} '{}' (OSTARSUPP-13212)",
|
||||
form.get_name(),
|
||||
item.get_name()
|
||||
),
|
||||
line: None,
|
||||
example: None,
|
||||
}
|
||||
}
|
||||
|
||||
self.editor.unterformular.iter().for_each(|form| {
|
||||
requirement_checked_forms.push(form.get_name());
|
||||
form.get_required_entries(self)
|
||||
.iter()
|
||||
.for_each(|entry| match entry {
|
||||
Requirement::DataFormReference(&ref item) => {
|
||||
if !requirement_checked_forms.contains(&item.get_name()) {
|
||||
result.push(requirement_error(form, item, "Formular"))
|
||||
}
|
||||
}
|
||||
Requirement::UnterformularReference(&ref item) => {
|
||||
if !requirement_checked_forms.contains(&item.get_name()) {
|
||||
result.push(requirement_error(form, item, "Unterformular"))
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
});
|
||||
});
|
||||
|
||||
self.editor.data_form.iter().for_each(|form| {
|
||||
requirement_checked_forms.push(form.get_name());
|
||||
form.get_required_entries(self)
|
||||
.iter()
|
||||
.for_each(|entry| match entry {
|
||||
Requirement::DataFormReference(&ref item) => {
|
||||
if !requirement_checked_forms.contains(&item.get_name()) {
|
||||
result.push(requirement_error(form, item, "Formular"))
|
||||
}
|
||||
}
|
||||
Requirement::UnterformularReference(&ref item) => {
|
||||
if !requirement_checked_forms.contains(&item.get_name()) {
|
||||
result.push(requirement_error(form, item, "Unterformular"))
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
});
|
||||
});
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct InfoXML {
|
||||
@@ -116,12 +507,12 @@ pub struct InfoXML {
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
struct Editor {
|
||||
#[serde(rename = "PropertyCatalogue")]
|
||||
#[serde(rename = "PropertyCatalogue", default)]
|
||||
property_catalogue: Vec<PropertyCatalogue>,
|
||||
#[serde(rename = "DataCatalogue")]
|
||||
#[serde(rename = "DataCatalogue", default)]
|
||||
data_catalogue: Vec<DataCatalogue>,
|
||||
#[serde(rename = "Unterformular")]
|
||||
#[serde(rename = "Unterformular", default)]
|
||||
unterformular: Vec<Unterformular>,
|
||||
#[serde(rename = "DataForm")]
|
||||
#[serde(rename = "DataForm", default)]
|
||||
data_form: Vec<DataForm>,
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
use console::style;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::model::{Listable, Ordner, Sortable};
|
||||
use crate::model::{Comparable, FolderContent, Listable, Ordner, Sortable};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
@@ -56,7 +56,11 @@ pub struct PropertyCatalogue {
|
||||
impl Listable for PropertyCatalogue {
|
||||
fn to_listed_string(&self) -> String {
|
||||
format!(
|
||||
"Merkmalskatalog '{}' in Revision '{}'",
|
||||
"Merkmalskatalog ({}) '{}' in Revision '{}'",
|
||||
match self.is_system_library_content() {
|
||||
true => style("S").yellow(),
|
||||
_ => style("u"),
|
||||
},
|
||||
style(&self.name).yellow(),
|
||||
style(&self.revision).yellow()
|
||||
)
|
||||
@@ -67,6 +71,32 @@ impl Sortable for PropertyCatalogue {
|
||||
fn sorting_key(&self) -> String {
|
||||
self.name.clone()
|
||||
}
|
||||
|
||||
fn sorted(&mut self) -> &Self {
|
||||
if let Some(ref mut versions) = self.versions.entry {
|
||||
versions.sort_unstable_by_key(|item| item.version_number);
|
||||
versions.iter_mut().for_each(|version| {
|
||||
version.sorted();
|
||||
});
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Comparable for PropertyCatalogue {
|
||||
fn get_name(&self) -> String {
|
||||
self.name.clone()
|
||||
}
|
||||
|
||||
fn get_revision(&self) -> u16 {
|
||||
self.revision
|
||||
}
|
||||
}
|
||||
|
||||
impl FolderContent for PropertyCatalogue {
|
||||
fn get_library_folder(&self) -> String {
|
||||
self.ordner.bibliothek.name.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
@@ -97,12 +127,50 @@ pub struct Version {
|
||||
#[serde(rename = "Revision")]
|
||||
revision: u16,
|
||||
#[serde(rename = "Entries")]
|
||||
entries: VersionEntries,
|
||||
entries: Option<VersionEntries>,
|
||||
#[serde(rename = "Abbildung")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
abbildung: Option<Vec<Abbildung>>,
|
||||
#[serde(rename = "Categories")]
|
||||
categories: Categories,
|
||||
categories: Option<Categories>,
|
||||
}
|
||||
|
||||
impl Sortable for Version {
|
||||
fn sorting_key(&self) -> String {
|
||||
self.oid.clone()
|
||||
}
|
||||
|
||||
fn sorted(&mut self) -> &Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
if let Some(ref mut abbildung) = self.abbildung {
|
||||
abbildung.sort_unstable_by_key(|item| item.sorting_key());
|
||||
abbildung.iter_mut().for_each(|item| {
|
||||
item.sorted();
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(ref mut entries) = self.entries {
|
||||
entries
|
||||
.content
|
||||
.sort_unstable_by_key(|item| item.sorting_key());
|
||||
entries.content.iter_mut().for_each(|item| {
|
||||
item.sorted();
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(ref mut categories) = self.categories {
|
||||
categories
|
||||
.content
|
||||
.sort_unstable_by_key(|item| item.sorting_key());
|
||||
categories.content.iter_mut().for_each(|item| {
|
||||
item.sorted();
|
||||
});
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
@@ -120,17 +188,26 @@ pub struct VersionEntry {
|
||||
#[serde(rename = "ShortDescription")]
|
||||
short_description: String,
|
||||
#[serde(rename = "Description")]
|
||||
description: String,
|
||||
description: Option<String>,
|
||||
#[serde(rename = "Synonyms", default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
synonyms: Option<String>,
|
||||
#[serde(rename = "Note", default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
note: Option<String>,
|
||||
#[serde(rename = "Type", default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
type_: Option<String>,
|
||||
#[serde(rename = "Position")]
|
||||
position: String,
|
||||
}
|
||||
|
||||
impl Sortable for VersionEntry {
|
||||
fn sorting_key(&self) -> String {
|
||||
self.code.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Categories {
|
||||
@@ -155,6 +232,26 @@ pub struct Category {
|
||||
category_entries: CategoryEntries,
|
||||
}
|
||||
|
||||
impl Sortable for Category {
|
||||
fn sorting_key(&self) -> String {
|
||||
self.name.clone()
|
||||
}
|
||||
|
||||
fn sorted(&mut self) -> &Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.category_entries
|
||||
.content
|
||||
.sort_unstable_by_key(|item| item.sorting_key());
|
||||
self.category_entries.content.iter_mut().for_each(|item| {
|
||||
item.sorted();
|
||||
});
|
||||
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct CategoryEntries {
|
||||
@@ -181,6 +278,12 @@ pub struct CategoryEntry {
|
||||
note: Option<String>,
|
||||
}
|
||||
|
||||
impl Sortable for CategoryEntry {
|
||||
fn sorting_key(&self) -> String {
|
||||
self.code.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Abbildung {
|
||||
@@ -190,6 +293,24 @@ pub struct Abbildung {
|
||||
content: Vec<AbbildungEintrag>,
|
||||
}
|
||||
|
||||
impl Sortable for Abbildung {
|
||||
fn sorting_key(&self) -> String {
|
||||
self.ziel_mk_version_oid.clone()
|
||||
}
|
||||
|
||||
fn sorted(&mut self) -> &Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.content.sort_unstable_by_key(|item| item.sorting_key());
|
||||
self.content.iter_mut().for_each(|item| {
|
||||
item.sorted();
|
||||
});
|
||||
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct AbbildungEintrag {
|
||||
@@ -199,6 +320,12 @@ pub struct AbbildungEintrag {
|
||||
entry_to: AbbildungEntry,
|
||||
}
|
||||
|
||||
impl Sortable for AbbildungEintrag {
|
||||
fn sorting_key(&self) -> String {
|
||||
format!("{}-{}", self.entry_from.code, self.entry_to.code)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct AbbildungEntry {
|
||||
@@ -211,8 +338,9 @@ pub struct AbbildungEntry {
|
||||
#[serde(rename = "Synonyms")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
synonyms: Option<String>,
|
||||
#[serde(rename = "Note")]
|
||||
note: String,
|
||||
#[serde(rename = "Note", default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
note: Option<String>,
|
||||
#[serde(rename = "Position")]
|
||||
position: String,
|
||||
}
|
||||
|
||||
163
src/model/requirements.rs
Normal file
163
src/model/requirements.rs
Normal file
@@ -0,0 +1,163 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2023 Comprehensive Cancer Center Mainfranken
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
use crate::model::data_catalogue::DataCatalogue;
|
||||
use crate::model::data_form::DataForm;
|
||||
use crate::model::onkostar_editor::OnkostarEditor;
|
||||
use crate::model::property_catalogue::PropertyCatalogue;
|
||||
use crate::model::unterformular::Unterformular;
|
||||
use crate::model::{Comparable, Listable, Sortable};
|
||||
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
pub enum Requirement<'a> {
|
||||
PropertyCatalogue(&'a PropertyCatalogue),
|
||||
DataCatalogue(&'a DataCatalogue),
|
||||
ExternalPropertyCatalogue(String),
|
||||
ExternalDataCatalogue(String),
|
||||
DataFormReference(&'a DataForm),
|
||||
UnterformularReference(&'a Unterformular),
|
||||
#[allow(dead_code)]
|
||||
ExternalDataFormReference(String),
|
||||
ExternalUnterformularReference(String),
|
||||
|
||||
DataFormSubform(&'a DataForm),
|
||||
UnterformularSubform(&'a Unterformular),
|
||||
#[allow(dead_code)]
|
||||
ExternalDataFormSubform(String),
|
||||
ExternalUnterformularSubform(String),
|
||||
}
|
||||
|
||||
impl Sortable for Requirement<'_> {
|
||||
fn sorting_key(&self) -> String {
|
||||
match self {
|
||||
Requirement::PropertyCatalogue(item) => item.get_name(),
|
||||
Requirement::DataCatalogue(item) => item.get_name(),
|
||||
Requirement::DataFormReference(item) => item.get_name(),
|
||||
Requirement::UnterformularReference(item) => item.get_name(),
|
||||
Requirement::DataFormSubform(item) => item.get_name(),
|
||||
Requirement::UnterformularSubform(item) => item.get_name(),
|
||||
Requirement::ExternalPropertyCatalogue(name)
|
||||
| Requirement::ExternalDataCatalogue(name)
|
||||
| Requirement::ExternalDataFormReference(name)
|
||||
| Requirement::ExternalDataFormSubform(name)
|
||||
| Requirement::ExternalUnterformularReference(name)
|
||||
| Requirement::ExternalUnterformularSubform(name) => name.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToString for Requirement<'_> {
|
||||
fn to_string(&self) -> String {
|
||||
match self {
|
||||
Requirement::PropertyCatalogue(item) => item.to_listed_string(),
|
||||
Requirement::DataCatalogue(item) => item.to_listed_string(),
|
||||
Requirement::DataFormReference(item) => item.to_listed_string(),
|
||||
Requirement::UnterformularReference(item) => item.to_listed_string(),
|
||||
Requirement::DataFormSubform(item) => item.to_listed_string(),
|
||||
Requirement::UnterformularSubform(item) => item.to_listed_string(),
|
||||
Requirement::ExternalPropertyCatalogue(name) => {
|
||||
format!("Merkmalskatalog (-) '{}' - hier nicht enthalten", name)
|
||||
}
|
||||
Requirement::ExternalDataCatalogue(name) => {
|
||||
format!("Datenkatalog (-) '{}' - hier nicht enthalten", name)
|
||||
}
|
||||
Requirement::ExternalDataFormReference(name)
|
||||
| Requirement::ExternalDataFormSubform(name) => {
|
||||
format!("Formular (-) '{}' - hier nicht enthalten", name)
|
||||
}
|
||||
Requirement::ExternalUnterformularReference(name)
|
||||
| Requirement::ExternalUnterformularSubform(name) => {
|
||||
format!("Unterformular (-) '{}' - hier nicht enthalten", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Requires
|
||||
where
|
||||
Self: Listable,
|
||||
{
|
||||
fn requires_form_reference(&self, _: &str) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn requires_subform(&self, _: &str) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn get_required_entries<'a>(&'a self, all: &'a OnkostarEditor) -> Vec<Requirement>;
|
||||
|
||||
fn to_requirement_string<'a>(&'a self, all: &'a OnkostarEditor) -> String {
|
||||
format!(
|
||||
"{}\n{}",
|
||||
self.to_listed_string(),
|
||||
self.get_required_entries(all)
|
||||
.iter()
|
||||
.map(|entry| match entry {
|
||||
Requirement::DataCatalogue(x) => {
|
||||
let inner = x
|
||||
.get_required_entries(all)
|
||||
.iter()
|
||||
.map(|inner_entry| match inner_entry {
|
||||
Requirement::PropertyCatalogue(_) => Some(inner_entry.to_string()),
|
||||
Requirement::ExternalPropertyCatalogue(_) => {
|
||||
Some(inner_entry.to_string())
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.filter(Option::is_some)
|
||||
.map(|item| format!(" - {}\n", item.unwrap()))
|
||||
.collect::<Vec<_>>()
|
||||
.join("");
|
||||
|
||||
if inner.is_empty() {
|
||||
Some(format!(" + {}\n", x.to_listed_string()))
|
||||
} else {
|
||||
Some(format!(" + {}\n{}", x.to_listed_string(), inner))
|
||||
}
|
||||
}
|
||||
Requirement::ExternalDataCatalogue(_) => {
|
||||
Some(format!(" + {}\n", entry.to_string()))
|
||||
}
|
||||
Requirement::DataFormReference(_)
|
||||
| Requirement::ExternalDataFormReference(_)
|
||||
| Requirement::UnterformularReference(_)
|
||||
| Requirement::ExternalUnterformularReference(_) => {
|
||||
Some(format!(" > {}\n", entry.to_string()))
|
||||
}
|
||||
Requirement::DataFormSubform(_)
|
||||
| Requirement::ExternalDataFormSubform(_)
|
||||
| Requirement::UnterformularSubform(_)
|
||||
| Requirement::ExternalUnterformularSubform(_) => {
|
||||
Some(format!(" * {}\n", entry.to_string()))
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.filter(Option::is_some)
|
||||
.flatten()
|
||||
.collect::<Vec<_>>()
|
||||
.join("")
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -22,12 +22,20 @@
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::HashSet;
|
||||
|
||||
use crate::checks::CheckNotice::ErrorWithCode;
|
||||
use crate::checks::{CheckNotice, Checkable};
|
||||
use console::style;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::model::onkostar_editor::OnkostarEditor;
|
||||
use crate::model::requirements::{Requirement, Requires};
|
||||
use crate::model::{
|
||||
apply_profile_to_form_entry, Ansichten, Entries, Filter, FormEntry, FormEntryContainer,
|
||||
Listable, MenuCategory, PlausibilityRules, Script, Sortable,
|
||||
apply_profile_to_form_entry, apply_profile_to_form_field, Ansichten, Comparable, Entries,
|
||||
Filter, FolderContent, FormEntry, FormEntryContainer, Kennzahlen, Listable, MenuCategory,
|
||||
PlausibilityRules, PunkteKategorien, RefEntries, Script, Sortable,
|
||||
};
|
||||
use crate::model::{Haeufigkeiten, Ordner};
|
||||
use crate::profile::Profile;
|
||||
@@ -74,7 +82,7 @@ pub struct Unterformular {
|
||||
#[serde(rename = "EmailTemplate")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
mail_template: Option<String>,
|
||||
#[serde(rename = "ErkrankungText")]
|
||||
#[serde(rename = "ErkrankungText", default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
erkrankung_text: Option<String>,
|
||||
#[serde(rename = "ErkrankungTextLong")]
|
||||
@@ -91,6 +99,9 @@ pub struct Unterformular {
|
||||
erkrankung_big_summary: Option<String>,
|
||||
#[serde(rename = "Kontext")]
|
||||
kontext: String,
|
||||
#[serde(rename = "Datenart")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
datenart: Option<String>,
|
||||
#[serde(rename = "TudokReadonly")]
|
||||
tudok_readonly: bool,
|
||||
#[serde(rename = "VitalstatusRelevant")]
|
||||
@@ -153,12 +164,15 @@ pub struct Unterformular {
|
||||
#[serde(rename = "Haeufigkeiten")]
|
||||
haeufigkeiten: Haeufigkeiten,
|
||||
#[serde(rename = "Kennzahlen")]
|
||||
kennzahlen: String,
|
||||
kennzahlen: Kennzahlen,
|
||||
#[serde(rename = "Ordner")]
|
||||
ordner: Ordner,
|
||||
#[serde(rename = "MenuCategory")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
menu_category: Option<MenuCategory>,
|
||||
#[serde(rename = "PunkteKategorien")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
punkte_kategorien: Option<PunkteKategorien>,
|
||||
#[serde(rename = "Ansichten")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
ansichten: Option<Ansichten>,
|
||||
@@ -176,6 +190,12 @@ impl FormEntryContainer for Unterformular {
|
||||
apply_profile_to_form_entry(entry, form_reference)
|
||||
});
|
||||
|
||||
// Hide form field using filter set to "false" if requested
|
||||
profile_form
|
||||
.form_fields
|
||||
.iter()
|
||||
.for_each(|form_field| apply_profile_to_form_field(entry, form_field));
|
||||
|
||||
if let Some(menu_category) = &profile_form.menu_category {
|
||||
self.menu_category = Some(MenuCategory {
|
||||
name: menu_category.name.clone(),
|
||||
@@ -191,18 +211,19 @@ impl FormEntryContainer for Unterformular {
|
||||
|
||||
impl Listable for Unterformular {
|
||||
fn to_listed_string(&self) -> String {
|
||||
if self.hat_unterformulare {
|
||||
return format!(
|
||||
"Unterformular '{}' in Revision '{}' {}",
|
||||
style(&self.name).yellow(),
|
||||
style(&self.revision).yellow(),
|
||||
style("Unterformular mit Markierung 'hat Unterformulare'!").red()
|
||||
);
|
||||
}
|
||||
format!(
|
||||
"Unterformular '{}' in Revision '{}'",
|
||||
"Unterformular ({}) '{}' in Revision '{}' {}",
|
||||
match self.is_system_library_content() {
|
||||
true => style("S").yellow(),
|
||||
_ => style("u"),
|
||||
},
|
||||
style(&self.name).yellow(),
|
||||
style(&self.revision).yellow()
|
||||
style(&self.revision).yellow(),
|
||||
if self.hat_unterformulare {
|
||||
style("Unterformular mit Markierung 'hat Unterformulare'!").red()
|
||||
} else {
|
||||
style("")
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -211,6 +232,172 @@ impl Sortable for Unterformular {
|
||||
fn sorting_key(&self) -> String {
|
||||
self.name.clone()
|
||||
}
|
||||
|
||||
fn sorted(&mut self) -> &Self {
|
||||
self.data_catalogues.data_catalogue.sort_unstable();
|
||||
|
||||
self.entries
|
||||
.entry
|
||||
.sort_unstable_by_key(|item| item.sorting_key());
|
||||
|
||||
self.entries.entry.iter_mut().for_each(|item| {
|
||||
item.sorted();
|
||||
});
|
||||
|
||||
if let Some(ref mut plausibility_rule) = self.plausibility_rules.plausibility_rule {
|
||||
plausibility_rule.sort_unstable_by_key(|item| item.bezeichnung.clone());
|
||||
|
||||
plausibility_rule.iter_mut().for_each(|item| {
|
||||
if let Some(ref mut data_form_entry_names) = item.data_form_entries.entry_name {
|
||||
data_form_entry_names.sort_unstable();
|
||||
}
|
||||
});
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Comparable for Unterformular {
|
||||
fn get_name(&self) -> String {
|
||||
self.name.clone()
|
||||
}
|
||||
|
||||
fn get_revision(&self) -> u16 {
|
||||
self.revision
|
||||
}
|
||||
|
||||
fn compare_by_requirement(a: &Self, b: &Self) -> Ordering {
|
||||
if a.get_name() == b.get_name()
|
||||
|| a.is_system_library_content()
|
||||
|| b.is_system_library_content()
|
||||
{
|
||||
return Ordering::Equal;
|
||||
}
|
||||
|
||||
if a.requires_form_reference(&b.get_name()) || a.requires_subform(&b.get_name()) {
|
||||
return Ordering::Greater;
|
||||
}
|
||||
|
||||
Ordering::Less
|
||||
}
|
||||
}
|
||||
|
||||
impl Requires for Unterformular {
|
||||
fn requires_form_reference(&self, name: &str) -> bool {
|
||||
self.entries
|
||||
.entry
|
||||
.iter()
|
||||
.map(|item| {
|
||||
item.type_ == "formReference"
|
||||
&& match item.referenced_data_form.as_ref() {
|
||||
Some(refname) => refname == name,
|
||||
_ => false,
|
||||
}
|
||||
})
|
||||
.filter(|&it| it)
|
||||
.last()
|
||||
.unwrap_or_default()
|
||||
}
|
||||
fn requires_subform(&self, name: &str) -> bool {
|
||||
self.entries
|
||||
.entry
|
||||
.iter()
|
||||
.map(|item| {
|
||||
item.type_ == "subform"
|
||||
&& match item.referenced_data_form.as_ref() {
|
||||
Some(refname) => refname == name,
|
||||
_ => false,
|
||||
}
|
||||
})
|
||||
.filter(|&it| it)
|
||||
.last()
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn get_required_entries<'a>(&'a self, all: &'a OnkostarEditor) -> Vec<Requirement> {
|
||||
let mut result = self
|
||||
.data_catalogues
|
||||
.data_catalogue
|
||||
.iter()
|
||||
.collect::<HashSet<_>>()
|
||||
.into_iter()
|
||||
.map(|entry| match all.find_data_catalogue(entry.as_str()) {
|
||||
Some(contained) => Requirement::DataCatalogue(contained),
|
||||
None => Requirement::ExternalDataCatalogue(entry.to_string()),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
result.sort_unstable_by_key(|item| item.sorting_key());
|
||||
|
||||
let referenced_forms = &mut self
|
||||
.entries
|
||||
.entry
|
||||
.iter()
|
||||
.filter(|&entry| entry.get_type() == "formReference")
|
||||
.filter_map(|entry| match &entry.referenced_data_form {
|
||||
Some(name) => Some(name),
|
||||
None => None,
|
||||
})
|
||||
.collect::<HashSet<_>>()
|
||||
.into_iter()
|
||||
.map(|entry| match all.find_data_form(entry.as_str()) {
|
||||
Some(contained) => Requirement::DataFormReference(contained),
|
||||
None => match all.find_unterformular(entry.as_str()) {
|
||||
Some(contained) => Requirement::UnterformularReference(contained),
|
||||
None => Requirement::ExternalUnterformularReference(entry.to_string()),
|
||||
},
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
referenced_forms.sort_unstable_by_key(|item| item.sorting_key());
|
||||
result.append(referenced_forms);
|
||||
|
||||
let sub_forms = &mut self
|
||||
.entries
|
||||
.entry
|
||||
.iter()
|
||||
.filter(|&entry| entry.get_type() == "subform")
|
||||
.filter_map(|entry| match &entry.referenced_data_form {
|
||||
Some(name) => Some(name),
|
||||
None => None,
|
||||
})
|
||||
.collect::<HashSet<_>>()
|
||||
.into_iter()
|
||||
.map(|entry| match all.find_data_form(entry.as_str()) {
|
||||
Some(contained) => Requirement::DataFormSubform(contained),
|
||||
None => match all.find_unterformular(entry.as_str()) {
|
||||
Some(contained) => Requirement::UnterformularSubform(contained),
|
||||
None => Requirement::ExternalUnterformularSubform(entry.to_string()),
|
||||
},
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
sub_forms.sort_unstable_by_key(|item| item.sorting_key());
|
||||
result.append(sub_forms);
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl FolderContent for Unterformular {
|
||||
fn get_library_folder(&self) -> String {
|
||||
self.ordner.bibliothek.name.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl Checkable for Unterformular {
|
||||
fn check(&self) -> Vec<CheckNotice> {
|
||||
if self.hat_unterformulare {
|
||||
return vec![ErrorWithCode {
|
||||
code: "2023-0001".to_string(),
|
||||
description: format!(
|
||||
"Unterformular '{}' mit Markierung 'hat Unterformulare'",
|
||||
self.name
|
||||
),
|
||||
line: None,
|
||||
example: None,
|
||||
}];
|
||||
}
|
||||
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
@@ -281,6 +468,9 @@ pub struct Entry {
|
||||
grafik_ausrichtung: Option<String>,
|
||||
#[serde(rename = "Mandatory")]
|
||||
mandatory: String,
|
||||
#[serde(rename = "Datenart", default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
datenart: Option<String>,
|
||||
#[serde(rename = "Filter")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
filter: Option<Filter>,
|
||||
@@ -300,6 +490,9 @@ pub struct Entry {
|
||||
#[serde(rename = "AnzeigeAuswahl")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
anzeige_auswahl: Option<String>,
|
||||
#[serde(rename = "Druckvorlage")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
druckvorlage: Option<String>,
|
||||
#[serde(rename = "VersionFrom")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
version_from: Option<String>,
|
||||
@@ -307,6 +500,9 @@ pub struct Entry {
|
||||
speichern: String,
|
||||
#[serde(rename = "LeerAusblenden")]
|
||||
leer_ausblenden: bool,
|
||||
#[serde(rename = "Inhalt")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
inhalt: Option<String>,
|
||||
#[serde(rename = "GeschlossenAnzeigen")]
|
||||
geschlossen_anzeigen: bool,
|
||||
#[serde(rename = "Min")]
|
||||
@@ -389,12 +585,21 @@ pub struct Entry {
|
||||
in_auswertung: bool,
|
||||
#[serde(rename = "InAuswertungGraph")]
|
||||
in_auswertung_graph: bool,
|
||||
#[serde(rename = "FragebogenItemNummer")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
fragebogen_item_nummer: Option<u8>,
|
||||
#[serde(rename = "Score")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
score: Option<String>,
|
||||
#[serde(rename = "AlignmentPatModul")]
|
||||
alignment_pat_modul: String,
|
||||
#[serde(rename = "DirectionPatModul")]
|
||||
direction_pat_modul: String,
|
||||
#[serde(rename = "SeitenumbruchPatModul")]
|
||||
seitenumbruch_pat_modul: bool,
|
||||
#[serde(rename = "Kontaktliste")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
kontaktliste: Option<String>,
|
||||
#[serde(rename = "MarkierungIgnorieren")]
|
||||
markierung_ignorieren: bool,
|
||||
#[serde(rename = "SucheArt")]
|
||||
@@ -441,6 +646,35 @@ impl FormEntry for Entry {
|
||||
valid: true,
|
||||
});
|
||||
}
|
||||
|
||||
fn hide(&mut self) {
|
||||
self.filter = Some(Filter {
|
||||
condition: "false".into(),
|
||||
valid: true,
|
||||
ref_entries: Some(RefEntries { ref_entry: None }),
|
||||
});
|
||||
self.speichern = "0".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Sortable for Entry {
|
||||
fn sorting_key(&self) -> String {
|
||||
self.name.clone()
|
||||
}
|
||||
|
||||
fn sorted(&mut self) -> &Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
if let Some(ref mut filter) = self.filter {
|
||||
if let Some(ref mut ref_entries) = filter.ref_entries {
|
||||
if let Some(ref mut ref_entry) = ref_entries.ref_entry {
|
||||
ref_entry.sort_unstable()
|
||||
}
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
|
||||
@@ -34,6 +34,19 @@ pub struct Profile {
|
||||
pub forms: Vec<Form>,
|
||||
}
|
||||
|
||||
impl Profile {
|
||||
pub fn embedded_profile(name: &str) -> Result<Profile, String> {
|
||||
let s = match name {
|
||||
"UKM" => include_str!("../examples/dnpm-ukm.yml"),
|
||||
"UKW" => include_str!("../examples/dnpm-ukw.yml"),
|
||||
"UMG" => include_str!("../examples/dnpm-umg.yml"),
|
||||
_ => return Err(format!("Not an embedded profile: '{name}'")),
|
||||
};
|
||||
|
||||
Profile::from_str(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Profile {
|
||||
type Err = String;
|
||||
|
||||
@@ -50,6 +63,8 @@ pub struct Form {
|
||||
pub name: String,
|
||||
#[serde(default)]
|
||||
pub form_references: Vec<FormReference>,
|
||||
#[serde(default)]
|
||||
pub form_fields: Vec<FormField>,
|
||||
pub menu_category: Option<MenuCategory>,
|
||||
}
|
||||
|
||||
@@ -68,6 +83,13 @@ impl FormReference {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct FormField {
|
||||
pub name: String,
|
||||
#[serde(default)]
|
||||
pub hide: bool,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct MenuCategory {
|
||||
pub name: String,
|
||||
@@ -93,6 +115,9 @@ mod tests {
|
||||
referenced_data_form: 'OS.Tumorkonferenz.VarianteUKW'
|
||||
anzeige: 'Datum: {Datum}'
|
||||
anzeige_auswahl: 'TK vom {Datum}'
|
||||
form_fields:
|
||||
- name: eingabefeld
|
||||
hide: true
|
||||
";
|
||||
|
||||
match Profile::from_str(content) {
|
||||
@@ -101,6 +126,7 @@ mod tests {
|
||||
assert_eq!(profile.forms[0].name, "DNPM Therapieplan");
|
||||
assert!(profile.forms[0].menu_category.is_some());
|
||||
assert_eq!(profile.forms[0].form_references.len(), 1);
|
||||
assert_eq!(profile.forms[0].form_fields.len(), 1)
|
||||
}
|
||||
Err(e) => panic!("Cannot deserialize profile: {}", e),
|
||||
}
|
||||
@@ -212,4 +238,29 @@ mod tests {
|
||||
let actual = Profile::from_str(content);
|
||||
assert!(actual.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_deserialize_form_fields() {
|
||||
let content = "forms:
|
||||
- name: 'DNPM Therapieplan'
|
||||
form_fields:
|
||||
- name: formularfeld_to_keep
|
||||
hide: false
|
||||
- name: formularfeld_to_hide
|
||||
hide: true
|
||||
";
|
||||
|
||||
match Profile::from_str(content) {
|
||||
Ok(profile) => {
|
||||
assert_eq!(profile.forms.len(), 1);
|
||||
assert_eq!(profile.forms[0].name, "DNPM Therapieplan");
|
||||
assert_eq!(profile.forms[0].form_fields.len(), 2);
|
||||
assert_eq!(profile.forms[0].form_fields[0].name, "formularfeld_to_keep");
|
||||
assert!(!profile.forms[0].form_fields[0].hide);
|
||||
assert_eq!(profile.forms[0].form_fields[1].name, "formularfeld_to_hide");
|
||||
assert!(profile.forms[0].form_fields[1].hide);
|
||||
}
|
||||
Err(e) => panic!("Cannot deserialize profile: {}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
135
src/unzip_osb.rs
Normal file
135
src/unzip_osb.rs
Normal file
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2023 Comprehensive Cancer Center Mainfranken
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
use console::style;
|
||||
use deob::deobfuscate;
|
||||
use std::path::Path;
|
||||
use std::{fs, io};
|
||||
|
||||
macro_rules! started {
|
||||
( $o:expr ) => {
|
||||
println!("{: <6}{}", style("[..]").cyan(), $o);
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! ok {
|
||||
( $o:expr ) => {
|
||||
use console::Term;
|
||||
let _ = Term::stdout().move_cursor_up(1);
|
||||
println!("{: <6}{}", style("[OK]").green(), $o);
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! error {
|
||||
( $o:expr, $e:expr ) => {
|
||||
use console::Term;
|
||||
let _ = Term::stdout().move_cursor_up(1);
|
||||
println!("{: <6}{} - Error: {}", style("[ERR]").red(), $o, $e);
|
||||
};
|
||||
}
|
||||
|
||||
pub fn unzip_osb_using_password(path: &str, dir: &str, password: &str) {
|
||||
println!("Entpacke OSB-Datei {}\n", style(path).yellow());
|
||||
|
||||
let file = match fs::File::open(path) {
|
||||
Ok(file) => file,
|
||||
Err(err) => {
|
||||
println!(
|
||||
"{: <6}Abbruch! - Kann Datei nicht entpacken: {}",
|
||||
style("[ERR]").red(),
|
||||
err
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let mut archive = match zip::ZipArchive::new(file) {
|
||||
Ok(file) => file,
|
||||
Err(err) => {
|
||||
println!(
|
||||
"{: <6}Abbruch! - Kann Datei nicht entpacken: {}",
|
||||
style("[ERR]").red(),
|
||||
err
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
for i in 0..archive.len() {
|
||||
let mut file = if let Ok(Ok(file)) = archive.by_index_decrypt(i, password.as_bytes()) {
|
||||
file
|
||||
} else {
|
||||
println!(
|
||||
"{: <6}Abbruch! - Kann Datei nicht entpacken",
|
||||
style("[ERR]").red()
|
||||
);
|
||||
return;
|
||||
};
|
||||
|
||||
let outpath = match file.enclosed_name() {
|
||||
Some(path) => Path::new(dir).join(path.to_owned()),
|
||||
None => continue,
|
||||
};
|
||||
|
||||
started!(outpath.display());
|
||||
|
||||
if !file.is_dir() {
|
||||
if let Some(p) = outpath.parent() {
|
||||
if !p.exists() {
|
||||
fs::create_dir_all(p).unwrap();
|
||||
}
|
||||
}
|
||||
let mut outfile = match fs::File::create(&outpath) {
|
||||
Ok(file) => file,
|
||||
Err(err) => {
|
||||
error!(outpath.display(), err);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
match io::copy(&mut file, &mut outfile) {
|
||||
Ok(_) => {}
|
||||
Err(err) => {
|
||||
error!(outpath.display(), err);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
ok!(outpath.display());
|
||||
} else {
|
||||
if !outpath.exists() {
|
||||
match fs::create_dir_all(&outpath) {
|
||||
Ok(_) => {}
|
||||
Err(err) => {
|
||||
error!(outpath.display(), err);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
ok!(outpath.display());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unzip_osb(path: &str, dir: &str) {
|
||||
unzip_osb_using_password(path, dir, deobfuscate(env!("OSB_KEY").trim()).as_str());
|
||||
}
|
||||
Reference in New Issue
Block a user