#include <iostream>
#include <fstream>
#include <string>
#include <map>
using namespace std;
// Dump à télécharger à : https://dumps.wikimedia.org/frwiktionary/latest/ :
// frwiktionary-latest-pages-meta-history.xml.7z , puis à décompresser
string input_folder = "C:\\Users\\automatik\\Documents\\Wiktionnaire\\Stats translation editor\\";
string input_file_history = input_folder + "frwiktionary-latest-pages-meta-history.xml";
// Chemins des fichiers contenant les résultats
string output_folder = "C:\\Users\\automatik\\Documents\\Wiktionnaire\\Stats translation editor\\";
string output_file_res = output_folder + "stats-summary.txt"; // Chiffres-clés
string output_file_langs = output_folder + "stats-langs.txt"; // Stats par langue
string output_file_dates = output_folder + "stats-months.txt"; // Stats par mois
string output_file_contribs = output_folder + "stats-trads-contributors.txt"; // Stats par contributeur
string output_file_ips = output_folder + "stats-trads-ips.txt"; // Stats par IP
string output_file_diffs = output_folder + "stats-trads-diffs-to-check.txt"; // Diffs à analyser en Partie 2
string from_date = "2014-07"; // Mois à partir duquel on dénombre les ajouts (mois inclus)
string to_date = "2024-01"; // Mois jusqu’auquel on dénombre les ajouts (mois exclus)
/**** STATS ****/
std::map<string, int> stats_langs = {}; // exemple (en début d’analyse) : { "italien": 3, "allemand" : 8 }
std::map<string, int> stats_dates = {}; // ex : { "2014-08": 6169, "2014-09" : 6203 }
std::map<string, int> stats_contribs = {}; // ex : { "Un utilisateur": 213, "Un deuxième utilisateur": 65 }
std::map<string, int> stats_ips = {}; // ex : { "184.252.23.16": 11, "174.145.64.87": 75 }
// Maps pour stocker temporairement des résultats (pour être sûr que les trads analysées n'ont pas été révoquées)
// Ces maps sont transférées vers les "map" finaux ci-dessus une fois qu'on est sûr que les traductions concernées n'ont pas été révoquées
// Note : pour s'assurer que les ajouts ne sont pas révoqués, on lit tous les résumés d'édition qui succèdent à un ajout de traduction
// jusqu'à ce qu'on tombe sur une révocation ou autre chose qu'un ajout de traduction (via l'outil). Si l'ajout correspond à la dernière version
// de l'article, on le considère comme non révoqué
std::map<string, int> stats_langs_tmp = {};
std::map<string, int> stats_dates_tmp = {};
std::map<string, int> stats_contribs_tmp = {};
std::map<string, int> stats_ips_tmp = {};
unsigned int counter_pages = 0; // Nombre de pages parcourues dans le dump (simplement pour afficher la progression de la tâche)
unsigned int counter_additions = 0; // Nombre d'ajouts avec l'outil de traductions (ajouts révoqués ou non)
unsigned int counter_pages_with_trans = 0; // Nombre de pages avec au moins un ajout de traduction non révoqué
unsigned int counter_trans = 0; // Nombre de traductions ajoutées et non révoquées
unsigned int total_count_contribs = 0, total_count_ips = 0; // Nombre de traductions non révoquées ajoutées par des contributeurs enregistrés / non enregistrés
unsigned int total_count_contribs_tmp = 0, total_count_ips_tmp = 0; // Dénombrements temporaires transférés si traductions pas révoquées
unsigned int counter_additions_contributors = 0; // Nombre d’ajouts de traduction(s) par des utilisateurs… inscrits
unsigned int counter_additions_ips = 0; // " " " " … non inscrits
unsigned int revoked_contributor_additions = 0; // Nombre de traductions révoquées… de contributeurs inscrits
unsigned int revoked_ip_additions = 0; // " " " … de contributeurs non inscrits
string remember; // diffs à mettre de côté car résumé d'édition tronqué
string remember_tmp; // Variable temporaire (transférée si traductions correspondantes non révoquées)
int cnt_rem = 0; // Nombre de diffs à analyser
int cnt_rem_tmp = 0;
/**************/
// Transfert d’une map (temporaire) à une autre (dénombrant les résultats finaux)
void transfer_between_maps(map<string, int> &map_source, map<string, int> &map_dest) {
for(auto const &item : map_source) {
if (map_dest) {
map_dest += item.second;
} else {
map_dest = item.second;
}
}
}
// Vide les variables temporaires après un transfert
void empty_all_tmp_vars() {
stats_langs_tmp = {};
stats_dates_tmp = {};
stats_contribs_tmp = {};
stats_ips_tmp = {};
total_count_contribs_tmp = 0;
total_count_ips_tmp = 0;
cnt_rem_tmp = 0;
remember_tmp = "";
}
// Transfert de toutes les variables temporaires vers leurs versions persistantes
void transfer_all_tmp_vars() {
transfer_between_maps(stats_langs_tmp, stats_langs);
transfer_between_maps(stats_dates_tmp, stats_dates);
transfer_between_maps(stats_contribs_tmp, stats_contribs);
transfer_between_maps(stats_ips_tmp, stats_ips);
total_count_contribs += total_count_contribs_tmp;
total_count_ips += total_count_ips_tmp;
counter_trans += (total_count_contribs_tmp + total_count_ips_tmp);
remember += remember_tmp;
cnt_rem += cnt_rem_tmp;
empty_all_tmp_vars();
}
// Fonction effectuant le traitement de la Partie 1
void count_trads_added_with_te()
{
/**** FICHIERS D'ENTREE ET DE SORTIE ****/
ifstream infile(input_file_history, ifstream::in);
if (!infile) {
cout << "Le dump n'est pas situe a " << input_file_history << endl;
return;
}
ofstream out_res(output_file_res, ofstream::out);
if (!out_res) {
cout << "Probleme avec le fichier de sortie global " << endl;
return;
}
ofstream out_langs(output_file_langs, ofstream::out);
if (!out_langs) {
cout << "Probleme avec le fichier de sortie sur les langues" << endl;
return;
}
ofstream out_dates(output_file_dates, ofstream::out);
if (!out_dates) {
cout << "Probleme avec le fichier de sortie sur les dates" << endl;
return;
}
ofstream out_contribs(output_file_contribs, ofstream::out);
if (!out_contribs) {
cout << "Probleme avec le fichier de sortie sur les contributeurs" << endl;
return;
}
ofstream out_ips(output_file_ips, ofstream::out);
if (!out_ips) {
cout << "Probleme avec le fichier de sortie sur les ips" << endl;
return;
}
ofstream out_diff(output_file_diffs, ofstream::out);
if (!out_diff) {
cout << "Probleme avec le fichier de sortie de diffs" << endl;
return;
}
/***************************************/
/*** PARAMETRES ***/
// Pour s’assurer que la révision en cours est bien dans la période d’analyse désirée (et définie plus haut)
int from_year = stoi(from_date.substr(0, 4));
int from_month = stoi(from_date.substr(5, 2));
int to_year = stoi(to_date.substr(0, 4));
int to_month = stoi(to_date.substr(5, 2));
/******************/
/**** REVISION EN COURS ****/
bool pending_rev = false;
bool additions_to_count = false; // Ajouts à décompter ? Variable vidée après s'être assurée que les ajouts décomptés n'ont pas été annulés
unsigned int ip_additions_to_count = 0; // Nombre d'ajouts d'IPs à décompter comme révoqués si tel est le cas
unsigned int contrib_additions_to_count = 0; // " " de contributeurs " " " "
bool id_read = false;
string title, rev_id, prev_rev_id; // Informations utiles pour les ajouts dont on veut analyser les diffs
int ns = -1;
string tstamp; // Timestamp de la révision en cours d'analyse
int year, month;
string contributor; // Nom du contributeur ou adresse IP
bool is_ip = false; // Si le contributeur est enregistré ou non
string comment = ""; // résumé d’édition en cours d’analyse
string lang; // langue de la traduction en cours d’analyse (langues des traductions mentionnées dans le résumé d'édition)
string line; // Ligne du dump actuellement en cours d’analyse
size_t pos1, pos2, pos3;
bool entry_with_trans = false;
/***************************/
while (getline(infile, line)) {
if (line.find("<title>") != string::npos) {
pos1 = line.find("<title>");
pos2 = line.find("</title>");
title= line.substr(pos1+7, pos2-pos1-7);
counter_pages++;
}
// Si pas main, on passe
if (line.find("<ns>") != string::npos) {
pos1 = line.find("<ns>");
pos2 = line.find("</ns>");
ns = stoi(line.substr(pos1+4, pos2-pos1-4));
}
if (ns != 0) {
continue;
}
if (line.find("<revision>") != string::npos) {
pending_rev = true;
id_read = false;
}
if (pending_rev) {
if (!id_read && line.find("<id>") != string::npos) {
pos1 = line.find("<id>");
pos2 = line.find("</id>");
prev_rev_id = rev_id;
rev_id = line.substr(pos1+4, pos2-pos1-4);
id_read = true;
}
// On verifie que l'édition est faite dans la période choisie
if (line.find("<timestamp>") != string::npos) {
pos1 = line.find("<timestamp>");
pos2 = line.find("</timestamp>");
tstamp = line.substr(pos1+11, 7);
year = stoi( tstamp.substr(0, 4) );
month = stoi( tstamp.substr(5, 2) );
if (year < from_year || (year == from_year && month < from_month) ||
year > to_year || (year == to_year && month >= to_month)
) {
pending_rev = false;
if (additions_to_count) {
transfer_all_tmp_vars();
additions_to_count = false;
}
continue;
}
}
// Pour un utilisateur on aura dans le fichier XML <username></username>, tandis que pour une IP on aura <ip></ip>
if (line.find("<username>") != string::npos) {
pos1 = line.find("<username>");
pos2 = line.find("</username>");
contributor = line.substr(pos1+10, pos2-pos1-10);
is_ip = false;
}
if (line.find("<ip>") != string::npos) {
pos1 = line.find("<ip>");
pos2 = line.find("</ip>");
contributor = line.substr(pos1+4, pos2-pos1-4);
is_ip = true;
}
// On analyse tous les commentaires des pages de l’espace principal à la recherche de
// résumés d’édition correspondant à des ajouts traductions avec Translation editor
if (line.find("<comment>") != string::npos) {
pos1 = line.find("<comment>");
pos2 = line.find("</comment>");
comment = line.substr(pos1+9, pos2-pos1-9);
if (comment.substr(0, 15) == "Traductions : +") { // cas 1 : traductions ajoutées avec l'outil : on met de côté
// avant de s’assurer qu'elles n'ont pas été révoquées
additions_to_count = true;
counter_additions++;
if (is_ip) {
ip_additions_to_count++;
counter_additions_ips++;
} else {
contrib_additions_to_count++;
counter_additions_contributors++;
}
// Si le résumé d’édition est tronqué, on garde les infos de côté pour analyser + tard les diffs
if (comment.find("(assist") == string::npos) {
remember_tmp += "title=" + title + ";prev_rev_id=" + prev_rev_id + ";rev_id=" + rev_id +
";contrib=" + contributor + ";is_ip=" + (is_ip ? "true" : "false") +
";date=" + tstamp + "\n";
cnt_rem_tmp++;
continue;
}
entry_with_trans = true;
pos3 = 11;
do {
pos3 += 4;
lang = comment.substr(pos3, comment.find(" :", pos3) - pos3);
if (lang == "en-tête") continue; // On ignore les modifications d’en-tête
if (stats_langs_tmp.count(lang)) {
stats_langs_tmp++;
} else {
stats_langs_tmp = 1;
}
if (stats_dates_tmp.count(tstamp)) {
stats_dates_tmp++;
} else {
stats_dates_tmp = 1;
}
if (!is_ip) {
if (stats_contribs_tmp.count(contributor)) {
stats_contribs_tmp++;
} else {
stats_contribs_tmp = 1;
}
total_count_contribs_tmp++;
} else {
if (stats_ips_tmp.count(contributor)) {
stats_ips_tmp++;
} else {
stats_ips_tmp = 1;
}
total_count_ips_tmp++;
}
pos3 = comment.find(" ; +", pos3);
} while (pos3 != string::npos); // On répète la boucle pour toutes les traductions présentes dans le résumé d’édition
} else if ((comment.substr(0, 15) == "R\u00E9vocation des"
|| comment.substr(0, 14) == "Annulation des")
&& additions_to_count
) { // cas 2 : révocation d'ajout(s) de traduction : on ignore
revoked_contributor_additions += contrib_additions_to_count;
revoked_ip_additions += ip_additions_to_count;
empty_all_tmp_vars();
additions_to_count = false;
ip_additions_to_count = 0;
contrib_additions_to_count = 0;
entry_with_trans = false;
} else if (additions_to_count) { // cas 3 : non-révocation après ajout de traductions :
// on compte les trads précédemment analysées
transfer_all_tmp_vars();
additions_to_count = false;
ip_additions_to_count = 0;
contrib_additions_to_count = 0;
}
}
if (line.find("</revision>") != string::npos) {
pending_rev = false;
}
}
if (line.find("</title>") != string::npos) {
if (additions_to_count) { // cas ou la derniere modif d'une page est un ajout de traduction (non révoqué) :
// on prend alors en compte les ajouts de trads
transfer_all_tmp_vars();
additions_to_count = false;
ip_additions_to_count = 0;
contrib_additions_to_count = 0;
}
if (entry_with_trans) {
counter_pages_with_trans++;
// Tous les 100 pages modifiées avec Translation editor, on affiche des stats récapitulatives
if (counter_pages_with_trans % 100 == 0) {
cout << counter_pages_with_trans << " pages modifiees sur " <<
counter_pages << " scannees - " << counter_trans << " trads ajoutees avec TE (et "
<< revoked_contributor_additions + revoked_ip_additions << "/"
<< counter_additions << " ajouts revoques) contribs: "
<< counter_additions_contributors << " ; IPs: " << counter_additions_ips << endl;
}
// Tous les 10 000 pages modifiées avec Translation editor, on affiche les stats accumulés jusqu’alors
// (pour le suivi et le repérage anticipé d’aberrations dans les résultats)
if (counter_pages_with_trans % 10000 == 0) {
for (const auto &p : stats_langs) {
std::cout << p.first << " : " << p.second << '\n';
}
for (const auto &p : stats_dates) {
std::cout << p.first << " : " << p.second << '\n';
}
for (const auto &p : stats_contribs) {
std::cout << p.first << " : " << p.second << '\n';
}
std::cout << "Total contributeurs : " << total_count_contribs << '\n';
std::cout << "Total IPs : " << total_count_ips << '\n';
cout << cnt_rem << " diffs a analyser en +" << endl;
}
}
entry_with_trans = false;
}
}
cout << cnt_rem << " diffs a analyser en +" << endl;
cout << counter_pages_with_trans << " pages modifiees avec translation_editor (" << counter_trans << " traductions)" << endl;
infile.close();
out_res << "* Traductions ajoutées : " << counter_trans << endl;
out_res << "* Ajouts avec l'outil : " << counter_additions << " (contributeurs : " << counter_additions_contributors
<< " ; IPs : " << counter_additions_ips << ")" << endl;
out_res << "* Pages modifiées avec translation_editor : " << counter_pages_with_trans << endl;
out_res << "* Traductions ajoutées par des utilisateurs inscrits : " << total_count_contribs << endl;
out_res << "* Traductions ajoutées par des utilisateurs non inscrits : " << total_count_ips << endl;
out_res << "* Ajouts de contributeurs révoqués : " << revoked_contributor_additions << endl;
out_res << "* Ajouts d'IPs révoqués : " << revoked_ip_additions << endl;
// Ecriture des resultats dans le fichier de sortie
for (const auto &p : stats_langs) {
out_langs << p.first << " : " << p.second << endl;
}
for (const auto &p : stats_dates) {
out_dates << p.first << " : " << p.second << endl;
}
for (const auto &p : stats_contribs) {
out_contribs << p.first << " : " << p.second << endl;
}
for (const auto &p : stats_ips) {
out_ips << p.first << " : " << p.second << endl;
}
out_res.close();
out_langs.close();
out_dates.close();
out_contribs.close();
out_ips.close();
// Entrees restantes à traiter en analysant les diffs => dans un fichier a part
out_diff << remember;
out_diff.close();
}
int main()
{
count_trads_added_with_te();
cout << "End of processing!" << endl;
return 0;
}
// Traductions décomptées sur le dump du 1/12/2023 : 494138
Ce script a pris ~30 minutes à s’exécuter (Windows 10, processeur quadricore (2.3GHz), 8 Go de RAM).