1#![deny(missing_docs)]
36#![deny(unsafe_code)]
37
38#[cfg(feature = "json")]
39pub mod json;
40pub mod taxonomy;
41
42mod util;
43
44use std::fmt::{self, Debug};
45use std::iter::repeat;
46use std::num::{NonZeroI16, NonZeroUsize};
47
48use quick_xml::de::{Deserializer, SliceReader};
49use serde::{Deserialize, Serialize};
50use taxonomy::{
51 DateVariable, Kind, Locator, NameVariable, NumberOrPageVariable, NumberVariable,
52 OtherTerm, Term, Variable,
53};
54
55use self::util::*;
56
57pub type XmlResult<T> = Result<T, XmlError>;
59
60pub type XmlError = quick_xml::de::DeError;
62
63const EVENT_BUFFER_SIZE: Option<NonZeroUsize> = NonZeroUsize::new(4096);
64
65pub trait ToFormatting {
67 fn to_formatting(&self) -> Formatting;
69}
70
71macro_rules! to_formatting {
72 ($name:ty, self) => {
73 impl ToFormatting for $name {
74 fn to_formatting(&self) -> Formatting {
75 Formatting {
76 font_style: self.font_style,
77 font_variant: self.font_variant,
78 font_weight: self.font_weight,
79 text_decoration: self.text_decoration,
80 vertical_align: self.vertical_align,
81 }
82 }
83 }
84 };
85 ($name:ty) => {
86 impl ToFormatting for $name {
87 fn to_formatting(&self) -> Formatting {
88 self.formatting.clone()
89 }
90 }
91 };
92}
93
94pub trait ToAffixes {
96 fn to_affixes(&self) -> Affixes;
98}
99
100macro_rules! to_affixes {
101 ($name:ty, self) => {
102 impl ToAffixes for $name {
103 fn to_affixes(&self) -> Affixes {
104 Affixes {
105 prefix: self.prefix.clone(),
106 suffix: self.suffix.clone(),
107 }
108 }
109 }
110 };
111 ($name:ty) => {
112 impl ToAffixes for $name {
113 fn to_affixes(&self) -> Affixes {
114 self.affixes.clone()
115 }
116 }
117 };
118}
119
120#[allow(clippy::large_enum_variant)]
122#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
123struct RawStyle {
124 pub info: StyleInfo,
126 #[serde(rename = "@default-locale")]
129 #[serde(skip_serializing_if = "Option::is_none")]
130 pub default_locale: Option<LocaleCode>,
131 #[serde(rename = "@version")]
133 pub version: String,
134 #[serde(skip_serializing_if = "Option::is_none")]
137 pub citation: Option<Citation>,
138 #[serde(skip_serializing_if = "Option::is_none")]
140 pub bibliography: Option<Bibliography>,
141 #[serde(flatten)]
143 pub independent_settings: Option<IndependentStyleSettings>,
144 #[serde(rename = "macro", default)]
146 pub macros: Vec<CslMacro>,
147 #[serde(default)]
149 pub locale: Vec<Locale>,
150}
151
152impl RawStyle {
153 pub fn parent_link(&self) -> Option<&InfoLink> {
155 self.info
156 .link
157 .iter()
158 .find(|link| link.rel == InfoLinkRel::IndependentParent)
159 }
160}
161
162impl From<IndependentStyle> for RawStyle {
163 fn from(value: IndependentStyle) -> Self {
164 Self {
165 info: value.info,
166 default_locale: value.default_locale,
167 version: value.version,
168 citation: Some(value.citation),
169 bibliography: value.bibliography,
170 independent_settings: Some(value.settings),
171 macros: value.macros,
172 locale: value.locale,
173 }
174 }
175}
176
177impl From<DependentStyle> for RawStyle {
178 fn from(value: DependentStyle) -> Self {
179 Self {
180 info: value.info,
181 default_locale: value.default_locale,
182 version: value.version,
183 citation: None,
184 bibliography: None,
185 independent_settings: None,
186 macros: Vec::new(),
187 locale: Vec::new(),
188 }
189 }
190}
191
192impl From<Style> for RawStyle {
193 fn from(value: Style) -> Self {
194 match value {
195 Style::Independent(i) => i.into(),
196 Style::Dependent(d) => d.into(),
197 }
198 }
199}
200
201#[derive(Debug, Clone, Eq, PartialEq, Hash)]
203pub struct IndependentStyle {
204 pub info: StyleInfo,
206 pub default_locale: Option<LocaleCode>,
208 pub version: String,
210 pub citation: Citation,
212 pub bibliography: Option<Bibliography>,
214 pub settings: IndependentStyleSettings,
216 pub macros: Vec<CslMacro>,
218 pub locale: Vec<Locale>,
220}
221
222impl IndependentStyle {
223 pub fn from_xml(xml: &str) -> XmlResult<Self> {
225 let de = &mut deserializer(xml);
226 IndependentStyle::deserialize(de)
227 }
228
229 pub fn purge(&mut self, level: PurgeLevel) {
232 self.info.purge(level);
233 }
234}
235
236#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
238pub enum PurgeLevel {
239 Basic,
241 Full,
243}
244
245impl<'de> Deserialize<'de> for IndependentStyle {
246 fn deserialize<D: serde::Deserializer<'de>>(
247 deserializer: D,
248 ) -> Result<Self, D::Error> {
249 let raw_style = RawStyle::deserialize(deserializer)?;
250 let style: Style = raw_style.try_into().map_err(serde::de::Error::custom)?;
251
252 match style {
253 Style::Independent(i) => Ok(i),
254 Style::Dependent(_) => Err(serde::de::Error::custom(
255 "expected an independent style but got a dependent style",
256 )),
257 }
258 }
259}
260
261#[derive(Debug, Clone, Eq, PartialEq, Hash)]
263pub struct DependentStyle {
264 pub info: StyleInfo,
266 pub default_locale: Option<LocaleCode>,
269 pub version: String,
271 pub parent_link: InfoLink,
273}
274
275impl DependentStyle {
276 pub fn from_xml(xml: &str) -> XmlResult<Self> {
278 let de = &mut deserializer(xml);
279 DependentStyle::deserialize(de)
280 }
281
282 pub fn purge(&mut self, level: PurgeLevel) {
285 self.info.purge(level);
286 }
287}
288
289impl<'de> Deserialize<'de> for DependentStyle {
290 fn deserialize<D: serde::Deserializer<'de>>(
291 deserializer: D,
292 ) -> Result<Self, D::Error> {
293 let raw_style = RawStyle::deserialize(deserializer)?;
294 let style: Style = raw_style.try_into().map_err(serde::de::Error::custom)?;
295
296 match style {
297 Style::Dependent(d) => Ok(d),
298 Style::Independent(_) => Err(serde::de::Error::custom(
299 "expected a dependent style but got an independent style",
300 )),
301 }
302 }
303}
304
305#[derive(Debug, Clone, Eq, PartialEq, Hash)]
307#[allow(clippy::large_enum_variant)]
308pub enum Style {
309 Independent(IndependentStyle),
311 Dependent(DependentStyle),
313}
314
315impl Style {
316 pub fn from_xml(xml: &str) -> XmlResult<Self> {
318 let de = &mut deserializer(xml);
319 Style::deserialize(de)
320 }
321
322 pub fn to_xml(&self) -> XmlResult<String> {
324 let mut buf = String::new();
325 let ser = quick_xml::se::Serializer::with_root(&mut buf, Some("style"))?;
326 self.serialize(ser)?;
327 Ok(buf)
328 }
329
330 pub fn purge(&mut self, level: PurgeLevel) {
333 match self {
334 Self::Independent(i) => i.purge(level),
335 Self::Dependent(d) => d.purge(level),
336 }
337 }
338
339 pub fn info(&self) -> &StyleInfo {
341 match self {
342 Self::Independent(i) => &i.info,
343 Self::Dependent(d) => &d.info,
344 }
345 }
346}
347
348impl<'de> Deserialize<'de> for Style {
349 fn deserialize<D: serde::Deserializer<'de>>(
350 deserializer: D,
351 ) -> Result<Self, D::Error> {
352 let raw_style = RawStyle::deserialize(deserializer)?;
353 raw_style.try_into().map_err(serde::de::Error::custom)
354 }
355}
356
357impl Serialize for Style {
358 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
359 where
360 S: serde::Serializer,
361 {
362 RawStyle::from(self.clone()).serialize(serializer)
363 }
364}
365
366impl TryFrom<RawStyle> for Style {
367 type Error = StyleValidationError;
368
369 fn try_from(value: RawStyle) -> Result<Self, Self::Error> {
370 let has_bibliography = value.bibliography.is_some();
371 if let Some(citation) = value.citation {
372 if let Some(settings) = value.independent_settings {
373 Ok(Self::Independent(IndependentStyle {
374 info: value.info,
375 default_locale: value.default_locale,
376 version: value.version,
377 citation,
378 bibliography: value.bibliography,
379 settings,
380 macros: value.macros,
381 locale: value.locale,
382 }))
383 } else {
384 Err(StyleValidationError::MissingClassAttr)
385 }
386 } else if has_bibliography {
387 Err(StyleValidationError::MissingCitation)
388 } else if let Some(parent_link) = value.parent_link().cloned() {
389 Ok(Self::Dependent(DependentStyle {
390 info: value.info,
391 default_locale: value.default_locale,
392 version: value.version,
393 parent_link,
394 }))
395 } else {
396 Err(StyleValidationError::MissingParent)
397 }
398 }
399}
400
401#[derive(Debug, Clone, Eq, PartialEq, Hash)]
403pub enum StyleValidationError {
404 MissingCitation,
407 MissingParent,
409 MissingClassAttr,
411}
412
413impl fmt::Display for StyleValidationError {
414 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
415 f.write_str(match self {
416 Self::MissingCitation => "root element is missing `cs:citation` child despite having a `cs:bibliography`",
417 Self::MissingParent => "`cs:link` tag with `independent-parent` as a `rel` attribute is missing but no `cs:citation` was defined",
418 Self::MissingClassAttr => "`cs:style` tag is missing the `class` attribute",
419 })
420 }
421}
422
423fn deserializer(xml: &str) -> Deserializer<SliceReader<'_>> {
424 let mut style_deserializer = Deserializer::from_str(xml);
425 style_deserializer.event_buffer_size(EVENT_BUFFER_SIZE);
426 style_deserializer
427}
428
429#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
431pub struct IndependentStyleSettings {
432 #[serde(rename = "@class")]
434 pub class: StyleClass,
435 #[serde(
439 rename = "@initialize-with-hyphen",
440 default = "IndependentStyleSettings::default_initialize_with_hyphen",
441 deserialize_with = "deserialize_bool"
442 )]
443 pub initialize_with_hyphen: bool,
444 #[serde(rename = "@page-range-format")]
446 #[serde(skip_serializing_if = "Option::is_none")]
447 pub page_range_format: Option<PageRangeFormat>,
448 #[serde(rename = "@demote-non-dropping-particle", default)]
450 pub demote_non_dropping_particle: DemoteNonDroppingParticle,
451 #[serde(flatten)]
453 pub options: InheritableNameOptions,
454}
455
456impl IndependentStyleSettings {
457 pub const fn default_initialize_with_hyphen() -> bool {
459 true
460 }
461}
462
463#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
465pub struct LocaleCode(pub String);
466
467impl<'a> LocaleCode {
468 pub fn en_us() -> Self {
470 Self("en-US".to_string())
471 }
472
473 pub fn parse_base(&self) -> Option<BaseLanguage> {
475 let mut parts = self.0.split('-').take(2);
476 let first = parts.next()?;
477
478 match first {
479 "i" | "I" => {
480 let second = parts.next()?;
481 if second.is_empty() {
482 return None;
483 }
484
485 Some(BaseLanguage::Iana(second.to_string()))
486 }
487 "x" | "X" => {
488 let second = parts.next()?;
489 if second.len() > 8 || second.is_empty() {
490 return None;
491 }
492
493 let mut code = [0; 8];
494 code[..second.len()].copy_from_slice(second.as_bytes());
495 Some(BaseLanguage::Unregistered(code))
496 }
497 _ if first.len() == 2 => {
498 let mut code = [0; 2];
499 code.copy_from_slice(first.as_bytes());
500 Some(BaseLanguage::Iso639_1(code))
501 }
502 _ => None,
503 }
504 }
505
506 pub fn extensions(&'a self) -> impl Iterator<Item = &'a str> + 'a {
508 self.0
509 .split('-')
510 .enumerate()
511 .filter_map(|(i, e)| {
512 if i == 0 && ["x", "X", "i", "I"].contains(&e) {
513 None
514 } else {
515 Some(e)
516 }
517 })
518 .skip(1)
519 }
520
521 pub fn is_english(&self) -> bool {
523 let en = "en";
524 let hyphen = "-";
525 self.0.starts_with(en)
526 && (self.0.len() == 2
527 || self.0.get(en.len()..en.len() + hyphen.len()) == Some(hyphen))
528 }
529
530 pub fn fallback(&self) -> Option<LocaleCode> {
532 match self.parse_base()? {
533 BaseLanguage::Iso639_1(code) => match &code {
534 b"af" => Some("af-ZA"),
535 b"bg" => Some("bg-BG"),
536 b"ca" => Some("ca-AD"),
537 b"cs" => Some("cs-CZ"),
538 b"da" => Some("da-DK"),
539 b"de" => Some("de-DE"),
540 b"el" => Some("el-GR"),
541 b"en" => Some("en-US"),
542 b"es" => Some("es-ES"),
543 b"et" => Some("et-EE"),
544 b"fa" => Some("fa-IR"),
545 b"fi" => Some("fi-FI"),
546 b"fr" => Some("fr-FR"),
547 b"he" => Some("he-IL"),
548 b"hr" => Some("hr-HR"),
549 b"hu" => Some("hu-HU"),
550 b"is" => Some("is-IS"),
551 b"it" => Some("it-IT"),
552 b"ja" => Some("ja-JP"),
553 b"km" => Some("km-KH"),
554 b"ko" => Some("ko-KR"),
555 b"lt" => Some("lt-LT"),
556 b"lv" => Some("lv-LV"),
557 b"mn" => Some("mn-MN"),
558 b"nb" => Some("nb-NO"),
559 b"nl" => Some("nl-NL"),
560 b"nn" => Some("nn-NO"),
561 b"pl" => Some("pl-PL"),
562 b"pt" => Some("pt-PT"),
563 b"ro" => Some("ro-RO"),
564 b"ru" => Some("ru-RU"),
565 b"sk" => Some("sk-SK"),
566 b"sl" => Some("sl-SI"),
567 b"sr" => Some("sr-RS"),
568 b"sv" => Some("sv-SE"),
569 b"th" => Some("th-TH"),
570 b"tr" => Some("tr-TR"),
571 b"uk" => Some("uk-UA"),
572 b"vi" => Some("vi-VN"),
573 b"zh" => Some("zh-CN"),
574 _ => None,
575 }
576 .map(ToString::to_string)
577 .map(LocaleCode)
578 .filter(|f| f != self),
579 _ => None,
580 }
581 }
582}
583
584impl fmt::Display for LocaleCode {
585 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
586 fmt::Display::fmt(&self.0, f)
587 }
588}
589
590pub enum BaseLanguage {
592 Iso639_1([u8; 2]),
594 Iana(String),
596 Unregistered([u8; 8]),
598}
599
600impl BaseLanguage {
601 pub fn as_str(&self) -> &str {
603 match self {
604 Self::Iso639_1(code) => std::str::from_utf8(code).unwrap(),
605 Self::Iana(code) => code,
606 Self::Unregistered(code) => std::str::from_utf8(code).unwrap(),
607 }
608 }
609}
610
611#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
613#[serde(rename_all = "kebab-case")]
614pub enum StyleClass {
615 InText,
617 Note,
619}
620
621#[derive(Debug, Copy, Clone, Default, Eq, PartialEq, Hash, Deserialize, Serialize)]
623#[serde(rename_all = "kebab-case")]
624pub enum PageRangeFormat {
625 #[serde(alias = "chicago")]
629 #[serde(rename = "chicago-15")]
630 Chicago15,
631 #[serde(rename = "chicago-16")]
633 Chicago16,
634 #[default]
636 Expanded,
637 Minimal,
639 MinimalTwo,
641}
642
643impl PageRangeFormat {
644 pub fn format(
648 self,
649 buf: &mut impl fmt::Write,
650 start: &str,
651 end: &str,
652 separator: Option<&str>,
653 ) -> Result<(), fmt::Error> {
654 let separator = separator.unwrap_or("ā");
655 let start = start.trim();
656 let end = end.trim();
657
658 let (start_pre, x) = split_max_digit_suffix(start);
661 let (end_pre, y) = split_max_digit_suffix(end);
662
663 if start_pre == end_pre {
664 let pref = start_pre;
665 let x_len = x.len();
666 let y_len = y.len();
667 let y = if x_len <= y_len {
669 y.to_string()
670 } else {
671 let mut s = x[..(x_len - y_len)].to_string();
673 s.push_str(y);
674 s
675 };
676
677 write!(buf, "{pref}{x}{separator}")?;
679
680 match self {
682 PageRangeFormat::Chicago15 | PageRangeFormat::Chicago16
683 if x_len < 3 || x.ends_with("00") =>
684 {
685 write!(buf, "{y}")
687 }
688 PageRangeFormat::Chicago15 | PageRangeFormat::Chicago16
689 if x[x_len - 2..].starts_with('0') =>
690 {
691 minimal(buf, 1, x, &y)
693 }
694 PageRangeFormat::Chicago15
695 if x_len == 4 && changed_digits(x, &y) >= 3 =>
696 {
697 write!(buf, "{y}")
699 }
700 PageRangeFormat::Chicago15 | PageRangeFormat::Chicago16 => {
701 minimal(buf, 2, x, &y)
703 }
704 PageRangeFormat::Expanded => write!(buf, "{pref}{y}"),
705 PageRangeFormat::Minimal => minimal(buf, 1, x, &y),
706 PageRangeFormat::MinimalTwo => minimal(buf, 2, x, &y),
707 }
708 } else {
709 write!(buf, "{start}{separator}{end}")
711 }
712 }
713}
714
715fn changed_digits(x: &str, y: &str) -> usize {
719 let x = if x.len() < y.len() {
720 let mut s = String::from_iter(repeat(' ').take(y.len() - x.len()));
721 s.push_str(x);
722 s
723 } else {
724 x.to_string()
725 };
726 debug_assert!(x.len() == y.len());
727 let xs = x.chars().rev();
728 let ys = y.chars().rev();
729
730 for (i, (c, d)) in xs.zip(ys).enumerate() {
731 if c == d {
732 return i;
733 }
734 }
735
736 x.len()
737}
738
739fn minimal(
741 buf: &mut impl fmt::Write,
742 thresh: usize,
743 x: &str,
744 y: &str,
745) -> Result<(), fmt::Error> {
746 if y.len() > x.len() {
747 return write!(buf, "{y}");
749 }
750
751 let mut xs = String::new();
752 let mut ys = String::new();
753 for (c, d) in x.chars().zip(y.chars()).skip_while(|(c, d)| c == d) {
754 xs.push(c);
755 ys.push(d);
756 }
757
758 if ys.len() < thresh && y.len() >= thresh {
759 write!(buf, "{}", &y[(y.len() - thresh)..])
760 } else {
761 write!(buf, "{ys}")
762 }
763}
764
765fn split_max_digit_suffix(s: &str) -> (&str, &str) {
767 let suffix_len = s.chars().rev().take_while(|c| c.is_ascii_digit()).count();
768 let idx = s.len() - suffix_len;
769 (&s[..idx], &s[idx..])
770}
771
772#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
774#[serde(rename_all = "kebab-case")]
775pub enum DemoteNonDroppingParticle {
776 Never,
778 SortOnly,
780 #[default]
782 DisplayAndSort,
783}
784
785#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
787#[serde(rename_all = "kebab-case")]
788pub struct StyleInfo {
789 #[serde(rename = "author")]
791 #[serde(default)]
792 pub authors: Vec<StyleAttribution>,
793 #[serde(rename = "contributor")]
795 #[serde(default)]
796 pub contibutors: Vec<StyleAttribution>,
797 #[serde(default)]
799 pub category: Vec<StyleCategory>,
800 #[serde(default)]
802 pub field: Vec<Field>,
803 pub id: String,
805 #[serde(default)]
807 pub issn: Vec<String>,
808 #[serde(skip_serializing_if = "Option::is_none")]
810 pub eissn: Option<String>,
811 #[serde(skip_serializing_if = "Option::is_none")]
813 pub issnl: Option<String>,
814 #[serde(default)]
816 pub link: Vec<InfoLink>,
817 #[serde(skip_serializing_if = "Option::is_none")]
819 pub published: Option<Timestamp>,
820 #[serde(skip_serializing_if = "Option::is_none")]
822 pub rights: Option<License>,
823 #[serde(skip_serializing_if = "Option::is_none")]
825 pub summary: Option<LocalString>,
826 pub title: LocalString,
828 #[serde(skip_serializing_if = "Option::is_none")]
830 pub title_short: Option<LocalString>,
831 #[serde(skip_serializing_if = "Option::is_none")]
833 pub updated: Option<Timestamp>,
834}
835
836impl StyleInfo {
837 pub fn purge(&mut self, level: PurgeLevel) {
839 self.field.clear();
840 self.issn.clear();
841 self.eissn = None;
842 self.issnl = None;
843 self.published = None;
844 self.summary = None;
845 self.updated = None;
846
847 match level {
848 PurgeLevel::Basic => {
849 for person in self.authors.iter_mut().chain(self.contibutors.iter_mut()) {
850 person.email = None;
851 person.uri = None;
852 }
853 self.link.retain(|i| {
854 matches!(i.rel, InfoLinkRel::IndependentParent | InfoLinkRel::Zelf)
855 });
856 }
857 PurgeLevel::Full => {
858 self.authors.clear();
859 self.contibutors.clear();
860 self.link.retain(|i| i.rel == InfoLinkRel::IndependentParent);
861 self.rights = None;
862 }
863 }
864 }
865}
866
867#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
869pub struct LocalString {
870 #[serde(rename = "@lang")]
872 #[serde(skip_serializing_if = "Option::is_none")]
873 pub lang: Option<LocaleCode>,
874 #[serde(rename = "$value", default)]
876 pub value: String,
877}
878
879#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
881pub struct StyleAttribution {
882 pub name: String,
884 #[serde(skip_serializing_if = "Option::is_none")]
886 pub email: Option<String>,
887 #[serde(skip_serializing_if = "Option::is_none")]
889 pub uri: Option<String>,
890}
891
892#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
894#[serde(untagged)]
895pub enum StyleCategory {
896 CitationFormat {
898 #[serde(rename = "@citation-format")]
900 format: CitationFormat,
901 },
902 Field {
904 #[serde(rename = "@field")]
906 field: Field,
907 },
908}
909
910#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
912#[serde(rename_all = "kebab-case")]
913pub enum CitationFormat {
914 AuthorDate,
916 Author,
918 Numeric,
920 Label,
922 Note,
924}
925
926#[allow(missing_docs)]
928#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
929#[serde(rename_all = "snake_case")]
930pub enum Field {
931 Anthropology,
932 Astronomy,
933 Biology,
934 Botany,
935 Chemistry,
936 Communications,
937 Engineering,
938 #[serde(rename = "generic-base")]
940 GenericBase,
941 Geography,
942 Geology,
943 History,
944 Humanities,
945 Law,
946 Linguistics,
947 Literature,
948 Math,
949 Medicine,
950 Philosophy,
951 Physics,
952 PoliticalScience,
953 Psychology,
954 Science,
955 SocialScience,
956 Sociology,
957 Theology,
958 Zoology,
959}
960
961#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
963pub struct InfoLink {
964 #[serde(rename = "@href")]
966 pub href: String,
967 #[serde(rename = "@rel")]
969 pub rel: InfoLinkRel,
970 #[serde(rename = "$value")]
972 #[serde(skip_serializing_if = "Option::is_none")]
973 pub description: Option<String>,
974 #[serde(rename = "@xml:lang")]
976 #[serde(skip_serializing_if = "Option::is_none")]
977 pub locale: Option<LocaleCode>,
978}
979
980#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
982#[serde(rename_all = "kebab-case")]
983pub enum InfoLinkRel {
984 #[serde(rename = "self")]
986 Zelf,
987 Template,
989 Documentation,
991 IndependentParent,
993}
994
995#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
997pub struct Timestamp {
998 #[serde(rename = "$text")]
1000 pub raw: String,
1001}
1002
1003#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
1005pub struct License {
1006 #[serde(rename = "$text")]
1008 pub name: String,
1009 #[serde(rename = "@license")]
1011 #[serde(skip_serializing_if = "Option::is_none")]
1012 pub license: Option<String>,
1013 #[serde(rename = "@xml:lang")]
1015 #[serde(skip_serializing_if = "Option::is_none")]
1016 pub lang: Option<LocaleCode>,
1017}
1018
1019#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
1021#[serde(rename_all = "kebab-case")]
1022pub struct Citation {
1023 #[serde(skip_serializing_if = "Option::is_none")]
1025 pub sort: Option<Sort>,
1026 pub layout: Layout,
1028 #[serde(
1032 rename = "@disambiguate-add-givenname",
1033 default,
1034 deserialize_with = "deserialize_bool"
1035 )]
1036 pub disambiguate_add_givenname: bool,
1037 #[serde(rename = "@givenname-disambiguation-rule", default)]
1039 pub givenname_disambiguation_rule: DisambiguationRule,
1040 #[serde(
1044 rename = "@disambiguate-add-names",
1045 default,
1046 deserialize_with = "deserialize_bool"
1047 )]
1048 pub disambiguate_add_names: bool,
1049 #[serde(
1053 rename = "@disambiguate-add-year-suffix",
1054 default,
1055 deserialize_with = "deserialize_bool"
1056 )]
1057 pub disambiguate_add_year_suffix: bool,
1058 #[serde(rename = "@cite-group-delimiter")]
1060 #[serde(skip_serializing_if = "Option::is_none")]
1061 pub cite_group_delimiter: Option<String>,
1062 #[serde(rename = "@collapse")]
1064 #[serde(skip_serializing_if = "Option::is_none")]
1065 pub collapse: Option<Collapse>,
1066 #[serde(rename = "@year-suffix-delimiter")]
1068 #[serde(skip_serializing_if = "Option::is_none")]
1069 pub year_suffix_delimiter: Option<String>,
1070 #[serde(rename = "@after-collapse-delimiter")]
1072 #[serde(skip_serializing_if = "Option::is_none")]
1073 pub after_collapse_delimiter: Option<String>,
1074 #[serde(
1078 rename = "@near-note-distance",
1079 default = "Citation::default_near_note_distance",
1080 deserialize_with = "deserialize_u32"
1081 )]
1082 pub near_note_distance: u32,
1083 #[serde(flatten)]
1085 pub name_options: InheritableNameOptions,
1086}
1087
1088impl Citation {
1089 pub const DEFAULT_CITE_GROUP_DELIMITER: &'static str = ", ";
1092
1093 pub fn with_layout(layout: Layout) -> Self {
1095 Self {
1096 sort: None,
1097 layout,
1098 disambiguate_add_givenname: false,
1099 givenname_disambiguation_rule: DisambiguationRule::default(),
1100 disambiguate_add_names: false,
1101 disambiguate_add_year_suffix: false,
1102 cite_group_delimiter: None,
1103 collapse: None,
1104 year_suffix_delimiter: None,
1105 after_collapse_delimiter: None,
1106 near_note_distance: Self::default_near_note_distance(),
1107 name_options: Default::default(),
1108 }
1109 }
1110
1111 pub fn get_year_suffix_delimiter(&self) -> &str {
1113 self.year_suffix_delimiter
1114 .as_deref()
1115 .or(self.layout.delimiter.as_deref())
1116 .unwrap_or_default()
1117 }
1118
1119 pub fn get_after_collapse_delimiter(&self) -> &str {
1121 self.after_collapse_delimiter
1122 .as_deref()
1123 .or(self.layout.delimiter.as_deref())
1124 .unwrap_or_default()
1125 }
1126
1127 pub const fn default_near_note_distance() -> u32 {
1129 5
1130 }
1131}
1132
1133#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
1135#[serde(rename_all = "kebab-case")]
1136pub enum DisambiguationRule {
1137 AllNames,
1139 AllNamesWithInitials,
1141 PrimaryName,
1143 PrimaryNameWithInitials,
1145 #[default]
1147 ByCite,
1148}
1149
1150impl DisambiguationRule {
1151 pub fn allows_full_first_names(self) -> bool {
1153 match self {
1154 Self::AllNames | Self::PrimaryName | Self::ByCite => true,
1155 Self::AllNamesWithInitials | Self::PrimaryNameWithInitials => false,
1156 }
1157 }
1158
1159 pub fn allows_multiple_names(self) -> bool {
1161 match self {
1162 Self::AllNames | Self::AllNamesWithInitials | Self::ByCite => true,
1163 Self::PrimaryName | Self::PrimaryNameWithInitials => false,
1164 }
1165 }
1166}
1167
1168#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
1170#[serde(rename_all = "kebab-case")]
1171pub enum Collapse {
1172 CitationNumber,
1174 Year,
1176 YearSuffix,
1178 YearSuffixRanged,
1180}
1181
1182#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
1184pub struct Bibliography {
1185 #[serde(skip_serializing_if = "Option::is_none")]
1187 pub sort: Option<Sort>,
1188 pub layout: Layout,
1190 #[serde(rename = "@hanging-indent", default, deserialize_with = "deserialize_bool")]
1194 pub hanging_indent: bool,
1195 #[serde(rename = "@second-field-align")]
1197 #[serde(skip_serializing_if = "Option::is_none")]
1198 pub second_field_align: Option<SecondFieldAlign>,
1199 #[serde(rename = "@line-spacing", default = "Bibliography::default_line_spacing")]
1201 pub line_spacing: NonZeroI16,
1202 #[serde(rename = "@entry-spacing", default = "Bibliography::default_entry_spacing")]
1204 pub entry_spacing: i16,
1205 #[serde(rename = "@subsequent-author-substitute")]
1207 #[serde(skip_serializing_if = "Option::is_none")]
1208 pub subsequent_author_substitute: Option<String>,
1209 #[serde(rename = "@subsequent-author-substitute-rule", default)]
1211 pub subsequent_author_substitute_rule: SubsequentAuthorSubstituteRule,
1212 #[serde(flatten)]
1214 pub name_options: InheritableNameOptions,
1215}
1216
1217impl Bibliography {
1218 pub fn with_layout(layout: Layout) -> Self {
1220 Self {
1221 sort: None,
1222 layout,
1223 hanging_indent: false,
1224 second_field_align: None,
1225 line_spacing: Self::default_line_spacing(),
1226 entry_spacing: Self::default_entry_spacing(),
1227 subsequent_author_substitute: None,
1228 subsequent_author_substitute_rule: Default::default(),
1229 name_options: Default::default(),
1230 }
1231 }
1232
1233 fn default_line_spacing() -> NonZeroI16 {
1235 NonZeroI16::new(1).unwrap()
1236 }
1237
1238 const fn default_entry_spacing() -> i16 {
1240 1
1241 }
1242}
1243
1244#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
1246#[serde(rename_all = "kebab-case")]
1247pub enum SecondFieldAlign {
1248 Margin,
1250 Flush,
1252}
1253
1254#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
1256#[serde(rename_all = "kebab-case")]
1257pub enum SubsequentAuthorSubstituteRule {
1258 #[default]
1260 CompleteAll,
1261 CompleteEach,
1263 PartialEach,
1265 PartialFirst,
1267}
1268
1269#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
1271pub struct Sort {
1272 #[serde(rename = "key")]
1274 pub keys: Vec<SortKey>,
1275}
1276
1277#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
1279#[serde(untagged)]
1280pub enum SortKey {
1281 Variable {
1283 #[serde(rename = "@variable")]
1285 variable: Variable,
1286 #[serde(rename = "@sort", default)]
1288 sort_direction: SortDirection,
1289 },
1290 MacroName {
1292 #[serde(rename = "@macro")]
1294 name: String,
1295 #[serde(
1298 rename = "@names-min",
1299 deserialize_with = "deserialize_u32_option",
1300 default
1301 )]
1302 #[serde(skip_serializing_if = "Option::is_none")]
1303 names_min: Option<u32>,
1304 #[serde(
1307 rename = "@names-use-first",
1308 deserialize_with = "deserialize_u32_option",
1309 default
1310 )]
1311 #[serde(skip_serializing_if = "Option::is_none")]
1312 names_use_first: Option<u32>,
1313 #[serde(
1315 rename = "@names-use-last",
1316 deserialize_with = "deserialize_bool_option",
1317 default
1318 )]
1319 #[serde(skip_serializing_if = "Option::is_none")]
1320 names_use_last: Option<bool>,
1321 #[serde(rename = "@sort", default)]
1323 sort_direction: SortDirection,
1324 },
1325}
1326
1327impl From<Variable> for SortKey {
1328 fn from(value: Variable) -> Self {
1329 Self::Variable {
1330 variable: value,
1331 sort_direction: SortDirection::default(),
1332 }
1333 }
1334}
1335
1336impl SortKey {
1337 pub const fn sort_direction(&self) -> SortDirection {
1339 match self {
1340 Self::Variable { sort_direction, .. } => *sort_direction,
1341 Self::MacroName { sort_direction, .. } => *sort_direction,
1342 }
1343 }
1344}
1345
1346#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
1348#[serde(rename_all = "kebab-case")]
1349pub enum SortDirection {
1350 #[default]
1352 Ascending,
1353 Descending,
1355}
1356
1357#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
1359pub struct Layout {
1360 #[serde(rename = "$value")]
1362 pub elements: Vec<LayoutRenderingElement>,
1363 #[serde(rename = "@font-style")]
1367 #[serde(skip_serializing_if = "Option::is_none")]
1368 pub font_style: Option<FontStyle>,
1369 #[serde(rename = "@font-variant")]
1371 #[serde(skip_serializing_if = "Option::is_none")]
1372 pub font_variant: Option<FontVariant>,
1373 #[serde(rename = "@font-weight")]
1375 #[serde(skip_serializing_if = "Option::is_none")]
1376 pub font_weight: Option<FontWeight>,
1377 #[serde(rename = "@text-decoration")]
1379 #[serde(skip_serializing_if = "Option::is_none")]
1380 pub text_decoration: Option<TextDecoration>,
1381 #[serde(rename = "@vertical-align")]
1383 #[serde(skip_serializing_if = "Option::is_none")]
1384 pub vertical_align: Option<VerticalAlign>,
1385 #[serde(rename = "@prefix")]
1387 #[serde(skip_serializing_if = "Option::is_none")]
1388 pub prefix: Option<String>,
1389 #[serde(rename = "@suffix")]
1391 #[serde(skip_serializing_if = "Option::is_none")]
1392 pub suffix: Option<String>,
1393 #[serde(rename = "@delimiter")]
1395 #[serde(skip_serializing_if = "Option::is_none")]
1396 pub delimiter: Option<String>,
1397}
1398
1399to_formatting!(Layout, self);
1400to_affixes!(Layout, self);
1401
1402impl Layout {
1403 pub fn new(
1405 elements: Vec<LayoutRenderingElement>,
1406 formatting: Formatting,
1407 affixes: Option<Affixes>,
1408 delimiter: Option<String>,
1409 ) -> Self {
1410 let (prefix, suffix) = if let Some(affixes) = affixes {
1411 (affixes.prefix, affixes.suffix)
1412 } else {
1413 (None, None)
1414 };
1415
1416 Self {
1417 elements,
1418 font_style: formatting.font_style,
1419 font_variant: formatting.font_variant,
1420 font_weight: formatting.font_weight,
1421 text_decoration: formatting.text_decoration,
1422 vertical_align: formatting.vertical_align,
1423 prefix,
1424 suffix,
1425 delimiter,
1426 }
1427 }
1428
1429 pub fn with_elements(elements: Vec<LayoutRenderingElement>) -> Self {
1431 Self::new(elements, Formatting::default(), None, None)
1432 }
1433
1434 pub fn find_variable_element(
1436 &self,
1437 variable: Variable,
1438 macros: &[CslMacro],
1439 ) -> Option<LayoutRenderingElement> {
1440 self.elements
1441 .iter()
1442 .find_map(|e| e.find_variable_element(variable, macros))
1443 }
1444}
1445
1446#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
1448#[serde(rename_all = "kebab-case")]
1449pub enum LayoutRenderingElement {
1450 Text(Text),
1452 Date(Date),
1454 Number(Number),
1456 Names(Names),
1458 Label(Label),
1460 Group(Group),
1462 Choose(Choose),
1464}
1465
1466impl LayoutRenderingElement {
1467 pub fn find_variable_element(
1469 &self,
1470 variable: Variable,
1471 macros: &[CslMacro],
1472 ) -> Option<Self> {
1473 match self {
1474 Self::Text(t) => t.find_variable_element(variable, macros),
1475 Self::Choose(c) => c.find_variable_element(variable, macros),
1476 Self::Date(d) => {
1477 if d.variable.map(Variable::Date) == Some(variable) {
1478 Some(self.clone())
1479 } else {
1480 None
1481 }
1482 }
1483 Self::Number(n) => {
1484 if Variable::Number(n.variable) == variable {
1485 Some(self.clone())
1486 } else {
1487 None
1488 }
1489 }
1490 Self::Names(n) => {
1491 if n.variable.iter().any(|v| Variable::Name(*v) == variable) {
1492 Some(self.clone())
1493 } else {
1494 None
1495 }
1496 }
1497 Self::Group(g) => g
1498 .children
1499 .iter()
1500 .find_map(|e| e.find_variable_element(variable, macros)),
1501 Self::Label(_) => None,
1502 }
1503 }
1504}
1505
1506#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
1508#[serde(untagged)]
1509pub enum RenderingElement {
1510 Layout(Layout),
1512 Other(LayoutRenderingElement),
1514}
1515
1516#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
1518pub struct Text {
1519 #[serde(flatten)]
1521 pub target: TextTarget,
1522 #[serde(flatten)]
1524 pub formatting: Formatting,
1525 #[serde(flatten)]
1527 pub affixes: Affixes,
1528 #[serde(rename = "@display")]
1530 #[serde(skip_serializing_if = "Option::is_none")]
1531 pub display: Option<Display>,
1532 #[serde(rename = "@quotes", default, deserialize_with = "deserialize_bool")]
1536 pub quotes: bool,
1537 #[serde(rename = "@strip-periods", default, deserialize_with = "deserialize_bool")]
1541 pub strip_periods: bool,
1542 #[serde(rename = "@text-case")]
1544 #[serde(skip_serializing_if = "Option::is_none")]
1545 pub text_case: Option<TextCase>,
1546}
1547
1548impl Text {
1549 pub fn with_target(target: impl Into<TextTarget>) -> Self {
1551 Self {
1552 target: target.into(),
1553 formatting: Default::default(),
1554 affixes: Default::default(),
1555 display: None,
1556 quotes: false,
1557 strip_periods: false,
1558 text_case: None,
1559 }
1560 }
1561
1562 pub fn find_variable_element(
1564 &self,
1565 variable: Variable,
1566 macros: &[CslMacro],
1567 ) -> Option<LayoutRenderingElement> {
1568 match &self.target {
1569 TextTarget::Variable { var, .. } => {
1570 if *var == variable {
1571 Some(LayoutRenderingElement::Text(self.clone()))
1572 } else {
1573 None
1574 }
1575 }
1576 TextTarget::Macro { name } => {
1577 if let Some(m) = macros.iter().find(|m| m.name == *name) {
1578 m.children
1579 .iter()
1580 .find_map(|e| e.find_variable_element(variable, macros))
1581 } else {
1582 None
1583 }
1584 }
1585 TextTarget::Term { .. } => None,
1586 TextTarget::Value { .. } => None,
1587 }
1588 }
1589}
1590
1591to_formatting!(Text);
1592to_affixes!(Text);
1593
1594#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
1596#[serde(untagged)]
1597pub enum TextTarget {
1598 Variable {
1600 #[serde(rename = "@variable")]
1601 var: Variable,
1603 #[serde(rename = "@form", default)]
1604 form: LongShortForm,
1606 },
1607 Macro {
1609 #[serde(rename = "@macro")]
1610 name: String,
1612 },
1613 Term {
1615 #[serde(rename = "@term")]
1617 term: Term,
1618 #[serde(rename = "@form", default)]
1620 form: TermForm,
1621 #[serde(rename = "@plural", default, deserialize_with = "deserialize_bool")]
1623 plural: bool,
1624 },
1625 Value {
1627 #[serde(rename = "@value")]
1628 val: String,
1630 },
1631}
1632
1633impl From<Variable> for TextTarget {
1634 fn from(value: Variable) -> Self {
1635 Self::Variable { var: value, form: LongShortForm::default() }
1636 }
1637}
1638
1639impl From<Term> for TextTarget {
1640 fn from(value: Term) -> Self {
1641 Self::Term {
1642 term: value,
1643 form: TermForm::default(),
1644 plural: bool::default(),
1645 }
1646 }
1647}
1648
1649#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
1651#[serde(rename_all = "kebab-case")]
1652pub struct Date {
1653 #[serde(rename = "@variable")]
1655 #[serde(skip_serializing_if = "Option::is_none")]
1656 pub variable: Option<DateVariable>,
1657 #[serde(rename = "@form")]
1659 #[serde(skip_serializing_if = "Option::is_none")]
1660 pub form: Option<DateForm>,
1661 #[serde(rename = "@date-parts")]
1663 #[serde(skip_serializing_if = "Option::is_none")]
1664 pub parts: Option<DateParts>,
1665 #[serde(default)]
1668 pub date_part: Vec<DatePart>,
1669 #[serde(flatten)]
1671 pub formatting: Formatting,
1672 #[serde(flatten)]
1674 pub affixes: Affixes,
1675 #[serde(rename = "@delimiter")]
1677 #[serde(skip_serializing_if = "Option::is_none")]
1678 pub delimiter: Option<String>,
1679 #[serde(rename = "@display")]
1681 #[serde(skip_serializing_if = "Option::is_none")]
1682 pub display: Option<Display>,
1683 #[serde(rename = "@text-case")]
1685 #[serde(skip_serializing_if = "Option::is_none")]
1686 pub text_case: Option<TextCase>,
1687}
1688
1689to_formatting!(Date);
1690to_affixes!(Date);
1691
1692impl Date {
1693 pub const fn is_localized(&self) -> bool {
1695 self.form.is_some()
1696 }
1697}
1698
1699#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
1701#[serde(rename_all = "kebab-case")]
1702pub enum DateForm {
1703 Numeric,
1705 Text,
1707}
1708
1709#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
1711#[allow(missing_docs)]
1712#[serde(rename_all = "kebab-case")]
1713pub enum DateParts {
1714 Year,
1715 YearMonth,
1716 #[default]
1717 YearMonthDay,
1718}
1719
1720impl DateParts {
1721 pub const fn has_month(self) -> bool {
1723 matches!(self, Self::YearMonth | Self::YearMonthDay)
1724 }
1725
1726 pub const fn has_day(self) -> bool {
1728 matches!(self, Self::YearMonthDay)
1729 }
1730}
1731
1732#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
1734pub struct DatePart {
1735 #[serde(rename = "@name")]
1737 pub name: DatePartName,
1738 #[serde(rename = "@form")]
1740 #[serde(skip_serializing_if = "Option::is_none")]
1741 form: Option<DateAnyForm>,
1742 #[serde(rename = "@range-delimiter")]
1744 #[serde(skip_serializing_if = "Option::is_none")]
1745 pub range_delimiter: Option<String>,
1746 #[serde(flatten)]
1748 pub formatting: Formatting,
1749 #[serde(flatten)]
1751 pub affixes: Affixes,
1752 #[serde(rename = "@strip-periods", default, deserialize_with = "deserialize_bool")]
1756 pub strip_periods: bool,
1757 #[serde(rename = "@text-case")]
1759 #[serde(skip_serializing_if = "Option::is_none")]
1760 pub text_case: Option<TextCase>,
1761}
1762
1763to_formatting!(DatePart);
1764to_affixes!(DatePart);
1765
1766impl DatePart {
1767 pub const DEFAULT_DELIMITER: &'static str = "ā";
1769
1770 pub fn form(&self) -> DateStrongAnyForm {
1772 DateStrongAnyForm::for_name(self.name, self.form)
1773 }
1774}
1775
1776#[allow(missing_docs)]
1778#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
1779#[serde(rename_all = "kebab-case")]
1780pub enum DatePartName {
1781 Day,
1782 Month,
1783 Year,
1784}
1785
1786#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
1788#[serde(rename_all = "kebab-case")]
1789pub enum DateAnyForm {
1790 Numeric,
1792 NumericLeadingZeros,
1794 Ordinal,
1796 Long,
1798 Short,
1800}
1801
1802#[allow(missing_docs)]
1804#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
1805pub enum DateStrongAnyForm {
1806 Day(DateDayForm),
1807 Month(DateMonthForm),
1808 Year(LongShortForm),
1809}
1810
1811impl DateStrongAnyForm {
1812 pub fn for_name(name: DatePartName, form: Option<DateAnyForm>) -> Self {
1815 match name {
1816 DatePartName::Day => {
1817 Self::Day(form.map(DateAnyForm::form_for_day).unwrap_or_default())
1818 }
1819 DatePartName::Month => {
1820 Self::Month(form.map(DateAnyForm::form_for_month).unwrap_or_default())
1821 }
1822 DatePartName::Year => {
1823 Self::Year(form.map(DateAnyForm::form_for_year).unwrap_or_default())
1824 }
1825 }
1826 }
1827}
1828
1829impl DateAnyForm {
1830 pub fn form_for_day(self) -> DateDayForm {
1832 match self {
1833 Self::NumericLeadingZeros => DateDayForm::NumericLeadingZeros,
1834 Self::Ordinal => DateDayForm::Ordinal,
1835 _ => DateDayForm::default(),
1836 }
1837 }
1838
1839 pub fn form_for_month(self) -> DateMonthForm {
1841 match self {
1842 Self::Short => DateMonthForm::Short,
1843 Self::Numeric => DateMonthForm::Numeric,
1844 Self::NumericLeadingZeros => DateMonthForm::NumericLeadingZeros,
1845 _ => DateMonthForm::default(),
1846 }
1847 }
1848
1849 pub fn form_for_year(self) -> LongShortForm {
1851 match self {
1852 Self::Short => LongShortForm::Short,
1853 _ => LongShortForm::default(),
1854 }
1855 }
1856}
1857
1858#[derive(Debug, Copy, Clone, Default, Eq, PartialEq, Hash, Deserialize, Serialize)]
1860#[serde(rename_all = "kebab-case")]
1861pub enum DateDayForm {
1862 #[default]
1864 Numeric,
1865 NumericLeadingZeros,
1867 Ordinal,
1869}
1870
1871#[derive(Debug, Copy, Clone, Default, Eq, PartialEq, Hash, Deserialize, Serialize)]
1873#[serde(rename_all = "kebab-case")]
1874pub enum DateMonthForm {
1875 #[default]
1877 Long,
1878 Short,
1880 Numeric,
1882 NumericLeadingZeros,
1884}
1885
1886#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
1888#[serde(rename_all = "kebab-case")]
1889#[allow(missing_docs)]
1890pub enum LongShortForm {
1891 #[default]
1892 Long,
1893 Short,
1894}
1895
1896#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
1898#[serde(rename_all = "kebab-case")]
1899pub struct Number {
1900 #[serde(rename = "@variable")]
1902 pub variable: NumberVariable,
1903 #[serde(rename = "@form", default)]
1905 pub form: NumberForm,
1906 #[serde(flatten)]
1908 pub formatting: Formatting,
1909 #[serde(flatten)]
1911 pub affixes: Affixes,
1912 #[serde(rename = "@display")]
1914 #[serde(skip_serializing_if = "Option::is_none")]
1915 pub display: Option<Display>,
1916 #[serde(rename = "@text-case")]
1918 #[serde(skip_serializing_if = "Option::is_none")]
1919 pub text_case: Option<TextCase>,
1920}
1921
1922to_formatting!(Number);
1923to_affixes!(Number);
1924
1925#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
1927#[serde(rename_all = "kebab-case")]
1928pub enum NumberForm {
1929 #[default]
1931 Numeric,
1932 Ordinal,
1934 LongOrdinal,
1936 Roman,
1938}
1939
1940#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
1942#[serde(rename_all = "kebab-case")]
1943pub struct Names {
1944 #[serde(rename = "@variable", default)]
1946 pub variable: Vec<NameVariable>,
1947 #[serde(rename = "$value", default)]
1949 pub children: Vec<NamesChild>,
1950 #[serde(rename = "@delimiter")]
1952 #[serde(skip_serializing_if = "Option::is_none")]
1953 delimiter: Option<String>,
1954
1955 #[serde(rename = "@and")]
1957 #[serde(skip_serializing_if = "Option::is_none")]
1958 pub and: Option<NameAnd>,
1959 #[serde(rename = "@delimiter-precedes-et-al")]
1961 #[serde(skip_serializing_if = "Option::is_none")]
1962 pub delimiter_precedes_et_al: Option<DelimiterBehavior>,
1963 #[serde(rename = "@delimiter-precedes-last")]
1965 #[serde(skip_serializing_if = "Option::is_none")]
1966 pub delimiter_precedes_last: Option<DelimiterBehavior>,
1967 #[serde(rename = "@et-al-min", deserialize_with = "deserialize_u32_option", default)]
1969 #[serde(skip_serializing_if = "Option::is_none")]
1970 pub et_al_min: Option<u32>,
1971 #[serde(
1973 rename = "@et-al-use-first",
1974 deserialize_with = "deserialize_u32_option",
1975 default
1976 )]
1977 #[serde(skip_serializing_if = "Option::is_none")]
1978 pub et_al_use_first: Option<u32>,
1979 #[serde(
1981 rename = "@et-al-subsequent-min",
1982 deserialize_with = "deserialize_u32_option",
1983 default
1984 )]
1985 #[serde(skip_serializing_if = "Option::is_none")]
1986 pub et_al_subsequent_min: Option<u32>,
1987 #[serde(
1989 rename = "@et-al-subsequent-use-first",
1990 deserialize_with = "deserialize_u32_option",
1991 default
1992 )]
1993 #[serde(skip_serializing_if = "Option::is_none")]
1994 pub et_al_subsequent_use_first: Option<u32>,
1995 #[serde(
1998 rename = "@et-al-use-last",
1999 deserialize_with = "deserialize_bool_option",
2000 default
2001 )]
2002 #[serde(skip_serializing_if = "Option::is_none")]
2003 pub et_al_use_last: Option<bool>,
2004 #[serde(rename = "@name-form")]
2006 #[serde(skip_serializing_if = "Option::is_none")]
2007 pub name_form: Option<NameForm>,
2008 #[serde(
2010 rename = "@initialize",
2011 deserialize_with = "deserialize_bool_option",
2012 default
2013 )]
2014 #[serde(skip_serializing_if = "Option::is_none")]
2015 pub initialize: Option<bool>,
2016 #[serde(rename = "@initialize-with")]
2018 #[serde(skip_serializing_if = "Option::is_none")]
2019 pub initialize_with: Option<String>,
2020 #[serde(rename = "@name-as-sort-order")]
2022 #[serde(skip_serializing_if = "Option::is_none")]
2023 pub name_as_sort_order: Option<NameAsSortOrder>,
2024 #[serde(rename = "@sort-separator")]
2027 #[serde(skip_serializing_if = "Option::is_none")]
2028 pub sort_separator: Option<String>,
2029
2030 #[serde(rename = "@font-style")]
2032 #[serde(skip_serializing_if = "Option::is_none")]
2033 pub font_style: Option<FontStyle>,
2034 #[serde(rename = "@font-variant")]
2036 #[serde(skip_serializing_if = "Option::is_none")]
2037 pub font_variant: Option<FontVariant>,
2038 #[serde(rename = "@font-weight")]
2040 #[serde(skip_serializing_if = "Option::is_none")]
2041 pub font_weight: Option<FontWeight>,
2042 #[serde(rename = "@text-decoration")]
2044 #[serde(skip_serializing_if = "Option::is_none")]
2045 pub text_decoration: Option<TextDecoration>,
2046 #[serde(rename = "@vertical-align")]
2048 #[serde(skip_serializing_if = "Option::is_none")]
2049 pub vertical_align: Option<VerticalAlign>,
2050
2051 #[serde(rename = "@prefix")]
2053 #[serde(skip_serializing_if = "Option::is_none")]
2054 pub prefix: Option<String>,
2055 #[serde(rename = "@suffix")]
2057 #[serde(skip_serializing_if = "Option::is_none")]
2058 pub suffix: Option<String>,
2059
2060 #[serde(rename = "@display")]
2062 #[serde(skip_serializing_if = "Option::is_none")]
2063 pub display: Option<Display>,
2064}
2065
2066impl Names {
2067 pub fn with_variables(variables: Vec<NameVariable>) -> Self {
2069 Self {
2070 variable: variables,
2071 children: Vec::default(),
2072 delimiter: None,
2073
2074 and: None,
2075 delimiter_precedes_et_al: None,
2076 delimiter_precedes_last: None,
2077 et_al_min: None,
2078 et_al_use_first: None,
2079 et_al_subsequent_min: None,
2080 et_al_subsequent_use_first: None,
2081 et_al_use_last: None,
2082 name_form: None,
2083 initialize: None,
2084 initialize_with: None,
2085 name_as_sort_order: None,
2086 sort_separator: None,
2087
2088 font_style: None,
2089 font_variant: None,
2090 font_weight: None,
2091 text_decoration: None,
2092 vertical_align: None,
2093
2094 prefix: None,
2095 suffix: None,
2096
2097 display: None,
2098 }
2099 }
2100
2101 pub fn delimiter<'a>(&'a self, name_options: &'a InheritableNameOptions) -> &'a str {
2103 self.delimiter
2104 .as_deref()
2105 .or(name_options.name_delimiter.as_deref())
2106 .unwrap_or_default()
2107 }
2108
2109 pub fn name(&self) -> Option<&Name> {
2111 self.children.iter().find_map(|c| match c {
2112 NamesChild::Name(n) => Some(n),
2113 _ => None,
2114 })
2115 }
2116
2117 pub fn et_al(&self) -> Option<&EtAl> {
2119 self.children.iter().find_map(|c| match c {
2120 NamesChild::EtAl(e) => Some(e),
2121 _ => None,
2122 })
2123 }
2124
2125 pub fn label(&self) -> Option<(&VariablelessLabel, NameLabelPosition)> {
2127 let mut pos = NameLabelPosition::BeforeName;
2128 self.children.iter().find_map(|c| match c {
2129 NamesChild::Label(l) => Some((l, pos)),
2130 NamesChild::Name(_) => {
2131 pos = NameLabelPosition::AfterName;
2132 None
2133 }
2134 _ => None,
2135 })
2136 }
2137
2138 pub fn substitute(&self) -> Option<&Substitute> {
2140 self.children.iter().find_map(|c| match c {
2141 NamesChild::Substitute(s) => Some(s),
2142 _ => None,
2143 })
2144 }
2145
2146 pub fn options(&self) -> InheritableNameOptions {
2148 InheritableNameOptions {
2149 and: self.and,
2150 delimiter_precedes_et_al: self.delimiter_precedes_et_al,
2151 delimiter_precedes_last: self.delimiter_precedes_last,
2152 et_al_min: self.et_al_min,
2153 et_al_use_first: self.et_al_use_first,
2154 et_al_subsequent_min: self.et_al_subsequent_min,
2155 et_al_subsequent_use_first: self.et_al_subsequent_use_first,
2156 et_al_use_last: self.et_al_use_last,
2157 name_form: self.name_form,
2158 initialize: self.initialize,
2159 initialize_with: self.initialize_with.clone(),
2160 name_as_sort_order: self.name_as_sort_order,
2161 sort_separator: self.sort_separator.clone(),
2162 name_delimiter: None,
2163 names_delimiter: self.delimiter.clone(),
2164 }
2165 }
2166
2167 pub fn from_names_substitute(&self, child: &Self) -> Names {
2169 if child.name().is_some()
2170 || child.et_al().is_some()
2171 || child.substitute().is_some()
2172 {
2173 return child.clone();
2174 }
2175
2176 let formatting = child.to_formatting().apply(self.to_formatting());
2177 let options = self.options().apply(&child.options());
2178
2179 Names {
2180 variable: if child.variable.is_empty() {
2181 self.variable.clone()
2182 } else {
2183 child.variable.clone()
2184 },
2185 children: self
2186 .children
2187 .iter()
2188 .filter(|c| !matches!(c, NamesChild::Substitute(_)))
2189 .cloned()
2190 .collect(),
2191 delimiter: child.delimiter.clone().or_else(|| self.delimiter.clone()),
2192
2193 and: options.and,
2194 delimiter_precedes_et_al: options.delimiter_precedes_et_al,
2195 delimiter_precedes_last: options.delimiter_precedes_last,
2196 et_al_min: options.et_al_min,
2197 et_al_use_first: options.et_al_use_first,
2198 et_al_subsequent_min: options.et_al_subsequent_min,
2199 et_al_subsequent_use_first: options.et_al_subsequent_use_first,
2200 et_al_use_last: options.et_al_use_last,
2201 name_form: options.name_form,
2202 initialize: options.initialize,
2203 initialize_with: options.initialize_with,
2204 name_as_sort_order: options.name_as_sort_order,
2205 sort_separator: options.sort_separator,
2206
2207 font_style: formatting.font_style,
2208 font_variant: formatting.font_variant,
2209 font_weight: formatting.font_weight,
2210 text_decoration: formatting.text_decoration,
2211 vertical_align: formatting.vertical_align,
2212
2213 prefix: child.prefix.clone().or_else(|| self.prefix.clone()),
2214 suffix: child.suffix.clone().or_else(|| self.suffix.clone()),
2215 display: child.display.or(self.display),
2216 }
2217 }
2218}
2219
2220to_formatting!(Names, self);
2221to_affixes!(Names, self);
2222
2223#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
2226pub enum NameLabelPosition {
2227 AfterName,
2229 BeforeName,
2231}
2232
2233#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
2235#[serde(rename_all = "kebab-case")]
2236pub enum NamesChild {
2237 Name(Name),
2239 EtAl(EtAl),
2241 Label(VariablelessLabel),
2243 Substitute(Substitute),
2245}
2246
2247#[derive(Debug, Default, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
2249#[serde(rename_all = "kebab-case", default)]
2250pub struct Name {
2251 #[serde(rename = "@delimiter")]
2253 #[serde(skip_serializing_if = "Option::is_none")]
2254 delimiter: Option<String>,
2255 #[serde(rename = "@form")]
2257 #[serde(skip_serializing_if = "Option::is_none")]
2258 pub form: Option<NameForm>,
2259 #[serde(rename = "name-part")]
2261 parts: Vec<NamePart>,
2262 #[serde(flatten)]
2264 options: InheritableNameOptions,
2265 #[serde(flatten)]
2267 pub formatting: Formatting,
2268 #[serde(flatten)]
2270 pub affixes: Affixes,
2271}
2272
2273to_formatting!(Name);
2274to_affixes!(Name);
2275
2276impl Name {
2277 pub fn name_part_given(&self) -> Option<&NamePart> {
2279 self.parts.iter().find(|p| p.name == NamePartName::Given)
2280 }
2281
2282 pub fn name_part_family(&self) -> Option<&NamePart> {
2284 self.parts.iter().find(|p| p.name == NamePartName::Family)
2285 }
2286
2287 pub fn options<'s>(&'s self, inherited: &'s InheritableNameOptions) -> NameOptions {
2289 let applied = inherited.apply(&self.options);
2290 NameOptions {
2291 and: applied.and,
2292 delimiter: self
2293 .delimiter
2294 .as_deref()
2295 .or(inherited.name_delimiter.as_deref())
2296 .unwrap_or(", "),
2297 delimiter_precedes_et_al: applied
2298 .delimiter_precedes_et_al
2299 .unwrap_or_default(),
2300 delimiter_precedes_last: applied.delimiter_precedes_last.unwrap_or_default(),
2301 et_al_min: applied.et_al_min,
2302 et_al_use_first: applied.et_al_use_first,
2303 et_al_subsequent_min: applied.et_al_subsequent_min,
2304 et_al_subsequent_use_first: applied.et_al_subsequent_use_first,
2305 et_al_use_last: applied.et_al_use_last.unwrap_or_default(),
2306 form: self.form.or(inherited.name_form).unwrap_or_default(),
2307 initialize: applied.initialize.unwrap_or(true),
2308 initialize_with: self
2309 .options
2310 .initialize_with
2311 .as_deref()
2312 .or(inherited.initialize_with.as_deref()),
2313 name_as_sort_order: applied.name_as_sort_order,
2314 sort_separator: self
2315 .options
2316 .sort_separator
2317 .as_deref()
2318 .or(inherited.sort_separator.as_deref())
2319 .unwrap_or(", "),
2320 }
2321 }
2322}
2323
2324#[derive(Debug, Clone, Default, Eq, PartialEq, Hash, Deserialize, Serialize)]
2326#[serde(default)]
2327pub struct InheritableNameOptions {
2328 #[serde(rename = "@and")]
2330 #[serde(skip_serializing_if = "Option::is_none")]
2331 pub and: Option<NameAnd>,
2332 #[serde(rename = "@name-delimiter")]
2334 #[serde(skip_serializing_if = "Option::is_none")]
2335 pub name_delimiter: Option<String>,
2336 #[serde(rename = "@names-delimiter")]
2338 #[serde(skip_serializing_if = "Option::is_none")]
2339 pub names_delimiter: Option<String>,
2340 #[serde(rename = "@delimiter-precedes-et-al")]
2342 #[serde(skip_serializing_if = "Option::is_none")]
2343 pub delimiter_precedes_et_al: Option<DelimiterBehavior>,
2344 #[serde(rename = "@delimiter-precedes-last")]
2346 #[serde(skip_serializing_if = "Option::is_none")]
2347 pub delimiter_precedes_last: Option<DelimiterBehavior>,
2348 #[serde(rename = "@et-al-min", deserialize_with = "deserialize_u32_option", default)]
2350 #[serde(skip_serializing_if = "Option::is_none")]
2351 pub et_al_min: Option<u32>,
2352 #[serde(
2354 rename = "@et-al-use-first",
2355 deserialize_with = "deserialize_u32_option",
2356 default
2357 )]
2358 #[serde(skip_serializing_if = "Option::is_none")]
2359 pub et_al_use_first: Option<u32>,
2360 #[serde(
2362 rename = "@et-al-subsequent-min",
2363 deserialize_with = "deserialize_u32_option",
2364 default
2365 )]
2366 #[serde(skip_serializing_if = "Option::is_none")]
2367 pub et_al_subsequent_min: Option<u32>,
2368 #[serde(
2370 rename = "@et-al-subsequent-use-first",
2371 deserialize_with = "deserialize_u32_option",
2372 default
2373 )]
2374 #[serde(skip_serializing_if = "Option::is_none")]
2375 pub et_al_subsequent_use_first: Option<u32>,
2376 #[serde(
2379 rename = "@et-al-use-last",
2380 deserialize_with = "deserialize_bool_option",
2381 default
2382 )]
2383 #[serde(skip_serializing_if = "Option::is_none")]
2384 pub et_al_use_last: Option<bool>,
2385 #[serde(rename = "@name-form")]
2387 #[serde(skip_serializing_if = "Option::is_none")]
2388 pub name_form: Option<NameForm>,
2389 #[serde(
2391 rename = "@initialize",
2392 deserialize_with = "deserialize_bool_option",
2393 default
2394 )]
2395 #[serde(skip_serializing_if = "Option::is_none")]
2396 pub initialize: Option<bool>,
2397 #[serde(rename = "@initialize-with")]
2399 #[serde(skip_serializing_if = "Option::is_none")]
2400 pub initialize_with: Option<String>,
2401 #[serde(rename = "@name-as-sort-order")]
2403 #[serde(skip_serializing_if = "Option::is_none")]
2404 pub name_as_sort_order: Option<NameAsSortOrder>,
2405 #[serde(rename = "@sort-separator")]
2408 #[serde(skip_serializing_if = "Option::is_none")]
2409 pub sort_separator: Option<String>,
2410}
2411
2412pub struct NameOptions<'s> {
2415 pub and: Option<NameAnd>,
2417 pub delimiter: &'s str,
2419 pub delimiter_precedes_et_al: DelimiterBehavior,
2421 pub delimiter_precedes_last: DelimiterBehavior,
2423 pub et_al_min: Option<u32>,
2425 pub et_al_use_first: Option<u32>,
2427 pub et_al_subsequent_min: Option<u32>,
2429 pub et_al_subsequent_use_first: Option<u32>,
2431 pub et_al_use_last: bool,
2434 pub form: NameForm,
2436 pub initialize: bool,
2438 pub initialize_with: Option<&'s str>,
2440 pub name_as_sort_order: Option<NameAsSortOrder>,
2442 pub sort_separator: &'s str,
2445}
2446
2447impl InheritableNameOptions {
2448 pub fn apply(&self, child: &Self) -> Self {
2450 Self {
2451 and: child.and.or(self.and),
2452 name_delimiter: child
2453 .name_delimiter
2454 .clone()
2455 .or_else(|| self.name_delimiter.clone()),
2456 names_delimiter: child
2457 .names_delimiter
2458 .clone()
2459 .or_else(|| self.names_delimiter.clone()),
2460 delimiter_precedes_et_al: child
2461 .delimiter_precedes_et_al
2462 .or(self.delimiter_precedes_et_al),
2463 delimiter_precedes_last: child
2464 .delimiter_precedes_last
2465 .or(self.delimiter_precedes_last),
2466 et_al_min: child.et_al_min.or(self.et_al_min),
2467 et_al_use_first: child.et_al_use_first.or(self.et_al_use_first),
2468 et_al_subsequent_min: child
2469 .et_al_subsequent_min
2470 .or(self.et_al_subsequent_min),
2471 et_al_subsequent_use_first: child
2472 .et_al_subsequent_use_first
2473 .or(self.et_al_subsequent_use_first),
2474 et_al_use_last: child.et_al_use_last.or(self.et_al_use_last),
2475 name_form: child.name_form.or(self.name_form),
2476 initialize: child.initialize.or(self.initialize),
2477 initialize_with: child
2478 .initialize_with
2479 .clone()
2480 .or_else(|| self.initialize_with.clone()),
2481 name_as_sort_order: child.name_as_sort_order.or(self.name_as_sort_order),
2482 sort_separator: child
2483 .sort_separator
2484 .clone()
2485 .or_else(|| self.sort_separator.clone()),
2486 }
2487 }
2488}
2489
2490impl NameOptions<'_> {
2491 pub fn is_suppressed(&self, idx: usize, length: usize, is_subsequent: bool) -> bool {
2494 if self.et_al_use_last && idx + 1 >= length {
2496 return false;
2497 }
2498
2499 let (et_al_min, et_al_use_first) = if is_subsequent {
2501 (
2502 self.et_al_subsequent_min.or(self.et_al_min),
2503 self.et_al_subsequent_use_first.or(self.et_al_use_first),
2504 )
2505 } else {
2506 (self.et_al_min, self.et_al_use_first)
2507 };
2508
2509 let et_al_min = et_al_min.map_or(usize::MAX, |u| u as usize);
2510 let et_al_use_first = et_al_use_first.map_or(usize::MAX, |u| u as usize);
2511
2512 length >= et_al_min && idx + 1 > et_al_use_first
2513 }
2514}
2515
2516#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
2518#[serde(rename_all = "kebab-case")]
2519pub enum NameAnd {
2520 Text,
2522 Symbol,
2524}
2525
2526#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
2528#[serde(rename_all = "kebab-case")]
2529pub enum DelimiterBehavior {
2530 #[default]
2533 Contextual,
2534 AfterInvertedName,
2536 Always,
2538 Never,
2540}
2541
2542#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
2544#[serde(rename_all = "kebab-case")]
2545pub enum NameForm {
2546 #[default]
2548 Long,
2549 Short,
2551 Count,
2553}
2554
2555#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
2557#[serde(rename_all = "kebab-case")]
2558pub enum NameAsSortOrder {
2559 First,
2561 All,
2563}
2564
2565#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
2567#[serde(rename_all = "kebab-case")]
2568pub struct NamePart {
2569 #[serde(rename = "@name")]
2571 pub name: NamePartName,
2572 #[serde(flatten)]
2574 pub formatting: Formatting,
2575 #[serde(flatten)]
2577 pub affixes: Affixes,
2578 #[serde(rename = "@text-case")]
2580 #[serde(skip_serializing_if = "Option::is_none")]
2581 pub text_case: Option<TextCase>,
2582}
2583
2584to_formatting!(NamePart);
2585to_affixes!(NamePart);
2586
2587#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
2589#[serde(rename_all = "kebab-case")]
2590pub enum NamePartName {
2591 Given,
2593 Family,
2595}
2596
2597#[derive(Debug, Copy, Clone, Default, Eq, PartialEq, Hash, Deserialize, Serialize)]
2599pub struct EtAl {
2600 #[serde(rename = "@term", default)]
2602 pub term: EtAlTerm,
2603 #[serde(flatten)]
2605 pub formatting: Formatting,
2606}
2607
2608to_formatting!(EtAl);
2609
2610#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
2612pub enum EtAlTerm {
2613 #[default]
2615 #[serde(rename = "et al", alias = "et-al")]
2616 EtAl,
2617 #[serde(rename = "and others", alias = "and-others")]
2619 AndOthers,
2620}
2621
2622impl From<EtAlTerm> for Term {
2623 fn from(term: EtAlTerm) -> Self {
2624 match term {
2625 EtAlTerm::EtAl => Term::Other(OtherTerm::EtAl),
2626 EtAlTerm::AndOthers => Term::Other(OtherTerm::AndOthers),
2627 }
2628 }
2629}
2630
2631#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
2633pub struct Substitute {
2634 #[serde(rename = "$value")]
2636 pub children: Vec<LayoutRenderingElement>,
2637}
2638
2639#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
2641pub struct Label {
2642 #[serde(rename = "@variable")]
2644 pub variable: NumberOrPageVariable,
2645 #[serde(flatten)]
2647 pub label: VariablelessLabel,
2648}
2649
2650#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
2652pub struct VariablelessLabel {
2653 #[serde(rename = "@form", default)]
2655 pub form: TermForm,
2656 #[serde(rename = "@plural", default)]
2658 pub plural: LabelPluralize,
2659 #[serde(flatten)]
2661 pub formatting: Formatting,
2662 #[serde(flatten)]
2664 pub affixes: Affixes,
2665 #[serde(rename = "@text-case")]
2667 #[serde(skip_serializing_if = "Option::is_none")]
2668 pub text_case: Option<TextCase>,
2669 #[serde(rename = "@strip-periods", default, deserialize_with = "deserialize_bool")]
2673 pub strip_periods: bool,
2674}
2675
2676to_formatting!(VariablelessLabel);
2677to_affixes!(VariablelessLabel);
2678
2679#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
2681#[serde(rename_all = "kebab-case")]
2682pub enum LabelPluralize {
2683 #[default]
2685 Contextual,
2686 Always,
2688 Never,
2690}
2691
2692#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
2695pub struct Group {
2696 #[serde(rename = "$value")]
2698 pub children: Vec<LayoutRenderingElement>,
2699 #[serde(rename = "@font-style")]
2703 #[serde(skip_serializing_if = "Option::is_none")]
2704 pub font_style: Option<FontStyle>,
2705 #[serde(rename = "@font-variant")]
2707 #[serde(skip_serializing_if = "Option::is_none")]
2708 pub font_variant: Option<FontVariant>,
2709 #[serde(rename = "@font-weight")]
2711 #[serde(skip_serializing_if = "Option::is_none")]
2712 pub font_weight: Option<FontWeight>,
2713 #[serde(rename = "@text-decoration")]
2715 #[serde(skip_serializing_if = "Option::is_none")]
2716 pub text_decoration: Option<TextDecoration>,
2717 #[serde(rename = "@vertical-align")]
2719 #[serde(skip_serializing_if = "Option::is_none")]
2720 pub vertical_align: Option<VerticalAlign>,
2721 #[serde(rename = "@prefix")]
2723 #[serde(skip_serializing_if = "Option::is_none")]
2724 pub prefix: Option<String>,
2725 #[serde(rename = "@suffix")]
2727 #[serde(skip_serializing_if = "Option::is_none")]
2728 pub suffix: Option<String>,
2729 #[serde(rename = "@delimiter")]
2731 #[serde(skip_serializing_if = "Option::is_none")]
2732 pub delimiter: Option<String>,
2733 #[serde(rename = "@display")]
2735 #[serde(skip_serializing_if = "Option::is_none")]
2736 pub display: Option<Display>,
2737}
2738
2739to_formatting!(Group, self);
2740to_affixes!(Group, self);
2741
2742#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
2744pub struct Choose {
2745 #[serde(rename = "if")]
2747 pub if_: ChooseBranch,
2748 #[serde(rename = "else-if")]
2750 #[serde(default)]
2751 pub else_if: Vec<ChooseBranch>,
2752 #[serde(rename = "else")]
2754 #[serde(skip_serializing_if = "Option::is_none")]
2755 pub otherwise: Option<ElseBranch>,
2756}
2757
2758impl Choose {
2759 pub fn branches(&self) -> impl Iterator<Item = &ChooseBranch> {
2761 std::iter::once(&self.if_).chain(self.else_if.iter())
2762 }
2763
2764 pub fn find_variable_element(
2766 &self,
2767 variable: Variable,
2768 macros: &[CslMacro],
2769 ) -> Option<LayoutRenderingElement> {
2770 self.branches()
2771 .find_map(|b| {
2772 b.children
2773 .iter()
2774 .find_map(|c| c.find_variable_element(variable, macros))
2775 })
2776 .clone()
2777 }
2778}
2779
2780#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
2782pub struct ChooseBranch {
2783 #[serde(
2786 rename = "@disambiguate",
2787 deserialize_with = "deserialize_bool_option",
2788 default
2789 )]
2790 #[serde(skip_serializing_if = "Option::is_none")]
2791 pub disambiguate: Option<bool>,
2792 #[serde(rename = "@is-numeric")]
2794 #[serde(skip_serializing_if = "Option::is_none")]
2796 pub is_numeric: Option<Vec<Variable>>,
2797 #[serde(rename = "@is-uncertain-date")]
2799 #[serde(skip_serializing_if = "Option::is_none")]
2800 pub is_uncertain_date: Option<Vec<DateVariable>>,
2801 #[serde(rename = "@locator")]
2803 #[serde(skip_serializing_if = "Option::is_none")]
2804 pub locator: Option<Vec<Locator>>,
2805 #[serde(rename = "@position")]
2808 #[serde(skip_serializing_if = "Option::is_none")]
2809 pub position: Option<Vec<TestPosition>>,
2810 #[serde(rename = "@type")]
2812 #[serde(skip_serializing_if = "Option::is_none")]
2813 pub type_: Option<Vec<Kind>>,
2814 #[serde(rename = "@variable")]
2816 #[serde(skip_serializing_if = "Option::is_none")]
2817 pub variable: Option<Vec<Variable>>,
2818 #[serde(rename = "@match")]
2820 #[serde(default)]
2821 pub match_: ChooseMatch,
2822 #[serde(rename = "$value", default)]
2823 pub children: Vec<LayoutRenderingElement>,
2825}
2826
2827impl ChooseBranch {
2828 pub fn test(&self) -> Option<ChooseTest> {
2831 if let Some(disambiguate) = self.disambiguate {
2832 if !disambiguate {
2833 None
2834 } else {
2835 Some(ChooseTest::Disambiguate)
2836 }
2837 } else if let Some(is_numeric) = &self.is_numeric {
2838 Some(ChooseTest::IsNumeric(is_numeric))
2839 } else if let Some(is_uncertain_date) = &self.is_uncertain_date {
2840 Some(ChooseTest::IsUncertainDate(is_uncertain_date))
2841 } else if let Some(locator) = &self.locator {
2842 Some(ChooseTest::Locator(locator))
2843 } else if let Some(position) = &self.position {
2844 Some(ChooseTest::Position(position))
2845 } else if let Some(type_) = &self.type_ {
2846 Some(ChooseTest::Type(type_))
2847 } else {
2848 self.variable.as_ref().map(|variable| ChooseTest::Variable(variable))
2849 }
2850 }
2851}
2852
2853#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
2855pub struct ElseBranch {
2856 #[serde(rename = "$value")]
2858 pub children: Vec<LayoutRenderingElement>,
2859}
2860
2861#[derive(Debug, Clone, Eq, PartialEq, Hash)]
2863pub enum ChooseTest<'a> {
2864 Disambiguate,
2867 IsNumeric(&'a [Variable]),
2869 IsUncertainDate(&'a [DateVariable]),
2871 Locator(&'a [Locator]),
2873 Position(&'a [TestPosition]),
2876 Type(&'a [Kind]),
2878 Variable(&'a [Variable]),
2880}
2881
2882#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
2884#[serde(rename_all = "kebab-case")]
2885pub enum TestPosition {
2886 First,
2888 Subsequent,
2890 IbidWithLocator,
2892 Ibid,
2894 NearNote,
2896}
2897
2898#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
2900#[serde(rename_all = "kebab-case")]
2901pub enum ChooseMatch {
2902 #[default]
2904 All,
2905 Any,
2907 None,
2909}
2910
2911impl ChooseMatch {
2912 pub fn test(self, mut tests: impl Iterator<Item = bool>) -> bool {
2914 match self {
2915 Self::All => tests.all(|t| t),
2916 Self::Any => tests.any(|t| t),
2917 Self::None => tests.all(|t| !t),
2918 }
2919 }
2920}
2921
2922#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
2924pub struct CslMacro {
2925 #[serde(rename = "@name")]
2927 pub name: String,
2928 #[serde(rename = "$value")]
2930 #[serde(default)]
2931 pub children: Vec<LayoutRenderingElement>,
2932}
2933
2934#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
2936#[serde(rename_all = "kebab-case")]
2937pub struct LocaleFile {
2938 #[serde(rename = "@version")]
2940 pub version: String,
2941 #[serde(rename = "@lang")]
2943 pub lang: LocaleCode,
2944 #[serde(skip_serializing_if = "Option::is_none")]
2946 pub info: Option<LocaleInfo>,
2947 #[serde(skip_serializing_if = "Option::is_none")]
2949 pub terms: Option<Terms>,
2950 #[serde(default)]
2952 pub date: Vec<Date>,
2953 #[serde(skip_serializing_if = "Option::is_none")]
2955 pub style_options: Option<LocaleOptions>,
2956}
2957
2958impl LocaleFile {
2959 pub fn from_xml(xml: &str) -> XmlResult<Self> {
2961 let locale: Self = quick_xml::de::from_str(xml)?;
2962 Ok(locale)
2963 }
2964
2965 pub fn to_xml(&self) -> XmlResult<String> {
2967 let mut buf = String::new();
2968 let ser = quick_xml::se::Serializer::with_root(&mut buf, Some("style"))?;
2969 self.serialize(ser)?;
2970 Ok(buf)
2971 }
2972}
2973
2974#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
2976#[serde(rename_all = "kebab-case")]
2977pub struct Locale {
2978 #[serde(rename = "@lang")]
2981 #[serde(skip_serializing_if = "Option::is_none")]
2982 pub lang: Option<LocaleCode>,
2983 #[serde(skip_serializing_if = "Option::is_none")]
2985 pub info: Option<LocaleInfo>,
2986 #[serde(skip_serializing_if = "Option::is_none")]
2988 pub terms: Option<Terms>,
2989 #[serde(default)]
2991 pub date: Vec<Date>,
2992 #[serde(skip_serializing_if = "Option::is_none")]
2994 pub style_options: Option<LocaleOptions>,
2995}
2996
2997impl Locale {
2998 pub fn term(&self, term: Term, form: TermForm) -> Option<&LocalizedTerm> {
3000 self.terms.as_ref().and_then(|terms| {
3001 terms
3002 .terms
3003 .iter()
3004 .find(|t| t.name.is_lexically_same(term) && t.form == form)
3005 })
3006 }
3007
3008 pub fn ordinals(&self) -> Option<OrdinalLookup<'_>> {
3011 self.terms.as_ref().and_then(|terms| {
3012 terms.terms.iter().any(|t| t.name.is_ordinal()).then(|| {
3013 OrdinalLookup::new(terms.terms.iter().filter(|t| t.name.is_ordinal()))
3014 })
3015 })
3016 }
3017}
3018
3019pub struct OrdinalLookup<'a> {
3021 terms: Vec<&'a LocalizedTerm>,
3022 legacy_behavior: bool,
3023}
3024
3025impl<'a> OrdinalLookup<'a> {
3026 fn new(ordinal_terms: impl Iterator<Item = &'a LocalizedTerm>) -> Self {
3027 let terms = ordinal_terms.collect::<Vec<_>>();
3028 let mut legacy_behavior = false;
3029 let defines_ordinal =
3031 terms.iter().any(|t| t.name == Term::Other(OtherTerm::Ordinal));
3032
3033 if !defines_ordinal {
3034 legacy_behavior = (1..=4).all(|n| {
3036 terms.iter().any(|t| t.name == Term::Other(OtherTerm::OrdinalN(n)))
3037 })
3038 }
3039
3040 Self { terms, legacy_behavior }
3041 }
3042
3043 pub const fn empty() -> Self {
3045 Self { terms: Vec::new(), legacy_behavior: false }
3046 }
3047
3048 pub fn lookup(&self, n: i32, gender: Option<GrammarGender>) -> Option<&'a str> {
3050 let mut best_match: Option<&'a LocalizedTerm> = None;
3051
3052 let mut change_match = |other_match: &'a LocalizedTerm| {
3054 let Some(current) = best_match else {
3055 best_match = Some(other_match);
3056 return;
3057 };
3058
3059 let Term::Other(OtherTerm::OrdinalN(other_n)) = other_match.name else {
3061 return;
3062 };
3063
3064 let Term::Other(OtherTerm::OrdinalN(curr_n)) = current.name else {
3065 best_match = Some(other_match);
3066 return;
3067 };
3068
3069 best_match = Some(if other_n >= 10 && curr_n < 10 {
3070 other_match
3071 } else if other_n < 10 && curr_n >= 10 {
3072 current
3073 } else {
3074 if gender == current.gender && gender != other_match.gender {
3077 current
3078 } else if gender != current.gender && gender == other_match.gender {
3079 other_match
3080 } else {
3081 let diff_other = (n - other_n as i32).abs();
3083 let diff_curr = (n - curr_n as i32).abs();
3084
3085 if diff_other <= diff_curr {
3086 other_match
3087 } else {
3088 current
3089 }
3090 }
3091 })
3092 };
3093
3094 for term in self.terms.iter().copied() {
3095 let Term::Other(term_name) = term.name else { continue };
3096
3097 let hit = match term_name {
3098 OtherTerm::Ordinal => true,
3099 OtherTerm::OrdinalN(o) if self.legacy_behavior => {
3100 let class = match (n, n % 10) {
3101 (11..=13, _) => 4,
3102 (_, v @ 1..=3) => v as u8,
3103 _ => 4,
3104 };
3105 o == class
3106 }
3107 OtherTerm::OrdinalN(o @ 0..=9) => match term.match_ {
3108 Some(OrdinalMatch::LastDigit) | None => n % 10 == o as i32,
3109 Some(OrdinalMatch::LastTwoDigits) => n % 100 == o as i32,
3110 Some(OrdinalMatch::WholeNumber) => n == o as i32,
3111 },
3112 OtherTerm::OrdinalN(o @ 10..=99) => match term.match_ {
3113 Some(OrdinalMatch::LastTwoDigits) | None => n % 100 == o as i32,
3114 Some(OrdinalMatch::WholeNumber) => n == o as i32,
3115 _ => false,
3116 },
3117 _ => false,
3118 };
3119
3120 if hit {
3121 change_match(term);
3122 }
3123 }
3124
3125 best_match.and_then(|t| t.single().or_else(|| t.multiple()))
3126 }
3127
3128 pub fn lookup_long(&self, n: i32) -> Option<&'a str> {
3131 self.terms
3132 .iter()
3133 .find(|t| {
3134 let Term::Other(OtherTerm::LongOrdinal(o)) = t.name else { return false };
3135 if n > 0 && n <= 10 {
3136 n == o as i32
3137 } else {
3138 match t.match_ {
3139 Some(OrdinalMatch::LastTwoDigits) | None => n % 100 == o as i32,
3140 Some(OrdinalMatch::WholeNumber) => n == o as i32,
3141 _ => false,
3142 }
3143 }
3144 })
3145 .and_then(|t| t.single().or_else(|| t.multiple()))
3146 }
3147}
3148
3149impl From<LocaleFile> for Locale {
3150 fn from(file: LocaleFile) -> Self {
3151 Self {
3152 lang: Some(file.lang),
3153 info: file.info,
3154 terms: file.terms,
3155 date: file.date,
3156 style_options: file.style_options,
3157 }
3158 }
3159}
3160
3161impl TryFrom<Locale> for LocaleFile {
3162 type Error = ();
3163
3164 fn try_from(value: Locale) -> Result<Self, Self::Error> {
3165 if value.lang.is_some() {
3166 Ok(Self {
3167 version: "1.0".to_string(),
3168 lang: value.lang.unwrap(),
3169 info: value.info,
3170 terms: value.terms,
3171 date: value.date,
3172 style_options: value.style_options,
3173 })
3174 } else {
3175 Err(())
3176 }
3177 }
3178}
3179
3180#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
3182pub struct LocaleInfo {
3183 #[serde(rename = "translator")]
3185 #[serde(default)]
3186 pub translators: Vec<StyleAttribution>,
3187 #[serde(skip_serializing_if = "Option::is_none")]
3189 pub rights: Option<License>,
3190 #[serde(skip_serializing_if = "Option::is_none")]
3192 pub updated: Option<Timestamp>,
3193}
3194
3195#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
3197pub struct Terms {
3198 #[serde(rename = "term")]
3200 pub terms: Vec<LocalizedTerm>,
3201}
3202
3203#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
3205pub struct LocalizedTerm {
3206 #[serde(rename = "@name")]
3208 pub name: Term,
3209 #[serde(rename = "$text")]
3211 #[serde(skip_serializing_if = "Option::is_none")]
3212 localization: Option<String>,
3213 #[serde(skip_serializing_if = "Option::is_none")]
3215 single: Option<String>,
3216 #[serde(skip_serializing_if = "Option::is_none")]
3218 multiple: Option<String>,
3219 #[serde(rename = "@form", default)]
3221 pub form: TermForm,
3222 #[serde(rename = "@match")]
3224 #[serde(skip_serializing_if = "Option::is_none")]
3225 pub match_: Option<OrdinalMatch>,
3226 #[serde(rename = "@gender")]
3228 #[serde(skip_serializing_if = "Option::is_none")]
3229 pub gender: Option<GrammarGender>,
3230 #[serde(rename = "@gender-form")]
3232 #[serde(skip_serializing_if = "Option::is_none")]
3233 pub gender_form: Option<GrammarGender>,
3234}
3235
3236impl LocalizedTerm {
3237 pub fn single(&self) -> Option<&str> {
3240 self.single.as_deref().or(self.localization.as_deref())
3241 }
3242
3243 pub fn multiple(&self) -> Option<&str> {
3246 self.multiple.as_deref().or(self.localization.as_deref())
3247 }
3248}
3249
3250#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
3252#[serde(rename_all = "kebab-case")]
3253pub enum TermForm {
3254 #[default]
3256 Long,
3257 Short,
3259 Verb,
3261 VerbShort,
3263 Symbol,
3265}
3266
3267impl TermForm {
3268 pub const fn fallback(self) -> Option<Self> {
3270 match self {
3271 Self::Long => None,
3272 Self::Short => Some(Self::Long),
3273 Self::Verb => Some(Self::Long),
3274 Self::VerbShort => Some(Self::Verb),
3275 Self::Symbol => Some(Self::Short),
3276 }
3277 }
3278}
3279
3280#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
3282#[serde(rename_all = "kebab-case")]
3283pub enum OrdinalMatch {
3284 #[default]
3287 LastDigit,
3288 LastTwoDigits,
3290 WholeNumber,
3292}
3293
3294#[allow(missing_docs)]
3296#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
3297#[serde(rename_all = "kebab-case")]
3298pub enum GrammarGender {
3299 Feminine,
3300 Masculine,
3301}
3302
3303#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
3305pub struct LocaleOptions {
3306 #[serde(
3310 rename = "@limit-day-ordinals-to-day-1",
3311 deserialize_with = "deserialize_bool_option",
3312 default
3313 )]
3314 #[serde(skip_serializing_if = "Option::is_none")]
3315 pub limit_day_ordinals_to_day_1: Option<bool>,
3316 #[serde(
3320 rename = "@punctuation-in-quote",
3321 deserialize_with = "deserialize_bool_option",
3322 default
3323 )]
3324 #[serde(skip_serializing_if = "Option::is_none")]
3325 pub punctuation_in_quote: Option<bool>,
3326}
3327
3328#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
3330pub struct Formatting {
3331 #[serde(rename = "@font-style")]
3333 #[serde(skip_serializing_if = "Option::is_none")]
3334 pub font_style: Option<FontStyle>,
3335 #[serde(rename = "@font-variant")]
3337 #[serde(skip_serializing_if = "Option::is_none")]
3338 pub font_variant: Option<FontVariant>,
3339 #[serde(rename = "@font-weight")]
3341 #[serde(skip_serializing_if = "Option::is_none")]
3342 pub font_weight: Option<FontWeight>,
3343 #[serde(rename = "@text-decoration")]
3345 #[serde(skip_serializing_if = "Option::is_none")]
3346 pub text_decoration: Option<TextDecoration>,
3347 #[serde(rename = "@vertical-align")]
3349 #[serde(skip_serializing_if = "Option::is_none")]
3350 pub vertical_align: Option<VerticalAlign>,
3351}
3352
3353impl Formatting {
3354 pub fn is_empty(&self) -> bool {
3356 self.font_style.is_none()
3357 && self.font_variant.is_none()
3358 && self.font_weight.is_none()
3359 && self.text_decoration.is_none()
3360 && self.vertical_align.is_none()
3361 }
3362
3363 pub fn apply(self, base: Self) -> Self {
3365 Self {
3366 font_style: self.font_style.or(base.font_style),
3367 font_variant: self.font_variant.or(base.font_variant),
3368 font_weight: self.font_weight.or(base.font_weight),
3369 text_decoration: self.text_decoration.or(base.text_decoration),
3370 vertical_align: self.vertical_align.or(base.vertical_align),
3371 }
3372 }
3373}
3374
3375#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
3377#[serde(rename_all = "lowercase")]
3378pub enum FontStyle {
3379 #[default]
3381 Normal,
3382 Italic,
3384}
3385
3386#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
3388#[serde(rename_all = "kebab-case")]
3389pub enum FontVariant {
3390 #[default]
3392 Normal,
3393 SmallCaps,
3395}
3396
3397#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
3399#[serde(rename_all = "lowercase")]
3400pub enum FontWeight {
3401 #[default]
3403 Normal,
3404 Bold,
3406 Light,
3408}
3409
3410#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
3412#[serde(rename_all = "lowercase")]
3413pub enum TextDecoration {
3414 #[default]
3416 None,
3417 Underline,
3419}
3420
3421#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
3423#[serde(rename_all = "lowercase")]
3424pub enum VerticalAlign {
3425 #[default]
3427 #[serde(rename = "")]
3428 None,
3429 Baseline,
3431 Sup,
3433 Sub,
3435}
3436
3437#[derive(Debug, Default, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
3439pub struct Affixes {
3440 #[serde(rename = "@prefix")]
3442 #[serde(skip_serializing_if = "Option::is_none")]
3443 pub prefix: Option<String>,
3444 #[serde(rename = "@suffix")]
3446 #[serde(skip_serializing_if = "Option::is_none")]
3447 pub suffix: Option<String>,
3448}
3449
3450#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
3452#[serde(rename_all = "kebab-case")]
3453pub enum Display {
3454 Block,
3456 LeftMargin,
3458 RightInline,
3460 Indent,
3462}
3463
3464#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
3466#[serde(rename_all = "kebab-case")]
3467pub enum TextCase {
3468 Lowercase,
3470 Uppercase,
3472 CapitalizeFirst,
3474 CapitalizeAll,
3476 #[serde(rename = "sentence")]
3478 SentenceCase,
3479 #[serde(rename = "title")]
3481 TitleCase,
3482}
3483
3484impl TextCase {
3485 pub fn is_language_independent(self) -> bool {
3487 match self {
3488 Self::Lowercase
3489 | Self::Uppercase
3490 | Self::CapitalizeFirst
3491 | Self::CapitalizeAll => true,
3492 Self::SentenceCase | Self::TitleCase => false,
3493 }
3494 }
3495}
3496
3497#[cfg(test)]
3498mod test {
3499 use super::*;
3500 use serde::de::DeserializeOwned;
3501 use std::{error::Error, fs};
3502
3503 fn folder<F>(
3504 files: &'static str,
3505 extension: &'static str,
3506 kind: &'static str,
3507 mut check: F,
3508 ) where
3509 F: FnMut(&str) -> Option<Box<dyn Error>>,
3510 {
3511 let mut failures = 0;
3512 let mut tests = 0;
3513
3514 for entry in fs::read_dir(files).unwrap() {
3516 let entry = entry.unwrap();
3517 let path = entry.path();
3518 if path.extension().map(|os| os.to_str().unwrap()) != Some(extension)
3519 || !entry.file_type().unwrap().is_file()
3520 {
3521 continue;
3522 }
3523
3524 tests += 1;
3525
3526 let source = fs::read_to_string(&path).unwrap();
3527 let result = check(&source);
3528 if let Some(err) = result {
3529 failures += 1;
3530 println!("ā {:?} failed: \n\n{:#?}", &path, &err);
3531 }
3532 }
3533
3534 if failures == 0 {
3535 print!("\nš")
3536 } else {
3537 print!("\nš¢")
3538 }
3539
3540 println!(
3541 " {} out of {} {} files parsed successfully",
3542 tests - failures,
3543 tests,
3544 kind
3545 );
3546
3547 if failures > 0 {
3548 panic!("{} tests failed", failures);
3549 }
3550 }
3551
3552 fn check_style(csl_files: &'static str, kind: &'static str) {
3553 folder(csl_files, "csl", kind, |source| {
3554 let de = &mut deserializer(source);
3555 let result: Result<RawStyle, _> = serde_path_to_error::deserialize(de);
3556 match result {
3557 Ok(_) => None,
3558 Err(err) => Some(Box::new(err)),
3559 }
3560 })
3561 }
3562
3563 fn check_locale(locale_files: &'static str) {
3564 folder(locale_files, "xml", "Locale", |source| {
3565 let de = &mut deserializer(source);
3566 let result: Result<LocaleFile, _> = serde_path_to_error::deserialize(de);
3567 match result {
3568 Ok(_) => None,
3569 Err(err) => Some(Box::new(err)),
3570 }
3571 })
3572 }
3573
3574 #[track_caller]
3575 fn to_cbor<T: Serialize>(style: &T) -> Vec<u8> {
3576 let mut buf = Vec::new();
3577 ciborium::ser::into_writer(style, &mut buf).unwrap();
3578 buf
3579 }
3580
3581 #[track_caller]
3582 fn from_cbor<T: DeserializeOwned>(reader: &[u8]) -> T {
3583 ciborium::de::from_reader(reader).unwrap()
3584 }
3585
3586 #[test]
3587 fn test_independent() {
3588 check_style("tests/independent", "independent CSL style");
3589 }
3590
3591 #[test]
3592 fn test_dependent() {
3593 check_style("tests/dependent", "dependent CSL style");
3594 }
3595
3596 #[test]
3597 fn test_locale() {
3598 check_locale("tests/locales");
3599 }
3600
3601 #[test]
3605 fn roundtrip_cbor_all() {
3606 fs::create_dir_all("tests/artifacts/styles").unwrap();
3607 for style_thing in
3608 fs::read_dir("../styles/").expect("please check out the CSL styles repo")
3609 {
3610 let thing = style_thing.unwrap();
3611 if thing.file_type().unwrap().is_dir() {
3612 continue;
3613 }
3614
3615 let path = thing.path();
3616 let extension = path.extension();
3617 if let Some(extension) = extension {
3618 if extension.to_str() != Some("csl") {
3619 continue;
3620 }
3621 } else {
3622 continue;
3623 }
3624
3625 eprintln!("Testing {}", path.display());
3626 let source = fs::read_to_string(&path).unwrap();
3627 let style = Style::from_xml(&source).unwrap();
3628 let cbor = to_cbor(&style);
3629 fs::write(
3630 format!(
3631 "tests/artifacts/styles/{}.cbor",
3632 path.file_stem().unwrap().to_str().unwrap()
3633 ),
3634 &cbor,
3635 )
3636 .unwrap();
3637 let style2 = from_cbor(&cbor);
3638 assert_eq!(style, style2);
3639 }
3640 }
3641
3642 #[test]
3646 fn roundtrip_cbor_all_locales() {
3647 fs::create_dir_all("tests/artifacts/locales").unwrap();
3648 for style_thing in
3649 fs::read_dir("../locales/").expect("please check out the CSL locales repo")
3650 {
3651 let thing = style_thing.unwrap();
3652 if thing.file_type().unwrap().is_dir() {
3653 continue;
3654 }
3655
3656 let path = thing.path();
3657 let extension = path.extension();
3658 if let Some(extension) = extension {
3659 if extension.to_str() != Some("xml")
3660 || !path
3661 .file_stem()
3662 .unwrap()
3663 .to_str()
3664 .unwrap()
3665 .starts_with("locales-")
3666 {
3667 continue;
3668 }
3669 } else {
3670 continue;
3671 }
3672
3673 eprintln!("Testing {}", path.display());
3674 let source = fs::read_to_string(&path).unwrap();
3675 let locale = LocaleFile::from_xml(&source).unwrap();
3676 let cbor = to_cbor(&locale);
3677 fs::write(
3678 format!(
3679 "tests/artifacts/locales/{}.cbor",
3680 path.file_stem().unwrap().to_str().unwrap()
3681 ),
3682 &cbor,
3683 )
3684 .unwrap();
3685 let locale2 = from_cbor(&cbor);
3686 assert_eq!(locale, locale2);
3687 }
3688 }
3689
3690 #[test]
3691 fn page_range() {
3692 fn run(format: PageRangeFormat, start: &str, end: &str) -> String {
3693 let mut buf = String::new();
3694 format.format(&mut buf, start, end, None).unwrap();
3695 buf
3696 }
3697
3698 let c15 = PageRangeFormat::Chicago15;
3699 let c16 = PageRangeFormat::Chicago16;
3700 let exp = PageRangeFormat::Expanded;
3701 let min = PageRangeFormat::Minimal;
3702 let mi2 = PageRangeFormat::MinimalTwo;
3703
3704 assert_eq!("3ā10", run(c15, "3", "10"));
3707 assert_eq!("71ā72", run(c15, "71", "72"));
3708 assert_eq!("100ā104", run(c15, "100", "4"));
3709 assert_eq!("600ā613", run(c15, "600", "613"));
3710 assert_eq!("1100ā1123", run(c15, "1100", "1123"));
3711 assert_eq!("107ā8", run(c15, "107", "108"));
3712 assert_eq!("505ā17", run(c15, "505", "517"));
3713 assert_eq!("1002ā6", run(c15, "1002", "1006"));
3714 assert_eq!("321ā25", run(c15, "321", "325"));
3715 assert_eq!("415ā532", run(c15, "415", "532"));
3716 assert_eq!("11564ā68", run(c15, "11564", "11568"));
3717 assert_eq!("13792ā803", run(c15, "13792", "13803"));
3718 assert_eq!("1496ā1504", run(c15, "1496", "1504"));
3719 assert_eq!("2787ā2816", run(c15, "2787", "2816"));
3720 assert_eq!("101ā8", run(c15, "101", "108"));
3721
3722 assert_eq!("3ā10", run(c16, "3", "10"));
3723 assert_eq!("71ā72", run(c16, "71", "72"));
3724 assert_eq!("92ā113", run(c16, "92", "113"));
3725 assert_eq!("100ā104", run(c16, "100", "4"));
3726 assert_eq!("600ā613", run(c16, "600", "613"));
3727 assert_eq!("1100ā1123", run(c16, "1100", "1123"));
3728 assert_eq!("107ā8", run(c16, "107", "108"));
3729 assert_eq!("505ā17", run(c16, "505", "517"));
3730 assert_eq!("1002ā6", run(c16, "1002", "1006"));
3731 assert_eq!("321ā25", run(c16, "321", "325"));
3732 assert_eq!("415ā532", run(c16, "415", "532"));
3733 assert_eq!("1087ā89", run(c16, "1087", "1089"));
3734 assert_eq!("1496ā500", run(c16, "1496", "1500"));
3735 assert_eq!("11564ā68", run(c16, "11564", "11568"));
3736 assert_eq!("13792ā803", run(c16, "13792", "13803"));
3737 assert_eq!("12991ā3001", run(c16, "12991", "13001"));
3738 assert_eq!("12991ā123001", run(c16, "12991", "123001"));
3739
3740 assert_eq!("42ā45", run(exp, "42", "45"));
3741 assert_eq!("321ā328", run(exp, "321", "328"));
3742 assert_eq!("2787ā2816", run(exp, "2787", "2816"));
3743
3744 assert_eq!("42ā5", run(min, "42", "45"));
3745 assert_eq!("321ā8", run(min, "321", "328"));
3746 assert_eq!("2787ā816", run(min, "2787", "2816"));
3747
3748 assert_eq!("7ā8", run(mi2, "7", "8"));
3749 assert_eq!("42ā45", run(mi2, "42", "45"));
3750 assert_eq!("321ā28", run(mi2, "321", "328"));
3751 assert_eq!("2787ā816", run(mi2, "2787", "2816"));
3752 }
3753
3754 #[test]
3756 fn test_bug_hayagriva_115() {
3757 fn run(format: PageRangeFormat, start: &str, end: &str) -> String {
3758 let mut buf = String::new();
3759 format.format(&mut buf, start, end, None).unwrap();
3760 buf
3761 }
3762 let c16 = PageRangeFormat::Chicago16;
3763
3764 assert_eq!("12991ā123001", run(c16, "12991", "123001"));
3765 }
3766
3767 #[test]
3768 fn page_range_prefix() {
3769 fn run(format: PageRangeFormat, start: &str, end: &str) -> String {
3770 let mut buf = String::new();
3771 format.format(&mut buf, start, end, None).unwrap();
3772 buf
3773 }
3774
3775 let c15 = PageRangeFormat::Chicago15;
3776 let exp = PageRangeFormat::Expanded;
3777 let min = PageRangeFormat::Minimal;
3778
3779 assert_eq!("8n11564ā68", run(c15, "8n11564", "8n1568"));
3780 assert_eq!("n11564ā68", run(c15, "n11564", "n1568"));
3781 assert_eq!("n11564ā1568", run(c15, "n11564", "1568"));
3782
3783 assert_eq!("N110ā5", run(exp, "N110 ", " 5"));
3784 assert_eq!("N110āN115", run(exp, "N110 ", " N5"));
3785 assert_eq!("110āN6", run(exp, "110 ", " N6"));
3786 assert_eq!("N110āP5", run(exp, "N110 ", " P5"));
3787 assert_eq!("123N110āN5", run(exp, "123N110 ", " N5"));
3788 assert_eq!("456K200ā99", run(exp, "456K200 ", " 99"));
3789 assert_eq!("000c23ā22", run(exp, "000c23 ", " 22"));
3790
3791 assert_eq!("n11564ā8", run(min, "n11564 ", " n1568"));
3792 assert_eq!("n11564ā1568", run(min, "n11564 ", " 1568"));
3793 }
3794}