feat(wikidata): enhance property value parsing and improve time handling

- Refactor `fetch_item_properties` to handle nested JSON types efficiently
- Introduce `parse_property_value` for structured processing of various value types
  - Handle time values with varying precision (year, month, day)
  - Parse and format dates from RFC 3339 format
  - Support fetching and displaying labels for Wikidata entity references
- Replace raw JSON object handling with cleaner structured parsing
- Update property label fetching and signal updates for better UI data synchronization
This commit is contained in:
ryan 2025-01-30 14:59:55 +03:00
parent a40e9c98c4
commit af921088f9
3 changed files with 145 additions and 9 deletions

77
Cargo.lock generated
View file

@ -287,6 +287,21 @@ version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
[[package]]
name = "android-tzdata"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
[[package]]
name = "android_system_properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.94" version = "1.0.94"
@ -687,6 +702,20 @@ dependencies = [
"zeroize", "zeroize",
] ]
[[package]]
name = "chrono"
version = "0.4.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825"
dependencies = [
"android-tzdata",
"iana-time-zone",
"js-sys",
"num-traits",
"wasm-bindgen",
"windows-targets 0.52.6",
]
[[package]] [[package]]
name = "ciborium" name = "ciborium"
version = "0.2.2" version = "0.2.2"
@ -737,6 +766,7 @@ version = "0.1.3"
dependencies = [ dependencies = [
"actix-files", "actix-files",
"actix-web", "actix-web",
"chrono",
"console_error_panic_hook", "console_error_panic_hook",
"futures", "futures",
"gloo-net 0.5.0", "gloo-net 0.5.0",
@ -831,6 +861,12 @@ dependencies = [
"version_check", "version_check",
] ]
[[package]]
name = "core-foundation-sys"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]] [[package]]
name = "cpufeatures" name = "cpufeatures"
version = "0.2.16" version = "0.2.16"
@ -1383,6 +1419,29 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]]
name = "iana-time-zone"
version = "0.1.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"wasm-bindgen",
"windows-core",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
dependencies = [
"cc",
]
[[package]] [[package]]
name = "icu_collections" name = "icu_collections"
version = "1.5.0" version = "1.5.0"
@ -2124,6 +2183,15 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]] [[package]]
name = "object" name = "object"
version = "0.36.5" version = "0.36.5"
@ -3507,6 +3575,15 @@ dependencies = [
"windows-sys 0.59.0", "windows-sys 0.59.0",
] ]
[[package]]
name = "windows-core"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
dependencies = [
"windows-targets 0.52.6",
]
[[package]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.48.0" version = "0.48.0"

View file

@ -31,6 +31,7 @@ serde_json="1.0.133"
thiserror = "2.0.9" thiserror = "2.0.9"
zerofrom = "0.1" zerofrom = "0.1"
mio = "0.8" mio = "0.8"
chrono = "0.4"
[features] [features]
default = ["ssr"] default = ["ssr"]

View file

@ -8,6 +8,7 @@ use crate::models::item::Item;
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
use wasm_bindgen::JsCast; use wasm_bindgen::JsCast;
use chrono::{DateTime, Utc};
#[derive(Deserialize, Clone, Debug)] #[derive(Deserialize, Clone, Debug)]
struct WikidataSuggestion { struct WikidataSuggestion {
@ -299,10 +300,68 @@ pub fn ItemsList(
}); });
}; };
// function to handle different nested JSON types for property values
async fn parse_property_value(value: &serde_json::Value) -> String {
match value {
serde_json::Value::String(text) => text.clone(),
serde_json::Value::Number(num) => num.to_string(),
serde_json::Value::Object(map) => {
// Handle time values
if let Some(time_value) = map.get("time") {
let precision = map.get("precision").and_then(|p| p.as_u64()).unwrap_or(11);
if let Some(time_str) = time_value.as_str() {
if let Ok(parsed_date) = chrono::DateTime::parse_from_rfc3339(time_str.trim_start_matches('+')) {
return match precision {
9 => parsed_date.format("%Y").to_string(), // Year precision
10 => parsed_date.format("%Y-%m").to_string(), // Month precision
11 => parsed_date.format("%Y-%m-%d").to_string(), // Day precision
_ => parsed_date.format("%Y-%m-%d %H:%M:%S").to_string(),
};
}
}
return "Invalid time format".to_string();
}
// Handle Wikidata entity references
if let Some(id) = map.get("id") {
// Handle Wikidata entity references
let entity_id = id.as_str().unwrap_or("");
if entity_id.starts_with("Q") {
return fetch_entity_label(entity_id).await;
}
}
serde_json::to_string(map).unwrap_or("Complex Object".to_string())
}
_ => "Unsupported data type".to_string(),
}
}
async fn fetch_entity_label(entity_id: &str) -> String {
let url = format!(
"https://www.wikidata.org/w/api.php?action=wbgetentities&ids={}&props=labels&languages=en&format=json&origin=*",
entity_id
);
match gloo_net::http::Request::get(&url).send().await {
Ok(response) => {
if let Ok(data) = response.json::<serde_json::Value>().await {
if let Some(entity) = data["entities"][entity_id]["labels"]["en"]["value"].as_str() {
return entity.to_string();
}
}
}
Err(err) => log!("Error fetching entity label: {:?}", err),
}
entity_id.to_string() // Fallback to entity ID if label fetch fails
}
//function to fetch properties //function to fetch properties
async fn fetch_item_properties(wikidata_id: &str, set_fetched_properties: WriteSignal<HashMap<String, String>>, set_property_labels: WriteSignal<HashMap<String, String>>,) -> HashMap<String, String> { async fn fetch_item_properties(wikidata_id: &str, set_fetched_properties: WriteSignal<HashMap<String, String>>, set_property_labels: WriteSignal<HashMap<String, String>>,) -> HashMap<String, String> {
let url = format!( let url = format!(
"https://www.wikidata.org/wiki/Special:EntityData/{}.json", "https://www.wikidata.org/w/api.php?action=wbgetentities&ids={}&format=json&props=claims&origin=*",
wikidata_id wikidata_id
); );
@ -313,17 +372,16 @@ pub fn ItemsList(
if let Some(entity) = entities.get(wikidata_id) { if let Some(entity) = entities.get(wikidata_id) {
if let Some(claims) = entity["claims"].as_object() { if let Some(claims) = entity["claims"].as_object() {
let mut result = HashMap::new(); let mut result = HashMap::new();
for (property, values) in claims { for (property, values) in claims {
if let Some(value) = values[0]["mainsnak"]["datavalue"]["value"].as_str() { for value_entry in values.as_array().unwrap_or(&vec![]) {
result.insert(property.clone(), value.to_string()); if let Some(datavalue) = value_entry["mainsnak"]["datavalue"].get("value") {
} else if let Some(value) = values[0]["mainsnak"]["datavalue"]["value"].as_object() { let parsed_value = parse_property_value(datavalue).await;
result.insert(property.clone(), serde_json::to_string(value).unwrap()); result.insert(property.clone(), parsed_value);
} else if let Some(value) = values[0]["mainsnak"]["datavalue"]["value"].as_f64() { }
result.insert(property.clone(), value.to_string());
} else {
result.insert(property.clone(), "Unsupported data type".to_string());
} }
} }
// Fetch labels for the properties // Fetch labels for the properties
let property_ids = result.keys().cloned().collect::<Vec<_>>(); let property_ids = result.keys().cloned().collect::<Vec<_>>();
let labels = fetch_property_labels(property_ids).await; let labels = fetch_property_labels(property_ids).await;