5 Commits
Author SHA1 Message Date
raymon 8937c5677f Give an example of applying a different template 2025-08-04 12:34:40 +02:00
raymon ddc8caa684 Add code repo to cargo file 2025-08-04 12:17:30 +02:00
raymon d938106f84 Show the property columns side by side
- infobox requirements
- wikidata entries
2025-08-04 12:12:33 +02:00
raymon ef9b5ad85f Call the existing helper methods to form a program 2025-08-04 11:01:34 +02:00
raymon 19a7658c51 Pass properties as integers internally 2025-08-04 11:01:34 +02:00
4 changed files with 132 additions and 30 deletions
+1 -1
View File
@@ -8,7 +8,7 @@ This analysis tool allows seeing the wikidata behind articles through the lens o
authors = ["Raymon Zutekouw <contact@raymon.dev>"]
repository = ""
repository = "https://codeberg.org/MeltingSilicon/wikipedia-infobox-analyzer"
license = "MIT OR Apache-2.0"
keywords = ["wikipedia", "wikidata", "infobox"]
+50 -1
View File
@@ -31,6 +31,55 @@ By default, the tool assumes that you are looking for tools on the English wikip
Make sure the passed title matches the article title, and the tool should be able to find the wikidata entry.
The next section will go over what these infobox templates files are, where you can find them on wikipedia, and how you can customize them locally for your wikidata analysis.
### Interpretation of output
The left column of the table first lists all the properties that the infobox requires.
The right column will display the same property name if it is present in wikidata.
If it is not present, the right column will be blank.
Remaining properties in wikidata that are not required by the infobox are trailed in the right column.
``` markdown
┌──────────────────┬─────────────────────┐
│ Infobox requires │ Wikidata Earth (Q2) │
├──────────────────┼─────────────────────┤
│ P18 │ P18 │ // property: image
│ P170 │ P170 │ // property: creator
│ P571 │ P571 │ // property: inception
│ │ P31 │ // property: instance of
│ │ P138 │ // property: inception
│ │ P361 │ // property: part of
│ │ .... │
└──────────────────┴─────────────────────┘
```
This (shortened) example is complete, but if we were to try a different infobox template on the earth entry you can see that it is not a good fit.
To demonstrate, we can apply the software template to the earth entry:
``` markdown
┌──────────────────┬─────────────────────┐
│ Infobox requires │ Wikidata Earth (Q2) │
├──────────────────┼─────────────────────┤
│ P18 │ P18 │ // property: image
│ P154 │ │
│ P170 │ P170 │ // property: creator
│ P178 │ │
│ P275 │ │
│ P277 │ │
│ P306 │ │
│ P348 │ │
│ P400 │ │
│ P548 │ │
│ P571 │ P571 │ // property: inception
│ P577 │ │
│ P856 │ │
│ P1324 │ │
│ P2096 │ │
│ │ P10 │
│ │ P31 │
│ │ P138 │
│ │ .... │
└──────────────────┴─────────────────────┘
```
## Templates
The wikipedia sites vary a lot when it comes to templates across the different languages.
The goal of this tool is to be universal, but these templates have not been standardized as far as I am aware.
@@ -45,7 +94,7 @@ Then you can add the following line to that file locally:
The program ignores what you put at the `...`.
It permits templates that include a listing of wikidata entries for their templates.
As an example, this is the case on the following template on the Dutch wikipedia (the first word means "uses"):
As an example, this is the case on the following software template on the Dutch wikipedia (the first word means "uses", it can be found here: https://nl.wikipedia.org/wiki/Sjabloon:Infobox_software):
``` text
{{Gebruikt Wikidata|P18|P154|P170|P178|P275|P277|P306|P348|P400|P548|P571|P577|P856|P1324|P2096}}
+65 -21
View File
@@ -5,34 +5,72 @@ pub mod wikipedia_infobox_analyzer {
use itertools::Itertools;
/// Looks for a used template that does lists wikidata properties listing
pub fn extract_used_properties_from_template(template: String) -> Vec<String> {
pub fn extract_used_properties_from_template(template: String) -> Vec<u64> {
let mut lines = template.lines();
let used = lines
.find(|line| line.starts_with("{{") && line.contains("Wikidata|"))
.expect("Template should have a line declaring which properties are used");
// The properties are listed inside the template with their number: P1|P2|P...
let re = Regex::new(r"P\d+").unwrap();
let re = Regex::new(r"P(\d+)").unwrap();
// Find all matches and collect the properties into vector
re.find_iter(used)
.map(|m| m.as_str().to_owned())
re.captures_iter(used)
.map(|m| m[1].parse().unwrap())
.collect()
}
/// Creates an ascii infobox out of the properties
pub fn format_infobox_from_used_properties(name: String, properties: Vec<String>) -> String {
pub fn format_infobox_from_used_properties(name: String, qid: String, properties_template: Vec<u64>, properties_item: Vec<u64>, ) -> String {
let mut ascii_table = AsciiTable::default();
ascii_table.set_max_width(26);
ascii_table.set_max_width(80);
ascii_table
.column(0)
.set_header(name)
.set_header("Infobox requires")
.set_align(Align::Left);
let data: Vec<Vec<&String>> = properties.iter().map(|v| vec![v]).collect();
ascii_table
.column(1)
.set_header(format!("Wikidata {name} ({qid})"))
.set_align(Align::Left);
// Display the property columns as a table
let data: Vec<Vec<String>> = join_columns(&properties_template, &properties_item)
.iter()
.map(|&m| {
let (v1, v2) = m;
vec![ v1.map_or("".to_owned(), |v| format!("P{v}").to_owned())
, v2.map_or("".to_owned(), |v| format!("P{v}").to_owned())]
}
).collect();
ascii_table.format(data)
}
/// Properties will be displayed in the right column if they are in the list for that column
/// All items in the left columns will be displayed
/// The remaining items in the right columns not in the left columns will trail in the right column
/// ┌───────┬───────┐
/// │ 1 │ 1 │
/// │ 3 │ │
/// │ │ 2 │
/// │ │ 4 │
/// └───────┴───────┘
/// So for example, [1, 3] and [1, 2, 4] become [1, 2, 3, 4], as shown above
fn join_columns<T: Copy + Eq>(vec1: &[T], vec2: &[T]) -> Vec<(Option<T>, Option<T>)> {
vec1.iter()
.flat_map(|n1| {
if let Some(&n2) = vec2.iter().find(|&x| x == n1) {
std::iter::once((Some(*n1), Some(n2)))
} else {
std::iter::once((Some(*n1), None))
}
})
.chain(vec2.iter()
.filter(|&n2| !vec1.contains(n2))
.map(|n2| (None, Some(*n2))))
.collect()
}
/// Fetches the wikidata label for the wiki property
pub async fn fetch_name_for_wiki_property(pid: u64) -> String {
let query = format!("
@@ -116,9 +154,9 @@ mod tests {
assert_eq!(
extract_used_properties_from_template(contents),
vec![
"P18", "P154", "P1128", "P4103", "P2139", "P2295", "P2226", "P856", "P169",
"P1448", "P159", "P749", "P355", "P17", "P1056", "P452", "P576", "P112", "P127",
"P856", "P2096"
18, 154, 1128, 4103, 2139, 2295, 2226, 856, 169,
1448, 159, 749, 355, 17, 1056, 452, 576, 112, 127,
856, 2096
]
);
@@ -128,8 +166,8 @@ mod tests {
assert_eq!(
extract_used_properties_from_template(contents),
vec![
"P18", "P154", "P170", "P178", "P275", "P277", "P306", "P348", "P400", "P548",
"P571", "P577", "P856", "P1324", "P2096"
18, 154, 170, 178, 275, 277, 306, 348, 400, 548,
571, 577, 856, 1324, 2096
]
);
}
@@ -139,16 +177,22 @@ mod tests {
assert_eq!(
format_infobox_from_used_properties(
"Earth".to_string(),
vec!["P31".to_string(), "P361".to_string(), "P571".to_string()]
"Q2".to_string(),
vec![18, 170, 571],
vec![18, 31, 138, 170, 361, 571]
),
concat![
"┌───────┐\n",
"│ Earth │\n", // item identifier: Q2
"├───────┤\n",
"│ P31 │\n", // property: instance of
"│ P361 │\n", // property: part of
"│ P571 │\n", // property: inception
"└───────┘\n"
"┌──────────────────┬─────────────────────┐\n",
"│ Infobox requires │ Wikidata Earth (Q2) │\n",
"├──────────────────┼─────────────────────┤\n",
"│ P18 │ P18 │\n", // property: image
"│ P170 │ P170 │\n", // property: creator
"│ P571 │ P571 │\n", // property: inception
"│ │ P31 │\n", // property: instance of
"│ │ P138 │\n", // property: inception
"│ │ P361 │\n", // property: part of
"└──────────────────┴─────────────────────┘\n",
]
);
}
+16 -7
View File
@@ -15,14 +15,23 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let args = Cli::from_args();
// Input to the program
let qid = fetch_wiki_item_by_article_title(
args.wikipedia_article_title,
args.wikipedia_language_code,
);
let _template_file = std::fs::read_to_string(&args.infobox_template_file)?;
let title = args.wikipedia_article_title;
let language = args.wikipedia_language_code;
let template = std::fs::read_to_string(&args.infobox_template_file)?;
println!("Qid: {qid}");
println!("Template file exists");
let qid = fetch_wiki_item_by_article_title(title.clone(), language.clone());
let properties_template = extract_used_properties_from_template(template);
let properties_item = fetch_properties_for_wiki_item(qid);
let table = format_infobox_from_used_properties(
title.clone(),
format!("Q{qid}"),
properties_template,
properties_item,
);
// Output of the program
println!("{table}");
Ok(())
}