Skip to content

Commit

Permalink
feat: implement AttributeFraming overlay
Browse files Browse the repository at this point in the history
  • Loading branch information
olichwiruk committed Jan 16, 2025
1 parent 0321796 commit 3798dcb
Show file tree
Hide file tree
Showing 7 changed files with 348 additions and 6 deletions.
54 changes: 54 additions & 0 deletions semantics/oca-bundle/src/build.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::state::oca::overlay::attribute_framing::{FramingScope, Framings};
use crate::state::oca::overlay::cardinality::Cardinalitys;
use crate::state::oca::overlay::character_encoding::CharacterEncodings;
use crate::state::oca::overlay::conditional::Conditionals;
Expand Down Expand Up @@ -471,6 +472,59 @@ pub fn apply_command(base: Option<OCABox>, op: ast::Command) -> Result<OCABox, V
}
}
}
ast::OverlayType::AttributeFraming => {
let mut frame_id = None;
let mut frame_meta = HashMap::new();
if let Some(ref properties) = content.properties {
for prop in properties {
if let (prop_name, ast::NestedValue::Value(prop_value)) = prop {
if prop_name.eq("id") {
frame_id = Some(prop_value.clone());
} else {
frame_meta.insert(format!("frame_{}", prop_name), prop_value.clone());
}
}
}
}
if frame_id.is_none() {
errors.push("Undefined frame id".to_string());
}

if let Some(ref attributes) = content.attributes {
for (attr_name, attr_framing_value) in attributes {
let mut attribute = oca
.attributes
.get(attr_name)
.ok_or_else(|| {
errors.push(format!("Undefined attribute: {attr_name}"));
errors.clone()
})?
.clone();
if let ast::NestedValue::Object(attr_framing) = attr_framing_value {
let mut framing = HashMap::new();
for (framing_key, framing_value) in attr_framing {
if let ast::NestedValue::Object(framing_value) = framing_value {
if let Some(ast::NestedValue::Value(predicate_id)) = framing_value.get("predicate_id") {
if let Some(ast::NestedValue::Value(framing_justification)) = framing_value.get("framing_justification") {
let framing_scope = FramingScope {
predicate_id: predicate_id.to_string(),
framing_justification: framing_justification.to_string(),
frame_meta: frame_meta.clone(),
};
framing.insert(
framing_key.clone(),
framing_scope
);
}
}
}
}
attribute.set_framing(frame_id.clone().unwrap(), framing.clone());
}
oca.add_attribute(attribute);
}
}
}
_ => (),
}
}
Expand Down
62 changes: 62 additions & 0 deletions semantics/oca-bundle/src/state/oca.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use crate::state::oca::overlay::label::Labels;
use crate::state::oca::overlay::meta::Metas;
use crate::state::oca::overlay::unit::Units;
use indexmap::IndexMap;
use overlay::attribute_framing::Framings;
use said::derivation::HashFunctionCode;
use said::sad::{SerializationFormats, SAD};
use said::version::SerializationInfo;
Expand Down Expand Up @@ -294,6 +295,26 @@ impl OCABox {
}
}
}

if let Some(framings) = &attribute.framings {
for frame_id in framings.keys() {
let mut framing_ov = overlays.iter_mut().find(|x| {
match x.as_any().downcast_ref::<overlay::AttributeFraming>() {
Some(o) => o.metadata.get("frame_id") == Some(frame_id),
None => false,
}
});

if framing_ov.is_none() {
overlays
.push(Box::new(overlay::AttributeFraming::new(frame_id.clone())));
framing_ov = overlays.last_mut();
}
if let Some(ov) = framing_ov {
ov.add(attribute);
}
}
}
}

overlays
Expand Down Expand Up @@ -503,6 +524,15 @@ impl<'de> Deserialize<'de> for DynOverlay {
})?,
));
}
OverlayType::AttributeFraming => {
return Ok(Box::new(
de_overlay
.deserialize_into::<overlay::AttributeFraming>()
.map_err(|e| {
serde::de::Error::custom(format!("AttributeFraming overlay: {e}"))
})?,
));
}
_ => {
return Err(serde::de::Error::custom(format!(
"Overlay type not supported: {:?}",
Expand Down Expand Up @@ -541,6 +571,7 @@ where
OverlayType::Label,
OverlayType::Information,
OverlayType::Link,
OverlayType::AttributeFraming,
];

let mut overlays_map: BTreeMap<Value, OverlayValue> = BTreeMap::new();
Expand Down Expand Up @@ -836,6 +867,29 @@ impl From<OCABundle> for OCABox {
}
}

let framing_overlays = oca_bundle
.overlays
.iter()
.filter_map(|x| x.as_any().downcast_ref::<overlay::AttributeFraming>())
.collect::<Vec<_>>();
for overlay in framing_overlays {
let frame_id = overlay.metadata.get("frame_id").unwrap();
let mut frame_meta = overlay.metadata.clone();
frame_meta.remove("frame_id");

for (attr_name, attr_framing) in overlay.attribute_framing.iter() {
let framing = attr_framing.clone().iter_mut().map(|(f, scope)| {
scope.frame_meta = frame_meta.clone();
(f.clone(), scope.clone())
}).collect::<HashMap<_, _>>();

attributes
.get_mut(attr_name)
.unwrap()
.set_framing(frame_id.to_string(), framing);
}
}

for (_, attribute) in attributes {
oca_box.add_attribute(attribute);
}
Expand Down Expand Up @@ -1250,6 +1304,7 @@ impl AttributeLayoutValues {
mod tests {
use maplit::hashmap;
use oca_ast_semantics::ast::{NestedAttrType, RefValue};
use overlay::attribute_framing::{FramingScope, Framings};
use said::SelfAddressingIdentifier;

use super::*;
Expand Down Expand Up @@ -1315,6 +1370,13 @@ mod tests {
let mut attr = Attribute::new("ref".to_string());
let said = SelfAddressingIdentifier::default();
attr.set_attribute_type(NestedAttrType::Reference(RefValue::Said(said)));
let mut framing = HashMap::new();
framing.insert("url".to_string(), FramingScope {
predicate_id: "skos:exactMatch".to_string(),
framing_justification: "semapv:ManualMappingCuration".to_string(),
frame_meta: HashMap::new()
});
attr.set_framing("frame_id".to_string(), framing);
oca.add_attribute(attr);

let oca_bundle = oca.generate_bundle();
Expand Down
29 changes: 28 additions & 1 deletion semantics/oca-bundle/src/state/oca/overlay/attribute_framing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ pub type Framing = HashMap<String, FramingScope>;
pub struct FramingScope {
pub predicate_id: String,
pub framing_justification: String,
#[serde(skip)]
pub frame_meta: HashMap<String, String>,
}

pub trait Framings {
Expand All @@ -38,6 +40,23 @@ impl Framings for Attribute {
}
}

pub fn serialize_metadata<S>(
metadata: &HashMap<String, String>,
s: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
use std::collections::BTreeMap;

let mut ser = s.serialize_map(Some(metadata.len()))?;
let sorted_metadata: BTreeMap<_, _> = metadata.iter().collect();
for (k, v) in sorted_metadata {
ser.serialize_entry(k, v)?;
}
ser.end()
}

pub fn serialize_framing<S>(
attributes: &HashMap<String, Framing>,
s: S,
Expand All @@ -64,7 +83,7 @@ pub struct AttributeFramingOverlay {
capture_base: Option<said::SelfAddressingIdentifier>,
#[serde(rename = "type")]
overlay_type: OverlayType,
#[serde(rename = "framing_metadata")]
#[serde(rename = "framing_metadata", serialize_with = "serialize_metadata")]
pub metadata: HashMap<String, String>,
#[serde(serialize_with = "serialize_framing")]
pub attribute_framing: HashMap<String, Framing>,
Expand Down Expand Up @@ -100,6 +119,12 @@ impl Overlay for AttributeFramingOverlay {
if let Some(value) = framing.get(id) {
self.attribute_framing
.insert(attribute.name.clone(), value.clone());

for framing_scope in value.values() {
for (k, v) in framing_scope.frame_meta.iter() {
self.metadata.insert(k.clone(), v.clone());
}
}
}
}
}
Expand Down Expand Up @@ -134,6 +159,7 @@ mod tests {
predicate_id: "skos:exactMatch".to_string(),
framing_justification: "semapv:ManualMappingCuration"
.to_string(),
frame_meta: HashMap::new(),
},
);
let mut loc2 = HashMap::new();
Expand All @@ -143,6 +169,7 @@ mod tests {
predicate_id: "skos:exactMatch".to_string(),
framing_justification: "semapv:ManualMappingCuration"
.to_string(),
frame_meta: HashMap::new(),
},
);
let attr = cascade! {
Expand Down
6 changes: 6 additions & 0 deletions semantics/oca-file/src/ocafile/instructions/add.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,12 @@ impl AddInstruction {
helpers::extract_content(object),
));
}
Rule::attribute_framing => {
object_kind = Some(ObjectKind::Overlay(
OverlayType::AttributeFraming,
helpers::extract_content(object),
));
}
Rule::flagged_attrs => {
object_kind = Some(ObjectKind::CaptureBase(CaptureContent {
properties: None,
Expand Down
84 changes: 81 additions & 3 deletions semantics/oca-file/src/ocafile/instructions/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,12 +89,14 @@ pub fn extract_attribute_key_pairs(attr_pair: Pair) -> Option<(String, NestedVal
debug!("Extracting the attribute from: {:?}", attr_pair);
for item in attr_pair.into_inner() {
match item.as_rule() {
Rule::attr_key => {
Rule::attr_key |
Rule::framing_metadata_key => {
key = item.as_str().to_string();
debug!("Extracting attribute key {:?}", key);
}
Rule::key_value |
Rule::unit_value => {
Rule::unit_value |
Rule::framing_metadata_value => {
if let Some(nested_item) = item.clone().into_inner().next() {
match nested_item.as_rule() {
Rule::string => {
Expand Down Expand Up @@ -284,6 +286,9 @@ pub fn extract_attribute_key_pairs(attr_pair: Pair) -> Option<(String, NestedVal
}
}
}
Rule::json_object => {
value = extract_json_object(item);
}
_ => {
panic!("Invalid attribute in {:?}", item.as_rule());
}
Expand All @@ -292,6 +297,66 @@ pub fn extract_attribute_key_pairs(attr_pair: Pair) -> Option<(String, NestedVal
Some((key, value))
}

pub fn extract_json_object(object: Pair) -> NestedValue {
let mut json_object = IndexMap::new();
for item in object.into_inner() {
match item.as_rule() {
Rule::json_pair => {
let mut key = String::new();
let mut value = NestedValue::Value(String::new());
for el in item.clone().into_inner() {
match el.as_rule() {
Rule::json_key => {
key = el.clone()
.into_inner()
.last()
.unwrap()
.into_inner()
.last()
.unwrap()
.as_span()
.as_str()
.to_lowercase();
}
Rule::json_value => {
if let Some(nested_item) = el.clone().into_inner().next() {
match nested_item.as_rule() {
Rule::string => {
value = NestedValue::Value(
nested_item
.clone()
.into_inner()
.last()
.unwrap()
.as_span()
.as_str()
.to_string(),
);
}
Rule::json_object => {
value = extract_json_object(nested_item);
}
_ => {
panic!("Invalid json value in {:?}", nested_item.as_rule());
}
}
}
}
_ => {
panic!("Invalid json pair in {:?}", el.as_rule());
}
}
}
json_object.insert(key, value);
}
_ => {
panic!("Invalid json object in {:?}", item.as_rule());
}
}
}
NestedValue::Object(json_object)
}

pub fn extract_attributes_key_paris(object: Pair) -> Option<IndexMap<String, NestedValue>> {
let mut attributes: IndexMap<String, NestedValue> = IndexMap::new();

Expand All @@ -302,7 +367,8 @@ pub fn extract_attributes_key_paris(object: Pair) -> Option<IndexMap<String, Nes
Rule::attr_key_pairs |
Rule::attr_entry_code_key_pairs |
Rule::attr_entry_key_pairs |
Rule::unit_attr_key_pairs => {
Rule::unit_attr_key_pairs |
Rule::attr_framing_key_pairs => {
for attr in attr.into_inner() {
debug!("Parsing attribute {:?}", attr);
if let Some((key, value)) = extract_attribute_key_pairs(attr) {
Expand Down Expand Up @@ -375,6 +441,18 @@ pub fn extract_properites_key_pairs(object: Pair) -> Option<IndexMap<String, Nes
NestedValue::Value(attr.as_str().to_string()),
);
}
Rule::framing_metadata => {
debug!("Parsing framing metadata: {:?}", attr.as_str());
for prop in attr.into_inner() {
debug!("Parsing property {:?}", prop);
if let Some((key, value)) = extract_attribute_key_pairs(prop) {
debug!("Parsed property: {:?} = {:?}", key, value);
properties.insert(key, value);
} else {
debug!("Skipping property");
}
}
}
_ => {
debug!(
"Unexpected token: Invalid attribute in instruction {:?}",
Expand Down
Loading

0 comments on commit 3798dcb

Please sign in to comment.