use std::collections::BTreeSet;
use serde_json::Value;
use crate::Linearization;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Text {
pub language: String,
pub value: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Scale {
pub axis: String,
pub required: bool,
pub multiple: String,
pub entities: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Entity {
pub id: String,
pub code: Option<String>,
pub class_kind: Option<String>,
pub parents: Vec<String>,
pub children: Vec<String>,
pub titles: Vec<Text>,
pub definitions: Vec<Text>,
pub fully_specified: Vec<Text>,
pub inclusions: Vec<Text>,
pub exclusions: Vec<Text>,
pub index_terms: Vec<Text>,
pub scales: Vec<Scale>,
pub source: Option<String>,
pub browser_url: Option<String>,
}
#[derive(Debug, thiserror::Error)]
pub enum EntityError {
#[error("the entity JSON has no `@id` in `{0}`")]
NoId(&'static str),
}
fn text(value: Option<&Value>) -> Option<Text> {
let object = value?.as_object()?;
Some(Text {
language: object
.get("@language")
.and_then(Value::as_str)
.unwrap_or("en")
.to_owned(),
value: object.get("@value")?.as_str()?.to_owned(),
})
}
fn labels(value: Option<&Value>) -> Vec<Text> {
value
.and_then(Value::as_array)
.map(|items| {
items
.iter()
.filter_map(|item| text(item.get("label")))
.collect()
})
.unwrap_or_default()
}
fn ids(value: Option<&Value>, linearization: Linearization) -> Vec<String> {
value
.and_then(Value::as_array)
.map(|items| {
items
.iter()
.filter_map(Value::as_str)
.filter_map(|uri| linearization.id_of(uri))
.collect()
})
.unwrap_or_default()
}
impl Entity {
pub fn parse(json: &Value, linearization: Linearization) -> Result<Self, EntityError> {
let id = json
.get("@id")
.and_then(Value::as_str)
.and_then(|uri| linearization.id_of(uri))
.ok_or(EntityError::NoId(linearization.name()))?;
let code = json
.get("code")
.and_then(Value::as_str)
.filter(|c| !c.is_empty())
.map(str::to_owned);
let scales = json
.get("postcoordinationScale")
.and_then(Value::as_array)
.map(|items| {
items
.iter()
.filter_map(|scale| {
Some(Scale {
axis: scale.get("axisName")?.as_str()?.to_owned(),
required: scale
.get("requiredPostcoordination")
.and_then(Value::as_str)
.is_some_and(|r| r == "true"),
multiple: scale
.get("allowMultipleValues")
.and_then(Value::as_str)
.unwrap_or("AllowAlways")
.to_owned(),
entities: ids(scale.get("scaleEntity"), linearization),
})
})
.collect()
})
.unwrap_or_default();
let mut index_terms = labels(json.get("indexTerm"));
index_terms.extend(labels(json.get("synonym")));
Ok(Self {
id,
code,
class_kind: json
.get("classKind")
.and_then(Value::as_str)
.map(str::to_owned),
parents: ids(json.get("parent"), linearization),
children: ids(json.get("child"), linearization),
titles: text(json.get("title")).into_iter().collect(),
definitions: text(json.get("definition")).into_iter().collect(),
fully_specified: text(json.get("fullySpecifiedName")).into_iter().collect(),
inclusions: labels(json.get("inclusion")),
exclusions: labels(json.get("exclusion")),
index_terms,
scales,
source: json
.get("source")
.and_then(Value::as_str)
.map(str::to_owned),
browser_url: json
.get("browserUrl")
.and_then(Value::as_str)
.map(str::to_owned),
})
}
pub fn merge_language(&mut self, other: Self) {
for (mine, theirs) in [
(&mut self.titles, other.titles),
(&mut self.definitions, other.definitions),
(&mut self.fully_specified, other.fully_specified),
(&mut self.inclusions, other.inclusions),
(&mut self.exclusions, other.exclusions),
(&mut self.index_terms, other.index_terms),
] {
for text in theirs {
if !mine.contains(&text) {
mine.push(text);
}
}
}
}
#[must_use]
pub fn title(&self, language: &str) -> Option<&str> {
let pick = |lang: &str| {
self.titles
.iter()
.find(|t| t.language.eq_ignore_ascii_case(lang))
.map(|t| t.value.as_str())
};
pick(language)
.or_else(|| pick("en"))
.or_else(|| self.titles.first().map(|t| t.value.as_str()))
}
#[must_use]
pub fn languages(&self) -> Vec<String> {
let mut out: BTreeSet<String> = BTreeSet::new();
for text in self
.titles
.iter()
.chain(&self.definitions)
.chain(&self.index_terms)
{
out.insert(text.language.clone());
}
out.into_iter().collect()
}
}