parent
5131b39a65
commit
47bf1a67f3
638
src/law.rs
638
src/law.rs
@ -1,638 +0,0 @@
|
|||||||
use log::{debug, info};
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::{
|
|
||||||
cell::RefCell,
|
|
||||||
fmt::{self, Display},
|
|
||||||
rc::Rc,
|
|
||||||
sync::Arc,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{overview, par};
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
|
||||||
pub(crate) struct Law {
|
|
||||||
name: String, //ABGB, UrhG
|
|
||||||
header: Vec<Heading>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Law {
|
|
||||||
pub(crate) fn to_md(&self) {
|
|
||||||
println!("# {}", self.name);
|
|
||||||
|
|
||||||
for header in &self.header {
|
|
||||||
Self::print_md(header, 2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn print_md(header: &Heading, level: usize) {
|
|
||||||
println!("{} {}", "#".repeat(level), header);
|
|
||||||
match &header.content {
|
|
||||||
HeadingContent::Heading(h) => {
|
|
||||||
for child in h {
|
|
||||||
Self::print_md(child, level + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
HeadingContent::Paragraph(p) => {
|
|
||||||
for par in p {
|
|
||||||
println!("{} {par}", "#".repeat(level + 1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<LawBuilder> for Law {
|
|
||||||
fn from(builder: LawBuilder) -> Self {
|
|
||||||
let mut ret = Vec::new();
|
|
||||||
|
|
||||||
for header in builder.header {
|
|
||||||
ret.push(Heading {
|
|
||||||
name: header.borrow().name.clone(),
|
|
||||||
desc: header.borrow().desc.clone(),
|
|
||||||
content: header.borrow().clone().into(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
Self {
|
|
||||||
name: builder.name,
|
|
||||||
header: ret,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
|
||||||
struct Heading {
|
|
||||||
name: String, //1. Hauptstück; 3. Theil; ...
|
|
||||||
desc: Option<String>,
|
|
||||||
content: HeadingContent, // 1. Theil; 1. Subtheil; ...
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for Heading {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
if let Some(desc) = &self.desc {
|
|
||||||
f.write_str(&format!("{} ({desc})\n", self.name))
|
|
||||||
} else {
|
|
||||||
f.write_str(&format!("{}\n", self.name))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
|
||||||
enum HeadingContent {
|
|
||||||
Paragraph(Vec<Section>),
|
|
||||||
Heading(Vec<Heading>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ClassifierInstance> for HeadingContent {
|
|
||||||
fn from(value: ClassifierInstance) -> Self {
|
|
||||||
if value.sections.is_empty() {
|
|
||||||
let mut ret = Vec::new();
|
|
||||||
for child in value.children {
|
|
||||||
ret.push(Heading {
|
|
||||||
name: child.borrow().name.clone(),
|
|
||||||
desc: child.borrow().desc.clone(),
|
|
||||||
content: child.borrow().clone().into(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
Self::Heading(ret)
|
|
||||||
} else {
|
|
||||||
Self::Paragraph(value.sections)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn contains_without_unter(classifier_name: &str, instance_name: &str) -> bool {
|
|
||||||
instance_name
|
|
||||||
.to_lowercase()
|
|
||||||
.contains(&classifier_name.to_lowercase())
|
|
||||||
&& !instance_name.to_lowercase().contains("unter")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn contains(classifier_name: &str, instance_name: &str) -> bool {
|
|
||||||
instance_name
|
|
||||||
.to_lowercase()
|
|
||||||
.contains(&classifier_name.to_lowercase())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn starts_with_roman_number(_: &str, s: &str) -> bool {
|
|
||||||
// Define the prefixes for Roman numerals.
|
|
||||||
let roman_prefixes = [
|
|
||||||
"I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X", "XI", "XII", "XIII", "XIV",
|
|
||||||
"XV", "XVI", "XVII", "XVIII", "XIX", "XX",
|
|
||||||
];
|
|
||||||
|
|
||||||
// Check if the string starts with one of the Roman numeral prefixes followed by a period.
|
|
||||||
roman_prefixes
|
|
||||||
.iter()
|
|
||||||
.any(|&prefix| s.starts_with(&(prefix.to_string() + ".")))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn contains_at_start(_classifier_name: &str, instance_name: &str) -> bool {
|
|
||||||
!instance_name.is_empty() && instance_name.starts_with('@')
|
|
||||||
}
|
|
||||||
|
|
||||||
fn starts_with_number(_classifier_name: &str, instance_name: &str) -> bool {
|
|
||||||
matches!(instance_name.trim().as_bytes().first(), Some(c) if c.is_ascii_digit())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn starts_with_letter(_classifier_name: &str, instance_name: &str) -> bool {
|
|
||||||
instance_name.starts_with(|c: char| c.is_ascii_lowercase())
|
|
||||||
&& (instance_name.chars().nth(1) == Some('.') || instance_name.chars().nth(1) == Some(')'))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn starts_with_uppercaseletter(_classifier_name: &str, instance_name: &str) -> bool {
|
|
||||||
instance_name.starts_with(|c: char| c.is_ascii_uppercase())
|
|
||||||
&& (instance_name.chars().nth(1) == Some('.') || instance_name.chars().nth(1) == Some(')'))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Is used to generate a law struct. It's organized mainly by classifier.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub(crate) struct LawBuilder {
|
|
||||||
/// Name of the law
|
|
||||||
pub(crate) name: String, //ABGB, UrhG
|
|
||||||
|
|
||||||
/// Structure of the law text
|
|
||||||
pub(crate) classifiers: Vec<Classifier>,
|
|
||||||
|
|
||||||
/// Instances
|
|
||||||
pub(crate) header: Vec<Rc<RefCell<ClassifierInstance>>>,
|
|
||||||
|
|
||||||
last_instance: Option<Rc<RefCell<ClassifierInstance>>>,
|
|
||||||
|
|
||||||
/// Stores the header of the next paragraph
|
|
||||||
pub(crate) next_para_header: Option<String>,
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
pub(crate) history: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq for LawBuilder {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
self.name == other.name
|
|
||||||
&& self.classifiers == other.classifiers
|
|
||||||
&& self.header == other.header
|
|
||||||
&& self.next_para_header == other.next_para_header
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LawBuilder {
|
|
||||||
#[cfg(test)]
|
|
||||||
pub(crate) fn test(name: &str) -> Self {
|
|
||||||
let mut classifiers = Vec::new();
|
|
||||||
|
|
||||||
if name == "new" {
|
|
||||||
classifiers.push(Classifier::new("a", Arc::new(&contains)).root());
|
|
||||||
classifiers.push(Classifier::new("b", Arc::new(&contains)));
|
|
||||||
classifiers.push(Classifier::new("c", Arc::new(&contains)));
|
|
||||||
classifiers.push(Classifier::new("d", Arc::new(&contains)));
|
|
||||||
} else if name == "UrhG" {
|
|
||||||
classifiers.push(Classifier::new("Hauptstück", Arc::new(&contains)).root());
|
|
||||||
classifiers.push(Classifier::new("Abschnitt", Arc::new(&contains)));
|
|
||||||
classifiers.push(Classifier::new("Number", Arc::new(&starts_with_number)));
|
|
||||||
}
|
|
||||||
|
|
||||||
Self {
|
|
||||||
name: name.into(),
|
|
||||||
classifiers,
|
|
||||||
header: Vec::new(),
|
|
||||||
next_para_header: None,
|
|
||||||
last_instance: None,
|
|
||||||
#[cfg(test)]
|
|
||||||
history: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a new law builder. Adds classifier for known law texts.
|
|
||||||
pub(crate) fn new(name: &str) -> Law {
|
|
||||||
let mut classifiers = Vec::new();
|
|
||||||
|
|
||||||
let mut law_id = None;
|
|
||||||
if name == "UrhG" {
|
|
||||||
law_id = Some(10001848);
|
|
||||||
|
|
||||||
classifiers.push(Classifier::new("Hauptstück", Arc::new(&contains)).root());
|
|
||||||
classifiers.push(Classifier::new("Abschnitt", Arc::new(&contains)));
|
|
||||||
classifiers.push(Classifier::new("Number", Arc::new(&starts_with_number)));
|
|
||||||
} else if name == "MSchG" {
|
|
||||||
law_id = Some(10002180);
|
|
||||||
|
|
||||||
classifiers.push(Classifier::new("Abschnitt", Arc::new(&contains)).root());
|
|
||||||
classifiers.push(Classifier::new("Number", Arc::new(&starts_with_number)));
|
|
||||||
} else if name == "ABGB" {
|
|
||||||
law_id = Some(10001622);
|
|
||||||
|
|
||||||
classifiers.push(Classifier::new("Einleitung", Arc::new(&contains)).root());
|
|
||||||
classifiers.push(Classifier::new("Theil", Arc::new(&contains)).root());
|
|
||||||
|
|
||||||
classifiers.push(Classifier::new("Hauptstück", Arc::new(&contains)));
|
|
||||||
classifiers.push(Classifier::new("Abschnitt", Arc::new(&contains)));
|
|
||||||
classifiers.push(Classifier::new("Abtheilung", Arc::new(&contains)));
|
|
||||||
|
|
||||||
classifiers.push(Classifier::new("heading", Arc::new(&contains_at_start)));
|
|
||||||
classifiers.push(Classifier::new("letter", Arc::new(&starts_with_letter)));
|
|
||||||
classifiers.push(Classifier::new("num", Arc::new(&starts_with_number)));
|
|
||||||
classifiers.push(Classifier::new("rom", Arc::new(&starts_with_roman_number)));
|
|
||||||
} else if name == "FSG" {
|
|
||||||
law_id = Some(10003898);
|
|
||||||
|
|
||||||
classifiers.push(Classifier::new("Artikel", Arc::new(&contains)).root());
|
|
||||||
|
|
||||||
classifiers.push(Classifier::new(
|
|
||||||
"Abschnitt",
|
|
||||||
Arc::new(&contains_without_unter),
|
|
||||||
));
|
|
||||||
classifiers.push(Classifier::new("Hauptstück", Arc::new(&contains)));
|
|
||||||
classifiers.push(Classifier::new("Unterabschnitt", Arc::new(&contains)));
|
|
||||||
|
|
||||||
classifiers.push(Classifier::new(
|
|
||||||
"uppercase letter",
|
|
||||||
Arc::new(&starts_with_uppercaseletter),
|
|
||||||
));
|
|
||||||
classifiers.push(Classifier::new("num", Arc::new(&starts_with_number)));
|
|
||||||
} else if name == "VVG" {
|
|
||||||
law_id = Some(20004425);
|
|
||||||
|
|
||||||
classifiers.push(Classifier::new("Abschnitt", Arc::new(&contains)).root());
|
|
||||||
} else if name == "KSchG" {
|
|
||||||
law_id = Some(10002462);
|
|
||||||
|
|
||||||
classifiers.push(Classifier::new("Hauptstück", Arc::new(&contains)).root());
|
|
||||||
classifiers.push(Classifier::new("Abschnitt", Arc::new(&contains)));
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut builder = Self {
|
|
||||||
name: name.into(),
|
|
||||||
classifiers,
|
|
||||||
header: Vec::new(),
|
|
||||||
next_para_header: None,
|
|
||||||
last_instance: None,
|
|
||||||
#[cfg(test)]
|
|
||||||
history: Vec::new(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let paragraphs = overview::parse(law_id.unwrap()).unwrap();
|
|
||||||
|
|
||||||
for paragraph in tqdm::tqdm(paragraphs.into_iter()) {
|
|
||||||
let cont = par::parse(¶graph, &mut builder).unwrap();
|
|
||||||
if !cont {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn responsible_classifier(&self, name: &str) -> Option<&Classifier> {
|
|
||||||
self.classifiers.iter().find(|&c| c.used_for(name))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn find_parent(
|
|
||||||
&self,
|
|
||||||
cur: Option<Rc<RefCell<ClassifierInstance>>>,
|
|
||||||
class: &Classifier,
|
|
||||||
) -> Option<Rc<RefCell<ClassifierInstance>>> {
|
|
||||||
let mut cur = cur;
|
|
||||||
while let Some(c) = cur {
|
|
||||||
let (cur_name, cur_parent) = {
|
|
||||||
let c_borrow = c.borrow();
|
|
||||||
(c_borrow.name.clone(), c_borrow.parent.clone())
|
|
||||||
};
|
|
||||||
|
|
||||||
let cur_responsible_class = self.responsible_classifier(&cur_name).unwrap();
|
|
||||||
if cur_responsible_class == class {
|
|
||||||
return c.borrow_mut().get_parent();
|
|
||||||
}
|
|
||||||
|
|
||||||
cur = cur_parent;
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets a new header.
|
|
||||||
pub(crate) fn new_header(&mut self, name: &str) {
|
|
||||||
let name = name.trim();
|
|
||||||
#[cfg(test)]
|
|
||||||
self.history.push(format!("New_header: {name}"));
|
|
||||||
|
|
||||||
info!("new_header={name}");
|
|
||||||
|
|
||||||
let responsible_class = self
|
|
||||||
.responsible_classifier(name)
|
|
||||||
.expect(&format!("No classifier for '{name}'"));
|
|
||||||
|
|
||||||
let mut heading: ClassifierInstance = name.into();
|
|
||||||
|
|
||||||
if let Some(last_instance) = &self.last_instance {
|
|
||||||
let cur = Some(last_instance.clone());
|
|
||||||
|
|
||||||
let parent = &self.find_parent(cur, responsible_class);
|
|
||||||
match parent {
|
|
||||||
None => {
|
|
||||||
if responsible_class.root {
|
|
||||||
let c = Rc::new(RefCell::new(heading));
|
|
||||||
self.header.push(c.clone());
|
|
||||||
self.last_instance = Some(c.clone());
|
|
||||||
} else {
|
|
||||||
heading.set_parent(last_instance.clone());
|
|
||||||
let c = Rc::new(RefCell::new(heading));
|
|
||||||
last_instance.borrow_mut().add_child(c.clone());
|
|
||||||
self.last_instance = Some(c.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some(parent) => {
|
|
||||||
heading.set_parent(parent.clone());
|
|
||||||
let c = Rc::new(RefCell::new(heading));
|
|
||||||
parent.borrow_mut().add_child(c.clone());
|
|
||||||
self.last_instance = Some(c.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let c = Rc::new(RefCell::new(heading));
|
|
||||||
self.header.push(c.clone());
|
|
||||||
self.last_instance = Some(c.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets a new description for the last classifier.
|
|
||||||
pub(crate) fn new_desc(&mut self, desc: &str) {
|
|
||||||
let desc = desc.trim();
|
|
||||||
#[cfg(test)]
|
|
||||||
self.history.push(format!("New desc: {desc}"));
|
|
||||||
|
|
||||||
debug!("new_desc={desc}");
|
|
||||||
self.last_instance
|
|
||||||
.clone()
|
|
||||||
.unwrap()
|
|
||||||
.borrow_mut()
|
|
||||||
.set_desc(desc);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Adds a new paragraph.
|
|
||||||
pub(crate) fn new_par(&mut self, par: String, content: Content) {
|
|
||||||
#[cfg(test)]
|
|
||||||
self.history.push(format!(
|
|
||||||
"New_par: {par};{}",
|
|
||||||
serde_json::to_string(&content).unwrap()
|
|
||||||
));
|
|
||||||
|
|
||||||
debug!("new_par=par:{par};content:{content:#?}");
|
|
||||||
|
|
||||||
let par_header = self.next_para_header.clone();
|
|
||||||
self.next_para_header = None;
|
|
||||||
|
|
||||||
self.last_instance
|
|
||||||
.clone()
|
|
||||||
.expect("Expect at least one classifier")
|
|
||||||
.borrow_mut()
|
|
||||||
.add_section(Section {
|
|
||||||
symb: par,
|
|
||||||
par_header,
|
|
||||||
content,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Next paragraph has a header, store its name.
|
|
||||||
pub(crate) fn new_next_para_header(&mut self, header: &str) {
|
|
||||||
#[cfg(test)]
|
|
||||||
self.history.push(format!("New_new_para_header: {header}"));
|
|
||||||
|
|
||||||
if let Some(next_para_header) = &self.next_para_header {
|
|
||||||
self.new_header(&next_para_header.clone()); // promote to bigger header :-)
|
|
||||||
}
|
|
||||||
|
|
||||||
debug!("new_next_para_header={header}");
|
|
||||||
self.next_para_header = Some(header.trim().into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Serialize, Deserialize)]
|
|
||||||
pub(crate) struct Section {
|
|
||||||
pub(crate) symb: String, // §"1", §"2", ...
|
|
||||||
pub(crate) par_header: Option<String>,
|
|
||||||
pub(crate) content: Content,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Debug for Section {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
let par_header = self.par_header.as_deref().unwrap_or("");
|
|
||||||
write!(f, "{} ({})", self.symb, par_header)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for Section {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
if let Some(header) = &self.par_header {
|
|
||||||
f.write_str(&format!("{} ({})\n{}", self.symb, header, self.content))
|
|
||||||
} else {
|
|
||||||
f.write_str(&format!("{}\n{}", self.symb, self.content))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, PartialEq)]
|
|
||||||
pub(crate) struct ClassifierInstance {
|
|
||||||
pub(crate) name: String, //e.g. 1 Theilstück
|
|
||||||
pub(crate) desc: Option<String>,
|
|
||||||
pub(crate) sections: Vec<Section>,
|
|
||||||
pub(crate) children: Vec<Rc<RefCell<ClassifierInstance>>>,
|
|
||||||
pub(crate) parent: Option<Rc<RefCell<ClassifierInstance>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ClassifierInstance {
|
|
||||||
fn new(name: &str) -> Self {
|
|
||||||
Self {
|
|
||||||
name: name.into(),
|
|
||||||
desc: None,
|
|
||||||
parent: None,
|
|
||||||
sections: Vec::new(),
|
|
||||||
children: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_parent(&mut self, parent: Rc<RefCell<ClassifierInstance>>) {
|
|
||||||
self.parent = Some(parent);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_parent(&self) -> Option<Rc<RefCell<ClassifierInstance>>> {
|
|
||||||
self.parent.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_child(&mut self, child: Rc<RefCell<ClassifierInstance>>) {
|
|
||||||
self.children.push(child);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_desc(&mut self, desc: &str) {
|
|
||||||
self.desc = Some(desc.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_section(&mut self, section: Section) {
|
|
||||||
self.sections.push(section);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Debug for ClassifierInstance {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
f.debug_struct("Classifier")
|
|
||||||
.field("name", &self.name)
|
|
||||||
.field("desc", &self.desc)
|
|
||||||
.field("sections", &self.sections)
|
|
||||||
.field("children", &self.children)
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&str> for ClassifierInstance {
|
|
||||||
fn from(value: &str) -> Self {
|
|
||||||
Self::new(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub(crate) struct Classifier {
|
|
||||||
pub(crate) name: String, // Hauptstück, Theil, Abschnitt, ol
|
|
||||||
pub(crate) used_for_fn: Arc<dyn Fn(&str, &str) -> bool>,
|
|
||||||
pub(crate) instances: Vec<ClassifierInstance>,
|
|
||||||
pub(crate) child: Vec<Rc<RefCell<Classifier>>>,
|
|
||||||
pub(crate) root: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq for Classifier {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
self.name == other.name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Classifier {
|
|
||||||
fn new(name: &str, used_for_fn: Arc<dyn Fn(&str, &str) -> bool>) -> Self {
|
|
||||||
Self {
|
|
||||||
name: name.into(),
|
|
||||||
used_for_fn,
|
|
||||||
child: Vec::new(),
|
|
||||||
instances: Vec::new(),
|
|
||||||
root: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn root(self) -> Self {
|
|
||||||
Self { root: true, ..self }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn used_for(&self, name: &str) -> bool {
|
|
||||||
(self.used_for_fn)(&self.name, name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Debug for Classifier {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
f.debug_struct("Classifier")
|
|
||||||
.field("name", &self.name)
|
|
||||||
.field("instances", &self.instances)
|
|
||||||
.field("child", &self.child)
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
|
||||||
pub(crate) enum Content {
|
|
||||||
Text(String), //This is my direct law text
|
|
||||||
Item(Vec<Content>), //(1) This is general law. (2) This is more specific law
|
|
||||||
List(Vec<Content>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for Content {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::Text(a) => f.write_str(&format!("{a}\n")),
|
|
||||||
Self::Item(a) => {
|
|
||||||
let mut ret = String::new();
|
|
||||||
for aa in a {
|
|
||||||
ret.push_str(&format!("{aa}"));
|
|
||||||
}
|
|
||||||
f.write_str(&ret)
|
|
||||||
}
|
|
||||||
Self::List(a) => {
|
|
||||||
let mut ret = String::new();
|
|
||||||
for aa in a {
|
|
||||||
ret.push_str(&format!("{aa}"));
|
|
||||||
}
|
|
||||||
f.write_str(&ret)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use pretty_assertions::assert_eq;
|
|
||||||
use std::{
|
|
||||||
fs::File,
|
|
||||||
io::{self, BufRead, Read},
|
|
||||||
path::Path,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
fn read_lines<P>(filename: P) -> io::Result<Vec<String>>
|
|
||||||
where
|
|
||||||
P: AsRef<Path>,
|
|
||||||
{
|
|
||||||
let file = File::open(filename)?;
|
|
||||||
let buf_reader = io::BufReader::new(file);
|
|
||||||
buf_reader.lines().collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[ignore]
|
|
||||||
#[test]
|
|
||||||
fn test_with_live_data() {
|
|
||||||
let law = LawBuilder::new("UrhG");
|
|
||||||
|
|
||||||
let path = Path::new("./data/urhg/builder.result");
|
|
||||||
let mut file = File::open(path).unwrap();
|
|
||||||
let mut json = String::new();
|
|
||||||
file.read_to_string(&mut json).unwrap();
|
|
||||||
|
|
||||||
let expected: Law = serde_json::from_str(&json).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(law, expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_builder_full_urhg() {
|
|
||||||
let mut builder = LawBuilder::test("UrhG");
|
|
||||||
|
|
||||||
let path = Path::new("./data/urhg/par");
|
|
||||||
let input = read_lines(path.join("../par.result")).unwrap();
|
|
||||||
|
|
||||||
for i in input {
|
|
||||||
let (command, content) = i.split_once(":").unwrap();
|
|
||||||
|
|
||||||
match command {
|
|
||||||
"New_header" => builder.new_header(content),
|
|
||||||
"New desc" => builder.new_desc(content),
|
|
||||||
"New_new_para_header" => builder.new_next_para_header(content),
|
|
||||||
"New_par" => {
|
|
||||||
let (par, real_content) = i.split_once(";").unwrap();
|
|
||||||
let (_, real_par) = par.split_once(":").unwrap();
|
|
||||||
let real_content: Content = serde_json::from_str(real_content).unwrap();
|
|
||||||
builder.new_par(real_par.trim().into(), real_content);
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
panic!("Don't know command '{command}'");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let actual: Law = builder.into();
|
|
||||||
|
|
||||||
//println!("{}", serde_json::to_string(&law).unwrap());
|
|
||||||
|
|
||||||
let mut file = File::open(path.join("../builder.result")).unwrap();
|
|
||||||
let mut json = String::new();
|
|
||||||
file.read_to_string(&mut json).unwrap();
|
|
||||||
|
|
||||||
let expected = serde_json::from_str(&json).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(actual, expected);
|
|
||||||
}
|
|
||||||
}
|
|
166
src/lawbuilder/classifier.rs
Normal file
166
src/lawbuilder/classifier.rs
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
//! The `Classifier` manages headings.
|
||||||
|
//!
|
||||||
|
//! # Example
|
||||||
|
//! ```
|
||||||
|
//! use risp::lawbuilder::classifier::Classifier;
|
||||||
|
//! use risp::lawbuilder::classifier_default::contains;
|
||||||
|
//!
|
||||||
|
//! let classifier = Classifier::new("Theilstück", std::sync::Arc::new(contains));
|
||||||
|
//!
|
||||||
|
//! assert!(classifier.is_responsible_for("1. Theilstück"));
|
||||||
|
//! assert!(!classifier.is_responsible_for("1. Unterstück"));
|
||||||
|
//! ```
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
cell::RefCell,
|
||||||
|
fmt::{self, Display},
|
||||||
|
rc::Rc,
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
pub struct Classifier {
|
||||||
|
pub(crate) name: String, // Hauptstück, Theil, Abschnitt, ol
|
||||||
|
is_responsible_for: Arc<dyn Fn(&Self, &str) -> bool>,
|
||||||
|
instances: Vec<ClassifierInstance>,
|
||||||
|
child: Vec<Rc<RefCell<Classifier>>>,
|
||||||
|
pub(crate) root: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for Classifier {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.name == other.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Classifier {
|
||||||
|
pub fn new(name: &str, is_responsible_for: Arc<dyn Fn(&Self, &str) -> bool>) -> Self {
|
||||||
|
Self {
|
||||||
|
name: name.into(),
|
||||||
|
is_responsible_for,
|
||||||
|
child: Vec::new(),
|
||||||
|
instances: Vec::new(),
|
||||||
|
root: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn root(self) -> Self {
|
||||||
|
Self { root: true, ..self }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_responsible_for(&self, name: &str) -> bool {
|
||||||
|
(self.is_responsible_for)(&self, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq)]
|
||||||
|
pub(crate) struct ClassifierInstance {
|
||||||
|
pub(crate) name: String, //e.g. 1 Theilstück
|
||||||
|
pub(crate) desc: Option<String>,
|
||||||
|
pub(crate) sections: Vec<Section>,
|
||||||
|
pub(crate) children: Vec<Rc<RefCell<ClassifierInstance>>>,
|
||||||
|
pub(crate) parent: Option<Rc<RefCell<ClassifierInstance>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ClassifierInstance {
|
||||||
|
fn new(name: &str) -> Self {
|
||||||
|
Self {
|
||||||
|
name: name.into(),
|
||||||
|
desc: None,
|
||||||
|
parent: None,
|
||||||
|
sections: Vec::new(),
|
||||||
|
children: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn set_parent(&mut self, parent: Rc<RefCell<ClassifierInstance>>) {
|
||||||
|
self.parent = Some(parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_parent(&self) -> Option<Rc<RefCell<ClassifierInstance>>> {
|
||||||
|
self.parent.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn add_child(&mut self, child: Rc<RefCell<ClassifierInstance>>) {
|
||||||
|
self.children.push(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn set_desc(&mut self, desc: &str) {
|
||||||
|
self.desc = Some(desc.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn add_section(&mut self, section: Section) {
|
||||||
|
self.sections.push(section);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for ClassifierInstance {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_struct("Classifier")
|
||||||
|
.field("name", &self.name)
|
||||||
|
.field("desc", &self.desc)
|
||||||
|
.field("sections", &self.sections)
|
||||||
|
.field("children", &self.children)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&str> for ClassifierInstance {
|
||||||
|
fn from(value: &str) -> Self {
|
||||||
|
Self::new(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct Section {
|
||||||
|
pub(crate) symb: String, // §"1", §"2", ...
|
||||||
|
pub(crate) par_header: Option<String>,
|
||||||
|
pub(crate) content: Content,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for Section {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
let par_header = self.par_header.as_deref().unwrap_or("");
|
||||||
|
write!(f, "{} ({})", self.symb, par_header)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Section {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
if let Some(header) = &self.par_header {
|
||||||
|
f.write_str(&format!("{} ({})\n{}", self.symb, header, self.content))
|
||||||
|
} else {
|
||||||
|
f.write_str(&format!("{}\n{}", self.symb, self.content))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub enum Content {
|
||||||
|
Text(String), //This is my direct law text
|
||||||
|
Item(Vec<Content>), //(1) This is general law. (2) This is more specific law
|
||||||
|
List(Vec<Content>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Content {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Text(a) => f.write_str(&format!("{a}\n")),
|
||||||
|
Self::Item(a) => {
|
||||||
|
let mut ret = String::new();
|
||||||
|
for aa in a {
|
||||||
|
ret.push_str(&format!("{aa}"));
|
||||||
|
}
|
||||||
|
f.write_str(&ret)
|
||||||
|
}
|
||||||
|
Self::List(a) => {
|
||||||
|
let mut ret = String::new();
|
||||||
|
for aa in a {
|
||||||
|
ret.push_str(&format!("{aa}"));
|
||||||
|
}
|
||||||
|
f.write_str(&ret)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
9
src/lawbuilder/classifier_default.rs
Normal file
9
src/lawbuilder/classifier_default.rs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
//! Defines ways to specify whether a classifier is responsible for a certain heading
|
||||||
|
|
||||||
|
use super::classifier::Classifier;
|
||||||
|
|
||||||
|
pub fn contains(classifier: &Classifier, header: &str) -> bool {
|
||||||
|
header
|
||||||
|
.to_lowercase()
|
||||||
|
.contains(&classifier.name.to_lowercase())
|
||||||
|
}
|
175
src/lawbuilder/mod.rs
Normal file
175
src/lawbuilder/mod.rs
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
//! The `LawBuilder` structures law texts in its (sub-)headings.
|
||||||
|
//!
|
||||||
|
//! # Example
|
||||||
|
//! ```
|
||||||
|
//! use risp::lawbuilder::{Law, LawBuilder};
|
||||||
|
//! use risp::lawbuilder::classifier::{Classifier, Content};
|
||||||
|
//! use risp::lawbuilder::classifier_default::contains;
|
||||||
|
//! use std::sync::Arc;
|
||||||
|
//!
|
||||||
|
//! let mut builder = LawBuilder::new();
|
||||||
|
//!
|
||||||
|
//! builder.new_classifier(Classifier::new("Theilstück", Arc::new(contains)));
|
||||||
|
//!
|
||||||
|
//! builder.new_header("1. Theilstück".into());
|
||||||
|
//! builder.new_desc("Desc of 1. Theilstück".into());
|
||||||
|
//! builder.next_par_header("Topic of next paragraph".into());
|
||||||
|
//! builder.new_par("Par 1:".into(), Content::Text("Test".into()));
|
||||||
|
//!
|
||||||
|
//! let law: Law = builder.into();
|
||||||
|
//! assert_eq!(format!("{law:?}"), r#"1. Theilstück (Desc of 1. Theilstück)
|
||||||
|
//! Topic of next paragraph
|
||||||
|
//! Par 1: Test"#);
|
||||||
|
//! ```
|
||||||
|
|
||||||
|
use std::{cell::RefCell, rc::Rc};
|
||||||
|
|
||||||
|
use log::{debug, info};
|
||||||
|
|
||||||
|
use crate::lawbuilder::classifier::{ClassifierInstance, Section};
|
||||||
|
|
||||||
|
use self::classifier::{Classifier, Content};
|
||||||
|
|
||||||
|
pub mod classifier;
|
||||||
|
pub mod classifier_default;
|
||||||
|
|
||||||
|
pub struct LawBuilder {
|
||||||
|
classifiers: Vec<Classifier>,
|
||||||
|
|
||||||
|
/// Instances
|
||||||
|
pub(crate) header: Vec<Rc<RefCell<ClassifierInstance>>>,
|
||||||
|
|
||||||
|
last_instance: Option<Rc<RefCell<ClassifierInstance>>>,
|
||||||
|
|
||||||
|
next_para_header: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LawBuilder {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
classifiers: Vec::new(),
|
||||||
|
last_instance: None,
|
||||||
|
header: Vec::new(),
|
||||||
|
next_para_header: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_classifier(&mut self, classifier: Classifier) {
|
||||||
|
self.classifiers.push(classifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_header(&mut self, name: String) {
|
||||||
|
let name = name.trim();
|
||||||
|
|
||||||
|
info!("new_header={name}");
|
||||||
|
|
||||||
|
let responsible_class = self
|
||||||
|
.responsible_classifier(name)
|
||||||
|
.expect(&format!("No classifier for '{name}'"));
|
||||||
|
|
||||||
|
let mut heading: ClassifierInstance = name.into();
|
||||||
|
|
||||||
|
if let Some(last_instance) = &self.last_instance {
|
||||||
|
let cur = Some(last_instance.clone());
|
||||||
|
|
||||||
|
let parent = &self.find_parent(cur, responsible_class);
|
||||||
|
match parent {
|
||||||
|
None => {
|
||||||
|
if responsible_class.root {
|
||||||
|
let c = Rc::new(RefCell::new(heading));
|
||||||
|
self.header.push(c.clone());
|
||||||
|
self.last_instance = Some(c.clone());
|
||||||
|
} else {
|
||||||
|
heading.set_parent(last_instance.clone());
|
||||||
|
let c = Rc::new(RefCell::new(heading));
|
||||||
|
last_instance.borrow_mut().add_child(c.clone());
|
||||||
|
self.last_instance = Some(c.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(parent) => {
|
||||||
|
heading.set_parent(parent.clone());
|
||||||
|
let c = Rc::new(RefCell::new(heading));
|
||||||
|
parent.borrow_mut().add_child(c.clone());
|
||||||
|
self.last_instance = Some(c.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let c = Rc::new(RefCell::new(heading));
|
||||||
|
self.header.push(c.clone());
|
||||||
|
self.last_instance = Some(c.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_desc(&self, desc: String) {
|
||||||
|
let desc = desc.trim();
|
||||||
|
|
||||||
|
debug!("new_desc={desc}");
|
||||||
|
self.last_instance
|
||||||
|
.clone()
|
||||||
|
.unwrap()
|
||||||
|
.borrow_mut()
|
||||||
|
.set_desc(desc);
|
||||||
|
}
|
||||||
|
pub fn next_par_header(&mut self, header: String) {
|
||||||
|
if let Some(next_para_header) = &self.next_para_header {
|
||||||
|
self.new_header(next_para_header.clone()); // promote to bigger header :-)
|
||||||
|
}
|
||||||
|
|
||||||
|
debug!("new_next_para_header={header}");
|
||||||
|
self.next_para_header = Some(header.trim().into());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_par(&mut self, par: String, content: Content) {
|
||||||
|
debug!("new_par=par:{par};content:{content:#?}");
|
||||||
|
|
||||||
|
let par_header = self.next_para_header.clone();
|
||||||
|
self.next_para_header = None;
|
||||||
|
|
||||||
|
self.last_instance
|
||||||
|
.clone()
|
||||||
|
.expect("Expect at least one classifier")
|
||||||
|
.borrow_mut()
|
||||||
|
.add_section(Section {
|
||||||
|
symb: par,
|
||||||
|
par_header,
|
||||||
|
content,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn responsible_classifier(&self, header: &str) -> Option<&Classifier> {
|
||||||
|
self.classifiers
|
||||||
|
.iter()
|
||||||
|
.find(|&c| c.is_responsible_for(header))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_parent(
|
||||||
|
&self,
|
||||||
|
cur: Option<Rc<RefCell<ClassifierInstance>>>,
|
||||||
|
class: &Classifier,
|
||||||
|
) -> Option<Rc<RefCell<ClassifierInstance>>> {
|
||||||
|
let mut cur = cur;
|
||||||
|
while let Some(c) = cur {
|
||||||
|
let (cur_name, cur_parent) = {
|
||||||
|
let c_borrow = c.borrow();
|
||||||
|
(c_borrow.name.clone(), c_borrow.parent.clone())
|
||||||
|
};
|
||||||
|
|
||||||
|
let cur_responsible_class = self.responsible_classifier(&cur_name).unwrap();
|
||||||
|
if cur_responsible_class == class {
|
||||||
|
return c.borrow_mut().get_parent();
|
||||||
|
}
|
||||||
|
|
||||||
|
cur = cur_parent;
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Law {}
|
||||||
|
|
||||||
|
impl From<LawBuilder> for Law {
|
||||||
|
fn from(builder: LawBuilder) -> Self {
|
||||||
|
Self {}
|
||||||
|
}
|
||||||
|
}
|
3
src/lib.rs
Normal file
3
src/lib.rs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
//! This projects parses Austrian law texts from RIS.
|
||||||
|
|
||||||
|
pub mod lawbuilder;
|
48
src/main.rs
48
src/main.rs
@ -1,48 +0,0 @@
|
|||||||
use std::io;
|
|
||||||
|
|
||||||
use law::LawBuilder;
|
|
||||||
|
|
||||||
mod law;
|
|
||||||
mod overview;
|
|
||||||
mod par;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Error {
|
|
||||||
msg: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ureq::Error> for Error {
|
|
||||||
fn from(value: ureq::Error) -> Self {
|
|
||||||
Self {
|
|
||||||
msg: value.to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<io::Error> for Error {
|
|
||||||
fn from(value: io::Error) -> Self {
|
|
||||||
Self {
|
|
||||||
msg: value.to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<serde_json::Error> for Error {
|
|
||||||
fn from(value: serde_json::Error) -> Self {
|
|
||||||
Self {
|
|
||||||
msg: value.to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<roxmltree::Error> for Error {
|
|
||||||
fn from(value: roxmltree::Error) -> Self {
|
|
||||||
Self {
|
|
||||||
msg: value.to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
env_logger::init();
|
|
||||||
let law = LawBuilder::new("KSchG");
|
|
||||||
|
|
||||||
law.to_md();
|
|
||||||
}
|
|
@ -1,86 +0,0 @@
|
|||||||
/// This module contains everything everything, to convert the given JSON file into Rust structs using serde.
|
|
||||||
mod parser;
|
|
||||||
|
|
||||||
use log::info;
|
|
||||||
use serde::Deserialize;
|
|
||||||
use time::{format_description, OffsetDateTime};
|
|
||||||
|
|
||||||
use crate::{overview::parser::OgdSearchResult, Error};
|
|
||||||
|
|
||||||
/// Returns the current date in YYYY-MM-DD format. Needed for RIS API query to get current version of the overview.
|
|
||||||
fn current_date() -> String {
|
|
||||||
let local_date = OffsetDateTime::now_utc();
|
|
||||||
let format = format_description::parse("[year]-[month]-[day]").unwrap(); //unwrap okay, supplied format is fine
|
|
||||||
local_date.format(&format).expect("Failed to format date")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Fetches the json content of the given overview (`law_id`) from the RIS API.
|
|
||||||
///
|
|
||||||
/// # Errors
|
|
||||||
/// Fails if `ureq` can't create a connection, probably because there's no internet connection? (Or RIS is not online.)
|
|
||||||
fn fetch_page(overview_id: usize, page: usize) -> Result<String, Error> {
|
|
||||||
Ok(
|
|
||||||
ureq::post("https://data.bka.gv.at/ris/api/v2.6/Bundesrecht")
|
|
||||||
.send_form(&[
|
|
||||||
("Applikation", "BrKons"),
|
|
||||||
("Gesetzesnummer", &format!("{}", overview_id)),
|
|
||||||
("DokumenteProSeite", "OneHundred"),
|
|
||||||
("Seitennummer", &format!("{}", page)),
|
|
||||||
("Fassung.FassungVom", ¤t_date()),
|
|
||||||
])?
|
|
||||||
.into_string()?,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
#[serde(rename_all = "PascalCase")]
|
|
||||||
pub(crate) struct Wrapper {
|
|
||||||
ogd_search_result: OgdSearchResult,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn parse(overview_id: usize) -> Result<Vec<String>, Error> {
|
|
||||||
let mut page = 1;
|
|
||||||
let mut skip = true;
|
|
||||||
let mut ret = Vec::new();
|
|
||||||
loop {
|
|
||||||
info!("=== Fetching overview page #{page} ===");
|
|
||||||
let json = fetch_page(overview_id, page)?;
|
|
||||||
let (cont, nodes) = parse_from_str(&json, skip)?;
|
|
||||||
for n in nodes {
|
|
||||||
ret.push(n.clone());
|
|
||||||
}
|
|
||||||
if !cont {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
skip = false;
|
|
||||||
page += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(ret)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn parse_from_str(
|
|
||||||
content: &str,
|
|
||||||
skip_first: bool,
|
|
||||||
) -> Result<(bool, Vec<String>), Error> {
|
|
||||||
let mut ret = Vec::new();
|
|
||||||
let wrapper: Wrapper = serde_json::from_str(content)?;
|
|
||||||
|
|
||||||
let iter = wrapper.ogd_search_result.get_par().into_iter();
|
|
||||||
let boxed_iter: Box<dyn Iterator<Item = String>> = if skip_first {
|
|
||||||
Box::new(iter.skip(1))
|
|
||||||
} else {
|
|
||||||
Box::new(iter)
|
|
||||||
};
|
|
||||||
for par in boxed_iter {
|
|
||||||
ret.push(par);
|
|
||||||
//if !crate::par::parse(&par).unwrap() {
|
|
||||||
// return Ok(false);
|
|
||||||
//}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !wrapper.ogd_search_result.has_next_page() {
|
|
||||||
return Ok((false, ret));
|
|
||||||
}
|
|
||||||
Ok((true, ret))
|
|
||||||
}
|
|
@ -1,299 +0,0 @@
|
|||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use serde::Deserialize;
|
|
||||||
|
|
||||||
fn deserialize_string_to_usize<'de, D>(deserializer: D) -> Result<usize, D::Error>
|
|
||||||
where
|
|
||||||
D: serde::Deserializer<'de>,
|
|
||||||
{
|
|
||||||
let s = String::deserialize(deserializer)?;
|
|
||||||
s.parse().map_err(serde::de::Error::custom)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
#[serde(rename_all = "PascalCase")]
|
|
||||||
pub(crate) struct OgdSearchResult {
|
|
||||||
ogd_document_results: OgdDocumentResults,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl OgdSearchResult {
|
|
||||||
pub(crate) fn has_next_page(&self) -> bool {
|
|
||||||
let hits = &self.ogd_document_results.hits;
|
|
||||||
let curr_page_number = hits.page_number;
|
|
||||||
let page_size = hits.page_size;
|
|
||||||
let elements = hits.text;
|
|
||||||
|
|
||||||
let parsed_so_far = curr_page_number * page_size;
|
|
||||||
|
|
||||||
elements > parsed_so_far
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn get_par(&self) -> Vec<String> {
|
|
||||||
let mut ret = Vec::new();
|
|
||||||
for doc_ref in &self.ogd_document_results.ogd_document_reference {
|
|
||||||
for urls in &doc_ref
|
|
||||||
.data
|
|
||||||
.document_list
|
|
||||||
.content_reference
|
|
||||||
.urls
|
|
||||||
.content_url
|
|
||||||
{
|
|
||||||
if urls.data_type == "Xml" {
|
|
||||||
ret.push(urls.url.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ret
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
#[serde(rename_all = "PascalCase")]
|
|
||||||
pub(crate) struct OgdDocumentResults {
|
|
||||||
hits: Hits,
|
|
||||||
ogd_document_reference: Vec<OgdDocumentReference>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
pub(crate) struct Hits {
|
|
||||||
#[serde(
|
|
||||||
rename = "@pageNumber",
|
|
||||||
deserialize_with = "deserialize_string_to_usize"
|
|
||||||
)]
|
|
||||||
page_number: usize,
|
|
||||||
#[serde(rename = "@pageSize", deserialize_with = "deserialize_string_to_usize")]
|
|
||||||
page_size: usize,
|
|
||||||
#[serde(rename = "#text", deserialize_with = "deserialize_string_to_usize")]
|
|
||||||
text: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
#[serde(rename_all = "PascalCase")]
|
|
||||||
pub(crate) struct OgdDocumentReference {
|
|
||||||
data: Data,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
pub(crate) struct Data {
|
|
||||||
#[serde(rename = "Metadaten")]
|
|
||||||
metadata: Metadata,
|
|
||||||
#[serde(rename = "Dokumentliste")]
|
|
||||||
document_list: DocumentList,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
pub(crate) struct Metadata {
|
|
||||||
#[serde(rename = "Technisch")]
|
|
||||||
technical: TechnicalMetadata,
|
|
||||||
|
|
||||||
#[serde(rename = "Allgemein")]
|
|
||||||
general: GeneralMetadata,
|
|
||||||
|
|
||||||
#[serde(rename = "Bundesrecht")]
|
|
||||||
fed: FedMetadata,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
#[serde(rename_all = "PascalCase")]
|
|
||||||
pub(crate) struct TechnicalMetadata {
|
|
||||||
#[serde(rename = "ID")]
|
|
||||||
id: String,
|
|
||||||
|
|
||||||
#[serde(rename = "Applikation")]
|
|
||||||
application: String,
|
|
||||||
|
|
||||||
organ: String,
|
|
||||||
|
|
||||||
import_timestamp: ImportTimestamp,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
pub(crate) struct ImportTimestamp {
|
|
||||||
#[serde(rename = "@xsi:nil")]
|
|
||||||
xsi_nil: String, //TODO: switch to bool
|
|
||||||
|
|
||||||
#[serde(rename = "@xmlns:xsi")]
|
|
||||||
xmlns_xsi: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
pub(crate) struct GeneralMetadata {
|
|
||||||
#[serde(rename = "Geaendert")]
|
|
||||||
changed: String, //TODO: switch to YYYY-MM-DD string
|
|
||||||
|
|
||||||
#[serde(rename = "DokumentUrl")]
|
|
||||||
document_url: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
#[serde(rename_all = "PascalCase")]
|
|
||||||
pub(crate) struct FedMetadata {
|
|
||||||
#[serde(rename = "Kurztitel")]
|
|
||||||
short_title: String,
|
|
||||||
|
|
||||||
#[serde(rename = "Titel")]
|
|
||||||
title: Option<String>,
|
|
||||||
|
|
||||||
eli: String,
|
|
||||||
|
|
||||||
br_kons: BrKons,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
#[serde(rename_all = "PascalCase")]
|
|
||||||
pub(crate) struct BrKons {
|
|
||||||
kundmachungsorgan: String,
|
|
||||||
typ: String,
|
|
||||||
#[serde(rename = "Dokumenttyp")]
|
|
||||||
documenttype: String,
|
|
||||||
artikel_paragraph_anlage: String,
|
|
||||||
paragraphnummer: Option<String>,
|
|
||||||
stammnorm_publikationsorgan: String,
|
|
||||||
stammnorm_bgblnummer: String,
|
|
||||||
inkrafttretensdatum: String, //TODO: switch to date
|
|
||||||
#[serde(rename = "Indizes")]
|
|
||||||
indices: HashMap<String, String>, //e.g. "item": "22/04 Sonstiges Zivilprozess, Außerstreitiges Verfahren"
|
|
||||||
#[serde(rename = "Aenderung")]
|
|
||||||
change: String,
|
|
||||||
anmerkung: Option<String>,
|
|
||||||
schlagworte: Option<String>,
|
|
||||||
gesetzesnummer: String,
|
|
||||||
alte_dokumentnummer: Option<String>,
|
|
||||||
#[serde(rename = "GesamteRechtsvorschriftUrl")]
|
|
||||||
full_url: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
#[serde(rename_all = "PascalCase")]
|
|
||||||
pub(crate) struct DocumentList {
|
|
||||||
content_reference: ContentReference,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
#[serde(rename_all = "PascalCase")]
|
|
||||||
pub(crate) struct ContentReference {
|
|
||||||
content_type: String,
|
|
||||||
name: String,
|
|
||||||
urls: ContentUrl,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
#[serde(rename_all = "PascalCase")]
|
|
||||||
pub(crate) struct ContentUrl {
|
|
||||||
content_url: Vec<ContentUrlItem>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
#[serde(rename_all = "PascalCase")]
|
|
||||||
pub(crate) struct ContentUrlItem {
|
|
||||||
data_type: String,
|
|
||||||
url: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use std::{
|
|
||||||
fs::{self, File},
|
|
||||||
io::{self, BufRead, Read},
|
|
||||||
path::Path,
|
|
||||||
};
|
|
||||||
|
|
||||||
use log::debug;
|
|
||||||
|
|
||||||
use crate::{law::LawBuilder, overview::parse_from_str};
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
fn read_lines<P>(filename: P) -> io::Result<Vec<String>>
|
|
||||||
where
|
|
||||||
P: AsRef<Path>,
|
|
||||||
{
|
|
||||||
let file = File::open(filename)?;
|
|
||||||
let buf_reader = io::BufReader::new(file);
|
|
||||||
buf_reader.lines().collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
#[serde(rename_all = "PascalCase")]
|
|
||||||
pub(crate) struct Wrapper {
|
|
||||||
ogd_search_result: OgdSearchResult,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn deserialize_teg_success() {
|
|
||||||
let mut file = File::open("data/teg.json").unwrap();
|
|
||||||
let mut json = String::new();
|
|
||||||
file.read_to_string(&mut json).unwrap();
|
|
||||||
|
|
||||||
let wrapper: serde_json::Result<Wrapper> = serde_json::from_str(&json);
|
|
||||||
if wrapper.is_err() {
|
|
||||||
let dbg = wrapper.as_ref().err().unwrap();
|
|
||||||
debug!("{dbg:#?}");
|
|
||||||
}
|
|
||||||
|
|
||||||
assert!(wrapper.is_ok());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn deserialize_abgb_success() {
|
|
||||||
let mut file = File::open("data/abgb.json").unwrap();
|
|
||||||
let mut json = String::new();
|
|
||||||
file.read_to_string(&mut json).unwrap();
|
|
||||||
|
|
||||||
let wrapper: serde_json::Result<Wrapper> = serde_json::from_str(&json);
|
|
||||||
if wrapper.is_err() {
|
|
||||||
let dbg = wrapper.as_ref().err().unwrap();
|
|
||||||
debug!("{dbg:#?}");
|
|
||||||
}
|
|
||||||
|
|
||||||
assert!(wrapper.is_ok());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn deserialize_urhg_success() {
|
|
||||||
let mut file = File::open("data/urhg.json").unwrap();
|
|
||||||
let mut json = String::new();
|
|
||||||
file.read_to_string(&mut json).unwrap();
|
|
||||||
|
|
||||||
let wrapper: serde_json::Result<Wrapper> = serde_json::from_str(&json);
|
|
||||||
if wrapper.is_err() {
|
|
||||||
let dbg = wrapper.as_ref().err().unwrap();
|
|
||||||
debug!("{dbg:#?}");
|
|
||||||
}
|
|
||||||
|
|
||||||
assert!(wrapper.is_ok());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_overview_full_urhg() {
|
|
||||||
let mut files = Vec::new();
|
|
||||||
let path = Path::new("./data/urhg/overview");
|
|
||||||
let mut entries: Vec<_> = fs::read_dir(path)
|
|
||||||
.unwrap()
|
|
||||||
.filter_map(|entry| entry.ok())
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
entries.sort_by_key(|entry| entry.file_name());
|
|
||||||
|
|
||||||
let last_index = fs::read_dir(path).unwrap().count() - 1;
|
|
||||||
let mut skip = true;
|
|
||||||
for (idx, entry) in entries.into_iter().enumerate() {
|
|
||||||
let mut file = File::open(path.join(entry.file_name())).unwrap();
|
|
||||||
let mut json = String::new();
|
|
||||||
file.read_to_string(&mut json).unwrap();
|
|
||||||
|
|
||||||
let expected_continue = !(idx == last_index);
|
|
||||||
let (cont, cur_files) = parse_from_str(&json, skip).unwrap();
|
|
||||||
assert_eq!(cont, expected_continue);
|
|
||||||
|
|
||||||
for file in cur_files {
|
|
||||||
files.push(file);
|
|
||||||
}
|
|
||||||
|
|
||||||
skip = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let expected = read_lines(path.join("../overview.result")).unwrap();
|
|
||||||
assert_eq!(files, expected);
|
|
||||||
}
|
|
||||||
}
|
|
173
src/par/mod.rs
173
src/par/mod.rs
@ -1,173 +0,0 @@
|
|||||||
mod parser;
|
|
||||||
|
|
||||||
use log::{debug, info};
|
|
||||||
|
|
||||||
use crate::{law::LawBuilder, par::parser::Risdok, Error};
|
|
||||||
|
|
||||||
fn fetch_page(url: &str) -> Result<String, Error> {
|
|
||||||
Ok(ureq::get(url).call()?.into_string()?)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn parse(url: &str, builder: &mut LawBuilder) -> Result<bool, Error> {
|
|
||||||
info!("Parsing {url}");
|
|
||||||
let xml = fetch_page(url)?;
|
|
||||||
parse_from_str(&xml, builder)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn parse_from_str(xml: &str, builder: &mut LawBuilder) -> Result<bool, Error> {
|
|
||||||
let xml = xml.replace("<gdash />", "-"); // used e.g. in §11 Abs. 3 UrhG
|
|
||||||
|
|
||||||
let xml = xml.replace(
|
|
||||||
// in § 17 (2)
|
|
||||||
r#"<liste><schlussteil ebene="0" art="normal" ct="text">(2) Einer Rundfunksendung steht es gleich, wenn ein Werk von einer im In- oder im Ausland gelegenen Stelle aus der Öffentlichkeit im Inland, ähnlich wie durch Rundfunk, aber mit Hilfe von Leitungen wahrnehmbar gemacht wird.</schlussteil></liste>"#,
|
|
||||||
r#"<absatz typ="abs" ct="text" halign="j">(2) Einer Rundfunksendung steht es gleich, wenn ein Werk von einer im In- oder im Ausland gelegenen Stelle aus der Öffentlichkeit im Inland, ähnlich wie durch Rundfunk, aber mit Hilfe von Leitungen wahrnehmbar gemacht wird.</absatz>"#,
|
|
||||||
);
|
|
||||||
|
|
||||||
let xml = xml.replace(
|
|
||||||
r#"<ueberschrift typ="para" ct="text" halign="c">1. Verwertungsrechte.</ueberschrift>"#,
|
|
||||||
r#"<ueberschrift typ="g1" ct="text" halign="c">1. Verwertungsrechte.</ueberschrift>"#,
|
|
||||||
); // 1. Verwertungsrechte. before § 14
|
|
||||||
|
|
||||||
let xml = xml.replace(
|
|
||||||
r#"<ueberschrift typ="g1" ct="text" halign="c">Medizinische Behandlung</ueberschrift>"#,
|
|
||||||
r#"<ueberschrift typ="art" ct="text" halign="c">Medizinische Behandlung</ueberschrift>"#,
|
|
||||||
); // 1. Verwertungsrechte. before § 14
|
|
||||||
|
|
||||||
let xml = xml.replace(
|
|
||||||
r#"<ueberschrift typ="para" ct="text" halign="c">4b. Presseveröffentlichungen.</ueberschrift>"#,
|
|
||||||
r#"<ueberschrift typ="g1" ct="text" halign="c">4b. Presseveröffentlichungen.</ueberschrift>"#,
|
|
||||||
); // § 99d UrhG, Titel kein Para.... //TODO: not working
|
|
||||||
|
|
||||||
let xml = xml.replace(
|
|
||||||
r#"<ueberschrift typ="para" ct="text" halign="c">Erfordernisse zur Ersitzung:</ueberschrift>"#,
|
|
||||||
r#"<ueberschrift typ="art" ct="text" halign="c">Erfordernisse zur Ersitzung:</ueberschrift>"#,
|
|
||||||
);
|
|
||||||
|
|
||||||
let xml = xml.replace(
|
|
||||||
r#"<ueberschrift typ="art" ct="text" halign="c">"#,
|
|
||||||
r#"<ueberschrift typ="g1" ct="text" halign="c">@"#,
|
|
||||||
);
|
|
||||||
|
|
||||||
let xml = xml.replace(
|
|
||||||
r#"<ueberschrift typ="g1" ct="text" halign="c">Zweyter Theil</ueberschrift><ueberschrift typ="g1" ct="text" halign="c">des</ueberschrift><ueberschrift typ="g1" ct="text" halign="c">bürgerlichen Gesetzbuches.</ueberschrift><ueberschrift typ="g1" ct="text" halign="c">Von dem Sachenrechte.</ueberschrift>"#,
|
|
||||||
r#"<ueberschrift typ="g1" ct="text" halign="c">Zweyter Theil des bürgerlichen Gesetzbuches. Von dem Sachenrechte.</ueberschrift>"#,
|
|
||||||
);
|
|
||||||
|
|
||||||
let xml = xml.replace(
|
|
||||||
r#"<ueberschrift typ="g1" ct="text" halign="c">Dritter Theil</ueberschrift><ueberschrift typ="g1min" ct="text" halign="c">des</ueberschrift><ueberschrift typ="g1min" ct="text" halign="c">bürgerlichen Gesetzbuches.</ueberschrift>"#,
|
|
||||||
r#"<ueberschrift typ="g1" ct="text" halign="c">Dritter Theil des bürgerlichen Gesetzbuches.</ueberschrift>"#,
|
|
||||||
);
|
|
||||||
let xml = xml.replace(
|
|
||||||
r#"<ueberschrift typ="g1" ct="text" halign="c">Von den gemeinschaftlichen Bestimmungen der Personen- und Sachenrechte.</ueberschrift>"#,
|
|
||||||
r#"<ueberschrift typ="g2" ct="text" halign="c">Von den gemeinschaftlichen Bestimmungen der Personen- und Sachenrechte.</ueberschrift>"#,
|
|
||||||
);
|
|
||||||
|
|
||||||
let xml = xml.replace(
|
|
||||||
r#"<ueberschrift typ="g1" ct="text" halign="c">Erste Abtheilung</ueberschrift><ueberschrift typ="g2" ct="text" halign="c">des Sachenrechtes.</ueberschrift>"#,
|
|
||||||
r#"<ueberschrift typ="g1" ct="text" halign="c">Erste Abtheilung des Sachenrechtes.</ueberschrift>"#,
|
|
||||||
);
|
|
||||||
|
|
||||||
let xml = xml.replace(
|
|
||||||
r#"<ueberschrift typ="g1min" ct="text" halign="c">Von den dinglichen Rechten.</ueberschrift>"#,
|
|
||||||
r#"<ueberschrift typ="g2" ct="text" halign="c">Von den dinglichen Rechten.</ueberschrift>"#,
|
|
||||||
);
|
|
||||||
|
|
||||||
let xml = xml.replace("<super>", ""); // e.g. § 23a in MSchG
|
|
||||||
let xml = xml.replace("</super>", ""); // e.g. § 23a in MSchG
|
|
||||||
let xml = xml.replace("<i>", ""); // § 69 in MSchG
|
|
||||||
let xml = xml.replace("</i>", "");
|
|
||||||
|
|
||||||
let xml = xml.replace("<n>", ""); // § 49b in FSG
|
|
||||||
let xml = xml.replace("</n>", "");
|
|
||||||
|
|
||||||
let xml = xml.replace("<b>", ""); // § 14 in FSG
|
|
||||||
let xml = xml.replace("</b>", "");
|
|
||||||
|
|
||||||
let xml = xml.replace("<tab />", "");
|
|
||||||
|
|
||||||
let xml = xml.replace(
|
|
||||||
r#"(Anm.: § 69 aufgehoben durch Art. 1 Z 12, BGBl. I Nr. 124/2017)"#,
|
|
||||||
r#"<gldsym>§ 69.</gldsym>(Anm.: § 69 aufgehoben durch Art. 1 Z 12, BGBl. I Nr. 124/2017)"#,
|
|
||||||
);
|
|
||||||
|
|
||||||
// § 49d FSG
|
|
||||||
let xml = xml.replace(
|
|
||||||
r#"</absatz><liste><schlussteil ebene="0" art="normal" ct="text">Die Finanzordnungswidrigkeit wird mit einer Geldstrafe bis zu 50 000 Euro geahndet.</schlussteil></liste>"#,
|
|
||||||
r#"Die Finanzordnungswidrigkeit wird mit einer Geldstrafe bis zu 50 000 Euro geahndet.</absatz>"#,
|
|
||||||
);
|
|
||||||
|
|
||||||
// § 127 FSG
|
|
||||||
let xml = xml.replace(
|
|
||||||
r#"<absatz typ="erltext" ct="text" halign="j">Die Öffentlichkeit ist auszuschließen:</absatz>"#,
|
|
||||||
r#"<absatz typ="abs" ct="text" halign="j">Die Öffentlichkeit ist auszuschließen:</absatz>"#,
|
|
||||||
);
|
|
||||||
|
|
||||||
// § 56 FSG
|
|
||||||
let xml = xml.replace(
|
|
||||||
r#"<ueberschrift typ="g2" ct="text" halign="c">A. Allgemeine Bestimmungen</ueberschrift>"#,
|
|
||||||
r#"<ueberschrift typ="g1" ct="text" halign="c">A. Allgemeine Bestimmungen</ueberschrift>"#,
|
|
||||||
);
|
|
||||||
|
|
||||||
// § 167 FSG
|
|
||||||
let xml = xml.replace(
|
|
||||||
r#"<absatz typ="abs" ct="text" halign="j">(1) Gegen die Versäumung einer Frist oder einer mündlichen Verhandlung ist auf Antrag des Beschuldigten oder der Nebenbeteiligten eines anhängigen oder abgeschlossenen Finanzstrafverfahrens die Wiedereinsetzung in den vorigen Stand zu bewilligen, wenn der Antragsteller durch die Versäumung einen Rechtsnachteil erleidet und glaubhaft macht, daß er durch ein unvorhergesehenes oder unabwendbares Ereignis verhindert war, die Frist einzuhalten oder zur Verhandlung zu erscheinen. Daß dem Beschuldigten oder dem Nebenbeteiligten ein Verschulden an der Versäumung zur Last liegt, hindert die Bewilligung der Wiedereinsetzung nicht, wenn es sich nur um einen minderen Grad des Versehens handelt.</absatz>"#,
|
|
||||||
r#"<absatz typ="abs" ct="text" halign="j"><gldsym>§ 167.</gldsym>(1) Gegen die Versäumung einer Frist oder einer mündlichen Verhandlung ist auf Antrag des Beschuldigten oder der Nebenbeteiligten eines anhängigen oder abgeschlossenen Finanzstrafverfahrens die Wiedereinsetzung in den vorigen Stand zu bewilligen, wenn der Antragsteller durch die Versäumung einen Rechtsnachteil erleidet und glaubhaft macht, daß er durch ein unvorhergesehenes oder unabwendbares Ereignis verhindert war, die Frist einzuhalten oder zur Verhandlung zu erscheinen. Daß dem Beschuldigten oder dem Nebenbeteiligten ein Verschulden an der Versäumung zur Last liegt, hindert die Bewilligung der Wiedereinsetzung nicht, wenn es sich nur um einen minderen Grad des Versehens handelt.</absatz>"#,
|
|
||||||
);
|
|
||||||
|
|
||||||
debug!("{xml}");
|
|
||||||
|
|
||||||
let continue_parsing = Risdok::from_str(&xml, builder)?;
|
|
||||||
|
|
||||||
Ok(continue_parsing)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use pretty_assertions::assert_eq;
|
|
||||||
use std::{
|
|
||||||
fs::{self, File},
|
|
||||||
io::{self, BufRead, Read},
|
|
||||||
path::Path,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
fn read_lines<P>(filename: P) -> io::Result<Vec<String>>
|
|
||||||
where
|
|
||||||
P: AsRef<Path>,
|
|
||||||
{
|
|
||||||
let file = File::open(filename)?;
|
|
||||||
let buf_reader = io::BufReader::new(file);
|
|
||||||
buf_reader.lines().collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_par_full_urhg() {
|
|
||||||
let mut builder = LawBuilder::test("UrhG");
|
|
||||||
|
|
||||||
let path = Path::new("./data/urhg/par");
|
|
||||||
let mut entries: Vec<_> = fs::read_dir(path)
|
|
||||||
.unwrap()
|
|
||||||
.filter_map(|entry| entry.ok())
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
entries.sort_by_key(|entry| entry.file_name());
|
|
||||||
|
|
||||||
for entry in entries {
|
|
||||||
let mut file = File::open(path.join(entry.file_name())).unwrap();
|
|
||||||
let mut json = String::new();
|
|
||||||
file.read_to_string(&mut json).unwrap();
|
|
||||||
|
|
||||||
let cont = parse_from_str(&json, &mut builder).unwrap();
|
|
||||||
if !cont {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let expected = read_lines(path.join("../par.result")).unwrap();
|
|
||||||
|
|
||||||
for (actual, expected) in builder.history.iter().zip(&expected) {
|
|
||||||
assert_eq!(actual, expected);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,560 +0,0 @@
|
|||||||
use roxmltree::Node;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
law::{Content, LawBuilder},
|
|
||||||
Error,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub(crate) struct Risdok {}
|
|
||||||
|
|
||||||
impl Risdok {
|
|
||||||
pub(crate) fn parse(n: Node, builder: &mut LawBuilder) -> bool {
|
|
||||||
assert!(n.tag_name().name() == "risdok");
|
|
||||||
|
|
||||||
let mut c = n.children();
|
|
||||||
|
|
||||||
Metadaten::parse(c.next().unwrap());
|
|
||||||
let nutzdaten = Nutzdaten::parse(c.next().unwrap(), builder);
|
|
||||||
if !nutzdaten {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
Layoutdaten::parse(c.next().unwrap());
|
|
||||||
|
|
||||||
assert_eq!(c.next(), None);
|
|
||||||
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn from_str(xml: &str, builder: &mut LawBuilder) -> Result<bool, Error> {
|
|
||||||
let doc = roxmltree::Document::parse(xml)?;
|
|
||||||
let root = doc.root();
|
|
||||||
assert_eq!(root.children().count(), 1);
|
|
||||||
Ok(Self::parse(root.children().next().unwrap(), builder))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub(crate) struct Metadaten;
|
|
||||||
impl Metadaten {
|
|
||||||
pub(crate) fn parse(n: Node) -> Self {
|
|
||||||
assert!(n.tag_name().name() == "metadaten");
|
|
||||||
|
|
||||||
assert_eq!(n.children().next(), None);
|
|
||||||
|
|
||||||
Self {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub(crate) struct Nutzdaten {}
|
|
||||||
impl Nutzdaten {
|
|
||||||
pub(crate) fn parse(n: Node, builder: &mut LawBuilder) -> bool {
|
|
||||||
assert!(n.tag_name().name() == "nutzdaten");
|
|
||||||
|
|
||||||
let mut c = n.children();
|
|
||||||
|
|
||||||
let ret = Abschnitt::parse(c.next().unwrap(), builder);
|
|
||||||
|
|
||||||
assert_eq!(c.next(), None);
|
|
||||||
|
|
||||||
ret
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub(crate) struct Abschnitt;
|
|
||||||
impl Abschnitt {
|
|
||||||
pub(crate) fn parse(n: Node, builder: &mut LawBuilder) -> bool {
|
|
||||||
assert!(n.tag_name().name() == "abschnitt");
|
|
||||||
|
|
||||||
let mut c = n.children().peekable();
|
|
||||||
|
|
||||||
Kzinhalt::parse(c.next().unwrap());
|
|
||||||
Kzinhalt::parse(c.next().unwrap());
|
|
||||||
Fzinhalt::parse(c.next().unwrap());
|
|
||||||
Fzinhalt::parse(c.next().unwrap());
|
|
||||||
|
|
||||||
// Skip all UeberschriftTitle and Absatz
|
|
||||||
while let Some(child) = c.peek() {
|
|
||||||
if Ueberschrift::test(child, "titel") {
|
|
||||||
c.next();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if Absatz::test_with_typ(child, "erltext") {
|
|
||||||
c.next();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
while let Some(child) = c.peek() {
|
|
||||||
if Ueberschrift::test(child, "g1") {
|
|
||||||
let ueberschrift = Ueberschrift::parse(c.next().unwrap(), "g1");
|
|
||||||
if ueberschrift.content.trim().starts_with("Artikel") {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
builder.new_header(&ueberschrift.content);
|
|
||||||
} else if Ueberschrift::test(child, "g2") {
|
|
||||||
let ueberschrift = Ueberschrift::parse(c.next().unwrap(), "g2");
|
|
||||||
builder.new_desc(&ueberschrift.content);
|
|
||||||
} else if Ueberschrift::test(child, "g1min") {
|
|
||||||
let ueberschrift = Ueberschrift::parse(c.next().unwrap(), "g1min");
|
|
||||||
builder.new_header(&ueberschrift.content);
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(child) = c.peek() {
|
|
||||||
if Ueberschrift::test(child, "para") {
|
|
||||||
builder
|
|
||||||
.new_next_para_header(&Ueberschrift::parse(c.next().unwrap(), "para").content);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// e.g. § 405 abgb has two para (of diseased paragraph)
|
|
||||||
if let Some(child) = c.peek() {
|
|
||||||
if Ueberschrift::test(child, "para") {
|
|
||||||
builder
|
|
||||||
.new_next_para_header(&Ueberschrift::parse(c.next().unwrap(), "para").content);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Continue here: We want to create a `Section`.
|
|
||||||
//
|
|
||||||
// We have 2 tasks
|
|
||||||
// 1) Get paragraph id
|
|
||||||
// 2) Get content
|
|
||||||
|
|
||||||
let mut absatze = Vec::new();
|
|
||||||
let absatz = AbsatzAbs::parse(c.next().expect("We need at least one 'Absatz'"));
|
|
||||||
let par_id = absatz
|
|
||||||
.gldsym
|
|
||||||
.clone()
|
|
||||||
.expect("First 'Absatz' needs to have § id");
|
|
||||||
|
|
||||||
// If there's a "liste" after an "absatz", the "liste" should be part of the "absatz"
|
|
||||||
if let Some(child) = c.peek() {
|
|
||||||
if Liste::test(child) {
|
|
||||||
let liste = Liste::parse(c.next().unwrap());
|
|
||||||
absatze.push(Content::List(vec![
|
|
||||||
Content::Text(absatz.content.replace('\u{a0}', " ")),
|
|
||||||
liste.get_content(),
|
|
||||||
]));
|
|
||||||
} else if Table::test(child) {
|
|
||||||
// If there's a "table" after an "absatz", the "table" should be part of the "absatz"
|
|
||||||
let table = Table::parse(c.next().unwrap());
|
|
||||||
if let Some(child) = c.peek() {
|
|
||||||
if Absatz::test_with_typ(child, "erltext") {
|
|
||||||
let after_absatz = Absatz::parse(c.next().unwrap());
|
|
||||||
absatze.push(Content::List(vec![
|
|
||||||
Content::Text(absatz.content.replace('\u{a0}', " ")),
|
|
||||||
Content::List(table.get_list()),
|
|
||||||
Content::Text(after_absatz.content),
|
|
||||||
]))
|
|
||||||
} else {
|
|
||||||
absatze.push(Content::List(vec![
|
|
||||||
Content::Text(absatz.content.replace('\u{a0}', " ")),
|
|
||||||
Content::List(table.get_list()),
|
|
||||||
]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
absatze.push(Content::Text(absatz.content.replace('\u{a0}', " ").clone()));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
absatze.push(Content::Text(absatz.content.replace('\u{a0}', " ").clone()));
|
|
||||||
}
|
|
||||||
|
|
||||||
//There can be as many 'Absätze' as our lovely lawsetter wants
|
|
||||||
while let Some(child) = c.peek() {
|
|
||||||
if AbsatzAbs::test(child) {
|
|
||||||
let abs = AbsatzAbs::parse(c.next().unwrap());
|
|
||||||
|
|
||||||
// If there's a "liste" after an "absatz", the "liste" should be part of the "absatz"
|
|
||||||
if let Some(child) = c.peek() {
|
|
||||||
if Liste::test(child) {
|
|
||||||
let liste = Liste::parse(c.next().unwrap());
|
|
||||||
absatze.push(Content::List(vec![
|
|
||||||
Content::Text(abs.content.replace('\u{a0}', " ")),
|
|
||||||
liste.get_content(),
|
|
||||||
]));
|
|
||||||
} else {
|
|
||||||
absatze.push(Content::Text(abs.content.replace('\u{a0}', " ")));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
absatze.push(Content::Text(abs.content.replace('\u{a0}', " ")));
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if absatze.len() == 1 {
|
|
||||||
builder.new_par(par_id, absatze[0].clone());
|
|
||||||
} else {
|
|
||||||
let mut contents = Vec::new();
|
|
||||||
for a in &absatze {
|
|
||||||
contents.push(a.clone());
|
|
||||||
}
|
|
||||||
builder.new_par(par_id, Content::Item(contents));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip all UeberschriftTitle and Absatz
|
|
||||||
while let Some(child) = c.peek() {
|
|
||||||
if Ueberschrift::test(child, "titel") {
|
|
||||||
c.next();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if Absatz::test(child) {
|
|
||||||
c.next();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
assert_eq!(c.next(), None);
|
|
||||||
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
|
||||||
pub(crate) struct Symbol {
|
|
||||||
stellen: String,
|
|
||||||
content: String,
|
|
||||||
}
|
|
||||||
impl Symbol {
|
|
||||||
pub(crate) fn parse(n: Node) -> Self {
|
|
||||||
assert!(n.tag_name().name() == "symbol");
|
|
||||||
assert_eq!(n.children().count(), 1);
|
|
||||||
|
|
||||||
let stellen = n.attribute("stellen").unwrap().into();
|
|
||||||
let content = n.text().unwrap().into();
|
|
||||||
|
|
||||||
Self { stellen, content }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
|
||||||
pub(crate) struct Listelem {
|
|
||||||
symbol: Symbol,
|
|
||||||
text: String,
|
|
||||||
}
|
|
||||||
impl Listelem {
|
|
||||||
pub(crate) fn parse(n: Node) -> Self {
|
|
||||||
assert!(n.tag_name().name() == "listelem");
|
|
||||||
|
|
||||||
let mut c = n.children();
|
|
||||||
|
|
||||||
let symbol = Symbol::parse(c.next().unwrap());
|
|
||||||
|
|
||||||
let text = c.next().unwrap().text().unwrap().into();
|
|
||||||
|
|
||||||
assert_eq!(c.next(), None);
|
|
||||||
|
|
||||||
Self { symbol, text }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
|
||||||
pub(crate) struct Ziffernliste {
|
|
||||||
ebene: String,
|
|
||||||
listelems: Vec<Listelem>,
|
|
||||||
}
|
|
||||||
impl Ziffernliste {
|
|
||||||
pub(crate) fn test(n: &Node) -> bool {
|
|
||||||
// strichliste -> § 194b FSG
|
|
||||||
["ziffernliste", "aufzaehlung", "literaliste", "strichliste"].contains(&n.tag_name().name())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn parse(n: Node) -> Self {
|
|
||||||
assert!(Self::test(&n));
|
|
||||||
|
|
||||||
let ebene = n.attribute("ebene").unwrap().into();
|
|
||||||
|
|
||||||
let mut listelems = Vec::new();
|
|
||||||
|
|
||||||
for child in n.children() {
|
|
||||||
listelems.push(Listelem::parse(child));
|
|
||||||
}
|
|
||||||
|
|
||||||
Self { ebene, listelems }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn get_content(&self) -> Content {
|
|
||||||
let mut elems = Vec::new();
|
|
||||||
|
|
||||||
for elem in &self.listelems {
|
|
||||||
elems.push(Content::Text(
|
|
||||||
format!("{} {}", elem.symbol.content, elem.text).replace('\u{a0}', " "),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
Content::List(elems)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub(crate) struct Td {
|
|
||||||
absatz: Absatz,
|
|
||||||
}
|
|
||||||
impl Td {
|
|
||||||
pub(crate) fn parse(n: &Node) -> Self {
|
|
||||||
assert!(n.tag_name().name() == "td");
|
|
||||||
|
|
||||||
let mut c = n.children();
|
|
||||||
let absatz = Absatz::parse(c.next().unwrap());
|
|
||||||
|
|
||||||
assert_eq!(c.next(), None);
|
|
||||||
|
|
||||||
Self { absatz }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub(crate) struct Tr {
|
|
||||||
tds: Vec<Td>,
|
|
||||||
}
|
|
||||||
impl Tr {
|
|
||||||
pub(crate) fn parse(n: &Node) -> Self {
|
|
||||||
assert!(n.tag_name().name() == "tr");
|
|
||||||
|
|
||||||
let mut tds = Vec::new();
|
|
||||||
|
|
||||||
for child in n.children() {
|
|
||||||
tds.push(Td::parse(&child));
|
|
||||||
}
|
|
||||||
|
|
||||||
Self { tds }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub(crate) struct Table {
|
|
||||||
trs: Vec<Tr>,
|
|
||||||
}
|
|
||||||
impl Table {
|
|
||||||
pub(crate) fn test(n: &Node) -> bool {
|
|
||||||
n.tag_name().name() == "table"
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn parse(n: Node) -> Self {
|
|
||||||
assert!(Self::test(&n));
|
|
||||||
let mut trs = Vec::new();
|
|
||||||
|
|
||||||
for child in n.children() {
|
|
||||||
trs.push(Tr::parse(&child));
|
|
||||||
}
|
|
||||||
|
|
||||||
Self { trs }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn get_list(&self) -> Vec<Content> {
|
|
||||||
let mut ret = Vec::new();
|
|
||||||
|
|
||||||
for tr in &self.trs {
|
|
||||||
let mut txt = String::new();
|
|
||||||
for td in &tr.tds {
|
|
||||||
txt.push_str(&format!("{} ", td.absatz.content));
|
|
||||||
}
|
|
||||||
|
|
||||||
ret.push(Content::Text(format!("- {txt}",).replace('\u{a0}', " ")));
|
|
||||||
}
|
|
||||||
|
|
||||||
ret
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub(crate) struct Schlussteil {
|
|
||||||
content: String,
|
|
||||||
}
|
|
||||||
impl Schlussteil {
|
|
||||||
pub(crate) fn test(n: &Node) -> bool {
|
|
||||||
(n.tag_name().name() == "schlussteil" || n.tag_name().name() == "schluss")
|
|
||||||
&& n.children().count() == 1
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn parse(n: Node) -> Self {
|
|
||||||
assert!(Self::test(&n));
|
|
||||||
|
|
||||||
let content = n.children().next().unwrap().text().unwrap().into(); //not sure
|
|
||||||
|
|
||||||
Self { content }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub(crate) struct Liste {
|
|
||||||
content: Vec<Content>,
|
|
||||||
}
|
|
||||||
impl Liste {
|
|
||||||
pub(crate) fn test(n: &Node) -> bool {
|
|
||||||
n.tag_name().name() == "liste"
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn parse(n: Node) -> Self {
|
|
||||||
assert!(Self::test(&n));
|
|
||||||
|
|
||||||
let mut content = Vec::new();
|
|
||||||
|
|
||||||
let mut c = n.children().peekable();
|
|
||||||
|
|
||||||
content.push(Ziffernliste::parse(c.next().unwrap()).get_content());
|
|
||||||
|
|
||||||
while let Some(child) = c.peek() {
|
|
||||||
if Ziffernliste::test(child) {
|
|
||||||
content.push(Ziffernliste::parse(c.next().unwrap()).get_content());
|
|
||||||
} else if Schlussteil::test(child) {
|
|
||||||
content.push(Content::Text(
|
|
||||||
Schlussteil::parse(c.next().unwrap())
|
|
||||||
.content
|
|
||||||
.replace('\u{a0}', " "),
|
|
||||||
));
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
assert_eq!(c.next(), None);
|
|
||||||
|
|
||||||
Self { content }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn get_content(&self) -> Content {
|
|
||||||
Content::List(self.content.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub(crate) struct AbsatzAbs {
|
|
||||||
gldsym: Option<String>,
|
|
||||||
content: String,
|
|
||||||
}
|
|
||||||
impl AbsatzAbs {
|
|
||||||
pub(crate) fn test(n: &Node) -> bool {
|
|
||||||
n.tag_name().name() == "absatz" && n.attribute("typ").unwrap() == "abs"
|
|
||||||
}
|
|
||||||
pub(crate) fn parse(n: Node) -> Self {
|
|
||||||
assert!(n.tag_name().name() == "absatz");
|
|
||||||
assert_eq!(n.attribute("typ").unwrap(), "abs");
|
|
||||||
|
|
||||||
let mut c = n.children().peekable();
|
|
||||||
|
|
||||||
let gldsym = match c.peek() {
|
|
||||||
Some(child) => {
|
|
||||||
if Leaf::test(child, "gldsym".into()) {
|
|
||||||
Some(Leaf::parse(c.next().unwrap(), "gldsym".into()).replace('\u{a0}', " "))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let ret = Self {
|
|
||||||
gldsym,
|
|
||||||
content: c.next().unwrap().text().unwrap().trim().into(),
|
|
||||||
};
|
|
||||||
|
|
||||||
assert_eq!(c.next(), None);
|
|
||||||
|
|
||||||
ret
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub(crate) struct Leaf {
|
|
||||||
content: String,
|
|
||||||
}
|
|
||||||
impl Leaf {
|
|
||||||
pub(crate) fn test(n: &Node, name: String) -> bool {
|
|
||||||
n.tag_name().name() == name && n.children().count() == 1
|
|
||||||
}
|
|
||||||
pub(crate) fn parse(n: Node, name: String) -> String {
|
|
||||||
assert!(n.tag_name().name() == name);
|
|
||||||
|
|
||||||
assert_eq!(n.children().count(), 1);
|
|
||||||
|
|
||||||
n.text().unwrap().into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub(crate) struct Absatz {
|
|
||||||
content: String,
|
|
||||||
}
|
|
||||||
impl Absatz {
|
|
||||||
pub(crate) fn test(n: &Node) -> bool {
|
|
||||||
n.tag_name().name() == "absatz"
|
|
||||||
}
|
|
||||||
pub(crate) fn test_with_typ(n: &Node, typ: &str) -> bool {
|
|
||||||
n.tag_name().name() == "absatz" && n.attribute("typ") == Some(typ)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn parse(n: Node) -> Self {
|
|
||||||
assert!(n.tag_name().name() == "absatz");
|
|
||||||
|
|
||||||
if let Some(text) = n.text() {
|
|
||||||
Self {
|
|
||||||
content: text.into(),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Self { content: "".into() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub(crate) struct Ueberschrift {
|
|
||||||
typ: String,
|
|
||||||
content: String,
|
|
||||||
}
|
|
||||||
impl Ueberschrift {
|
|
||||||
fn test(n: &Node, typ: &str) -> bool {
|
|
||||||
n.tag_name().name() == "ueberschrift" && n.attribute("typ").unwrap() == typ
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn parse(n: Node, typ: &str) -> Self {
|
|
||||||
assert!(n.tag_name().name() == "ueberschrift");
|
|
||||||
|
|
||||||
assert_eq!(n.attribute("typ").unwrap(), typ);
|
|
||||||
|
|
||||||
Self {
|
|
||||||
content: n.text().unwrap().into(),
|
|
||||||
typ: typ.into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub(crate) struct Kzinhalt;
|
|
||||||
impl Kzinhalt {
|
|
||||||
pub(crate) fn parse(n: Node) -> Self {
|
|
||||||
assert!(n.tag_name().name() == "kzinhalt");
|
|
||||||
//TODO parse if necessary
|
|
||||||
Self {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub(crate) struct Fzinhalt;
|
|
||||||
impl Fzinhalt {
|
|
||||||
pub(crate) fn parse(n: Node) -> Self {
|
|
||||||
assert!(n.tag_name().name() == "fzinhalt");
|
|
||||||
//TODO parse if necessary
|
|
||||||
Self {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub(crate) struct Layoutdaten;
|
|
||||||
impl Layoutdaten {
|
|
||||||
pub(crate) fn parse(n: Node) -> Self {
|
|
||||||
assert!(n.tag_name().name() == "layoutdaten");
|
|
||||||
|
|
||||||
assert_eq!(n.children().next(), None);
|
|
||||||
|
|
||||||
Self {}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user