citationberg-0.6.1/.cargo_vcs_info.json0000644000000001360000000000100135120ustar { "git": { "sha1": "72d836001170490db71a230716987eb8c563f3b2" }, "path_in_vcs": "" }citationberg-0.6.1/.github/workflows/ci.yml000064400000000000000000000023121046102023000170130ustar 00000000000000name: Continuous integration on: [push, pull_request] env: RUSTFLAGS: "-Dwarnings" RUSTDOCFLAGS: "-Dwarnings" jobs: ci: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@stable - name: Check out CSL repos run: | cd .. git clone --depth 1 https://github.com/citation-style-language/styles git clone --depth 1 https://github.com/citation-style-language/locales - uses: Swatinem/rust-cache@v2 - run: cargo build - run: cargo test --all-features checks: name: Check clippy, formatting, and documentation runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@stable with: components: clippy, rustfmt - uses: Swatinem/rust-cache@v2 - run: cargo clippy --all-targets --all-features - run: cargo fmt --check --all - run: cargo doc --no-deps min-version: name: Check minimum Rust version runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@1.88.0 - uses: Swatinem/rust-cache@v2 - run: cargo check --all-features --all-targets citationberg-0.6.1/.gitignore000064400000000000000000000000731046102023000142720ustar 00000000000000/target /tests/artifacts Cargo.lock .DS_Store .vscode .zed citationberg-0.6.1/Cargo.lock0000644000000111520000000000100114650ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "cfg-if" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" [[package]] name = "ciborium" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" dependencies = [ "ciborium-io", "ciborium-ll", "serde", ] [[package]] name = "ciborium-io" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" [[package]] name = "ciborium-ll" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" dependencies = [ "ciborium-io", "half", ] [[package]] name = "citationberg" version = "0.6.1" dependencies = [ "ciborium", "quick-xml", "serde", "serde_json", "serde_path_to_error", "unscanny", ] [[package]] name = "crunchy" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "half" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" dependencies = [ "cfg-if", "crunchy", ] [[package]] name = "itoa" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "memchr" version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "proc-macro2" version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] [[package]] name = "quick-xml" version = "0.38.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42a232e7487fc2ef313d96dde7948e7a3c05101870d8985e4fd8d26aedd27b89" dependencies = [ "memchr", "serde", ] [[package]] name = "quote" version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] [[package]] name = "ryu" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "serde" version = "1.0.226" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dca6411025b24b60bfa7ec1fe1f8e710ac09782dca409ee8237ba74b51295fd" dependencies = [ "serde_core", "serde_derive", ] [[package]] name = "serde_core" version = "1.0.226" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba2ba63999edb9dac981fb34b3e5c0d111a69b0924e253ed29d83f7c99e966a4" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.226" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8db53ae22f34573731bafa1db20f04027b2d25e02d8205921b569171699cdb33" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" dependencies = [ "itoa", "memchr", "ryu", "serde", "serde_core", ] [[package]] name = "serde_path_to_error" version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" dependencies = [ "itoa", "serde", "serde_core", ] [[package]] name = "syn" version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "unicode-ident" version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" [[package]] name = "unscanny" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9df2af067a7953e9c3831320f35c1cc0600c30d44d9f7a12b01db1cd88d6b47" citationberg-0.6.1/Cargo.toml0000644000000026440000000000100115160ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2024" name = "citationberg" version = "0.6.1" authors = ["Martin Haug "] build = false exclude = ["assets/*"] autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "A parser for CSL files" readme = "README.md" keywords = [ "csl", "bibliography", "citation", ] categories = [ "parser-implementations", "text-processing", ] license = "MIT OR Apache-2.0" repository = "https://github.com/typst/citationberg" [features] default = [] json = ["unscanny"] [lib] name = "citationberg" path = "src/lib.rs" [dependencies.quick-xml] version = "0.38.1" features = [ "serialize", "overlapped-lists", ] [dependencies.serde] version = "1.0" features = ["derive"] [dependencies.unscanny] version = "0.1.0" optional = true [dev-dependencies.ciborium] version = "0.2.2" [dev-dependencies.serde_json] version = "1.0" [dev-dependencies.serde_path_to_error] version = "0.1" citationberg-0.6.1/Cargo.toml.orig000064400000000000000000000013521046102023000151720ustar 00000000000000[package] name = "citationberg" version = "0.6.1" authors = ["Martin Haug "] license = "MIT OR Apache-2.0" description = "A parser for CSL files" repository = "https://github.com/typst/citationberg" readme = "README.md" categories = ["parser-implementations", "text-processing"] keywords = ["csl", "bibliography", "citation"] edition = "2024" exclude = ["assets/*"] [features] default = [] json = ["unscanny"] # adds support for CSL-json parsing [dependencies] quick-xml = { version = "0.38.1", features = ["serialize", "overlapped-lists"] } serde = { version = "1.0", features = ["derive"] } unscanny = { version = "0.1.0", optional = true } [dev-dependencies] ciborium = "0.2.2" serde_json = "1.0" serde_path_to_error = "0.1" citationberg-0.6.1/LICENSE-APACHE000064400000000000000000000251171046102023000142340ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2023 Martin Haug Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. citationberg-0.6.1/LICENSE-MIT000064400000000000000000000017771046102023000137520ustar 00000000000000Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. citationberg-0.6.1/NOTICE000064400000000000000000000006231046102023000132070ustar 00000000000000Licenses for third party components used by this project can be found below. ================================================================================ The Creative Commons BY-SA 3.0 DEED License applies to: * The CSL styles found in `tests/dependent/*` * The CSL styles found in `tests/independent/*` * The CSL locales found in `tests/locales/*` https://creativecommons.org/licenses/by-sa/3.0/ citationberg-0.6.1/README.md000064400000000000000000000034011046102023000135570ustar 00000000000000# Citationberg [![Crates.io](https://img.shields.io/crates/v/citationberg.svg)](https://crates.io/crates/citationberg) [![Documentation](https://docs.rs/citationberg/badge.svg)](https://docs.rs/citationberg) [![Build status](https://github.com/typst/citationberg/workflows/Continuous%20integration/badge.svg)](https://github.com/typst/citationberg/actions)

Dinkelberg meme: Dad from the TV show The Fairly Odd Parents exclaiming Citationberg

A library for parsing CSL styles. ```toml [dependencies] citationberg = "0.6" ``` Citationberg deserializes CSL styles from XML into Rust structs. It supports [CSL 1.0.2](https://docs.citationstyles.org/en/stable/specification.html). This crate is not a CSL processor, so you are free to choose whatever data model and data types you need for your bibliographic needs. If you need to render citations, you can use [Hayagriva](https://github.com/typst/hayagriva) which uses this crate under the hood. Parse your style like this: ```rust use std::fs; use citationberg::Style; let string = fs::read_to_string("tests/independent/ieee.csl")?; let style = citationberg::Style::from_xml(&string)?; let Style::Independent(independent) = style else { panic!("IEEE is an independent style"); }; assert_eq!(independent.info.title.value, "IEEE"); ``` Be sure to check out the CSL [styles](https://github.com/citation-style-language/styles) and [locales](https://github.com/citation-style-language/locales) repositories into sibling folders of `citationberg` if you want to run the tests. ## Safety This crate forbids unsafe code. ## License This crate is dual-licensed under the MIT and Apache 2.0 licenses. citationberg-0.6.1/rustfmt.toml000064400000000000000000000002111046102023000146750ustar 00000000000000use_small_heuristics = "Max" max_width = 90 chain_width = 70 struct_lit_width = 50 use_field_init_shorthand = true merge_derives = false citationberg-0.6.1/src/json.rs000064400000000000000000000341301046102023000144110ustar 00000000000000//! Parser for CSL-JSON. //! //! This is only available when the `json` feature is enabled. use std::borrow::Cow; use std::{collections::BTreeMap, str::FromStr}; use serde::{Deserialize, Serialize}; use unscanny::Scanner; use crate::taxonomy::Season; /// A CSL-JSON item. #[derive(Debug, Serialize, Deserialize, Hash, PartialEq, Eq)] #[serde(transparent)] pub struct Item(pub BTreeMap); impl Item { /// The item's ID. pub fn id(&self) -> Option> { self.0.get("id")?.to_str() } /// The item type. pub fn type_(&self) -> Option> { self.0.get("type")?.to_str() } /// Whether any of the fields values contains any HTML. pub fn has_html(&self) -> bool { self.0.values().any(|v| v.has_html()) } /// Whether this entry may contain "cheater syntax" for odd fields. pub fn may_have_hack(&self) -> bool { self.0.contains_key("note") } } /// A field in an CSL-JSON item. #[derive(Debug, Serialize, Deserialize, Hash, PartialEq, Eq)] #[serde(untagged)] pub enum Value { /// A string value. String(String), /// A number value. Number(i64), /// A list of names. Names(Vec), /// A date value. Date(DateValue), } impl Value { /// Convert to a string if this is a string or number. pub fn to_str(&self) -> Option> { match self { Value::String(s) => Some(s.as_str().into()), Value::Number(n) => Some(n.to_string().into()), Value::Date(_) => None, Value::Names(_) => None, } } /// Whether the value contains any HTML. pub fn has_html(&self) -> bool { match self { Value::String(s) => s.contains('<'), Value::Number(_) => false, Value::Date(_) => false, Value::Names(_) => false, } } } /// The representation of a name. #[derive(Debug, Serialize, Deserialize, Hash, PartialEq, Eq)] #[serde(untagged)] pub enum NameValue { /// A name that doesn't necessarily follow the schema of a `NameItem`. Literal(LiteralName), /// A name that is defined by a collection of parts. Item(NameItem), } /// A name that is defined by a collection of parts. #[derive(Debug, Serialize, Deserialize, Hash, PartialEq, Eq)] #[serde(rename_all = "kebab-case")] pub struct NameItem { /// The family name. #[serde(default)] pub family: String, /// The given name. pub given: Option, /// A name particle like `"de las"`. pub non_dropping_particle: Option, /// A name particle like `"Rev."`. pub dropping_particle: Option, /// A name suffix like `"Jr., Ph.D."`. pub suffix: Option, } /// A name that doesn't necessarily follow the schema of a `NameItem`. May be /// useful for institutional names. #[derive(Debug, Serialize, Deserialize, Hash, PartialEq, Eq)] pub struct LiteralName { /// The literal name. pub literal: String, } /// The representation of a date. #[derive(Clone, Debug, Hash, PartialEq, Eq)] #[allow(missing_docs)] pub enum DateValue { Raw { raw: FixedDateRange, literal: Option, season: Option, }, DateParts { date_parts: VecDateRange, literal: Option, season: Option, }, } impl TryFrom for FixedDateRange { type Error = (); fn try_from(value: DateValue) -> Result { let (mut fixed, season) = match value { DateValue::Raw { raw, season, .. } => (raw, season), DateValue::DateParts { date_parts, season, .. } => { let res = date_parts.try_into()?; (res, season) } }; fixed.start.season = season .and_then(|s| s.parse::().ok()) .and_then(|u| Season::try_from_csl_number(u).ok()); Ok(fixed) } } impl From for VecDateRange { fn from(value: DateValue) -> Self { match value { DateValue::Raw { raw, .. } => raw.into(), DateValue::DateParts { date_parts, .. } => date_parts, } } } impl<'de> Deserialize<'de> for DateValue { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { #[derive(Deserialize)] #[serde(rename_all = "kebab-case", untagged)] enum DateReprRaw { Raw { raw: FixedDateRange, literal: Option, season: Option, }, DateParts { #[serde(rename = "date-parts")] date_parts: VecDateRange, literal: Option, season: Option, }, } let raw = DateReprRaw::deserialize(deserializer)?; Ok(match raw { DateReprRaw::Raw { raw, literal, season } => DateValue::Raw { raw, literal, season: season.map(NumberOrString::into_string), }, DateReprRaw::DateParts { date_parts, literal, season } => { DateValue::DateParts { date_parts, literal, season: season.map(NumberOrString::into_string), } } }) } } impl Serialize for DateValue { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { match self { DateValue::Raw { raw, .. } => VecDateRange::from(*raw).serialize(serializer), DateValue::DateParts { date_parts, .. } => date_parts.serialize(serializer), } } } /// A range of dates defined by arbitrary sequences of integer components. #[derive(Clone, Debug, Serialize, Deserialize, Hash, PartialEq, Eq)] #[serde(transparent)] pub struct VecDateRange(pub Vec); impl From for VecDateRange { fn from(value: FixedDateRange) -> Self { let mut v = Vec::new(); v.push(value.start.into()); if let Some(end) = value.end { v.push(end.into()); } VecDateRange(v) } } /// A date defined by an arbitrary sequence integer components. #[derive(Clone, Debug, Serialize, Hash, PartialEq, Eq)] #[serde(transparent)] pub struct VecDate(pub Vec); impl From for VecDate { fn from(value: FixedDate) -> Self { let mut v = Vec::new(); v.push(value.year); if let Some(month) = value.month { v.push(month as i16); if let Some(day) = value.day { v.push(day as i16); } } VecDate(v) } } impl<'de> Deserialize<'de> for VecDate { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { let v = Vec::::deserialize(deserializer)?; Ok(VecDate( v.into_iter() .filter_map(|v| match v { NumberOrString::Number(n) => Some(Ok(n)), NumberOrString::String(s) if s.is_empty() => None, NumberOrString::String(s) => Some(s.parse().map_err(|_| { serde::de::Error::custom(format!("invalid number: {}", s)) })), }) .collect::>()?, )) } } /// A range of dates defined by fixed components. #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] pub struct FixedDateRange { /// The start of the range. pub start: FixedDate, /// The optional end of the range. pub end: Option, } impl TryFrom for FixedDateRange { type Error = (); fn try_from(value: VecDateRange) -> Result { let mut v = value.0.into_iter(); let start = v.next().ok_or(())?.into(); let end = v.next().map(|v| v.into()); if v.next().is_some() { return Err(()); } Ok(FixedDateRange { start, end }) } } impl FromStr for FixedDateRange { type Err = (); fn from_str(s: &str) -> Result { let mut s = Scanner::new(s); let start = parse_date(&mut s).ok_or(())?; let end = if s.eat() == Some('/') { Some(parse_date(&mut s).ok_or(())?) } else { None }; Ok(FixedDateRange { start, end }) } } impl<'de> Deserialize<'de> for FixedDateRange { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { let s = String::deserialize(deserializer)?; Self::from_str(&s).map_err(|_| serde::de::Error::custom("invalid date")) } } /// A date defined by fixed components. #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] #[allow(missing_docs)] pub struct FixedDate { pub year: i16, pub month: Option, pub day: Option, pub season: Option, } impl From for FixedDate { fn from(value: VecDate) -> Self { let mut v = value.0.into_iter(); let year = v.next().unwrap(); let month = v.next().map(|v| (v - 1) as u8); let day = v.next().map(|v| (v - 1) as u8); FixedDate { year, month, day, season: None } } } impl FromStr for FixedDate { type Err = (); fn from_str(s: &str) -> Result { let mut s = Scanner::new(s); parse_date(&mut s).ok_or(()) } } impl<'de> Deserialize<'de> for FixedDate { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { let s = String::deserialize(deserializer)?; Self::from_str(&s).map_err(|_| serde::de::Error::custom("invalid date")) } } fn parse_date(s: &mut Scanner<'_>) -> Option { let year = s.eat_while(char::is_ascii_digit); let year = year.parse().ok()?; if s.peek() != Some('-') { return Some(FixedDate { year, month: None, day: None, season: None }); } s.eat(); let month = s.eat_while(char::is_ascii_digit); let month = month.parse::().ok()? - 1; if month > 11 { return None; } if s.peek() != Some('-') { return Some(FixedDate { year, month: Some(month), day: None, season: None }); } s.eat(); let day = s.eat_while(char::is_ascii_digit); let day = day.parse::().ok()? - 1; if day > 31 { return None; } Some(FixedDate { year, month: Some(month), day: Some(day), season: None, }) } /// A CSL-JSON citation. #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Citation { /// A unique ID for the citation. pub citation_id: String, /// The individual parts of the citation. pub citation_items: Vec, /// The citation's properties. pub properties: CitationProperties, } /// An individual part of a citation. #[derive(Debug, Serialize)] #[serde(rename_all = "kebab-case")] pub struct CitationItem { /// A unique ID for the citation item. pub id: String, /// A locator value (e.g. a page number). pub locator: Option, /// What kind of locator to use (e.g. `"page"`). pub label: Option, /// Whether to suppress the author for this item. #[serde(default)] pub suppress_author: bool, /// Something to print before this item. pub prefix: Option, /// Something to print after this item. pub suffix: Option, /// Defines the relationship of this item to other cited items with the same /// key. pub position: Option, /// Whether this key was already cited in close range before. pub near_note: Option, } impl<'de> Deserialize<'de> for CitationItem { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { #[derive(Deserialize)] #[serde(rename_all = "kebab-case")] struct CitationItemRaw { id: NumberOrString, locator: Option, label: Option, #[serde(default)] suppress_author: bool, prefix: Option, suffix: Option, position: Option, near_note: Option, } let raw = CitationItemRaw::deserialize(deserializer)?; Ok(CitationItem { id: raw.id.into_string(), locator: raw.locator.map(NumberOrString::into_string), label: raw.label, suppress_author: raw.suppress_author, prefix: raw.prefix, suffix: raw.suffix, position: raw.position, near_note: raw.near_note, }) } } /// Properties of a citation. #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct CitationProperties { /// The footnote number in which the citation is located in the document. note_index: Option, } #[derive(Deserialize)] #[serde(untagged)] enum NumberOrString { Number(i16), String(String), } impl NumberOrString { fn into_string(self) -> String { match self { NumberOrString::Number(n) => n.to_string(), NumberOrString::String(s) => s, } } } #[cfg(test)] mod tests { use super::*; #[test] fn test_serialize() { let mut map = BTreeMap::new(); map.insert("title".to_string(), Value::String("The Title".to_string())); map.insert( "author".to_string(), Value::Names(vec![NameValue::Item(NameItem { family: "Doe".to_string(), given: Some("John".to_string()), non_dropping_particle: None, dropping_particle: None, suffix: None, })]), ); map.insert( "date".to_string(), Value::Date(DateValue::Raw { raw: FixedDateRange::from_str("2021-09-10/2022-01-01").unwrap(), literal: None, season: None, }), ); let item = Item(map); println!("{}", serde_json::to_string_pretty(&item).unwrap()); } } citationberg-0.6.1/src/lib.rs000064400000000000000000003727561046102023000142310ustar 00000000000000/*! A library for parsing CSL styles. Citationberg deserializes CSL styles from XML into Rust structs. It supports [CSL 1.0.2](https://docs.citationstyles.org/en/stable/specification.html). This crate is not a CSL processor, so you are free to choose whatever data model and data types you need for your bibliographic needs. If you need to render citations, you can use [Hayagriva](https://github.com/typst/hayagriva) which uses this crate under the hood. Parse your style like this: ```rust # fn main() -> Result<(), Box> { use std::fs; use citationberg::Style; let string = fs::read_to_string("tests/independent/ieee.csl")?; let style = citationberg::Style::from_xml(&string)?; let Style::Independent(independent) = style else { panic!("IEEE is an independent style"); }; assert_eq!(independent.info.title.value, "IEEE"); # Ok(()) # } ``` You can also parse a [`DependentStyle`] or a [`IndependentStyle`] directly. */ #![deny(missing_docs)] #![deny(unsafe_code)] #[cfg(feature = "json")] pub mod json; pub mod taxonomy; mod util; use std::fmt::{self, Debug}; use std::num::{NonZeroI16, NonZeroUsize}; use quick_xml::de::{Deserializer, SliceReader}; use serde::{Deserialize, Serialize}; use taxonomy::{ DateVariable, Kind, Locator, NameVariable, NumberOrPageVariable, OtherTerm, Term, Variable, }; use self::util::*; /// Result type for functions that deserialize XML. pub type XmlDeResult = Result; /// Error type for functions that deserialize XML. pub type XmlDeError = quick_xml::de::DeError; /// Result type for functions that serialize XML. pub type XmlSeResult = Result; /// Error type for functions that serialize XML. pub type XmlSeError = quick_xml::se::SeError; const EVENT_BUFFER_SIZE: Option = NonZeroUsize::new(u32::MAX as usize); /// Allow every struct with formatting properties to convert to a `Formatting`. pub trait ToFormatting { /// Obtain a `Formatting`. fn to_formatting(&self) -> Formatting; } macro_rules! to_formatting { ($name:ty, self) => { impl ToFormatting for $name { fn to_formatting(&self) -> Formatting { Formatting { font_style: self.font_style, font_variant: self.font_variant, font_weight: self.font_weight, text_decoration: self.text_decoration, vertical_align: self.vertical_align, } } } }; ($name:ty) => { impl ToFormatting for $name { fn to_formatting(&self) -> Formatting { self.formatting.clone() } } }; } /// Allow every struct with affix properties to convert to a `Affixes`. pub trait ToAffixes { /// Obtain the `Affixes`. fn to_affixes(&self) -> Affixes; } macro_rules! to_affixes { ($name:ty, self) => { impl ToAffixes for $name { fn to_affixes(&self) -> Affixes { Affixes { prefix: self.prefix.clone(), suffix: self.suffix.clone(), } } } }; ($name:ty) => { impl ToAffixes for $name { fn to_affixes(&self) -> Affixes { self.affixes.clone() } } }; } /// A CSL style. #[allow(clippy::large_enum_variant)] #[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)] struct RawStyle { /// The style's metadata. pub info: StyleInfo, /// The locale used if the user didn't specify one. /// Overrides the default locale of the parent style. #[serde(rename = "@default-locale")] #[serde(skip_serializing_if = "Option::is_none")] pub default_locale: Option, /// The CSL version the style is compatible with. #[serde(rename = "@version")] pub version: String, /// How notes or in-text citations are displayed. Must be present in /// independent styles. #[serde(skip_serializing_if = "Option::is_none")] pub citation: Option, /// How bibliographies are displayed. #[serde(skip_serializing_if = "Option::is_none")] pub bibliography: Option, /// The style's settings. Must be present in dependent styles. #[serde(flatten)] pub independent_settings: Option, /// Reusable formatting rules. #[serde(rename = "macro", default)] pub macros: Vec, /// Override localized strings. #[serde(default)] pub locale: Vec, } impl RawStyle { /// Retrieve the link to the parent style for dependent styles. pub fn parent_link(&self) -> Option<&InfoLink> { self.info .link .iter() .find(|link| link.rel == InfoLinkRel::IndependentParent) } } impl From for RawStyle { fn from(value: IndependentStyle) -> Self { Self { info: value.info, default_locale: value.default_locale, version: value.version, citation: Some(value.citation), bibliography: value.bibliography, independent_settings: Some(value.settings), macros: value.macros, locale: value.locale, } } } impl From for RawStyle { fn from(value: DependentStyle) -> Self { Self { info: value.info, default_locale: value.default_locale, version: value.version, citation: None, bibliography: None, independent_settings: None, macros: Vec::new(), locale: Vec::new(), } } } impl From"#; let de = &mut deserializer(style_str); let result: Result = serde_path_to_error::deserialize(de); let style = result.unwrap(); assert!(style.locale[1].lang.as_ref().unwrap().is_english()); } } citationberg-0.6.1/src/taxonomy.rs000064400000000000000000001604161046102023000153250ustar 00000000000000//! CSL constants that describe entries, terms, and variables. use std::fmt; use std::num::IntErrorKind; use std::str::FromStr; use serde::{Deserialize, Deserializer, Serialize, de}; /// A CSL variable. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)] #[serde(untagged)] pub enum Variable { /// The set of variables with no other attributes. Standard(StandardVariable), /// The page variable. Can be formatted as a page range. /// /// CSL does not distinguish between page ranges and other number variables. /// See the [`NumberOrPageVariable`] enum for a CSL-like view on the /// variants. Page(PageVariable), /// Variables that can be formatted as numbers and are not page ranges. Number(NumberVariable), /// Variables that can be formatted as dates. Date(DateVariable), /// Variables that can be formatted as names. Name(NameVariable), } impl Variable { /// Check if the variable starts with `number-of-` to control contextual /// label behavior. pub const fn is_number_of_variable(self) -> bool { if let Self::Number(v) = self { v.is_number_of_variable() } else { false } } } impl fmt::Display for Variable { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Standard(v) => v.fmt(f), Self::Number(v) => v.fmt(f), Self::Date(v) => v.fmt(f), Self::Name(v) => v.fmt(f), Self::Page(v) => v.fmt(f), } } } /// The set of variables with no other attributes. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)] #[serde(rename_all = "kebab-case")] pub enum StandardVariable { /// Abstract of the item (e.g. the abstract of a journal article). Abstract, /// Short markup, decoration, or annotation to the item (e.g., to indicate /// items included in a review); For descriptive text (e.g., in an annotated /// bibliography), use note instead. Annote, /// Archive storing the item. Archive, /// Collection the item is part of within an archive. #[serde(rename = "archive_collection")] ArchiveCollection, /// Storage location within an archive (e.g. a box and folder number). #[serde(rename = "archive_location")] ArchiveLocation, /// Geographic location of the archive. ArchivePlace, /// Issuing or judicial authority (e.g. “USPTO” for a patent, “Fairfax /// Circuit Court” for a legal case). Authority, /// Call number (to locate the item in a library). CallNumber, /// Identifier of the item in the input data file (analogous to BibTeX /// entrykey); Use this variable to facilitate conversion between /// word-processor and plain-text writing systems; For an identifier intended /// as formatted output label for a citation (e.g. “Ferr78”), use /// citation-label instead. CitationKey, /// Label identifying the item in in-text citations of label styles (e.g. /// “Ferr78”); May be assigned by the CSL processor based on item metadata; /// For the identifier of the item in the input data file, use citation-key /// instead. CitationLabel, /// Title of the collection holding the item (e.g. the series title for a /// book; the lecture series title for a presentation). CollectionTitle, /// Title of the container holding the item (e.g. the book title for a book /// chapter, the journal title for a journal article; the album title for a /// recording; the session title for multi-part presentation at a /// conference). ContainerTitle, /// Short/abbreviated form of container-title; Deprecated; use /// variable="container-title" form="short" instead. ContainerTitleShort, /// Physical (e.g. size) or temporal (e.g. running time) dimensions of the /// item. Dimensions, /// Minor subdivision of a court with a jurisdiction for a legal item. Division, /// Digital Object Identifier (e.g. “10.1128/AEM.02591-07”). #[serde(rename = "DOI")] DOI, /// Deprecated legacy variant of event-title. Event, /// Name of the event related to the item (e.g. the conference name when /// citing a conference paper; the meeting where presentation was made). EventTitle, /// Geographic location of the event related to the item (e.g. “Amsterdam, /// The Netherlands”). EventPlace, /// Type, class, or subtype of the item (e.g. “Doctoral dissertation” for a /// PhD thesis; “NIH Publication” for an NIH technical report); Do not use /// for topical descriptions or categories (e.g. “adventure” for an /// adventure movie). Genre, /// International Standard Book Number (e.g. “978-3-8474-1017-1”). #[serde(rename = "ISBN")] ISBN, /// International Standard Serial Number. #[serde(rename = "ISSN")] ISSN, /// Geographic scope of relevance (e.g. “US” for a US patent; the court /// hearing a legal case). Jurisdiction, /// Keyword(s) or tag(s) attached to the item. Keyword, /// The language of the item. Language, /// The license information applicable to an item (e.g. the license an /// article or software is released under; the copyright information for an /// item; the classification status of a document). License, /// Description of the item’s format or medium (e.g. “CD”, “DVD”, “Album”, /// etc.). Medium, /// Descriptive text or notes about an item (e.g. in an annotated /// bibliography). Note, /// Original publisher, for items that have been republished by a different /// publisher. OriginalPublisher, /// Geographic location of the original publisher (e.g. “London, UK”). OriginalPublisherPlace, /// Title of the original version (e.g. “Война и мир”, the untranslated /// Russian title of “War and Peace”). OriginalTitle, /// Title of the specific part of an item being cited. PartTitle, /// PubMed Central reference number. #[serde(rename = "PMCID")] PMCID, /// PubMed reference number. #[serde(rename = "PMID")] PMID, /// Publisher. Publisher, /// Geographic location of the publisher. PublisherPlace, /// Resources related to the procedural history of a legal case or /// legislation; Can also be used to refer to the procedural history of /// other items (e.g. “Conference canceled” for a presentation accepted as a /// conference that was subsequently canceled; details of a retraction or /// correction notice). References, /// Type of the item being reviewed by the current item (e.g. book, film). ReviewedGenre, /// Title of the item reviewed by the current item. ReviewedTitle, /// Scale of e.g. a map or model. Scale, /// Source from whence the item originates (e.g. a library catalog or /// database). Source, /// Publication status of the item (e.g. “forthcoming”; “in press”; “advance /// online publication”; “retracted”). Status, /// Primary title of the item. Title, /// Short/abbreviated form of title; Deprecated; use variable="title" /// form="short" instead. TitleShort, /// Uniform Resource Locator (e.g. /// “https://aem.asm.org/cgi/content/full/74/9/2766”). #[allow(rustdoc::bare_urls)] #[serde(rename = "URL")] URL, /// Title of the volume of the item or container holding the item; Also use /// for titles of periodical special issues, special sections, and the like. VolumeTitle, /// Disambiguating year suffix in author-date styles (e.g. “a” in “Doe, /// 1999a”). YearSuffix, } impl From for Variable { fn from(value: StandardVariable) -> Self { Variable::Standard(value) } } impl fmt::Display for StandardVariable { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Abstract => write!(f, "abstract"), Self::Annote => write!(f, "annote"), Self::Archive => write!(f, "archive"), Self::ArchiveCollection => write!(f, "archive_collection"), Self::ArchiveLocation => write!(f, "archive_location"), Self::ArchivePlace => write!(f, "archive_place"), Self::Authority => write!(f, "authority"), Self::CallNumber => write!(f, "call-number"), Self::CitationKey => write!(f, "citation-key"), Self::CitationLabel => write!(f, "citation-label"), Self::CollectionTitle => write!(f, "collection-title"), Self::ContainerTitle => write!(f, "container-title"), Self::ContainerTitleShort => write!(f, "container-title-short"), Self::Dimensions => write!(f, "dimensions"), Self::Division => write!(f, "division"), Self::DOI => write!(f, "DOI"), Self::Event => write!(f, "event"), Self::EventTitle => write!(f, "event-title"), Self::EventPlace => write!(f, "event-place"), Self::Genre => write!(f, "genre"), Self::ISBN => write!(f, "ISBN"), Self::ISSN => write!(f, "ISSN"), Self::Jurisdiction => write!(f, "jurisdiction"), Self::Keyword => write!(f, "keyword"), Self::Language => write!(f, "language"), Self::License => write!(f, "license"), Self::Medium => write!(f, "medium"), Self::Note => write!(f, "note"), Self::OriginalPublisher => write!(f, "original-publisher"), Self::OriginalPublisherPlace => write!(f, "original-publisher-place"), Self::OriginalTitle => write!(f, "original-title"), Self::PartTitle => write!(f, "part-title"), Self::PMCID => write!(f, "PMCID"), Self::PMID => write!(f, "PMID"), Self::Publisher => write!(f, "publisher"), Self::PublisherPlace => write!(f, "publisher-place"), Self::References => write!(f, "references"), Self::ReviewedGenre => write!(f, "reviewed-genre"), Self::ReviewedTitle => write!(f, "reviewed-title"), Self::Scale => write!(f, "scale"), Self::Source => write!(f, "source"), Self::Status => write!(f, "status"), Self::Title => write!(f, "title"), Self::TitleShort => write!(f, "title-short"), Self::URL => write!(f, "URL"), Self::VolumeTitle => write!(f, "volume-title"), Self::YearSuffix => write!(f, "year-suffix"), } } } /// Number variables as the CSL spec knows them, though we separate between /// number variables and page ranges. #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, Serialize, Deserialize)] #[serde(untagged)] pub enum NumberOrPageVariable { /// The page variable. Page(PageVariable), /// Variables formattable as numbers. Number(NumberVariable), } impl From for Variable { fn from(value: NumberOrPageVariable) -> Self { match value { NumberOrPageVariable::Page(p) => p.into(), NumberOrPageVariable::Number(n) => n.into(), } } } impl From for Term { fn from(value: NumberOrPageVariable) -> Self { match value { NumberOrPageVariable::Page(p) => p.into(), NumberOrPageVariable::Number(n) => n.into(), } } } /// Variable that can be formatted as page numbers #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, Serialize, Deserialize)] pub enum PageVariable { #[serde(rename = "page")] /// Range of pages the item (e.g. a journal article) covers in a container /// (e.g. a journal issue). Page, } impl From for Term { fn from(_: PageVariable) -> Self { Self::PageVariable } } impl From for Variable { fn from(value: PageVariable) -> Self { Self::Page(value) } } impl fmt::Display for PageVariable { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "page") } } /// Variables that can be formatted as numbers. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)] #[serde(rename_all = "kebab-case")] pub enum NumberVariable { /// Chapter number (e.g. chapter number in a book; track number on an /// album). ChapterNumber, /// Index (starting at 1) of the cited reference in the bibliography /// (generated by the CSL processor). CitationNumber, /// Number identifying the collection holding the item (e.g. the series /// number for a book). CollectionNumber, /// (Container) edition holding the item (e.g. “3” when citing a chapter in /// the third edition of a book). Edition, /// Number of a preceding note containing the first reference to the item; /// Assigned by the CSL processor; Empty in non-note-based styles or when /// the item hasn’t been cited in any preceding notes in a document. FirstReferenceNoteNumber, /// Issue number of the item or container holding the item (e.g. “5” when /// citing a journal article from journal volume 2, issue 5); Use /// volume-title for the title of the issue, if any. Issue, /// A cite-specific pinpointer within the item (e.g. a page number within a /// book, or a volume in a multi-volume work); Must be accompanied in the /// input data by a label indicating the locator type (see the Locators term /// list), which determines which term is rendered by cs:label when the /// locator variable is selected. Locator, /// Number identifying the item (e.g. a report number). Number, /// Total number of pages of the cited item. NumberOfPages, /// Total number of volumes, used when citing multi-volume books and such. NumberOfVolumes, /// First page of the range of pages the item (e.g. a journal article) /// covers in a container (e.g. a journal issue). PageFirst, /// Number of the specific part of the item being cited (e.g. part 2 of a /// journal article); Use part-title for the title of the part, if any. PartNumber, /// Printing number of the item or container holding the item. #[serde(alias = "printing")] PrintingNumber, /// Section of the item or container holding the item (e.g. “§2.0.1” for a /// law; “politics” for a newspaper article). Section, /// Supplement number of the item or container holding the item (e.g. for /// secondary legal items that are regularly updated between editions). SupplementNumber, /// Version of the item (e.g. “2.0.9” for a software program). Version, /// Volume number of the item (e.g. “2” when citing volume 2 of a book) or /// the container holding the item (e.g. “2” when citing a chapter from /// volume 2 of a book); Use volume-title for the title of the volume, if /// any. Volume, } impl fmt::Display for NumberVariable { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::ChapterNumber => write!(f, "chapter-number"), Self::CitationNumber => write!(f, "citation-number"), Self::CollectionNumber => write!(f, "collection-number"), Self::Edition => write!(f, "edition"), Self::FirstReferenceNoteNumber => write!(f, "first-reference-note-number"), Self::Issue => write!(f, "issue"), Self::Locator => write!(f, "locator"), Self::Number => write!(f, "number"), Self::NumberOfPages => write!(f, "number-of-pages"), Self::NumberOfVolumes => write!(f, "number-of-volumes"), Self::PageFirst => write!(f, "page-first"), Self::PartNumber => write!(f, "part-number"), Self::PrintingNumber => write!(f, "printing-number"), Self::Section => write!(f, "section"), Self::SupplementNumber => write!(f, "supplement-number"), Self::Version => write!(f, "version"), Self::Volume => write!(f, "volume"), } } } impl NumberVariable { /// Check if the variable starts with `number-of-` to control contextual /// label behavior. pub const fn is_number_of_variable(self) -> bool { matches!(self, Self::NumberOfPages | Self::NumberOfVolumes) } } impl From for Variable { fn from(value: NumberVariable) -> Self { Self::Number(value) } } impl From for Term { fn from(value: NumberVariable) -> Self { Self::NumberVariable(value) } } /// Variables that can be formatted as dates. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)] #[serde(rename_all = "kebab-case")] pub enum DateVariable { /// Date the item has been accessed. Accessed, /// Date the item was initially available (e.g. the online publication date /// of a journal article before its formal publication date; the date a /// treaty was made available for signing). AvailableDate, /// Date the event related to an item took place. EventDate, /// Date the item was issued/published. Issued, /// Issue date of the original version. OriginalDate, /// Date the item (e.g. a manuscript) was submitted for publication. Submitted, } impl From for Variable { fn from(value: DateVariable) -> Self { Self::Date(value) } } impl fmt::Display for DateVariable { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Accessed => write!(f, "accessed"), Self::AvailableDate => write!(f, "available-date"), Self::EventDate => write!(f, "event-date"), Self::Issued => write!(f, "issued"), Self::OriginalDate => write!(f, "original-date"), Self::Submitted => write!(f, "submitted"), } } } /// Variables that can be formatted as names. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)] #[serde(rename_all = "kebab-case")] pub enum NameVariable { /// Author. Author, /// The person leading the session containing a presentation (e.g. the /// organizer of the container-title of a speech). Chair, /// Editor of the collection holding the item (e.g. the series editor for a /// book). CollectionEditor, /// Person compiling or selecting material for an item from the works of /// various persons or bodies (e.g. for an anthology). Compiler, /// Composer (e.g. of a musical score). Composer, /// Author of the container holding the item (e.g. the book author for a /// book chapter). ContainerAuthor, /// A minor contributor to the item; typically cited using “with” before the /// name when listed in a bibliography. Contributor, /// Curator of an exhibit or collection (e.g. in a museum). Curator, /// Director (e.g. of a film). Director, /// Editor. Editor, /// Managing editor (“Directeur de la Publication” in French). EditorialDirector, /// Combined editor and translator of a work; The citation processory must /// be automatically generate if editor and translator variables are /// identical; May also be provided directly in item data. #[serde(alias = "editortranslator")] EditorTranslator, /// Executive producer (e.g. of a television series). ExecutiveProducer, /// Guest (e.g. on a TV show or podcast). Guest, /// Host (e.g. of a TV show or podcast). Host, /// Illustrator (e.g. of a children’s book or graphic novel). Illustrator, /// Interviewer (e.g. of an interview). Interviewer, /// Narrator (e.g. of an audio book). Narrator, /// Organizer of an event (e.g. organizer of a workshop or conference). Organizer, /// The original creator of a work (e.g. the form of the author name listed /// on the original version of a book; the historical author of a work; the /// original songwriter or performer for a musical piece; the original /// developer or programmer for a piece of software; the original author of /// an adapted work such as a book adapted into a screenplay). OriginalAuthor, /// Performer of an item (e.g. an actor appearing in a film; a muscian /// performing a piece of music). Performer, /// Producer (e.g. of a television or radio broadcast). Producer, /// Recipient (e.g. of a letter). Recipient, /// Author of the item reviewed by the current item. ReviewedAuthor, /// Writer of a script or screenplay (e.g. of a film). ScriptWriter, /// Creator of a series (e.g. of a television series). SeriesCreator, /// Translator. Translator, } impl From for Variable { fn from(value: NameVariable) -> Self { Self::Name(value) } } impl From for Term { fn from(value: NameVariable) -> Self { Self::NameVariable(value) } } impl fmt::Display for NameVariable { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Author => write!(f, "author"), Self::Chair => write!(f, "chair"), Self::CollectionEditor => write!(f, "collection-editor"), Self::Compiler => write!(f, "compiler"), Self::Composer => write!(f, "composer"), Self::ContainerAuthor => write!(f, "container-author"), Self::Contributor => write!(f, "contributor"), Self::Curator => write!(f, "curator"), Self::Director => write!(f, "director"), Self::Editor => write!(f, "editor"), Self::EditorialDirector => write!(f, "editorial-director"), Self::EditorTranslator => write!(f, "editor-translator"), Self::ExecutiveProducer => write!(f, "executive-producer"), Self::Guest => write!(f, "guest"), Self::Host => write!(f, "host"), Self::Illustrator => write!(f, "illustrator"), Self::Interviewer => write!(f, "interviewer"), Self::Narrator => write!(f, "narrator"), Self::Organizer => write!(f, "organizer"), Self::OriginalAuthor => write!(f, "original-author"), Self::Performer => write!(f, "performer"), Self::Producer => write!(f, "producer"), Self::Recipient => write!(f, "recipient"), Self::ReviewedAuthor => write!(f, "reviewed-author"), Self::ScriptWriter => write!(f, "script-writer"), Self::SeriesCreator => write!(f, "series-creator"), Self::Translator => write!(f, "translator"), } } } /// Localizable terms. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)] #[serde(untagged)] pub enum Term { /// Kind of the cited item. Kind(Kind), /// Variables that can be formatted as names. NameVariable(NameVariable), /// Variables that can be formatted as numbers. NumberVariable(NumberVariable), /// The Page variable. PageVariable, /// A locator. Locator(Locator), /// Terms that are not defined via types, name or number variables. Other(OtherTerm), } impl Term { /// On which term this falls back if the given term is not available. pub const fn term_fallback(self) -> Self { match self { Self::Other(OtherTerm::LongOrdinal(i)) => Self::Other(OtherTerm::OrdinalN(i)), _ => self, } } /// Whether this is an ordinal term. pub const fn is_ordinal(self) -> bool { match self { Self::Other(other) => other.is_ordinal(), _ => false, } } /// Whether this is a numbered ordinal term. pub const fn is_n_ordinal(self) -> bool { match self { Self::Other(other) => other.is_n_ordinal(), _ => false, } } /// Whether this is a gendered term. pub const fn is_gendered(self) -> bool { if self.is_ordinal() { return true; }; matches!( self, Self::Other( OtherTerm::Month01 | OtherTerm::Month02 | OtherTerm::Month03 | OtherTerm::Month04 | OtherTerm::Month05 | OtherTerm::Month06 | OtherTerm::Month07 | OtherTerm::Month08 | OtherTerm::Month09 | OtherTerm::Month10 | OtherTerm::Month11 | OtherTerm::Month12 ) ) } /// Compare a term to another one. Return `true` if they are serialized the /// same. pub fn is_lexically_same(self, other: Self) -> bool { if self == other { return true; } let cmp = |a: Self, b: Self| { matches!( (a, b), ( Self::Locator(Locator::Issue), Self::NumberVariable(NumberVariable::Issue), ) | (Self::Locator(Locator::Page), Self::PageVariable,) | ( Self::Locator(Locator::Section), Self::NumberVariable(NumberVariable::Section), ) | ( Self::Locator(Locator::Volume), Self::NumberVariable(NumberVariable::Volume), ) | (Self::Locator(Locator::Book), Self::Kind(Kind::Book)) | (Self::Locator(Locator::Chapter), Self::Kind(Kind::Chapter)) | (Self::Locator(Locator::Figure), Self::Kind(Kind::Figure)) ) }; cmp(self, other) || cmp(other, self) } } /// Kind of the cited item. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)] #[serde(rename_all = "kebab-case")] pub enum Kind { /// A self-contained work made widely available but not published in a /// journal or other publication; Use for preprints, working papers, and /// similar works posted on a platform where some level of persistence or /// stewardship is expected (e.g. arXiv or other preprint repositories, /// working paper series); For unpublished works not made widely available /// or only hosted on personal websites, use manuscript. Article, /// An article published in an academic journal. ArticleJournal, /// An article published in a non-academic magazine. ArticleMagazine, /// An article published in a newspaper. ArticleNewspaper, /// A proposed piece of legislation. Bill, /// A book or similar work; Can be an authored book or an edited collection /// of self-contained chapters; Can be a physical book or an ebook; The /// format for an ebook may be specified using medium; Can be a /// single-volume work, a multivolume work, or one volume of a multivolume /// work; If a container-title is present, the item is interpreted as a book /// republished in a collection or anthology; Also used for whole conference /// proceedings volumes or exhibition catalogs by specifying event and /// related variables. Book, /// A recorded work broadcast over an electronic medium (e.g. a radio /// broadcast, a television show, a podcast); The type of broadcast may be /// specified using genre; If container-title is present, the item is /// interpreted as an episode contained within a larger broadcast series /// (e.g. an episode in a television show or an episode of a podcast). Broadcast, /// A part of a book cited separately from the book as a whole (e.g. a /// chapter in an edited book); Also used for introductions, forewords, and /// similar supplemental components of a book. Chapter, /// A classical or ancient work, sometimes cited using a common /// abbreviation. Classic, /// An archival collection in a museum or other institution. Collection, /// A data set or a similar collection of (mostly) raw data. Dataset, /// A catch-all category for items not belonging to other types; Use a more /// specific type when appropriate. Document, /// An entry in a database, directory, or catalog; For entries in a /// dictionary, use entry-dictionary; For entries in an encyclopedia, use /// entry-encyclopedia. Entry, /// An entry in a dictionary. EntryDictionary, /// An entry in an encyclopedia or similar reference work. EntryEncyclopedia, /// An organized event (e.g., an exhibition or conference); Use for direct /// citations to the event, rather than to works contained within an event /// (e.g. a presentation in a conference, a graphic in an exhibition) or /// based on an event (e.g. a paper-conference published in a proceedings, /// an exhibition catalog). Event, /// A illustration or representation of data, typically as part of a journal /// article or other larger work; May be in any format (e.g. image, video, /// audio recording, 3D model); The format of the item can be specified /// using medium. Figure, /// A still visual work; Can be used for artwork or other works (e.g. /// journalistic or historical photographs); Can be used for any still /// visual work (e.g. photographs, drawings, paintings, sculptures, /// clothing); The format of the item can be specified using medium. Graphic, /// A hearing by a government committee or transcript thereof. Hearing, /// An interview of a person; Also used for a recording or transcript of an /// interview; author is interpreted as the interviewee. Interview, /// A legal case. #[serde(rename = "legal_case")] LegalCase, /// A law or resolution enacted by a governing body. Legislation, /// An unpublished manuscript; Use for both modern unpublished works and /// classical manuscripts; For working papers, preprints, and similar works /// posted to a repository, use article. Manuscript, /// A geographic map. Map, /// A video or visual recording; If a container-title is present, the item /// is interpreted as a part contained within a larger compilation of /// recordings (e.g. a part of a multipart documentary)). #[serde(rename = "motion_picture")] MotionPicture, /// The printed score for a piece of music; For a live performance of the /// music, use performance; For recordings of the music, use song (for audio /// recordings) or motionPicture (for video recordings). #[serde(rename = "musical_score")] MusicalScore, /// A fragment, historical document, or other unusually-published or /// ephemeral work (e.g. a sales brochure). Pamphlet, /// A paper formally published in conference proceedings; For papers /// presented at a conference, but not published in a proceedings, use /// speech. PaperConference, /// A patent for an invention. Patent, /// A live performance of an artistic work; For non-artistic presentations, /// use speech; For recordings of a performance, use song or motionPicture. Performance, /// A full issue or run of issues in a periodical publication (e.g. a /// special issue of a journal). Periodical, /// Personal communications between multiple parties; May be unpublished /// (e.g. private correspondence between two researchers) or /// collected/published (e.g. a letter published in a collection). #[serde(rename = "personal_communication")] PersonalCommunication, /// A post on a online forum, social media platform, or similar platform; /// Also used for comments posted to online items. Post, /// A blog post. PostWeblog, /// An administrative order from any level of government. Regulation, /// A technical report, government report, white paper, brief, or similar /// work distributed by an institution; Also used for manuals and similar /// technical documentation (e.g. a software, instrument, or test manual); /// If a container-title is present, the item is interpreted as a chapter /// contained within a larger report. Report, /// A review of an item other than a book (e.g. a film review, posted peer /// review of an article); If reviewed-title is absent, title is taken to be /// the title of the reviewed item. Review, /// A review of a book; If reviewed-title is absent, title is taken to be /// the title of the reviewed book. ReviewBook, /// A computer program, app, or other piece of software. Software, /// An audio recording; Can be used for any audio recording (not only /// music); If a container-title is present, the item is interpreted as a /// track contained within a larger album or compilation of recordings. Song, /// A speech or other presentation (e.g. a paper, talk, poster, or symposium /// at a conference); Use genre to specify the type of presentation; Use /// event to indicate the event where the presentation was made (e.g. the /// conference name); Use container-title if the presentation is part of a /// larger session (e.g. a paper in a symposium); For papers published in /// conference proceedings, use paper-conference; For artistic performances, /// use performance. Speech, /// A technical standard or similar set of rules or norms. Standard, /// A thesis written to satisfy requirements for a degree; Use genre to /// specify the type of thesis. Thesis, /// A treaty agreement among political authorities. Treaty, /// A website or page on a website; Intended for sources which are /// intrinsically online; use a more specific type when appropriate (e.g. /// article-journal, post-weblog, report, entry); If a container-title is /// present, the item is interpreted as a page contained within a larger /// website. Webpage, } impl From for Term { fn from(value: Kind) -> Self { Self::Kind(value) } } impl FromStr for Kind { type Err = (); fn from_str(s: &str) -> Result { match s { "article" => Ok(Self::Article), "article-journal" => Ok(Self::ArticleJournal), "article-magazine" => Ok(Self::ArticleMagazine), "article-newspaper" => Ok(Self::ArticleNewspaper), "bill" => Ok(Self::Bill), "book" => Ok(Self::Book), "broadcast" => Ok(Self::Broadcast), "chapter" => Ok(Self::Chapter), "classic" => Ok(Self::Classic), "collection" => Ok(Self::Collection), "dataset" => Ok(Self::Dataset), "document" => Ok(Self::Document), "entry" => Ok(Self::Entry), "entry-dictionary" => Ok(Self::EntryDictionary), "entry-encyclopedia" => Ok(Self::EntryEncyclopedia), "event" => Ok(Self::Event), "figure" => Ok(Self::Figure), "graphic" => Ok(Self::Graphic), "hearing" => Ok(Self::Hearing), "interview" => Ok(Self::Interview), "legal_case" => Ok(Self::LegalCase), "legislation" => Ok(Self::Legislation), "manuscript" => Ok(Self::Manuscript), "map" => Ok(Self::Map), "motion_picture" => Ok(Self::MotionPicture), "musical_score" => Ok(Self::MusicalScore), "pamphlet" => Ok(Self::Pamphlet), "paper-conference" => Ok(Self::PaperConference), "patent" => Ok(Self::Patent), "performance" => Ok(Self::Performance), "periodical" => Ok(Self::Periodical), "personal_communication" => Ok(Self::PersonalCommunication), "post" => Ok(Self::Post), "post-weblog" => Ok(Self::PostWeblog), "regulation" => Ok(Self::Regulation), "report" => Ok(Self::Report), "review" => Ok(Self::Review), "review-book" => Ok(Self::ReviewBook), "software" => Ok(Self::Software), "song" => Ok(Self::Song), "speech" => Ok(Self::Speech), "standard" => Ok(Self::Standard), "thesis" => Ok(Self::Thesis), "treaty" => Ok(Self::Treaty), "webpage" => Ok(Self::Webpage), _ => Err(()), } } } /// A locator. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Serialize)] #[serde(rename_all = "kebab-case")] #[allow(missing_docs)] pub enum Locator { Act, Appendix, ArticleLocator, Book, Canon, Chapter, Column, Elocation, Equation, Figure, Folio, Issue, Line, Note, Opus, Page, Paragraph, Part, Rule, Scene, Section, #[serde(rename = "sub verbo", alias = "sub-verbo")] SubVerbo, Supplement, Table, Timestamp, Title, TitleLocator, Verse, Volume, /// The custom type is a `citationberg` addition. It will render nothing in /// the locator's `cs:label` element. #[serde(skip)] Custom, } impl From for Term { fn from(value: Locator) -> Self { Self::Locator(value) } } impl FromStr for Locator { type Err = (); fn from_str(s: &str) -> Result { match s { "act" => Ok(Self::Act), "appendix" => Ok(Self::Appendix), "article-locator" => Ok(Self::ArticleLocator), "book" => Ok(Self::Book), "canon" => Ok(Self::Canon), "chapter" => Ok(Self::Chapter), "column" => Ok(Self::Column), "elocation" => Ok(Self::Elocation), "equation" => Ok(Self::Equation), "figure" => Ok(Self::Figure), "folio" => Ok(Self::Folio), "issue" => Ok(Self::Issue), "line" => Ok(Self::Line), "note" => Ok(Self::Note), "opus" => Ok(Self::Opus), "page" => Ok(Self::Page), "paragraph" => Ok(Self::Paragraph), "part" => Ok(Self::Part), "rule" => Ok(Self::Rule), "scene" => Ok(Self::Scene), "section" => Ok(Self::Section), "sub verbo" | "sub-verbo" => Ok(Self::SubVerbo), "supplement" => Ok(Self::Supplement), "table" => Ok(Self::Table), "timestamp" => Ok(Self::Timestamp), "title" => Ok(Self::Title), "title-locator" => Ok(Self::TitleLocator), "verse" => Ok(Self::Verse), "volume" => Ok(Self::Volume), _ => Err(()), } } } impl<'de> Deserialize<'de> for Locator { fn deserialize>(deserializer: D) -> Result { let s = String::deserialize(deserializer)?; Self::from_str(&s).map_err(|_| de::Error::custom("invalid locator")) } } /// Terms that are not defined via types, name or number variables. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] #[allow(missing_docs)] pub enum OtherTerm { // Months Month01, Month02, Month03, Month04, Month05, Month06, Month07, Month08, Month09, Month10, Month11, Month12, // Ordinals Ordinal, /// Between 0 and 99. OrdinalN(u8), /// Between 0 and 10. LongOrdinal(u8), // Punctuation OpenQuote, CloseQuote, OpenInnerQuote, CloseInnerQuote, PageRangeDelimiter, Colon, Comma, Semicolon, // Seasons Season01, Season02, Season03, Season04, // Disciplines Anthropology, Astronomy, Biology, Botany, Chemistry, Engineering, GenericBase, Geography, Geology, History, Humanities, Literature, Math, Medicine, Philosophy, Physics, Psychology, Sociology, Science, PoliticalScience, SocialScience, Theology, Zoology, // Miscellaneous Accessed, Ad, AdvanceOnlinePublication, Album, And, AndOthers, Anonymous, At, AudioRecording, AvailableAt, Bc, Bce, By, Ce, Circa, Cited, EtAl, Film, Forthcoming, From, Henceforth, Ibid, In, InPress, Internet, Interview, Letter, LocCit, NoDate, NoPlace, NoPublisher, On, Online, OpCit, OriginalWorkPublished, PersonalCommunication, Podcast, PodcastEpisode, Preprint, PresentedAt, RadioBroadcast, RadioSeries, RadioSeriesEpisode, Reference, Retrieved, ReviewOf, Scale, SpecialIssue, SpecialSection, TelevisionBroadcast, TelevisionSeries, TelevisionSeriesEpisode, Video, WorkingPaper, } impl OtherTerm { /// Whether this is a numbered ordinal term. pub const fn is_n_ordinal(self) -> bool { matches!(self, Self::OrdinalN(_) | Self::LongOrdinal(_)) } /// Whether this is an ordinal term. pub const fn is_ordinal(self) -> bool { matches!(self, Self::Ordinal | Self::OrdinalN(_) | Self::LongOrdinal(_)) } /// Get the month for a number between 0 and 11. pub const fn month(i: u8) -> Option { match i { 0 => Some(Self::Month01), 1 => Some(Self::Month02), 2 => Some(Self::Month03), 3 => Some(Self::Month04), 4 => Some(Self::Month05), 5 => Some(Self::Month06), 6 => Some(Self::Month07), 7 => Some(Self::Month08), 8 => Some(Self::Month09), 9 => Some(Self::Month10), 10 => Some(Self::Month11), 11 => Some(Self::Month12), _ => None, } } /// Get the term to describe a particular season. pub const fn season(value: Season) -> Self { match value { Season::Spring => OtherTerm::Season01, Season::Summer => OtherTerm::Season02, Season::Autumn => OtherTerm::Season03, Season::Winter => OtherTerm::Season04, } } } impl FromStr for OtherTerm { type Err = TermConversionError; fn from_str(value: &str) -> Result { let parse_int = |s: &str| { s.parse::().map_err(|e| { if matches!( e.kind(), IntErrorKind::NegOverflow | IntErrorKind::PosOverflow ) { TermConversionError::OutOfRange } else { TermConversionError::Unknown } }) }; let month_prefix = "month-"; let season_prefix = "season-"; let ordinal_prefix = "ordinal-"; let long_ordinal_prefix = "long-ordinal-"; if let Some(month) = value.strip_prefix(month_prefix) { return match parse_int(month)? { 1 => Ok(Self::Month01), 2 => Ok(Self::Month02), 3 => Ok(Self::Month03), 4 => Ok(Self::Month04), 5 => Ok(Self::Month05), 6 => Ok(Self::Month06), 7 => Ok(Self::Month07), 8 => Ok(Self::Month08), 9 => Ok(Self::Month09), 10 => Ok(Self::Month10), 11 => Ok(Self::Month11), 12 => Ok(Self::Month12), _ => Err(TermConversionError::OutOfRange), }; } if let Some(season) = value.strip_prefix(season_prefix) { return match Season::try_from_csl_number(parse_int(season)?) { Ok(season) => Ok(Self::season(season)), Err(_) => Err(TermConversionError::OutOfRange), }; } if let Some(ordinal) = value.strip_prefix(ordinal_prefix) { let ordinal = parse_int(ordinal)?; if ordinal > 99 { return Err(TermConversionError::OutOfRange); } return Ok(Self::OrdinalN(ordinal)); } if let Some(long_ordinal) = value.strip_prefix(long_ordinal_prefix) { let ordinal = parse_int(long_ordinal)?; if ordinal > 10 { return Err(TermConversionError::OutOfRange); } return Ok(Self::LongOrdinal(ordinal)); } match value { "ordinal" => Ok(Self::Ordinal), "open-quote" => Ok(Self::OpenQuote), "close-quote" => Ok(Self::CloseQuote), "open-inner-quote" => Ok(Self::OpenInnerQuote), "close-inner-quote" => Ok(Self::CloseInnerQuote), "page-range-delimiter" => Ok(Self::PageRangeDelimiter), "colon" => Ok(Self::Colon), "comma" => Ok(Self::Comma), "semicolon" => Ok(Self::Semicolon), "anthropology" => Ok(Self::Anthropology), "astronomy" => Ok(Self::Astronomy), "biology" => Ok(Self::Biology), "botany" => Ok(Self::Botany), "chemistry" => Ok(Self::Chemistry), "engineering" => Ok(Self::Engineering), "generic-base" => Ok(Self::GenericBase), "geography" => Ok(Self::Geography), "geology" => Ok(Self::Geology), "history" => Ok(Self::History), "humanities" => Ok(Self::Humanities), "literature" => Ok(Self::Literature), "math" => Ok(Self::Math), "medicine" => Ok(Self::Medicine), "philosophy" => Ok(Self::Philosophy), "physics" => Ok(Self::Physics), "psychology" => Ok(Self::Psychology), "sociology" => Ok(Self::Sociology), "science" => Ok(Self::Science), "political-science" | "political_science" => Ok(Self::PoliticalScience), "social-science" | "social_science" => Ok(Self::SocialScience), "theology" => Ok(Self::Theology), "zoology" => Ok(Self::Zoology), "accessed" => Ok(Self::Accessed), "ad" => Ok(Self::Ad), "advance-online-publication" => Ok(Self::AdvanceOnlinePublication), "album" => Ok(Self::Album), "and" => Ok(Self::And), "and-others" | "and others" => Ok(Self::AndOthers), "anonymous" => Ok(Self::Anonymous), "at" => Ok(Self::At), "audio-recording" => Ok(Self::AudioRecording), "available at" | "available-at" => Ok(Self::AvailableAt), "bc" => Ok(Self::Bc), "bce" => Ok(Self::Bce), "by" => Ok(Self::By), "ce" => Ok(Self::Ce), "circa" => Ok(Self::Circa), "cited" => Ok(Self::Cited), "et-al" => Ok(Self::EtAl), "film" => Ok(Self::Film), "forthcoming" => Ok(Self::Forthcoming), "from" => Ok(Self::From), "henceforth" => Ok(Self::Henceforth), "ibid" => Ok(Self::Ibid), "in" => Ok(Self::In), "in press" | "in-press" => Ok(Self::InPress), "internet" => Ok(Self::Internet), "interview" => Ok(Self::Interview), "letter" => Ok(Self::Letter), "loc-cit" => Ok(Self::LocCit), "no date" | "no-date" => Ok(Self::NoDate), "no-place" => Ok(Self::NoPlace), "no-publisher" => Ok(Self::NoPublisher), "on" => Ok(Self::On), "online" => Ok(Self::Online), "op-cit" => Ok(Self::OpCit), "original-work-published" => Ok(Self::OriginalWorkPublished), "personal-communication" => Ok(Self::PersonalCommunication), "podcast" => Ok(Self::Podcast), "podcast-episode" => Ok(Self::PodcastEpisode), "preprint" => Ok(Self::Preprint), "presented at" | "presented-at" => Ok(Self::PresentedAt), "radio-broadcast" => Ok(Self::RadioBroadcast), "radio-series" => Ok(Self::RadioSeries), "radio-series-episode" => Ok(Self::RadioSeriesEpisode), "reference" => Ok(Self::Reference), "retrieved" => Ok(Self::Retrieved), "review-of" => Ok(Self::ReviewOf), "scale" => Ok(Self::Scale), "special-issue" => Ok(Self::SpecialIssue), "special-section" => Ok(Self::SpecialSection), "television-broadcast" => Ok(Self::TelevisionBroadcast), "television-series" => Ok(Self::TelevisionSeries), "television-series-episode" => Ok(Self::TelevisionSeriesEpisode), "video" => Ok(Self::Video), "working-paper" => Ok(Self::WorkingPaper), _ => Err(TermConversionError::Unknown), } } } impl fmt::Display for OtherTerm { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Ordinal => write!(f, "ordinal"), Self::OpenQuote => write!(f, "open-quote"), Self::CloseQuote => write!(f, "close-quote"), Self::OpenInnerQuote => write!(f, "open-inner-quote"), Self::CloseInnerQuote => write!(f, "close-inner-quote"), Self::PageRangeDelimiter => write!(f, "page-range-delimiter"), Self::Colon => write!(f, "colon"), Self::Comma => write!(f, "comma"), Self::Semicolon => write!(f, "semicolon"), Self::Anthropology => write!(f, "anthropology"), Self::Astronomy => write!(f, "astronomy"), Self::Biology => write!(f, "biology"), Self::Botany => write!(f, "botany"), Self::Chemistry => write!(f, "chemistry"), Self::Engineering => write!(f, "engineering"), Self::GenericBase => write!(f, "generic-base"), Self::Geography => write!(f, "geography"), Self::Geology => write!(f, "geology"), Self::History => write!(f, "history"), Self::Humanities => write!(f, "humanities"), Self::Literature => write!(f, "literature"), Self::Math => write!(f, "math"), Self::Medicine => write!(f, "medicine"), Self::Philosophy => write!(f, "philosophy"), Self::Physics => write!(f, "physics"), Self::Psychology => write!(f, "psychology"), Self::Sociology => write!(f, "sociology"), Self::Science => write!(f, "science"), Self::PoliticalScience => write!(f, "political_science"), Self::SocialScience => write!(f, "social_science"), Self::Theology => write!(f, "theology"), Self::Zoology => write!(f, "zoology"), Self::Accessed => write!(f, "accessed"), Self::Ad => write!(f, "ad"), Self::AdvanceOnlinePublication => write!(f, "advance-online-publication"), Self::Album => write!(f, "album"), Self::And => write!(f, "and"), Self::AndOthers => write!(f, "and-others"), Self::Anonymous => write!(f, "anonymous"), Self::At => write!(f, "at"), Self::AudioRecording => write!(f, "audio-recording"), Self::AvailableAt => write!(f, "available at"), Self::Bc => write!(f, "bc"), Self::Bce => write!(f, "bce"), Self::By => write!(f, "by"), Self::Ce => write!(f, "ce"), Self::Circa => write!(f, "circa"), Self::Cited => write!(f, "cited"), Self::EtAl => write!(f, "et-al"), Self::Film => write!(f, "film"), Self::Forthcoming => write!(f, "forthcoming"), Self::From => write!(f, "from"), Self::Henceforth => write!(f, "henceforth"), Self::Ibid => write!(f, "ibid"), Self::In => write!(f, "in"), Self::InPress => write!(f, "in press"), Self::Internet => write!(f, "internet"), Self::Interview => write!(f, "interview"), Self::Letter => write!(f, "letter"), Self::LocCit => write!(f, "loc-cit"), Self::NoDate => write!(f, "no date"), Self::NoPlace => write!(f, "no-place"), Self::NoPublisher => write!(f, "no-publisher"), Self::On => write!(f, "on"), Self::Online => write!(f, "online"), Self::OpCit => write!(f, "op-cit"), Self::OriginalWorkPublished => write!(f, "original-work-published"), Self::PersonalCommunication => write!(f, "personal-communication"), Self::Podcast => write!(f, "podcast"), Self::PodcastEpisode => write!(f, "podcast-episode"), Self::Preprint => write!(f, "preprint"), Self::PresentedAt => write!(f, "presented at"), Self::RadioBroadcast => write!(f, "radio-broadcast"), Self::RadioSeries => write!(f, "radio-series"), Self::RadioSeriesEpisode => write!(f, "radio-series-episode"), Self::Reference => write!(f, "reference"), Self::Retrieved => write!(f, "retrieved"), Self::ReviewOf => write!(f, "review-of"), Self::Scale => write!(f, "scale"), Self::SpecialIssue => write!(f, "special-issue"), Self::SpecialSection => write!(f, "special-section"), Self::TelevisionBroadcast => write!(f, "television-broadcast"), Self::TelevisionSeries => write!(f, "television-series"), Self::TelevisionSeriesEpisode => write!(f, "television-series-episode"), Self::Video => write!(f, "video"), Self::WorkingPaper => write!(f, "working-paper"), Self::OrdinalN(i) => write!(f, "ordinal-{i:02}"), Self::LongOrdinal(i) => write!(f, "long-ordinal-{i:02}"), Self::Season01 => write!(f, "season-01"), Self::Season02 => write!(f, "season-02"), Self::Season03 => write!(f, "season-03"), Self::Season04 => write!(f, "season-04"), Self::Month01 => write!(f, "month-01"), Self::Month02 => write!(f, "month-02"), Self::Month03 => write!(f, "month-03"), Self::Month04 => write!(f, "month-04"), Self::Month05 => write!(f, "month-05"), Self::Month06 => write!(f, "month-06"), Self::Month07 => write!(f, "month-07"), Self::Month08 => write!(f, "month-08"), Self::Month09 => write!(f, "month-09"), Self::Month10 => write!(f, "month-10"), Self::Month11 => write!(f, "month-11"), Self::Month12 => write!(f, "month-12"), } } } impl<'de> Deserialize<'de> for OtherTerm { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { let s = String::deserialize(deserializer)?; FromStr::from_str(&s).map_err(de::Error::custom) } } impl Serialize for OtherTerm { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { serializer.collect_str(self) } } /// An error that can occur when converting a string to a term. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub enum TermConversionError { /// The month, season, ordinal, or long ordinal is out of range. OutOfRange, /// The term is unknown. Unknown, } impl fmt::Display for TermConversionError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::OutOfRange => write!(f, "value out of range"), Self::Unknown => write!(f, "unknown term"), } } } impl From for Term { fn from(value: OtherTerm) -> Self { Self::Other(value) } } /// Seasons of the year. #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] pub enum Season { /// Spring (CSL season 01). Spring, /// Summer (CSL season 02). Summer, /// Autumn (CSL season 03). Autumn, /// Winter (CSL season 04). Winter, } impl Season { /// Converts a season number in the range 1-4 (as defined by the CSL spec) /// to a Season instance. /// /// Fails if the number is out of range. pub const fn try_from_csl_number(i: u8) -> Result { match i { 1 => Ok(Season::Spring), 2 => Ok(Season::Summer), 3 => Ok(Season::Autumn), 4 => Ok(Season::Winter), i => Err(SeasonConversionError(i)), } } /// Converts a season to its number as defined by the CSL spec. pub const fn to_csl_number(self) -> u8 { match self { Season::Spring => 1, Season::Summer => 2, Season::Autumn => 3, Season::Winter => 4, } } } impl From for OtherTerm { fn from(value: Season) -> Self { OtherTerm::season(value) } } /// Error from converting a [u8] to a [Season]. #[derive(Debug, Clone, Copy)] pub struct SeasonConversionError(u8); impl fmt::Display for SeasonConversionError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "Cannot convert {} to a season", self.0) } } citationberg-0.6.1/src/util.rs000064400000000000000000000035731046102023000144240ustar 00000000000000use serde::Deserialize; pub fn deserialize_bool<'de, D: serde::Deserializer<'de>>( deserializer: D, ) -> Result { #[derive(Deserialize)] #[serde(untagged)] enum StringOrBool { Bool(bool), String(String), } let deser = StringOrBool::deserialize(deserializer)?; Ok(match deser { StringOrBool::Bool(b) => b, StringOrBool::String(s) => s.eq_ignore_ascii_case("true"), }) } pub fn deserialize_bool_option<'de, D: serde::Deserializer<'de>>( deserializer: D, ) -> Result, D::Error> { #[derive(Deserialize)] #[serde(untagged)] enum StringOrBool { Bool(bool), String(String), } let res = Option::::deserialize(deserializer)?; Ok(res.map(|s| match s { StringOrBool::Bool(b) => b, StringOrBool::String(s) => s.eq_ignore_ascii_case("true"), })) } pub fn deserialize_u32<'de, D: serde::Deserializer<'de>>( deserializer: D, ) -> Result { #[derive(Deserialize)] #[serde(untagged)] enum StringOrUnsigned { Unsigned(u32), String(String), } let res = StringOrUnsigned::deserialize(deserializer)?; Ok(match res { StringOrUnsigned::Unsigned(u) => u, StringOrUnsigned::String(s) => { s.trim().parse().map_err(serde::de::Error::custom)? } }) } pub fn deserialize_u32_option<'de, D: serde::Deserializer<'de>>( deserializer: D, ) -> Result, D::Error> { #[derive(Deserialize)] #[serde(untagged)] enum StringOrUnsigned { Unsigned(u32), String(String), } let res = Option::::deserialize(deserializer)?; res.map(|s| match s { StringOrUnsigned::Unsigned(u) => Ok(u), StringOrUnsigned::String(s) => s.trim().parse().map_err(serde::de::Error::custom), }) .transpose() } citationberg-0.6.1/tests/dependent/academic-questions.csl000064400000000000000000000022751046102023000217010ustar 00000000000000 citationberg-0.6.1/tests/dependent/accounts-of-chemical-research.csl000064400000000000000000000021001046102023000236640ustar 00000000000000 citationberg-0.6.1/tests/independent/art-history.csl000064400000000000000000000073221046102023000207350ustar 00000000000000 citationberg-0.6.1/tests/independent/chicago-author-date.csl000064400000000000000000000547131046102023000222660ustar 00000000000000 citationberg-0.6.1/tests/independent/chicago-fullnote-bibliography.csl000064400000000000000000001375121046102023000243510ustar 00000000000000 citationberg-0.6.1/tests/independent/ieee.csl000064400000000000000000000366371046102023000173720ustar 00000000000000 citationberg-0.6.1/tests/locales/locales-en-US.xml000064400000000000000000000772211046102023000201700ustar 00000000000000 Andrew Dunning https://orcid.org/0000-0003-0464-5036 Sebastian Karcher https://orcid.org/0000-0001-8249-7388 Rintze M. Zelle https://orcid.org/0000-0003-1779-8883 Denis Meier Brenton M. Wiernik https://orcid.org/0000-0001-9560-6336 This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License 2025-06-24T00:00:00+00:00 accessed advance online publication album and and others anonymous at audio recording available at by circa cited et al. film forthcoming from henceforth ibid. in in press internet letter loc. cit. no date no place no publisher on online op. cit. original work published personal communication podcast podcast episode preprint presented at the radio broadcast radio series radio series episode reference references retrieved review of scale special issue special section television broadcast television series television series episode video working paper adv. online pub. anon. au. rec. avail. at c. cit. et al. flm. fr. let. n.d. n.p. n.p. orig. pub. pers. comm. podcast ep. radio bdcst. radio ser. radio ser. ep. ref. refs. rtvd. rev. of sc. spec. iss. spec. sec. TV bdcst. TV ser. TV ser. ep. vid. wkg. paper & @ preprint journal article magazine article newspaper article bill broadcast classical work archival collection dataset document entry dictionary entry encyclopedia entry event graphic hearing interview legal case legislation manuscript map video recording musical score pamphlet conference paper patent performance periodical personal communication post blog post regulation report review book review software audio recording presentation standard thesis treaty webpage jour. art. mag. art. newspaper art. bdcst. class. wk. arch. coll. doc. dict. entry ency. entry gr. int. leg. case legis. MS MSS vid. rec. mus. score pam. conf. paper pat. prfm. pers. comm. reg. rep. rev. bk. rev. sftw. au. rec. std. thes. webpg. testimony of review of review of the book test. of rev. of rev. of the bk. AD BC BCE CE : , ; th st nd rd th th th first second third fourth fifth sixth seventh eighth ninth tenth act acts appendix appendices article articles book books canon canons chapter chapters column columns location locations equation equations figure figures folio folios issue issues line lines note notes opus opera page pages paragraph paragraphs part parts rule rules scene scenes section sections sub verbo sub verbis supplement supplements table tables title titles verse verses volume volumes app. apps. art. arts. bk. bks. can. cann. chap. chaps. col. cols. loc. locs. eq. eqq. fig. figs. fol. fols. no. nos. l. ll. n. nn. op. opp. p. pp. para. paras. pt. pts. r. rr. sc. scs. sec. secs. s.v. s.vv. supp. supps. tbl. tbls. tit. titt. v. vv. vol. vols. c. cc. ¶¶ § §§ chapter chapters citation citations number numbers edition editions reference references number numbers page pages volume volumes page pages printing printings version versions chap. chaps. cit. cits. no. nos. ed. eds. ref. refs. no. nos. p. pp. vol. vols. p. pp. ptg. ptgs. v. chair chairs editor editors compiler compilers contributor contributors curator curators director directors editor editors editor & translator editors & translators editor & translator editors & translators editor editors executive producer executive producers guest guests host hosts illustrator illustrators narrator narrators organizer organizers performer performers producer producers writer writers series creator series creators translator translators ed. eds. comp. comps. contrib. contribs. cur. curs. dir. dirs. ed. eds. ed. & trans. eds. & trans. ed. & trans. eds. & trans. ed. eds. exec. prod. exec. prods. ill. ills. narr. narrs. org. orgs. perf. perfs. prod. prods. wrtr. wrtrs. ser. creator ser. creators trans. chaired by edited by compiled by composed by by with curated by directed by edited by edited & translated by edited & translated by edited by executive produced by with guest hosted by illustrated by interview by narrated by organized by by performed by produced by to by written by created by translated by ed. by comp. by comp. by cur. by dir. by ed. by ed. & trans. by ed. & trans. by ed. by exec. prod. by ill. by narr. by org. by perf. by prod. by writ. by trans. by January February March April May June July August September October November December Jan. Feb. Mar. Apr. May June July Aug. Sept. Oct. Nov. Dec. Spring Summer Autumn Winter citationberg-0.6.1/tests/locales/locales-zh-CN.xml000064400000000000000000000453311046102023000201550ustar 00000000000000 rongls sati-bodhi Heromyth This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License 2014-05-15T23:31:02+00:00 advance online publication album audio recording film henceforth loc. cit. no place n.p. no publisher n.p. on op. cit. original work published 的私人交流 podcast podcast episode preprint radio broadcast radio series radio series episode special issue special section television broadcast television series television series episode video working paper 见于 及其他 作者不详 无名氏 载于 介于 见引于 版本 即将出版 同上 收入 送印中 网际网络 信函 日期不详 不详 在线 发表于 参考 取读于 比例 preprint journal article magazine article newspaper article bill broadcast classic collection dataset document entry dictionary entry encyclopedia entry event graphic hearing 访谈 legal case legislation manuscript map video recording musical score pamphlet conference paper patent performance periodical 的私人交流 post blog post regulation report review book review software audio recording presentation standard thesis treaty webpage journal art. mag. art. newspaper art. doc. graph. interv. MS video rec. rep. rev. bk. rev. audio rec. 公元 公元前 BCE CE : , ; act acts appendix appendices article articles canon canons location locations equation equations rule rules scene scenes table tables title titles 图表 注脚 作品 总页数 段落 部分 另见 app. apps. art. arts. loc. locs. eq. eqs. r. rr. sc. scs. tbl. tbls. tit. tits. op. 另见 ¶¶ § §§ chair chairs compiler compilers contributor contributors curator curators executive producer executive producers guest guests host hosts narrator narrators organizer organizers performer performers producer producers writer writers series creator series creators 导演 编辑 主编 绘图 翻译 编译 comp. comps. contrib. contribs. cur. curs. exec. prod. exec. prods. narr. narrs. org. orgs. perf. perfs. prod. prods. writ. writs. cre. cres. 导演 主编 编译 chaired by compiled by with curated by executive produced by with guest hosted by narrated by organized by performed by produced by written by created by 指导 编辑 主编 绘图 采访 受函 校订 翻译 编译 comp. by w. cur. by exec. prod. by w. guest hosted by narr. by org. by perf. by prod. by writ. by cre. by 主编 编译 一月 二月 三月 四月 五月 六月 七月 八月 九月 十月 十一月 十二月 1月 2月 3月 4月 5月 6月 7月 8月 9月 10月 11月 12月