use std::{
collections::{BTreeMap, HashSet},
num::NonZeroU64,
str::FromStr,
};
use crate::{
EMLError, EMLVersion, NS_EML, NS_KR, OASIS_EML_SCHEMA_VERSION,
common::{
Agent, CandidateIdentifier, CanonicalizationMethod, Contact, ContestIdentifier,
CreationDateTime, ElectionDomain, IssueDate, ListData, ListDataBelongsToCombination,
ManagingAuthority, PersonNameStructure, QualifyingAddress, TransactionId,
},
documents::ElectionIdentifierBuilder,
error::EMLErrorKind,
io::{
EMLDocument, EMLElement, EMLElementReader, EMLElementWriter, EMLReadElement as _,
EMLWriteElement as _, QualifiedName, collect_struct,
},
utils::{
AffiliationId, AffiliationType, CandidateId, ElectionCategory, ElectionId,
ElectionSubcategory, Gender, GenderAnnex, NameShortCode, PublicationLanguage, StringValue,
XsDate, XsDateOrDateTime, XsDateTime,
},
};
#[derive(Debug, Clone)]
pub struct CandidateLists {
pub version: EMLVersion,
pub lists_type: CandidateListsType,
pub transaction_id: TransactionId,
pub managing_authority: ManagingAuthority,
pub issue_date: IssueDate,
pub creation_date_time: CreationDateTime,
pub canonicalization_method: Option<CanonicalizationMethod>,
pub candidate_list: CandidateListsCandidateList,
}
impl CandidateLists {
pub fn builder() -> CandidateListsBuilder {
CandidateListsBuilder::new()
}
}
impl EMLDocument for CandidateLists {
fn document_version(&self) -> EMLVersion {
self.version
}
fn document_eml_id(&self) -> &'static str {
self.lists_type.to_eml_id()
}
fn document_friendly_name(&self) -> &'static str {
self.lists_type.to_friendly_name()
}
fn document_namespaces(&self) -> Option<BTreeMap<&'static str, &'static str>> {
None
}
}
impl FromStr for CandidateLists {
type Err = EMLError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
use crate::io::EMLRead as _;
Self::parse_eml(s, crate::io::EMLParsingMode::Strict).ok()
}
}
impl TryFrom<&str> for CandidateLists {
type Error = EMLError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
use crate::io::EMLRead as _;
Self::parse_eml(value, crate::io::EMLParsingMode::Strict).ok()
}
}
impl TryFrom<CandidateLists> for String {
type Error = EMLError;
fn try_from(value: CandidateLists) -> Result<Self, Self::Error> {
use crate::io::EMLWrite as _;
value.write_eml_root_str(true, true)
}
}
#[derive(Debug, Clone)]
pub struct CandidateListsBuilder {
version: Option<EMLVersion>,
lists_type: Option<CandidateListsType>,
transaction_id: Option<TransactionId>,
managing_authority: Option<ManagingAuthority>,
issue_date: Option<IssueDate>,
creation_date_time: Option<CreationDateTime>,
canonicalization_method: Option<CanonicalizationMethod>,
candidate_list: Option<CandidateListsCandidateList>,
election_identifier: Option<CandidateListsElectionIdentifier>,
list_date: Option<CandidateListsListDate>,
contests: Vec<CandidateListsContest>,
}
impl CandidateListsBuilder {
pub fn new() -> Self {
CandidateListsBuilder {
version: None,
lists_type: None,
transaction_id: None,
managing_authority: None,
issue_date: None,
creation_date_time: None,
canonicalization_method: None,
candidate_list: None,
election_identifier: None,
list_date: None,
contests: vec![],
}
}
pub fn version(mut self, version: impl Into<EMLVersion>) -> Self {
self.version = Some(version.into());
self
}
pub fn lists_type(mut self, list_type: impl Into<CandidateListsType>) -> Self {
self.lists_type = Some(list_type.into());
self
}
pub fn transaction_id(mut self, transaction_id: impl Into<TransactionId>) -> Self {
self.transaction_id = Some(transaction_id.into());
self
}
pub fn managing_authority(mut self, managing_authority: impl Into<ManagingAuthority>) -> Self {
self.managing_authority = Some(managing_authority.into());
self
}
pub fn issue_date(mut self, issue_date: impl Into<XsDateOrDateTime>) -> Self {
self.issue_date = Some(IssueDate::new(issue_date.into()));
self
}
pub fn creation_date_time(mut self, creation_date_time: impl Into<XsDateTime>) -> Self {
self.creation_date_time = Some(CreationDateTime::new(creation_date_time.into()));
self
}
pub fn canonicalization_method(
mut self,
canonicalization_method: impl Into<CanonicalizationMethod>,
) -> Self {
self.canonicalization_method = Some(canonicalization_method.into());
self
}
pub fn candidate_list(
mut self,
candidate_list: impl Into<CandidateListsCandidateList>,
) -> Self {
self.candidate_list = Some(candidate_list.into());
self
}
pub fn list_date(mut self, list_date: impl Into<XsDateOrDateTime>) -> Self {
self.list_date = Some(CandidateListsListDate::from(list_date.into()));
self
}
pub fn election_identifier(
mut self,
election_identifier: impl Into<CandidateListsElectionIdentifier>,
) -> Self {
self.election_identifier = Some(election_identifier.into());
self
}
pub fn contests(mut self, contests: impl Into<Vec<CandidateListsContest>>) -> Self {
self.contests = contests.into();
self
}
pub fn push_contest(mut self, contest: impl Into<CandidateListsContest>) -> Self {
self.contests.push(contest.into());
self
}
pub fn build(self) -> Result<CandidateLists, EMLError> {
let candidate_lists = CandidateLists {
version: self.version.unwrap_or_default(),
lists_type: self
.lists_type
.ok_or_else(|| EMLErrorKind::MissingBuildProperty("lists_type").without_span())?,
transaction_id: self
.transaction_id
.ok_or(EMLErrorKind::MissingBuildProperty("transaction_id").without_span())?,
managing_authority: self
.managing_authority
.ok_or(EMLErrorKind::MissingBuildProperty("managing_authority").without_span())?,
issue_date: self
.issue_date
.ok_or(EMLErrorKind::MissingBuildProperty("issue_date").without_span())?,
creation_date_time: self
.creation_date_time
.ok_or(EMLErrorKind::MissingBuildProperty("creation_date_time").without_span())?,
canonicalization_method: self.canonicalization_method,
candidate_list: self.candidate_list.map_or_else(
|| {
if self.contests.is_empty() {
return Err(EMLErrorKind::MissingBuildProperty("contests").without_span());
}
let election = CandidateListsElection::new(self.election_identifier.ok_or(
EMLErrorKind::MissingBuildProperty("election_identifier").without_span(),
)?)
.with_contests(self.contests);
let list = CandidateListsCandidateList::new(election);
let list = if let Some(list_date) = self.list_date {
list.with_list_date(list_date)
} else {
list
};
Ok(list)
},
Ok,
)?,
};
candidate_lists
.candidate_list
.election
.validate_short_codes()?;
Ok(candidate_lists)
}
}
impl Default for CandidateListsBuilder {
fn default() -> Self {
Self::new()
}
}
impl EMLElement for CandidateLists {
const EML_NAME: QualifiedName<'_, '_> = QualifiedName::from_static("EML", Some(NS_EML));
fn read_eml(elem: &mut EMLElementReader<'_, '_>) -> Result<Self, EMLError> {
let document_id = elem.attribute_value_req(("Id", None))?;
let candidate_lists_type = CandidateListsType::from_eml_id(document_id.as_ref())
.map_err(|e| e.into_kind().with_span(elem.span()))?;
Ok(collect_struct!(elem, CandidateLists {
version as Default(EMLVersion::V1_2_2): EMLVersion::EML_NAME => |elem| elem.read_element::<EMLVersion>()?,
lists_type: candidate_lists_type,
transaction_id: TransactionId::EML_NAME => |elem| elem.read_element::<TransactionId>()?,
managing_authority: ManagingAuthority::EML_NAME => |elem| elem.read_element::<ManagingAuthority>()?,
issue_date: IssueDate::EML_NAME => |elem| elem.read_element::<IssueDate>()?,
creation_date_time: CreationDateTime::EML_NAME => |elem| elem.read_element::<CreationDateTime>()?,
canonicalization_method as Option: CanonicalizationMethod::EML_NAME => |elem| elem.read_element::<CanonicalizationMethod>()?,
candidate_list: CandidateListsCandidateList::EML_NAME => |elem| elem.read_element::<CandidateListsCandidateList>()?,
}))
}
fn write_eml(&self, writer: EMLElementWriter) -> Result<(), EMLError> {
writer
.attr(("Id", None), self.lists_type.to_eml_id())?
.attr(("SchemaVersion", None), OASIS_EML_SCHEMA_VERSION)?
.child_elem(TransactionId::EML_NAME, &self.transaction_id)?
.child_elem(ManagingAuthority::EML_NAME, &self.managing_authority)?
.child_elem(IssueDate::EML_NAME, &self.issue_date)?
.child_option(
EMLVersion::EML_NAME,
self.version.to_str(),
|elem, version| elem.attr("Version", version)?.empty(),
)?
.child_elem(CreationDateTime::EML_NAME, &self.creation_date_time)?
.child_elem(CandidateListsCandidateList::EML_NAME, &self.candidate_list)?
.finish()
}
}
pub(crate) const EML_CANDIDATE_LISTS_SINGLE_ID: &str = "230b";
pub(crate) const EML_CANDIDATE_LISTS_MULTIPLE_ID: &str = "230c";
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CandidateListsType {
Single,
Multiple,
}
impl CandidateListsType {
pub fn from_eml_id(s: impl AsRef<str>) -> Result<Self, EMLError> {
let data = s.as_ref();
match data {
EML_CANDIDATE_LISTS_SINGLE_ID => Ok(CandidateListsType::Single),
EML_CANDIDATE_LISTS_MULTIPLE_ID => Ok(CandidateListsType::Multiple),
_ => {
Err(EMLErrorKind::InvalidDocumentType("230b/230c", data.to_string()).without_span())
}
}
}
pub fn to_eml_id(&self) -> &'static str {
match self {
CandidateListsType::Single => EML_CANDIDATE_LISTS_SINGLE_ID,
CandidateListsType::Multiple => EML_CANDIDATE_LISTS_MULTIPLE_ID,
}
}
pub fn to_friendly_name(&self) -> &'static str {
match self {
CandidateListsType::Single => "Candidate Lists",
CandidateListsType::Multiple => "Candidate Lists Total",
}
}
pub fn is_valid_eml_id(s: &str) -> bool {
matches!(
s,
EML_CANDIDATE_LISTS_SINGLE_ID | EML_CANDIDATE_LISTS_MULTIPLE_ID
)
}
}
#[derive(Debug, Clone)]
pub struct CandidateListsCandidateList {
pub list_date: Option<CandidateListsListDate>,
pub election: CandidateListsElection,
}
impl CandidateListsCandidateList {
pub fn new(election: CandidateListsElection) -> Self {
Self {
list_date: None,
election,
}
}
pub fn with_list_date(mut self, list_date: CandidateListsListDate) -> Self {
self.list_date = Some(list_date);
self
}
}
impl From<CandidateListsElection> for CandidateListsCandidateList {
fn from(election: CandidateListsElection) -> Self {
Self::new(election)
}
}
impl EMLElement for CandidateListsCandidateList {
const EML_NAME: QualifiedName<'_, '_> =
QualifiedName::from_static("CandidateList", Some(NS_EML));
fn read_eml(elem: &mut EMLElementReader<'_, '_>) -> Result<Self, EMLError> {
Ok(collect_struct!(elem, CandidateListsCandidateList {
list_date as Option: CandidateListsListDate::EML_NAME => |elem| elem.read_element::<CandidateListsListDate>()?,
election: CandidateListsElection::EML_NAME => |elem| elem.read_element::<CandidateListsElection>()?,
}))
}
fn write_eml(&self, writer: EMLElementWriter) -> Result<(), EMLError> {
writer
.child_elem_option(CandidateListsListDate::EML_NAME, self.list_date.as_ref())?
.child_elem(CandidateListsElection::EML_NAME, &self.election)?
.finish()
}
}
#[derive(Debug, Clone)]
pub struct CandidateListsListDate(pub StringValue<XsDateOrDateTime>);
impl From<XsDateOrDateTime> for CandidateListsListDate {
fn from(value: XsDateOrDateTime) -> Self {
CandidateListsListDate(StringValue::from_value(value))
}
}
impl EMLElement for CandidateListsListDate {
const EML_NAME: QualifiedName<'_, '_> = QualifiedName::from_static("ListDate", Some(NS_EML));
fn read_eml(elem: &mut EMLElementReader<'_, '_>) -> Result<Self, EMLError> {
let value = elem.string_value()?;
Ok(CandidateListsListDate(value))
}
fn write_eml(&self, writer: EMLElementWriter) -> Result<(), EMLError> {
writer.text(self.0.raw().as_ref())?.finish()
}
}
#[derive(Debug, Clone)]
pub struct CandidateListsElection {
pub identifier: CandidateListsElectionIdentifier,
pub contests: Vec<CandidateListsContest>,
}
impl CandidateListsElection {
pub fn new(identifier: impl Into<CandidateListsElectionIdentifier>) -> Self {
Self {
identifier: identifier.into(),
contests: vec![],
}
}
pub fn with_contests(mut self, contests: impl Into<Vec<CandidateListsContest>>) -> Self {
self.contests = contests.into();
self
}
pub fn push_contest(mut self, contest: impl Into<CandidateListsContest>) -> Self {
self.contests.push(contest.into());
self
}
fn all_candidates(&self) -> impl Iterator<Item = &CandidateListsCandidate> {
self.contests
.iter()
.flat_map(|contest| &contest.affiliations)
.flat_map(|affiliation| &affiliation.candidates)
}
fn validate_short_codes(&self) -> Result<(), EMLError> {
let mut defined = HashSet::new();
for candidate in self.all_candidates() {
if let Some(short_code) = &candidate.identifier.short_code
&& !defined.insert(short_code.raw())
{
return Err(
EMLErrorKind::DuplicateShortCode(short_code.raw().into_owned()).without_span(),
);
}
}
for candidate in self.all_candidates() {
if let Some(short_code_ref) = &candidate.identifier.short_code_ref
&& !defined.contains(&short_code_ref.raw())
{
return Err(EMLErrorKind::UnknownShortCodeReference(
short_code_ref.raw().into_owned(),
)
.without_span());
}
}
Ok(())
}
}
impl EMLElement for CandidateListsElection {
const EML_NAME: QualifiedName<'_, '_> = QualifiedName::from_static("Election", Some(NS_EML));
fn read_eml(elem: &mut EMLElementReader<'_, '_>) -> Result<Self, EMLError> {
let data = collect_struct!(elem, CandidateListsElection {
identifier: CandidateListsElectionIdentifier::EML_NAME => |elem| elem.read_element::<CandidateListsElectionIdentifier>()?,
contests as Vec: CandidateListsContest::EML_NAME => |elem| elem.read_element::<CandidateListsContest>()?,
});
if data.contests.is_empty() {
let err = EMLErrorKind::MissingElement(CandidateListsContest::EML_NAME.as_owned())
.with_span(elem.full_span());
if elem.parsing_mode().is_strict() {
return Err(err);
} else {
elem.push_err(err);
}
}
elem.report_validation(data.validate_short_codes(), elem.full_span())?;
Ok(data)
}
fn write_eml(&self, writer: EMLElementWriter) -> Result<(), EMLError> {
writer
.child_elem(CandidateListsElectionIdentifier::EML_NAME, &self.identifier)?
.child_elems(CandidateListsContest::EML_NAME, &self.contests)?
.finish()
}
}
#[derive(Debug, Clone)]
pub struct CandidateListsElectionIdentifier {
pub id: StringValue<ElectionId>,
pub name: Option<Box<str>>,
pub category: StringValue<ElectionCategory>,
pub subcategory: Option<StringValue<ElectionSubcategory>>,
pub domain: Option<ElectionDomain>,
pub election_date: StringValue<XsDate>,
pub nomination_date: StringValue<XsDate>,
}
impl CandidateListsElectionIdentifier {
pub fn builder() -> ElectionIdentifierBuilder {
ElectionIdentifierBuilder::new()
}
}
impl EMLElement for CandidateListsElectionIdentifier {
const EML_NAME: QualifiedName<'_, '_> =
QualifiedName::from_static("ElectionIdentifier", Some(NS_EML));
fn read_eml(elem: &mut EMLElementReader<'_, '_>) -> Result<Self, EMLError> {
let data = collect_struct!(
elem,
CandidateListsElectionIdentifier {
id: elem.string_value_attr("Id", None)?,
name as Option: ("ElectionName", NS_EML) => |elem| elem.text_without_children()?,
category: ("ElectionCategory", NS_EML) => |elem| ElectionCategory::read_and_validate(elem)?,
subcategory as Option: ("ElectionSubcategory", NS_KR) => |elem| elem.string_value()?,
domain as Option: ElectionDomain::EML_NAME => |elem| elem.read_element::<ElectionDomain>()?,
election_date: ("ElectionDate", NS_KR) => |elem| elem.string_value()?,
nomination_date: ("NominationDate", NS_KR) => |elem| elem.string_value()?,
}
);
elem.report_validation(
data.election_date.validate_is_after(
&data.nomination_date,
EMLErrorKind::NominationDateNotBeforeElectionDate,
),
elem.full_span(),
)?;
elem.report_validation(
data.category
.validate_subcategory(data.subcategory.as_ref()),
elem.full_span(),
)?;
Ok(data)
}
fn write_eml(&self, writer: EMLElementWriter) -> Result<(), EMLError> {
self.category.validate_version(writer.document_version())?;
writer
.attr("Id", self.id.raw().as_ref())?
.child_option(
("ElectionName", NS_EML),
self.name.as_ref(),
|elem, value| elem.text(value.as_ref())?.finish(),
)?
.child(("ElectionCategory", NS_EML), |elem| {
elem.text(self.category.raw().as_ref())?.finish()
})?
.child_option(
("ElectionSubcategory", NS_KR),
self.subcategory.as_ref(),
|elem, value| elem.text(value.raw().as_ref())?.finish(),
)?
.child_elem_option(ElectionDomain::EML_NAME, self.domain.as_ref())?
.child(("ElectionDate", NS_KR), |elem| {
elem.text(self.election_date.raw().as_ref())?.finish()
})?
.child(("NominationDate", NS_KR), |elem| {
elem.text(self.nomination_date.raw().as_ref())?.finish()
})?
.finish()
}
}
#[derive(Debug, Clone)]
pub struct CandidateListsContest {
pub identifier: ContestIdentifier,
pub affiliations: Vec<CandidateListsAffiliation>,
}
pub struct CandidateListsContestBuilder {
identifier: Option<ContestIdentifier>,
affiliations: Vec<CandidateListsAffiliation>,
}
impl CandidateListsContestBuilder {
pub fn new() -> Self {
Self {
identifier: None,
affiliations: vec![],
}
}
pub fn identifier(mut self, identifier: impl Into<ContestIdentifier>) -> Self {
self.identifier = Some(identifier.into());
self
}
pub fn affiliations(mut self, affiliations: impl Into<Vec<CandidateListsAffiliation>>) -> Self {
self.affiliations = affiliations.into();
self
}
pub fn push_affiliation(mut self, affiliation: impl Into<CandidateListsAffiliation>) -> Self {
self.affiliations.push(affiliation.into());
self
}
pub fn build(self) -> Result<CandidateListsContest, EMLError> {
if self.affiliations.is_empty() {
return Err(EMLErrorKind::MissingBuildProperty("affiliations").without_span());
}
Ok(CandidateListsContest {
identifier: self
.identifier
.ok_or_else(|| EMLErrorKind::MissingBuildProperty("identifier").without_span())?,
affiliations: self.affiliations,
})
}
}
impl Default for CandidateListsContestBuilder {
fn default() -> Self {
Self::new()
}
}
impl CandidateListsContest {
pub fn builder() -> CandidateListsContestBuilder {
CandidateListsContestBuilder::new()
}
}
impl EMLElement for CandidateListsContest {
const EML_NAME: QualifiedName<'_, '_> = QualifiedName::from_static("Contest", Some(NS_EML));
fn read_eml(elem: &mut EMLElementReader<'_, '_>) -> Result<Self, EMLError> {
let data = collect_struct!(elem, CandidateListsContest {
identifier: ContestIdentifier::EML_NAME => |elem| elem.read_element::<ContestIdentifier>()?,
affiliations as Vec: CandidateListsAffiliation::EML_NAME => |elem| elem.read_element::<CandidateListsAffiliation>()?,
});
if data.affiliations.is_empty() {
let err = EMLErrorKind::MissingElement(CandidateListsAffiliation::EML_NAME.as_owned())
.with_span(elem.full_span());
if elem.parsing_mode().is_strict() {
return Err(err);
} else {
elem.push_err(err);
}
}
Ok(data)
}
fn write_eml(&self, writer: EMLElementWriter) -> Result<(), EMLError> {
writer
.child_elem(ContestIdentifier::EML_NAME, &self.identifier)?
.child_elems(CandidateListsAffiliation::EML_NAME, &self.affiliations)?
.finish()
}
}
#[derive(Debug, Clone)]
pub struct CandidateListsAffiliation {
pub identifier: AffiliationIdentifier,
pub affiliation_type: StringValue<AffiliationType>,
pub list_data: ListData,
pub candidates: Vec<CandidateListsCandidate>,
}
impl CandidateListsAffiliation {
pub fn builder() -> CandidateListsAffiliationBuilder {
CandidateListsAffiliationBuilder::new()
}
}
pub struct CandidateListsAffiliationBuilder {
id: Option<AffiliationId>,
registered_name: Option<Box<str>>,
affiliation_type: Option<AffiliationType>,
publish_gender: Option<bool>,
publication_language: Option<PublicationLanguage>,
belongs_to_set: Option<NonZeroU64>,
belongs_to_combination: Option<ListDataBelongsToCombination>,
candidates: Vec<CandidateListsCandidate>,
}
impl CandidateListsAffiliationBuilder {
pub fn new() -> Self {
CandidateListsAffiliationBuilder {
id: None,
registered_name: None,
affiliation_type: None,
publish_gender: None,
publication_language: None,
belongs_to_set: None,
belongs_to_combination: None,
candidates: vec![],
}
}
pub fn id(mut self, id: impl Into<AffiliationId>) -> Self {
self.id = Some(id.into());
self
}
pub fn registered_name(mut self, registered_name: impl Into<Box<str>>) -> Self {
self.registered_name = Some(registered_name.into());
self
}
pub fn affiliation_type(mut self, affiliation_type: impl Into<AffiliationType>) -> Self {
self.affiliation_type = Some(affiliation_type.into());
self
}
pub fn publish_gender(mut self, publish_gender: bool) -> Self {
self.publish_gender = Some(publish_gender);
self
}
pub fn publication_language(
mut self,
publication_language: impl Into<PublicationLanguage>,
) -> Self {
self.publication_language = Some(publication_language.into());
self
}
pub fn belongs_to_set(mut self, belongs_to_set: NonZeroU64) -> Self {
self.belongs_to_set = Some(belongs_to_set);
self
}
pub fn belongs_to_combination(
mut self,
belongs_to_combination: impl Into<ListDataBelongsToCombination>,
) -> Self {
self.belongs_to_combination = Some(belongs_to_combination.into());
self
}
pub fn candidates(mut self, candidates: impl Into<Vec<CandidateListsCandidate>>) -> Self {
self.candidates = candidates.into();
self
}
pub fn push_candidate(mut self, candidate: impl Into<CandidateListsCandidate>) -> Self {
self.candidates.push(candidate.into());
self
}
pub fn build(self) -> Result<CandidateListsAffiliation, EMLError> {
if self.candidates.is_empty() {
return Err(EMLErrorKind::MissingBuildProperty("candidates").without_span());
}
Ok(CandidateListsAffiliation {
identifier: AffiliationIdentifier::new(
self.id
.ok_or_else(|| EMLErrorKind::MissingBuildProperty("id").without_span())?,
self.registered_name,
),
affiliation_type: StringValue::from_value(self.affiliation_type.ok_or_else(|| {
EMLErrorKind::MissingBuildProperty("affiliation_type").without_span()
})?),
list_data: ListData {
publish_gender: StringValue::from_value(self.publish_gender.ok_or_else(|| {
EMLErrorKind::MissingBuildProperty("publish_gender").without_span()
})?),
publication_language: self.publication_language.map(StringValue::from_value),
belongs_to_set: self.belongs_to_set.map(StringValue::from_value),
belongs_to_combination: self.belongs_to_combination.map(StringValue::from_value),
contests: vec![],
},
candidates: self.candidates,
})
}
}
impl Default for CandidateListsAffiliationBuilder {
fn default() -> Self {
Self::new()
}
}
impl EMLElement for CandidateListsAffiliation {
const EML_NAME: QualifiedName<'_, '_> = QualifiedName::from_static("Affiliation", Some(NS_EML));
fn read_eml(elem: &mut EMLElementReader<'_, '_>) -> Result<Self, EMLError> {
let data = collect_struct!(elem, CandidateListsAffiliation {
identifier: AffiliationIdentifier::EML_NAME => |elem| elem.read_element::<AffiliationIdentifier>()?,
affiliation_type: ("Type", NS_EML) => |elem| elem.string_value()?,
list_data: ListData::EML_NAME => |elem| elem.read_element::<ListData>()?,
candidates as Vec: CandidateListsCandidate::EML_NAME => |elem| elem.read_element::<CandidateListsCandidate>()?,
});
if data.candidates.is_empty() {
let err = EMLErrorKind::MissingElement(CandidateListsCandidate::EML_NAME.as_owned())
.with_span(elem.full_span());
if elem.parsing_mode().is_strict() {
return Err(err);
} else {
elem.push_err(err);
}
}
Ok(data)
}
fn write_eml(&self, writer: EMLElementWriter) -> Result<(), EMLError> {
writer
.child_elem(AffiliationIdentifier::EML_NAME, &self.identifier)?
.child(("Type", NS_EML), |elem| {
elem.text(self.affiliation_type.raw().as_ref())?.finish()
})?
.child_elem(ListData::EML_NAME, &self.list_data)?
.child_elems(CandidateListsCandidate::EML_NAME, &self.candidates)?
.finish()
}
}
#[derive(Debug, Clone)]
pub struct AffiliationIdentifier {
pub id: StringValue<AffiliationId>,
pub registered_name: Option<Box<str>>,
}
impl AffiliationIdentifier {
pub fn new(id: AffiliationId, registered_name: Option<impl Into<Box<str>>>) -> Self {
Self {
id: StringValue::Parsed(id),
registered_name: registered_name.map(|name| name.into()),
}
}
}
impl EMLElement for AffiliationIdentifier {
const EML_NAME: QualifiedName<'_, '_> =
QualifiedName::from_static("AffiliationIdentifier", Some(NS_EML));
fn read_eml(elem: &mut EMLElementReader<'_, '_>) -> Result<Self, EMLError> {
Ok(collect_struct!(
elem,
AffiliationIdentifier {
id: elem.string_value_attr("Id", None)?,
registered_name: ("RegisteredName", NS_EML) => |elem| elem.text_without_children_opt()?,
}
))
}
fn write_eml(&self, writer: EMLElementWriter) -> Result<(), EMLError> {
writer
.attr("Id", self.id.raw().as_ref())?
.child(("RegisteredName", NS_EML), |w| {
if let Some(name) = &self.registered_name {
w.text(name)?.finish()
} else {
w.empty()
}
})?
.finish()?;
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct CandidateListsCandidate {
pub identifier: CandidateIdentifier,
pub full_name: Option<PersonNameStructure>,
pub date_of_birth: Option<StringValue<XsDate>>,
pub gender: Option<StringValue<Gender>>,
pub gender_annex: Option<StringValue<GenderAnnex>>,
pub qualifying_address: Option<QualifyingAddress>,
pub contact: Option<Contact>,
pub agent: Option<Agent>,
pub date_of_birth_annex: Option<Box<str>>,
pub national_identification_number: Option<Box<str>>,
}
impl CandidateListsCandidate {
pub fn builder() -> CandidateListsCandidateBuilder {
CandidateListsCandidateBuilder::new()
}
pub fn reference(id: impl Into<CandidateId>, short_code: NameShortCode) -> Self {
CandidateListsCandidate {
identifier: CandidateIdentifier::new(id.into()).with_short_code_ref(short_code),
full_name: None,
date_of_birth: None,
gender: None,
gender_annex: None,
qualifying_address: None,
contact: None,
agent: None,
date_of_birth_annex: None,
national_identification_number: None,
}
}
}
#[derive(Debug, Clone)]
pub struct CandidateListsCandidateBuilder {
identifier: Option<CandidateIdentifier>,
date_of_birth: Option<XsDate>,
gender: Option<Gender>,
gender_annex: Option<GenderAnnex>,
full_name: Option<PersonNameStructure>,
qualifying_address: Option<QualifyingAddress>,
contact: Option<Contact>,
agent: Option<Agent>,
date_of_birth_annex: Option<Box<str>>,
national_identification_number: Option<Box<str>>,
}
impl CandidateListsCandidateBuilder {
pub fn new() -> Self {
Self {
identifier: None,
date_of_birth: None,
gender: None,
gender_annex: None,
full_name: None,
qualifying_address: None,
contact: None,
agent: None,
date_of_birth_annex: None,
national_identification_number: None,
}
}
pub fn identifier(mut self, identifier: impl Into<CandidateIdentifier>) -> Self {
self.identifier = Some(identifier.into());
self
}
pub fn date_of_birth(mut self, date_of_birth: impl Into<XsDate>) -> Self {
self.date_of_birth = Some(date_of_birth.into());
self
}
pub fn gender(mut self, gender: impl Into<Gender>) -> Self {
self.gender = Some(gender.into());
self
}
pub fn gender_annex(mut self, gender_annex: impl Into<GenderAnnex>) -> Self {
self.gender_annex = Some(gender_annex.into());
self
}
pub fn full_name(mut self, full_name: impl Into<PersonNameStructure>) -> Self {
self.full_name = Some(full_name.into());
self
}
pub fn qualifying_address(mut self, qualifying_address: impl Into<QualifyingAddress>) -> Self {
self.qualifying_address = Some(qualifying_address.into());
self
}
pub fn contact(mut self, contact: impl Into<Contact>) -> Self {
self.contact = Some(contact.into());
self
}
pub fn agent(mut self, agent: impl Into<Agent>) -> Self {
self.agent = Some(agent.into());
self
}
pub fn date_of_birth_annex(mut self, date_of_birth_annex: impl Into<Box<str>>) -> Self {
self.date_of_birth_annex = Some(date_of_birth_annex.into());
self
}
pub fn national_identification_number(
mut self,
national_identification_number: impl Into<Box<str>>,
) -> Self {
self.national_identification_number = Some(national_identification_number.into());
self
}
pub fn build(self) -> Result<CandidateListsCandidate, EMLError> {
let identifier = self
.identifier
.ok_or_else(|| EMLErrorKind::MissingBuildProperty("identifier").without_span())?;
if !identifier.has_reference() && self.full_name.is_none() {
return Err(EMLErrorKind::MissingBuildProperty("full_name").without_span());
}
Ok(CandidateListsCandidate {
identifier,
full_name: self.full_name,
date_of_birth: self.date_of_birth.map(StringValue::from_value),
gender: self.gender.map(StringValue::from_value),
gender_annex: self.gender_annex.map(StringValue::from_value),
qualifying_address: self.qualifying_address,
contact: self.contact,
agent: self.agent,
date_of_birth_annex: self.date_of_birth_annex,
national_identification_number: self.national_identification_number,
})
}
}
impl Default for CandidateListsCandidateBuilder {
fn default() -> Self {
Self::new()
}
}
impl EMLElement for CandidateListsCandidate {
const EML_NAME: QualifiedName<'_, '_> = QualifiedName::from_static("Candidate", Some(NS_EML));
fn read_eml(elem: &mut EMLElementReader<'_, '_>) -> Result<Self, EMLError> {
let clc = collect_struct!(elem, CandidateListsCandidate {
identifier: CandidateIdentifier::EML_NAME => |elem| elem.read_element::<CandidateIdentifier>()?,
full_name as Option: ("CandidateFullName", NS_EML) => |elem| PersonNameStructure::read_eml_element(elem)?,
date_of_birth as Option: ("DateOfBirth", NS_EML) => |elem| elem.string_value()?,
gender as Option: StringValue::<Gender>::EML_NAME => |elem| elem.read_element::<StringValue<Gender>>()?,
gender_annex as Option: StringValue::<GenderAnnex>::EML_NAME => |elem| elem.read_element::<StringValue<GenderAnnex>>()?,
qualifying_address as Option: QualifyingAddress::EML_NAME => |elem| elem.read_element::<QualifyingAddress>()?,
contact as Option: Contact::EML_NAME => |elem| elem.read_element::<Contact>()?,
agent as Option: Agent::EML_NAME => |elem| elem.read_element::<Agent>()?,
date_of_birth_annex as Option: ("DateOfBirthAnnex", NS_KR) => |elem| elem.text_without_children()?,
national_identification_number as Option: ("NationalIdentificationNumber", NS_KR) => |elem| elem.text_without_children()?,
});
if !clc.identifier.has_reference() && clc.full_name.is_none() {
let err = EMLErrorKind::MissingElement(
QualifiedName::from_static("CandidateFullName", Some(NS_EML)).as_owned(),
)
.with_span(elem.full_span());
if elem.parsing_mode().is_strict() {
return Err(err);
} else {
elem.push_err(err);
}
}
if clc.gender.is_some() && clc.gender_annex.is_some() {
let err = EMLErrorKind::InvalidGenderElement.with_span(elem.full_span());
if elem.parsing_mode().is_strict() {
return Err(err);
} else {
elem.push_err(err);
}
}
Ok(clc)
}
fn write_eml(&self, writer: EMLElementWriter) -> Result<(), EMLError> {
writer
.child_elem(CandidateIdentifier::EML_NAME, &self.identifier)?
.child_option(
("CandidateFullName", NS_EML),
self.full_name.as_ref(),
|elem, full_name| full_name.write_eml_element(elem),
)?
.child_option(
("DateOfBirth", NS_EML),
self.date_of_birth.as_ref(),
|elem, value| elem.text(value.raw().as_ref())?.finish(),
)?
.child_option(
StringValue::<Gender>::EML_NAME,
self.gender.as_ref(),
|elem, value| elem.text(value.raw().as_ref())?.finish(),
)?
.child_option(
StringValue::<GenderAnnex>::EML_NAME,
self.gender_annex.as_ref(),
|elem, value| elem.text(value.raw().as_ref())?.finish(),
)?
.child_elem_option(
QualifyingAddress::EML_NAME,
self.qualifying_address.as_ref(),
)?
.child_elem_option(Contact::EML_NAME, self.contact.as_ref())?
.child_elem_option(Agent::EML_NAME, self.agent.as_ref())?
.child_option(
("DateOfBirthAnnex", NS_KR),
self.date_of_birth_annex.as_ref(),
|elem, value| elem.text(value.as_ref())?.finish(),
)?
.child_option(
("NationalIdentificationNumber", NS_KR),
self.national_identification_number.as_ref(),
|elem, value| elem.text(value.as_ref())?.finish(),
)?
.finish()
}
}
#[cfg(test)]
mod tests {
use chrono::TimeZone as _;
use super::*;
use crate::{
EMLVersion,
common::{
AgentIdentifier, CountryNameCode, LivingAddress, MailingAddress, PersonName,
QualifyingAddressCountry, QualifyingAddressLocality,
},
io::{
EMLParsingMode, EMLRead as _, EMLWrite as _, test_write_eml_element, test_xml_fragment,
},
utils::{AuthorityId, ContestId},
};
#[test]
fn test_affiliation_identifier() {
let xml = test_xml_fragment(
r#"
<AffiliationIdentifier xmlns="urn:oasis:names:tc:evs:schema:eml" Id="1">
<RegisteredName>Affiliation 1</RegisteredName>
</AffiliationIdentifier>
"#,
);
let affiliation_identifier = AffiliationIdentifier::parse_eml_fragment(
&xml,
EMLParsingMode::Strict,
EMLVersion::default(),
)
.ok()
.unwrap();
assert_eq!(
affiliation_identifier.id,
StringValue::Parsed(AffiliationId::new(NonZeroU64::new(1).unwrap()))
);
assert_eq!(
affiliation_identifier.registered_name,
Some("Affiliation 1".into())
);
let xml_output =
test_write_eml_element(&affiliation_identifier, &[NS_EML], EMLVersion::default())
.unwrap();
assert_eq!(xml_output, xml);
}
#[test]
fn test_empty_affiliation_identifier() {
let xml = test_xml_fragment(
r#"
<AffiliationIdentifier xmlns="urn:oasis:names:tc:evs:schema:eml" Id="2">
<RegisteredName/>
</AffiliationIdentifier>
"#,
);
let affiliation_identifier = AffiliationIdentifier::parse_eml_fragment(
&xml,
EMLParsingMode::Strict,
EMLVersion::default(),
)
.ok()
.unwrap();
assert_eq!(
affiliation_identifier.id,
StringValue::Parsed(AffiliationId::new(NonZeroU64::new(2).unwrap()))
);
assert_eq!(affiliation_identifier.registered_name, None);
let xml_output =
test_write_eml_element(&affiliation_identifier, &[NS_EML], EMLVersion::default())
.unwrap();
assert_eq!(xml_output, xml);
}
#[test]
fn test_candidate_without_full_name() {
let xml = test_xml_fragment(
r#"
<Candidate xmlns="urn:oasis:names:tc:evs:schema:eml">
<CandidateIdentifier Id="1" ShortCode="VlagW"/>
</Candidate>
"#,
);
assert!(
CandidateListsCandidate::parse_eml_fragment(
&xml,
EMLParsingMode::Strict,
EMLVersion::default(),
)
.ok()
.is_err()
);
let (candidate, errors) = CandidateListsCandidate::parse_eml_fragment(
&xml,
EMLParsingMode::Loose,
EMLVersion::default(),
)
.ok_with_errors()
.unwrap();
assert!(candidate.full_name.is_none());
assert_eq!(errors.len(), 1);
assert!(matches!(errors[0].kind(), EMLErrorKind::MissingElement(_)));
}
#[test]
fn candidate_lists_construction() {
let cl = CandidateLists::builder()
.version(EMLVersion::V1_2_2)
.lists_type(CandidateListsType::Single)
.transaction_id(TransactionId::new(1))
.managing_authority(ManagingAuthority::new(AuthorityId::new("1234").unwrap()))
.issue_date(XsDate::from_date(2024, 6, 10).unwrap())
.creation_date_time(
chrono::Utc
.with_ymd_and_hms(2014, 11, 28, 12, 0, 9)
.unwrap(),
)
.election_identifier(
CandidateListsElectionIdentifier::builder()
.id(ElectionId::new("GR2026_Test").unwrap())
.category(ElectionCategory::GR)
.election_date(XsDate::from_date(2024, 11, 5).unwrap())
.nomination_date(XsDate::from_date(2024, 10, 1).unwrap())
.build_for_candidate_lists()
.unwrap(),
)
.contests([CandidateListsContest::builder()
.identifier(ContestIdentifier::geen())
.affiliations([CandidateListsAffiliation::builder()
.id(AffiliationId::new(NonZeroU64::new(1).unwrap()))
.registered_name("Affiliation 1")
.affiliation_type(AffiliationType::StandAloneList)
.publish_gender(true)
.candidates([CandidateListsCandidate::builder()
.identifier(CandidateId::new(NonZeroU64::new(1).unwrap()))
.full_name(
PersonName::new("Pietersen")
.with_initials("P.")
.with_first_name("Piet"),
)
.qualifying_address(QualifyingAddressCountry::new(Some("NL"), "Amsterdam"))
.build()
.unwrap()])
.build()
.unwrap()])
.build()
.unwrap()])
.build()
.unwrap();
let xml = cl.write_eml_root_str(true, true).unwrap();
assert_eq!(
xml,
include_str!(
"../../test-files/candidate_lists/eml230b_candidate_lists_construction_output.eml.xml"
)
);
let parsed = CandidateLists::parse_eml(&xml, EMLParsingMode::Strict).unwrap();
let xml2 = parsed.write_eml_root_str(true, true).unwrap();
assert_eq!(xml, xml2);
}
#[test]
fn test_invalid_document_type() {
assert!(
CandidateLists::parse_eml(
include_str!(
"../../test-files/candidate_lists/eml230b_invalid_document_type.eml.xml"
),
EMLParsingMode::Strict
)
.ok_with_errors()
.is_err()
);
}
#[test]
fn test_invalid_empty_affiliates() {
assert!(
CandidateLists::parse_eml(
include_str!(
"../../test-files/candidate_lists/eml230b_invalid_empty_affiliates.eml.xml"
),
EMLParsingMode::Strict
)
.ok_with_errors()
.is_err()
);
}
#[test]
fn test_invalid_empty_candidates() {
assert!(
CandidateLists::parse_eml(
include_str!(
"../../test-files/candidate_lists/eml230b_invalid_empty_candidates.eml.xml"
),
EMLParsingMode::Strict
)
.ok_with_errors()
.is_err()
);
}
#[test]
fn test_invalid_incorrect_election_date() {
assert!(
CandidateLists::parse_eml(
include_str!(
"../../test-files/candidate_lists/eml230b_invalid_incorrect_election_date.eml.xml"
),
EMLParsingMode::Strict
)
.ok_with_errors()
.is_err()
);
}
#[test]
fn test_incorrect_election_domain() {
assert!(
CandidateLists::parse_eml(
include_str!(
"../../test-files/candidate_lists/eml230b_invalid_incorrect_election_domain.eml.xml"
),
EMLParsingMode::Strict
)
.ok_with_errors()
.is_err()
);
}
#[test]
fn test_incorrect_election_category() {
assert!(
CandidateLists::parse_eml(
include_str!(
"../../test-files/candidate_lists/eml230b_invalid_incorrect_election_category.eml.xml"
),
EMLParsingMode::Strict
)
.ok_with_errors()
.is_err()
);
}
#[test]
fn test_incorrect_missing_authority() {
assert!(
CandidateLists::parse_eml(
include_str!(
"../../test-files/candidate_lists/eml230b_invalid_missing_authority.eml.xml"
),
EMLParsingMode::Strict
)
.ok_with_errors()
.is_err()
);
}
#[test]
fn test_with_missing_addresses() {
assert!(
CandidateLists::parse_eml(
include_str!(
"../../test-files/candidate_lists/eml230b_test_without_addresses.eml.xml"
),
EMLParsingMode::Strict
)
.ok_with_errors()
.is_ok()
);
}
#[test]
fn test_invalid_duplicate_short_code() {
assert!(
CandidateLists::parse_eml(
include_str!(
"../../test-files/candidate_lists/eml230c_invalid_duplicate_short_code.eml.xml"
),
EMLParsingMode::Strict
)
.ok_with_errors()
.is_err()
);
}
#[test]
fn test_invalid_unknown_short_code_reference() {
let doc = include_str!(
"../../test-files/candidate_lists/eml230c_invalid_unknown_short_code_reference.eml.xml"
);
assert!(
CandidateLists::parse_eml(doc, EMLParsingMode::Strict)
.ok_with_errors()
.is_err()
);
let (_, errors) = CandidateLists::parse_eml(doc, EMLParsingMode::Loose)
.ok_with_errors()
.unwrap();
assert_eq!(errors.len(), 1);
assert!(matches!(
errors[0].kind(),
EMLErrorKind::UnknownShortCodeReference(code) if code == "OnbekendX"
));
}
fn build_with_candidates(
candidates: impl Into<Vec<CandidateListsCandidate>>,
) -> Result<CandidateLists, EMLError> {
CandidateLists::builder()
.lists_type(CandidateListsType::Multiple)
.transaction_id(TransactionId::new(1))
.managing_authority(ManagingAuthority::new(AuthorityId::new("1234").unwrap()))
.issue_date(XsDate::from_date(2024, 6, 10).unwrap())
.creation_date_time(
chrono::Utc
.with_ymd_and_hms(2014, 11, 28, 12, 0, 9)
.unwrap(),
)
.election_identifier(
CandidateListsElectionIdentifier::builder()
.id(ElectionId::new("GR2026_Test").unwrap())
.category(ElectionCategory::GR)
.election_date(XsDate::from_date(2024, 11, 5).unwrap())
.nomination_date(XsDate::from_date(2024, 10, 1).unwrap())
.build_for_candidate_lists()
.unwrap(),
)
.contests([CandidateListsContest::builder()
.identifier(ContestIdentifier::geen())
.affiliations([CandidateListsAffiliation::builder()
.id(AffiliationId::new(NonZeroU64::new(1).unwrap()))
.affiliation_type(AffiliationType::StandAloneList)
.publish_gender(true)
.candidates(candidates)
.build()
.unwrap()])
.build()
.unwrap()])
.build()
}
fn defined_candidate(id: u64, short_code: &str) -> CandidateListsCandidate {
CandidateListsCandidate::builder()
.identifier(
CandidateIdentifier::new(CandidateId::from_u64(id).unwrap())
.with_short_code(NameShortCode::new(short_code).unwrap()),
)
.full_name(PersonName::new(short_code))
.build()
.unwrap()
}
#[test]
fn test_build_with_short_code_references() {
assert!(
build_with_candidates([
defined_candidate(1, "VlagW"),
CandidateListsCandidate::reference(
CandidateId::from_u64(2).unwrap(),
NameShortCode::new("VlagW").unwrap(),
),
])
.is_ok()
);
}
#[test]
fn test_build_with_duplicate_short_code() {
let err =
build_with_candidates([defined_candidate(1, "VlagW"), defined_candidate(2, "VlagW")])
.unwrap_err();
assert!(matches!(
err.kind(),
EMLErrorKind::DuplicateShortCode(code) if code == "VlagW"
));
}
#[test]
fn test_build_with_unknown_short_code_reference() {
let err = build_with_candidates([
defined_candidate(1, "VlagW"),
CandidateListsCandidate::reference(
CandidateId::from_u64(2).unwrap(),
NameShortCode::new("OnbekendX").unwrap(),
),
])
.unwrap_err();
assert!(matches!(
err.kind(),
EMLErrorKind::UnknownShortCodeReference(code) if code == "OnbekendX"
));
}
#[test]
fn test_short_code_references() {
let doc = CandidateLists::parse_eml(
include_str!("../../test-files/candidate_lists/eml230c_test.eml.xml"),
EMLParsingMode::Strict,
)
.unwrap();
let short_code = |s| Some(StringValue::Parsed(NameShortCode::new(s).unwrap()));
let contests = &doc.candidate_list.election.contests;
let full = &contests[0].affiliations[0].candidates[0];
assert!(full.full_name.is_some());
assert_eq!(full.identifier.short_code, short_code("StreeveldHT"));
assert_eq!(full.identifier.short_code_ref, None);
let reference = &contests[1].affiliations[0].candidates[0];
assert!(reference.full_name.is_none());
assert_eq!(reference.identifier.short_code, None);
assert_eq!(
reference.identifier.short_code_ref,
short_code("StreeveldHT")
);
}
#[test]
fn multiple_candidate_lists_construction() {
let cl = CandidateLists::builder()
.version(EMLVersion::V1_3)
.lists_type(CandidateListsType::Multiple)
.transaction_id(TransactionId::new(1))
.managing_authority(ManagingAuthority::new(AuthorityId::new("1234").unwrap()))
.issue_date(XsDate::from_date(2024, 6, 10).unwrap())
.creation_date_time(
chrono::Utc
.with_ymd_and_hms(2014, 11, 28, 12, 0, 9)
.unwrap(),
)
.election_identifier(
CandidateListsElectionIdentifier::builder()
.id(ElectionId::new("GR2026_Test").unwrap())
.category(ElectionCategory::GR)
.election_date(XsDate::from_date(2024, 11, 5).unwrap())
.nomination_date(XsDate::from_date(2024, 10, 1).unwrap())
.build_for_candidate_lists()
.unwrap(),
)
.contests([
CandidateListsContest::builder()
.identifier(ContestIdentifier::new(ContestId::new("1").unwrap()))
.affiliations([
CandidateListsAffiliation::builder()
.id(AffiliationId::new(NonZeroU64::new(1).unwrap()))
.registered_name("Affiliation 1")
.affiliation_type(AffiliationType::StandAloneList)
.publish_gender(true)
.candidates([
CandidateListsCandidate::builder()
.identifier(CandidateId::new(NonZeroU64::new(1).unwrap()))
.full_name(
PersonName::new("Pietersen")
.with_initials("P.")
.with_first_name("Piet"),
)
.qualifying_address(QualifyingAddressCountry::new(
Some("NL"),
"Amsterdam",
))
.build()
.unwrap(),
CandidateListsCandidate::builder()
.identifier(
CandidateIdentifier::new(CandidateId::new(
NonZeroU64::new(2).unwrap(),
))
.with_short_code(NameShortCode::new("VlagW").unwrap()),
)
.full_name(PersonName::new("Vlag").with_initials("W."))
.date_of_birth(XsDate::from_date(1921, 8, 8).unwrap())
.gender_annex(GenderAnnex::Male)
.qualifying_address(QualifyingAddressLocality::new(
"'s-Gravenhage",
))
.contact(Contact::new(MailingAddress::new(
QualifyingAddress::new(
QualifyingAddressLocality::new("'s-Gravenhage")
.with_address_line("Houttuinen 49")
.with_postal_code("2551 VN"),
None::<CountryNameCode>,
),
)))
.build()
.unwrap(),
])
.build()
.unwrap(),
CandidateListsAffiliation::builder()
.id(AffiliationId::new(NonZeroU64::new(2).unwrap()))
.affiliation_type(AffiliationType::StandAloneList)
.publish_gender(true)
.candidates([CandidateListsCandidate::builder()
.identifier(
CandidateIdentifier::new(CandidateId::new(
NonZeroU64::new(9).unwrap(),
))
.with_short_code(NameShortCode::new("BultenaarB").unwrap()),
)
.full_name(
PersonName::new("Bultenaar")
.with_initials("B.")
.with_first_name("Berend"),
)
.date_of_birth(XsDate::from_date(2005, 1, 23).unwrap())
.gender_annex(GenderAnnex::Male)
.qualifying_address(QualifyingAddressCountry::new(
Some("JP"),
"Zdjapan",
))
.contact(Contact::new(MailingAddress::new(QualifyingAddress::new(
QualifyingAddressLocality::new("Hellevoetsluis")
.with_address_line("Eik 555")
.with_postal_code("3224 TB"),
None::<CountryNameCode>,
))))
.agent(Agent {
role: Some("H10".to_string()),
agent_identifier: AgentIdentifier::new(
PersonName::new("Grupstal")
.with_initials("H.J.T.")
.with_first_name("Hendrika Johanna Theodora"),
),
contact: Some(Contact::new(MailingAddress::new(
QualifyingAddress::new(
QualifyingAddressLocality::new("Amsterdam")
.with_address_line("J.C.Schröderstraat 1")
.with_postal_code("1068 JT"),
None::<CountryNameCode>,
),
))),
living_address: LivingAddress::new("Amsterdam"),
})
.build()
.unwrap()])
.build()
.unwrap(),
])
.build()
.unwrap(),
CandidateListsContest::builder()
.identifier(ContestIdentifier::new(ContestId::new("2").unwrap()))
.affiliations([CandidateListsAffiliation::builder()
.id(AffiliationId::new(NonZeroU64::new(1).unwrap()))
.registered_name("Affiliation 1")
.affiliation_type(AffiliationType::StandAloneList)
.publish_gender(true)
.candidates([
CandidateListsCandidate::reference(
CandidateId::new(NonZeroU64::new(1).unwrap()),
NameShortCode::new("BultenaarB").unwrap(),
),
CandidateListsCandidate::reference(
CandidateId::new(NonZeroU64::new(2).unwrap()),
NameShortCode::new("VlagW").unwrap(),
),
])
.build()
.unwrap()])
.build()
.unwrap(),
])
.build()
.unwrap();
let xml = cl.write_eml_root_str(true, true).unwrap();
assert_eq!(
xml,
include_str!(
"../../test-files/candidate_lists/eml230c_candidate_lists_construction_output.eml.xml"
)
);
let parsed = CandidateLists::parse_eml(&xml, EMLParsingMode::Strict).unwrap();
let xml2 = parsed.write_eml_root_str(true, true).unwrap();
assert_eq!(xml, xml2);
}
}