Benutzer:Mps/AnimeListenUpdater.cs
Erscheinungsbild
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Xml;
namespace AnimeListenUpdater
{
#region Exceptions
internal class BusinessException : Exception
{
public BusinessException(string message)
: base(message)
{
}
}
internal class MediawikiException : BusinessException
{
public MediawikiException(string message)
: base(message)
{
}
public MediawikiException(string code, string info)
: base($"Mediawiki error \"{code}: {info}\"")
{
Code = code;
}
public string Code { get; }
}
internal class MediawikiLagException : MediawikiException
{
public MediawikiLagException(string code, string info)
: base(code, info)
{
LagTime = GetLagTime(info);
}
public int LagTime { get; }
public static int GetLagTime(string info)
{
var lagtime = -1;
var match = Regex.Match(info, "Waiting for [^ ]*: ([0-9.-]+) seconds lagged");
if (match.Success)
{
int.TryParse(match.Groups[1].Value, out lagtime);
}
return lagtime;
}
}
#endregion
public static class Mediawiki
{
#region Netzwerk- und Mediawiki-Funktionen
private static readonly CookieContainer cookieContainer = new CookieContainer();
private static readonly string userAgentString = GetUserAgentString();
private static string GetUserAgentString()
{
var assemblyName = Assembly.GetExecutingAssembly().GetName();
// Programmname und -version
var userAgent = $"{assemblyName.Name}/{assemblyName.Version.Major}.{assemblyName.Version.Minor}";
// Umgebungsinformationen (OS, Laufzeitumgebung)
userAgent += $" ({Environment.OSVersion.VersionString}; .NET CLR {Environment.Version})";
return userAgent;
}
private static string EncodeQuery(string[,] query)
{
var result = "";
for (var i = 0; i < query.GetLength(0); i++)
{
result += "&" + query[i, 0];
var value = query[i, 1];
if (value != null)
{
result += "=" + Uri.EscapeDataString(value);
}
}
return result;
}
private static string MultipartQuery(string[,] postData, string boundaryIdentifier)
{
var sb = new StringBuilder();
for (var i = 0; i < postData.GetLength(0); i++)
{
sb.Append("--");
sb.Append(boundaryIdentifier);
sb.Append("\r\nContent-Disposition: form-data; name=\"");
sb.Append(postData[i, 0]);
sb.Append("\"\r\nContent-Type: text/plain; charset=UTF-8\r\nContent-Transfer-Encoding: 8bit\r\n\r\n");
sb.Append(postData[i, 1]);
sb.Append("\r\n");
}
return sb.ToString();
}
private static HttpWebRequest GenerateHttpWebRequest(string language, string[,] query)
{
var url = "https://";
if (language == "wikidata")
{
url += "www.wikidata.org";
}
else
{
url += language + ".wikipedia.org";
}
url += "/w/api.php?format=xml";
url += EncodeQuery(query);
var request = (HttpWebRequest)WebRequest.Create(url);
request.UserAgent = userAgentString;
request.CookieContainer = cookieContainer;
request.AutomaticDecompression = DecompressionMethods.GZip;
request.KeepAlive = true;
return request;
}
public static HttpWebResponse HttpGet(string language, string[,] query)
{
var request = GenerateHttpWebRequest(language, query);
return (HttpWebResponse)request.GetResponse();
}
public static HttpWebResponse HttpPost(string language, string[,] query, string[,] postData)
{
var request = GenerateHttpWebRequest(language, query);
request.Method = "POST";
if (postData != null)
{
var boundary = Guid.NewGuid().ToString();
var postContent = new UTF8Encoding(false).GetBytes(MultipartQuery(postData, boundary));
request.ContentType = "multipart/form-data; boundary=" + boundary;
using (var stream = request.GetRequestStream())
{
stream.Write(postContent, 0, postContent.Length);
}
}
return (HttpWebResponse)request.GetResponse();
}
public static void TraverseHttpWebResponse(HttpWebResponse response, Action<XmlReader> onXmlNode)
{
using (response)
{
using (var stream = response.GetResponseStream())
{
using (var reader = XmlReader.Create(stream))
{
if (!reader.ReadToFollowing("api"))
{
throw new MediawikiException("Malformed response");
}
while (reader.Read())
{
CheckForError(reader);
onXmlNode(reader);
}
}
}
}
}
public static string CalcMd5Hash(byte[] data)
{
MD5 md5 = new MD5CryptoServiceProvider();
var md5Hash = md5.ComputeHash(data);
return BitConverter.ToString(md5Hash).Replace("-", "").ToLowerInvariant();
}
public static void CheckForError(XmlReader reader)
{
if (reader.NodeType == XmlNodeType.Element)
{
if (reader.LocalName == "warnings")
{
Debug.WriteLine(" warning: " + reader.ReadInnerXml());
}
else if (reader.LocalName == "error")
{
var code = reader.GetAttribute("code");
var info = reader.GetAttribute("info");
Debug.WriteLine($" error \"{code}\": {info}");
if (code == "maxlag")
{
throw new MediawikiLagException(code, info);
}
throw new MediawikiException(code, info);
}
}
}
public enum TokenType
{
Csrf,
Watch,
Patrol,
Rollback,
UserRights,
Login,
CreateAccount
}
public static string GetToken(string lang, TokenType tokenType)
{
var tokenName = tokenType.ToString().ToLowerInvariant();
string[,] paramList =
{
{ "action", "query" },
{ "meta", "tokens" },
{ "type", tokenName }
};
string token = null;
TraverseHttpWebResponse(HttpPost(lang, paramList, null), reader =>
{
if (reader.NodeType == XmlNodeType.Element && reader.LocalName == "tokens")
{
token = reader.GetAttribute(tokenName + "token");
}
});
return token;
}
public static void Login(string lang, NetworkCredential credentials)
{
string[,] post =
{
{ "lgpassword", credentials.Password },
{ "lgtoken", GetToken(lang, TokenType.Login) }
};
string[,] paramList =
{
{ "action", "login" },
{ "lgname", credentials.UserName }
};
TraverseHttpWebResponse(HttpPost(lang, paramList, post), reader =>
{
if (reader.NodeType == XmlNodeType.Element && reader.LocalName == "login")
{
var result = reader.GetAttribute("result");
if (result != "Success")
{
if (result == "Throttled")
{
result += $" (Please wait {reader.GetAttribute("wait")}s)";
}
throw new MediawikiException(result, reader.GetAttribute("reason"));
}
}
});
}
public static void Logout(string lang)
{
using (HttpGet(lang, new[,] { { "action", "logout" } })) { }
}
#endregion
}
public class Program
{
private static string editToken;
private static Dictionary<string, string> ParseTemplate(string template)
{
var result = new Dictionary<string, string>();
var startPos = template.IndexOf("{{", StringComparison.Ordinal);
var lastPos = template.LastIndexOf("}}", StringComparison.Ordinal);
if (startPos < 0 || lastPos < 0) { return result; }
var parameters = new List<string>();
var doubleSquareBracketCount = 0;
var doubleCurlyBracketCount = 0;
var parameterValue = new StringBuilder();
var lastCh = '\0';
foreach (var ch in template.Substring(startPos + 2, lastPos - startPos - 2))
{
if (ch == '|' && doubleSquareBracketCount == 0 && doubleCurlyBracketCount == 0)
{
parameters.Add(parameterValue.ToString());
parameterValue.Clear();
}
else
{
parameterValue.Append(ch);
if (ch == '[' && lastCh == '[') { doubleSquareBracketCount++; }
else if (ch == ']' && lastCh == ']') { doubleSquareBracketCount--; }
else if (ch == '{' && lastCh == '{') { doubleCurlyBracketCount++; }
else if (ch == '}' && lastCh == '}') { doubleCurlyBracketCount--; }
}
lastCh = ch;
}
parameters.Add(parameterValue.ToString());
for (var paramIdx = 0; paramIdx < parameters.Count; paramIdx++)
{
var keyValue = parameters[paramIdx].Split(new[] { '=' }, 2);
try
{
if (keyValue.Length == 2)
{
result.Add(keyValue[0], keyValue[1]);
}
else
{
result.Add(paramIdx.ToString(), parameters[paramIdx]);
}
}
catch (ArgumentException)
{
Console.Error.WriteLine($"Doppelte Parameter: {template}");
}
}
return result;
}
// zur Sortierung alle Zeichen außer Buchstaben, Zahlen und Leerzeichen entfernen und Buchstaben in Kleinschreibung umwandeln
private static string RemoveDiacriticsAndSpecialChars(string text)
{
if (text.StartsWith("The ") || text.StartsWith("Der ") || text.StartsWith("Die ") || text.StartsWith("Das "))
{
text = text.Substring(4);
}
return string.Concat(text.Normalize(NormalizationForm.FormKD).Where(ch => validCharTypes.Contains(CharUnicodeInfo.GetUnicodeCategory(ch)))).Normalize(NormalizationForm.FormKC).ToLowerInvariant();
}
private static void WriteContent(string pagetitle, string content, string basetimestamp = null)
{
if (editToken == null)
{
editToken = Mediawiki.GetToken("de", Mediawiki.TokenType.Csrf);
}
bool badToken;
do
{
badToken = false;
try
{
Console.Write("Schreibe " + pagetitle);
var md5Hash = Mediawiki.CalcMd5Hash(new UTF8Encoding(false).GetBytes(content));
var post = new string[3 + (basetimestamp != null ? 1 : 0), 2];
post[0, 0] = "token";
post[0, 1] = editToken;
post[1, 0] = "text";
post[1, 1] = content;
post[2, 0] = "md5";
post[2, 1] = md5Hash;
if (basetimestamp != null)
{
post[3, 0] = "basetimestamp";
post[3, 1] = basetimestamp;
}
using (var response = Mediawiki.HttpPost("de", new[,]
{
{ "assert", "user" },
{ "action", "edit" },
{ "title", pagetitle },
{ "notminor", null },
{ "summary", "Listen-Aktualisierung per [[Benutzer:Mps/AnimeListenUpdater.cs]]" }
},
post))
{
using (var stream = response.GetResponseStream())
{
using (var reader = XmlReader.Create(stream))
{
while (reader.Read())
{
Mediawiki.CheckForError(reader);
}
}
}
}
Console.WriteLine(".");
}
catch (MediawikiException mwe)
{
Console.Error.WriteLine("\n" + pagetitle + ": " + mwe.Message);
if (mwe.Code == "badtoken")
{
// Die API wirft manchmal "badtoken", daher einfach Edit nochmal probieren.
Console.Error.WriteLine(" Neuer Versuch.");
badToken = true;
}
}
} while (badToken);
}
private static bool BuildAnimeYearList(List<AnimeItem> animeList, int startYear, int endYear, TextWriter writer)
{
writer.WriteLine("__ABSCHNITTE_NICHT_BEARBEITEN__ __KEIN_INHALTSVERZEICHNIS__");
writer.WriteLine("Dies ist eine '''chronologisch sortierte Liste der Anime-Titel''' von " + startYear + " bis " + endYear + ".");
writer.WriteLine("<!--");
writer.WriteLine("##########");
writer.WriteLine("Bitte editiere diese Liste _nicht_, da sie von einem Bot automatisch generiert wird, der durchgeführte Änderungen überschreibt. Stattdessen können und sollten die alphabetisch sortierten Listen der Liste der Anime-Titel http://de.wikipedia.org/wiki/Liste_der_Anime-Titel bearbeitet werden, aus denen diese Liste generiert wird.");
writer.WriteLine("##########");
writer.WriteLine("-->");
writer.WriteLine("{{Navigationsleiste Liste der Anime-Titel}}");
writer.WriteLine();
var hasAtLeastOneYear = false;
for (var year = startYear; year <= endYear; year++)
{
var yearClosure = year;
var animesInYear = animeList
.Where(anime => anime.Year == yearClosure)
.OrderBy(anime => RemoveDiacriticsAndSpecialChars(anime.Title))
.ToArray();
if (animesInYear.Any())
{
hasAtLeastOneYear = true;
writer.WriteLine("== " + year + " ==");
writer.WriteLine("{{Anime-Listenkopf|");
foreach (var anime in animesInYear)
{
writer.WriteLine(anime.GetYearListEntry());
}
writer.WriteLine("|mark=year}}");
writer.WriteLine();
}
}
writer.WriteLine("[[Kategorie:Liste (Anime)|#" + startYear + "]]");
return hasAtLeastOneYear;
}
private static bool BuildAnimeStudioList(List<AnimeItem> animeList, TextWriter writer)
{
IEnumerable<string> linkedStudios = animeList.SelectMany(anime => anime.Studios) // Studiolisten zusammenführen
.Where(studio => studio.StartsWith("[[")) // nach verlinkten Studios filtern
.Select(AnimeItem.GetWikiArticle) // Linkklammern entfernen, nach Pipe separieren und nur erstes Element (Linkziel) zurückgeben
.Distinct() // doppelte Einträge entfernen
.OrderBy(RemoveDiacriticsAndSpecialChars); // sortieren
foreach (var curStudio in linkedStudios)
{
writer.WriteLine("{{#ifeq: {{{1}}}|" + curStudio + "|");
var animesOfStudio = animeList
.Where(anime => anime.Studios.Any(studio => AnimeItem.GetWikiArticle(studio) == curStudio)) // nach Anime die vom aktuellen Studio gefertigt wurden filtern
.OrderBy(anime => anime.Year + RemoveDiacriticsAndSpecialChars(anime.Title)) // nach Jahr und Titel sortieren
.GroupBy(anime => anime.Type) // nach Typ gruppieren
.ToDictionary(group => group.Key, group => group.ToList()); // in assoziative Liste überführen
// Generierungsregeln:
// ab 10 Einträge pro Typ: eigene Gruppe (Reihenfolge: Fernsehserien, Filme, Original Video Animations, Specials, Kurzfilme, Weitere Anime-Produktionen)
// ab 10 Einträge pro Gruppe: zwei Spalten, wobei bei ungerader Anzahl die erste Spalte länger ist
var hasCaptions = false;
foreach (var animeType in animeTypes.Keys)
{
#region wenn ein Studio mehr als 10 Animes eines Typs produziert hat, diese separat aufführen
if (animesOfStudio.ContainsKey(animeType) && animesOfStudio[animeType].Count >= 10)
{
hasCaptions = true;
switch (animeType)
{
case "S":
writer.WriteLine("=== Fernsehserien ===");
break;
case "F":
writer.WriteLine("=== Filme ===");
break;
case "W":
writer.WriteLine("=== Web-Animes ===");
break;
case "O":
writer.WriteLine("=== Original Video Animations ===");
break;
case "SP":
writer.WriteLine("=== Specials ===");
break;
case "K":
writer.WriteLine("=== Kurzfilme ===");
break;
}
writer.WriteLine("{{Mehrspaltige Liste|");
foreach (var anime in animesOfStudio[animeType])
{
writer.WriteLine($"* {anime.Year}: ''{anime.GetWikilink()}''");
}
writer.WriteLine("}}");
animesOfStudio.Remove(animeType);
}
#endregion
}
var otherAnimes = animesOfStudio
.SelectMany(kvp => kvp.Value)
.OrderBy(anime => anime.Year + RemoveDiacriticsAndSpecialChars(anime.Title))
.ToArray();
if (otherAnimes.Any())
{
if (hasCaptions)
{
writer.WriteLine("=== Weitere Anime-Produktionen ===");
}
writer.WriteLine("{{Mehrspaltige Liste|");
foreach (var anime in otherAnimes)
{
string desc;
if (animeTypes.ContainsKey(anime.Type))
{
desc = $" ({animeTypes[anime.Type]})";
}
else
{
desc = "";
}
writer.WriteLine($"* {anime.Year}: ''{anime.GetWikilink()}''{desc}");
}
writer.Write("}}");
}
writer.Write("}}");
}
writer.Write("<div class=\"noprint\" style=\"padding-left:1em;\"><small>''Diese Liste wird aus den Einträgen der [[Liste der Anime-Titel]] generiert. Einträge können und sollten dort ergänzt werden.''</small></div>");
writer.Write("<noinclude>[[Kategorie:Vorlage:Film und Fernsehen]]</noinclude>");
return true;
}
private static bool BuildMaintainenceInfo(List<AnimeItem> animeList, TextWriter writer)
{
var allStudios = animeList.SelectMany(anime => anime.Studios, (anime, studio) => new { Name = studio, anime.List }).ToList();
var linkedStudios = allStudios
.Where(studio => studio.Name.StartsWith("[["))
.Select(studio => new { Name = AnimeItem.GetWikiArticle(studio.Name), studio.List })
.GroupBy(studio => studio.Name)
.Select(studio => new { Name = studio.Key, Count = studio.Count(), Lists = studio.Select(list => list.List).Distinct().OrderBy(list => list) })
.OrderBy(studio => studio.Name)
.ToArray();
var unlinkedStudios = allStudios
.Where(studio => !studio.Name.StartsWith("[["))
.Select(studio => new { studio.Name, studio.List })
.GroupBy(studio => studio.Name)
.Select(studio => new { Name = studio.Key, Count = studio.Count(), Lists = studio.Select(list => list.List).Distinct().OrderBy(list => list) })
.ToArray();
writer.WriteLine("== Nur teilweise verlinkte Studios ==");
writer.WriteLine("{| class=\"wikitable sortable\"");
writer.WriteLine("! Studio || Auftreten || Listen");
foreach (var linkedStudio in linkedStudios)
{
var match = unlinkedStudios.FirstOrDefault(unlinkedStudio => unlinkedStudio.Name == linkedStudio.Name);
if (match != null)
{
writer.WriteLine("|-\n| {0} || {1} || {2}", linkedStudio.Name, match.Count, string.Join(", ", match.Lists.Select(list => "[[Liste der Anime-Titel/" + list + "|" + list + "]]")));
}
}
writer.WriteLine("|}\n");
writer.WriteLine("== Potentiell verlinkbare Studios ==");
writer.WriteLine("{| class=\"wikitable sortable\"");
writer.WriteLine("! Studio || Anzahl || Listen");
foreach (var unlinkedStudio in unlinkedStudios)
{
if (!string.IsNullOrEmpty(unlinkedStudio.Name) && unlinkedStudio.Count > 5 && linkedStudios.All(linkedStudio => linkedStudio.Name != unlinkedStudio.Name))
{
writer.WriteLine($"|-\n| {unlinkedStudio.Name} || {unlinkedStudio.Count} || {string.Join(", ", unlinkedStudio.Lists.Select(list => "[[Liste der Anime-Titel/" + list + "|" + list + "]]"))}");
}
}
writer.WriteLine("|}\n");
var combinedStudioList = allStudios
.Where(studio => !string.IsNullOrEmpty(studio.Name))
.Select(studio => new { Name = AnimeItem.GetWikiArticle(studio.Name), studio.List })
.GroupBy(studio => studio.Name)
.OrderBy(studio => studio.Key)
.ToDictionary(group => group.Key, group => group.Select(studio => studio.List).ToHashSet());
writer.WriteLine("== Ähnliche Studionamen ==");
writer.WriteLine("{| class=\"wikitable sortable\"");
writer.WriteLine("! Studio || Ähnliche Namen || Listen");
var allStudiosArray = combinedStudioList.Keys.ToArray();
for (var i = 0; i < allStudiosArray.Length; i++)
{
var targetStudio = allStudiosArray[i];
var similarStudios = new Dictionary<string, int>();
for (var j = 0 /*i + 1*/; j < allStudiosArray.Length; j++)
{
if (i == j)
{
continue;
}
var otherStudio = allStudiosArray[j];
var dist = LevenshteinDistance(RemoveDiacriticsAndSpecialChars(targetStudio), RemoveDiacriticsAndSpecialChars(otherStudio));
if (dist <= 2)
{
similarStudios.Add(otherStudio, dist);
}
}
if (similarStudios.Count > 0)
{
writer.Write($"|-\n| rowspan=\"{similarStudios.Count}\" | {targetStudio} |");
foreach (var similarName in similarStudios.OrderBy(item => item.Value).Select(item => item.Key))
{
writer.WriteLine($"| {similarName} || {string.Join(", ", combinedStudioList[similarName].Select(list => "[[Liste der Anime-Titel/" + list + "|" + list + "]]"))}\r\n|-");
}
}
}
writer.WriteLine("|}");
return true;
}
private static int LevenshteinDistance(string source, string target)
{
if (string.IsNullOrEmpty(source) && string.IsNullOrEmpty(target))
{
return 0;
}
if (string.IsNullOrEmpty(source))
{
return target.Length;
}
if (string.IsNullOrEmpty(target))
{
return source.Length;
}
var d = new int[source.Length + 1, target.Length + 1];
for (var i = 1; i <= source.Length; i++)
{
d[i, 0] = i;
}
for (var j = 1; j <= target.Length; j++)
{
d[0, j] = j;
}
for (var j = 1; j <= target.Length; j++)
{
for (var i = 1; i <= source.Length; i++)
{
if (source[i - 1] == target[j - 1])
{
d[i, j] = d[i - 1, j - 1];
}
else
{
d[i, j] = new[]
{
d[i - 1, j] + 1, // Löschung
d[i, j - 1] + 1, // Einfügung
d[i - 1, j - 1] + 1 // Ersetzung
}.Min();
}
}
}
return d[source.Length, target.Length];
}
private static NetworkCredential GetCredentials()
{
Console.Write("Benutzer: ");
var user = Console.ReadLine();
Console.Write("Passwort: ");
var password = string.Empty;
// Sternchen zeigen, statt eingetipptem Passwort
while (true)
{
var keyInfo = Console.ReadKey(true);
if (keyInfo.Key == ConsoleKey.Enter)
{
Console.WriteLine();
break;
}
if (keyInfo.Key == ConsoleKey.Backspace)
{
if (password.Length > 0)
{
password = password.Remove(password.Length - 1);
// bei Backspace: Cursor eine Position zurück, mit Leerzeichen überschreiben, wieder eine Position zurück
Console.Write(keyInfo.KeyChar + " " + keyInfo.KeyChar);
}
}
else
{
password += keyInfo.KeyChar;
Console.Write("*");
}
}
Console.WriteLine();
return new NetworkCredential(user, password);
}
private static void Main(string[] args)
{
string matchStudiosFilename = null;
NetworkCredential credentials = null;
#region Kommandozeilenparameter auswerten
string user = null, password = null;
for (var i = 0; i < args.Length; i++)
{
if (args[i] == "-?")
{
Console.WriteLine(Assembly.GetExecutingAssembly().GetName() + " [Parameter]");
Console.WriteLine();
Console.WriteLine("-user Benutzername Benutzername angeben");
Console.WriteLine("-pass Passwort Passwort angeben");
Console.WriteLine("-matchstudios Datei Studionamen in den Jahreslisten angleichen.");
Console.WriteLine(" Eingabe ist eine UTF8-kodierte Datei die zeilenweise die Ersetzungen angibt im Format: Ist<TAB>Soll");
return;
}
if (i + 1 < args.Length)
{
switch (args[i])
{
case "-user":
user = args[i + 1];
i++;
break;
case "-pass":
password = args[i + 1];
i++;
break;
case "-matchstudios":
matchStudiosFilename = args[i + 1];
if (!File.Exists(matchStudiosFilename))
{
Console.WriteLine("Datei nicht gefunden!");
return;
}
break;
}
}
}
if (!string.IsNullOrEmpty(user) && !string.IsNullOrEmpty(password)) { credentials = new NetworkCredential(user, password); }
#endregion
try
{
if (credentials == null) { credentials = GetCredentials(); }
Mediawiki.Login("de", credentials);
#region Eingabelisten lesen
Console.WriteLine("Alphabetische Listen lesen:");
Console.Write(" ");
var alphabeticLists = new Dictionary<string, ContentAndTimestamp>();
Parallel.ForEach(alphaList, letter =>
{
Mediawiki.TraverseHttpWebResponse(Mediawiki.HttpGet("de", new[,]
{
{ "action", "query" },
{ "prop", "revisions" },
{ "titles", "Liste der Anime-Titel/" + letter },
{ "rvprop", "content|timestamp" }
}),
delegate (XmlReader reader)
{
if (reader.NodeType == XmlNodeType.Element && reader.Name == "rev")
{
var curTimestamp = reader.GetAttribute("timestamp");
var content = reader.ReadString();
lock (alphabeticLists)
{
alphabeticLists.Add(letter, new ContentAndTimestamp { Content = content, Timestamp = curTimestamp });
Console.Write(letter);
}
}
});
});
Console.WriteLine();
#endregion
if (matchStudiosFilename != null)
{
#region Studionamen ersetzen
Console.WriteLine("\nErsetzungen:");
var replacers = new Dictionary<Regex, string>();
foreach (var line in File.ReadAllLines(matchStudiosFilename, Encoding.UTF8))
{
var items = line.Split('\t');
if (items.Length >= 2)
{
replacers.Add(new Regex(@"(\|\s*s[0-9]+\s*=\s*\[*)" + Regex.Escape(items[0]) + @"(\s*[\|}\]])", RegexOptions.Singleline | RegexOptions.CultureInvariant), items[1]);
}
Console.WriteLine(" " + items[0] + " -> " + items[1]);
}
Console.Write("Fortfahren [j/n]: ");
if (Console.ReadLine()?.ToLowerInvariant() == "j")
{
foreach (var letter in alphabeticLists.Keys.OrderBy(key => key))
{
var content = alphabeticLists[letter].Content;
foreach (var replace in replacers.Keys)
{
content = replace.Replace(content, "$1" + replacers[replace] + "$2");
}
WriteContent("Liste der Anime-Titel/" + letter, content, alphabeticLists[letter].Timestamp);
}
}
#endregion
}
else
{
#region Eingabelisten parsen
var animeList = new List<AnimeItem>();
var templateRegex = new Regex(@"\{\{\s*Anime\-Listeneintrag.+}}", RegexOptions.Singleline);
var commentRegex = new Regex(@"<!--.+?-->", RegexOptions.Singleline);
foreach (var letter in alphabeticLists.Keys.OrderBy(key => key))
{
Console.WriteLine("Liste " + letter);
var content = new StringReader(commentRegex.Replace(alphabeticLists[letter].Content, ""));
string line;
while ((line = content.ReadLine()) != null)
{
var match = templateRegex.Match(line);
if (!match.Success) { continue; }
var entry = ParseTemplate(match.Value);
var anime = AnimeItem.Parse(entry, letter);
if (anime != null)
{
animeList.Add(anime);
//Console.WriteLine(" " + anime);
}
else
{
Console.Error.WriteLine("Nicht auswertbarer Eintrag: " + match.Value);
}
}
}
#endregion
// Jahreslisten ergänzen
var maxYear = animeList.Select(anime => anime.Year).Max();
if (maxYear > DateTime.Now.Year + 1)
{
Console.Write($"Das höchste gefundene Jahr ist {maxYear}. Jahreslisten bis dahin erstellen [j/n]: ");
if (Console.ReadLine()?.ToLowerInvariant() != "j")
{
return;
}
}
for (var year = 2000; year <= maxYear; year = year + 2)
{
yearList.Add(year);
yearList.Add(year + 1);
}
for (var yearIdx = 0; yearIdx < yearList.Count / 2; yearIdx++)
{
var startYear = yearList[yearIdx * 2];
var endYear = yearList[yearIdx * 2 + 1];
TextWriter yearWriter = new StringWriter();
if (BuildAnimeYearList(animeList, startYear, endYear, yearWriter))
{
WriteContent($"Liste der Anime-Titel ({startYear}–{endYear})", yearWriter.ToString());
}
}
TextWriter studioWriter = new StringWriter();
if (BuildAnimeStudioList(animeList, studioWriter))
{
WriteContent("Vorlage:Animestudio Werksliste", studioWriter.ToString());
}
TextWriter maintainenceWriter = new StringWriter();
if (BuildMaintainenceInfo(animeList, maintainenceWriter))
{
WriteContent("Benutzer:Mps/Test", maintainenceWriter.ToString());
}
}
Console.WriteLine("Fertig.");
}
catch (MediawikiException ex)
{
Console.Error.WriteLine("Mediawiki-Fehler: " + ex.Message);
}
}
public class AnimeItem
{
public List<string> AlternativeTitles { get; set; }
public string EpisodeCount { get; set; }
public string JapaneseTitle { get; set; }
public string JapaneseTranscription { get; set; }
public int Level { get; set; }
public string Link { get; set; }
public string List { get; set; }
public List<string> Studios { get; set; }
public string Title { get; set; }
public string Type { get; set; }
public int Year { get; set; }
public static AnimeItem Parse(Dictionary<string, string> parsedTemplate, string list)
{
var result = new AnimeItem();
try
{
result.List = list;
result.Level = int.Parse(parsedTemplate["1"]);
result.Year = int.Parse(parsedTemplate["2"]);
result.Type = parsedTemplate["3"].Trim();
result.EpisodeCount = parsedTemplate["4"].Trim();
result.Title = parsedTemplate["5"].Trim();
if (parsedTemplate.TryGetValue("link", out var link))
{
result.Link = link.Trim();
}
result.JapaneseTitle = parsedTemplate.ContainsKey("j") ? parsedTemplate["j"].Trim() : "";
result.JapaneseTranscription = parsedTemplate.ContainsKey("jt") ? parsedTemplate["jt"].Trim() : "";
result.AlternativeTitles = new List<string>();
result.Studios = new List<string>();
for (var i = 1; i <= 10; i++)
{
var paramName = "a" + i;
if (parsedTemplate.ContainsKey(paramName))
{
result.AlternativeTitles.Add(parsedTemplate[paramName].Trim());
}
paramName = "s" + i;
if (parsedTemplate.ContainsKey(paramName))
{
result.Studios.Add(parsedTemplate[paramName].Trim());
}
}
return result;
}
catch (KeyNotFoundException)
{
return null;
}
catch (FormatException)
{
return null;
}
}
public string GetWikilink()
{
var result = "[[";
if (!string.IsNullOrEmpty(Link))
{
result += Link + "|";
}
result += Title + "]]";
return result;
}
public static string GetWikiArticle(string wikilink)
{
if (string.IsNullOrEmpty(wikilink))
{
return "";
}
// entferne Wikilinkklammern und splitte an der ersten Pipe
var link = wikilink.TrimStart('[').TrimEnd(']').Split(new[] { '|' }, 2)[0];
// erstes Element (Zielartikel) mit Großbuchstaben beginnen lassen und zurückgeben
link = char.ToUpperInvariant(link[0]) + link.Substring(1);
return link;
}
public string GetYearListEntry()
{
var sb = new StringBuilder();
sb.Append("{{Anime-Listeneintrag|");
sb.Append(0);
sb.Append("|");
sb.Append(Year);
sb.Append("|");
sb.Append(Type);
sb.Append("|");
sb.Append(EpisodeCount);
sb.Append("|");
sb.Append(Title);
if (!string.IsNullOrEmpty(Link))
{
sb.Append("|link=");
sb.Append(Link);
}
if (!string.IsNullOrEmpty(JapaneseTitle))
{
sb.Append("|j=");
sb.Append(JapaneseTitle);
}
if (!string.IsNullOrEmpty(JapaneseTranscription))
{
sb.Append("|jt=");
sb.Append(JapaneseTranscription);
}
for (var aIdx = 0; aIdx < AlternativeTitles.Count; aIdx++)
{
sb.Append("|a");
sb.Append(aIdx + 1);
sb.Append("=");
sb.Append(AlternativeTitles[aIdx]);
}
for (var sIdx = 0; sIdx < Studios.Count; sIdx++)
{
sb.Append("|s");
sb.Append(sIdx + 1);
sb.Append("=");
sb.Append(Studios[sIdx]);
}
sb.Append("|mark=year}}");
return sb.ToString();
}
public override string ToString()
{
return Title;
}
}
private struct ContentAndTimestamp
{
public string Content { get; set; }
public string Timestamp { get; set; }
}
#region Konfiguration
private static readonly UnicodeCategory[] validCharTypes =
{
UnicodeCategory.UppercaseLetter, UnicodeCategory.LowercaseLetter, UnicodeCategory.TitlecaseLetter, UnicodeCategory.ModifierLetter, UnicodeCategory.OtherLetter,
UnicodeCategory.DecimalDigitNumber, UnicodeCategory.LetterNumber, UnicodeCategory.OtherNumber, UnicodeCategory.SpaceSeparator
};
private static readonly string[] alphaList = { "0–9", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z" };
private static readonly List<int> yearList = new List<int> { 1900, 1945, 1946, 1962, 1963, 1969, 1970, 1979, 1980, 1984, 1985, 1989, 1990, 1994, 1995, 1997, 1998, 1999 }; // ab 2000 wird automatisch generiert
private static readonly Dictionary<string, string> animeTypes = new Dictionary<string, string>
{
{ "F", "Film" },
{ "S", "Fernsehserie" },
{ "W", "Web-Anime" },
{ "O", "OVA" },
{ "SP", "Special" },
{ "K", "Kurzfilm" }
};
#endregion
}
}