use std::str::FromStr;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use time::Date;
pub mod bill_parser;
pub mod parser;
pub mod path;
#[derive(Error, Debug)]
pub enum USLMError {
#[error("Unknown Document Type {0}")]
UnknownDocumentType(String),
#[error("Unknown Amending Action {0}")]
UnknownAmendingAction(String),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum DocumentType {
#[serde(rename = "us_code")]
USCode {
usc_type: USCType,
},
Bill {
bill_type: BillType,
bill_id: String,
},
}
impl DocumentType {
pub fn from_str(s: &str, meta_str: Option<&str>) -> Result<Self, USLMError> {
match s.to_lowercase().as_str() {
"publiclaw" | "public_law" | "plaw" => match meta_str {
Some(val) => Ok(Self::Bill {
bill_type: BillType::PublicLaw,
bill_id: val.to_string(),
}),
None => Err(USLMError::UnknownDocumentType(
"Bill types must pass the bill_id as the meta_str parameter".to_string(),
)),
},
"uscode" | "us_code" | "uscdoc" => match meta_str {
Some(val) => match val.to_lowercase().as_str() {
"usctitle" => Ok(DocumentType::USCode {
usc_type: USCType::Title,
}),
"usctitleappendix" => Ok(DocumentType::USCode {
usc_type: USCType::TitleAppendix,
}),
_ => Err(USLMError::UnknownDocumentType(format!(
"Unhandled type for USCode document: {}",
val.to_lowercase()
))),
},
None => Err(USLMError::UnknownDocumentType(
"USCode types need to provide a type_str".to_string(),
)),
},
_ => Err(USLMError::UnknownDocumentType(s.to_string())),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum BillType {
PublicLaw,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum USCType {
Title,
TitleAppendix,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ElementType {
#[serde(rename = "us_code_document")]
USCodeDocument,
PublicLawDocument,
Title,
Appendix,
Subtitle,
Chapter,
Subchapter,
Part,
Subpart,
Section,
Subsection,
Paragraph,
Subparagraph,
Clause,
Subclause,
Level,
Item,
Subitem,
Subsubitem,
Division,
Subdivision,
Unknown,
}
impl std::str::FromStr for ElementType {
type Err = USLMError;
fn from_str(s: &str) -> Result<ElementType, USLMError> {
match s.to_lowercase().as_str() {
"title" => Ok(Self::Title),
"subtitle" => Ok(Self::Subtitle),
"chapter" => Ok(Self::Chapter),
"subchapter" => Ok(Self::Subchapter),
"part" => Ok(Self::Part),
"subpart" => Ok(Self::Subpart),
"section" => Ok(Self::Section),
"subsection" => Ok(Self::Subsection),
"paragraph" => Ok(Self::Paragraph),
"subparagraph" => Ok(Self::Subparagraph),
"clause" => Ok(Self::Clause),
"subclause" => Ok(Self::Subclause),
"level" => Ok(Self::Level),
"item" => Ok(Self::Item),
"subitem" => Ok(Self::Subitem),
"subsubitem" => Ok(Self::Subsubitem),
"division" => Ok(Self::Division),
"subdivision" => Ok(Self::Subdivision),
"publiclaw" | "public_law" | "plaw" => Ok(Self::PublicLawDocument),
"uscode" | "us_code" | "uscdoc" => Ok(Self::USCodeDocument),
"appendix" => Ok(Self::Appendix),
_ => Ok(Self::Unknown),
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum TextContentField {
Heading,
Chapeau,
Proviso,
Content,
Continuation,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AmendingAction {
Amend,
Add,
Delete,
Insert,
Redesignate,
Repeal,
Move,
Strike,
StrikeAndInsert,
}
impl FromStr for AmendingAction {
type Err = USLMError;
fn from_str(s: &str) -> std::result::Result<Self, <Self as std::str::FromStr>::Err> {
match s.to_lowercase().as_str() {
"amend" => Ok(AmendingAction::Amend),
"add" => Ok(AmendingAction::Add),
"delete" => Ok(AmendingAction::Delete),
"insert" => Ok(AmendingAction::Insert),
"redesignate" => Ok(AmendingAction::Redesignate),
"repeal" => Ok(AmendingAction::Repeal),
"move" => Ok(AmendingAction::Move),
"strike" => Ok(AmendingAction::Strike),
"strikeandinsert" | "strike_and_insert" => Ok(AmendingAction::StrikeAndInsert),
_ => Err(USLMError::UnknownAmendingAction(s.to_lowercase())),
}
}
}
impl AmendingAction {
#[allow(dead_code)]
fn extract_all_text(node: &roxmltree::Node) -> String {
let mut text = String::new();
for descendant in node.descendants() {
if let Some(t) = descendant.text() {
if !text.is_empty() {
text.push(' ');
}
text.push_str(t);
}
}
text
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
pub struct UscReference {
pub path: String,
pub display_text: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
pub struct BillAmendment {
pub id: String,
pub action_types: Vec<AmendingAction>,
pub amending_text: String,
pub changes: Vec<BillDiff>,
}
impl BillAmendment {
pub fn update_changes(&self, changes: &[BillDiff]) -> Self {
BillAmendment {
id: self.id.clone(),
action_types: self.action_types.clone(),
amending_text: self.amending_text.clone(),
changes: changes.to_vec(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
pub struct BillDiff {
pub added: Vec<String>,
pub removed: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct SourceCredit {
pub ref_pairs: Vec<RefPair>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct RefPair {
pub ref_id: String,
pub description: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct ElementData {
pub path: String,
pub element_type: ElementType,
pub document_type: DocumentType,
pub date: Date,
pub number_value: String,
pub number_display: String,
pub verbose_name: String,
pub heading: Option<String>,
pub chapeau: Option<String>,
pub proviso: Option<String>,
pub content: Option<String>,
pub continuation: Option<String>,
pub uslm_id: Option<String>,
pub uslm_uuid: Option<String>,
pub source_credits: Vec<SourceCredit>,
}
impl ElementData {
pub fn get_text_content(&self, field: TextContentField) -> Option<String> {
match field {
TextContentField::Heading => self.heading.clone(),
TextContentField::Chapeau => self.chapeau.clone(),
TextContentField::Proviso => self.proviso.clone(),
TextContentField::Content => self.content.clone(),
TextContentField::Continuation => self.continuation.clone(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct USLMElement {
pub data: ElementData,
pub children: Vec<USLMElement>,
}
impl USLMElement {
pub fn find(&self, path: &str) -> Option<&USLMElement> {
if path == self.data.path.as_str() {
return Some(self);
}
let remaining_path = path.strip_prefix(self.data.path.as_str())?;
let next_step: Vec<&str> = remaining_path.split("/").collect();
assert!(next_step.len() > 1);
let child_id = next_step[1];
let child_vec: Vec<&USLMElement> = self
.children
.iter()
.filter(|c| c.data.path.ends_with(child_id))
.collect();
if child_vec.is_empty() {
None
} else {
assert!(child_vec.len() == 1);
child_vec[0].find(path)
}
}
}