wikipedia_infobox_analyzer/
lib.rs1#[doc = include_str!("../README.md")]
2pub mod wikipedia_infobox_analyzer {
3 use regex::Regex;
4 use itertools::Itertools;
5
6 pub fn extract_used_properties_from_template(template: String) -> Vec<u64> {
8 let mut lines = template.lines();
9 let used = lines
10 .find(|line| line.starts_with("{{") && line.contains("Wikidata|"))
11 .expect("Template should have a line declaring which properties are used");
12
13 let re = Regex::new(r"P(\d+)").unwrap();
15
16 re.captures_iter(used)
18 .map(|m| m[1].parse().unwrap())
19 .collect()
20 }
21
22 pub fn join_columns<T: Copy + Eq>(vec1: &[T], vec2: &[T]) -> Vec<(Option<T>, Option<T>)> {
36 vec1.iter()
37 .flat_map(|n1| {
38 if let Some(&n2) = vec2.iter().find(|&x| x == n1) {
39 std::iter::once((Some(*n1), Some(n2)))
40 } else {
41 std::iter::once((Some(*n1), None))
42 }
43 })
44 .chain(vec2.iter()
45 .filter(|&n2| !vec1.contains(n2))
46 .map(|n2| (None, Some(*n2))))
47 .collect()
48 }
49
50 pub async fn fetch_name_for_wiki_property(pid: u64) -> String {
52 let query = format!("
53 SELECT ?label
54 WHERE
55 {{
56 wd:P{pid} rdfs:label ?label.
57 FILTER(lang(?label) = \"en\")
58 SERVICE wikibase:label {{ bd:serviceParam wikibase:language 'en'.}}
59 }}
60 ");
61
62 let api = mediawiki::api::Api::new("https://www.wikidata.org/w/api.php")
63 .await
64 .expect("Wikidata should return the api endpoint");
65 let res = api.sparql_query(&query)
66 .await
67 .expect("Query should be able to retrieve label");
68 let res_s = serde_json::to_string_pretty(&res).unwrap();
69
70 let re = Regex::new("\"value\": \"(.+)\"").unwrap();
71 let Some(label) = re.captures(&res_s) else { return "<unknown>".to_string() };
72
73 label[1].to_string()
74 }
75
76 pub fn fetch_properties_for_wiki_item(qid: u64) -> Vec<u64> {
78 let mut ids: Vec<u64> = vec![];
79 let uri = format!("https://www.wikidata.org/wiki/Special:EntityData/Q{qid}.json");
80 let res = reqwest::blocking::get(uri).unwrap();
81 let text = res.text().unwrap();
82 if text.contains("<h1>Not Found</h1><p>No entity with ID ") {
83 return vec![];
84 }
85 let ent = wikidata::Entity::from_json(serde_json::from_str(&text).unwrap()).unwrap();
86 if ent.claims.is_empty() {
87 return vec![];
88 }
89
90 for i in 0..ent.claims.len() {
91 let (wikidata::Pid(pid), _) = &ent.claims[i];
92 ids.push(*pid);
93 }
94
95 ids.sort();
96
97 ids.iter()
98 .map(|n| n.to_owned())
99 .unique()
100 .collect::<Vec<u64>>()
101 }
102
103 pub fn fetch_wiki_item_by_article_title(title: String, language_code: String) -> u64 {
105 let uri = format!("https://{}.wikipedia.org/w/api.php?action=query&prop=pageprops&format=json&titles={}", &language_code, &title);
106
107 let res = reqwest::blocking::get(uri).unwrap();
108 let text = res.text().unwrap();
109 if text.contains("-1") {
110 return 0;
111 }
112
113 let re = Regex::new("\"wikibase_item\":\"Q(\\d+)\"").unwrap();
114 let Some(qid) = re.captures(&text) else { return 0 };
115
116 qid[1].parse().expect("Should be a numeric value")
117 }
118}
119
120#[cfg(test)]
121mod tests_lib {
122 use super::wikipedia_infobox_analyzer::*;
123 use std::fs;
124
125 #[test]
126 fn test_extract_used_properties_from_template() {
127 let contents = fs::read_to_string("examples/templates/nl_infobox_bedrijf")
128 .expect("Template file should exist");
129
130 assert_eq!(
131 extract_used_properties_from_template(contents),
132 vec![
133 18, 154, 1128, 4103, 2139, 2295, 2226, 856, 169,
134 1448, 159, 749, 355, 17, 1056, 452, 576, 112, 127,
135 856, 2096
136 ]
137 );
138
139 let contents = fs::read_to_string("examples/templates/nl_infobox_software")
140 .expect("Template file should exist");
141
142 assert_eq!(
143 extract_used_properties_from_template(contents),
144 vec![
145 18, 154, 170, 178, 275, 277, 306, 348, 400, 548,
146 571, 577, 856, 1324, 2096
147 ]
148 );
149 }
150
151 #[tokio::test]
152 async fn test_fetch_name_for_wiki_property() {
153 let name = fetch_name_for_wiki_property(31).await;
154 assert_eq!(name, "instance of")
155 }
156
157 #[test]
158 fn test_fetch_properties_for_wiki_item() {
159 let qid = 16639197; let properties = fetch_properties_for_wiki_item(qid);
161
162 assert!(properties.contains(&10)); assert!(properties.contains(&18)); assert!(properties.contains(&154)); assert!(properties.contains(&178)); assert!(!properties.contains(&19)); assert!(!properties.contains(&30)); }
172
173 #[test]
174 fn test_fetch_wiki_item_by_article_title() {
175 assert_eq!(fetch_wiki_item_by_article_title("Earth".to_string(), "en".to_string()), 2);
176 assert_eq!(fetch_wiki_item_by_article_title("Train".to_string(), "en".to_string()), 870);
177 assert_eq!(fetch_wiki_item_by_article_title("Baum".to_string(), "de".to_string()), 10884);
178 }
179}