{"id":2302,"date":"2026-01-06T15:02:10","date_gmt":"2026-01-06T14:02:10","guid":{"rendered":"https:\/\/www.strehle.de\/tim\/?p=2302"},"modified":"2026-01-06T15:09:38","modified_gmt":"2026-01-06T14:09:38","slug":"laender-schreibweisen-aus-linked-open-data","status":"publish","type":"post","link":"https:\/\/www.strehle.de\/tim\/weblog\/archives\/2026\/01\/06\/laender-schreibweisen-aus-linked-open-data\/","title":{"rendered":"L\u00e4nder-Schreibweisen aus Linked Open Data"},"content":{"rendered":"\n<h3 class=\"wp-block-heading\">Unsaubere Daten<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Wenn man Daten aus unterschiedlichen Quellen konsolidiert (oder sie frei eintippen l\u00e4sst, anstatt eine kontrollierte Liste vorzugeben), staunt man immer wieder, wie viele M\u00f6glichkeiten es gibt, etwas zu schreiben \u2013 bei L\u00e4ndern steht zum Beispiel statt \u201eDeutschland\u201c gerne mal \u201eDE\u201c, \u201eGermany\u201c oder \u201eAllemagne\u201c, dazu kommen Kombinationen (\u201eDeutschland \/ Germany\u201c), Abk\u00fcrzungen (\u201eDeutschl.\u201c) und Tippfehler. Weil solcher Wildwuchs in der eigenen Datenbank Recherchen und Reports unn\u00f6tig verkomplizieren w\u00fcrde, muss man irgendwie daf\u00fcr sorgen, dass am Ende eine einheitliche Schreibweise vorliegt.<\/p>\n\n\n\n\n<h3 class=\"wp-block-heading\">L\u00f6sungsans\u00e4tze<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">M\u00f6chte man einen \u201eBatch\u201c an Daten interaktiv bereinigen, ist die Open Source-Software <a href=\"https:\/\/openrefine.org\">OpenRefine<\/a> ein tolles Werkzeug; ihre \u201eReconcile\u201c-Funktion schl\u00e4gt <a href=\"https:\/\/openrefine.org\/docs\/manual\/wikibase\/reconciling\">mithilfe von Wikidata<\/a> passende Ersetzungen vor. Solche \u201eReconciliation Services\u201c kann man auch aus der eigenen Anwendung per API aufrufen. Weil in meinem Fall aber laufend neue potentiell \u201eunsaubere\u201c Daten eintreffen, die vollautomatisch und performant (ohne API-Aufruf) bereinigt werden sollen, w\u00e4hle ich hier einen anderen Ansatz und setze auf \u201eSuchen und Ersetzen\u201c auf Basis einer statischen Liste.<\/p>\n\n\n\n\n<p class=\"wp-block-paragraph\">\u201eSuchen und Ersetzen\u201c an sich ist technisch trivial. Spannend ist die Frage, wie ich an die Liste der zu ersetzenden Schreibweisen komme. Was bisher alles so eingegeben wurde, sehe ich zwar in den mir vorliegenden Daten. Aber die Zuordnung \u2013 dass \u201eSverige\u201c durch \u201eSchweden\u201c ersetzt werden sollte und \u201e\u0423\u043a\u0440\u0430\u0457\u043d\u0430\u201c durch \u201eUkraine\u201c \u2013 m\u00f6chte ich nicht von Hand f\u00fcr Hunderte von L\u00e4ndern definieren.<\/p>\n\n\n\n\n<h3 class=\"wp-block-heading\">Linked Open Data<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Als L\u00f6sung bietet sich \u201eLinked Open Data\u201c an: \u00f6ffentliche maschinenlesbare Datensammlungen wie <a href=\"https:\/\/www.wikidata.org\/\">Wikidata<\/a>, <a href=\"https:\/\/www.geonames.org\">GeoNames<\/a> oder die <a href=\"https:\/\/www.dnb.de\/DE\/Professionell\/Standardisierung\/GND\/gnd_node.html\">Gemeinsame Normdatei<\/a> (GND) der Deutschen Nationalbibliothek. Die drei genannten enthalten jeweils Datens\u00e4tze zu L\u00e4ndern, siehe z. B. <a href=\"https:\/\/www.wikidata.org\/wiki\/Q34\">\u201eSweden\u201c bei Wikidata<\/a>, <a href=\"https:\/\/d-nb.info\/standards\/vocab\/gnd\/geographic-area-code.html#XA-SE\">\u201eSchweden\u201c in der GND<\/a> und <a href=\"https:\/\/www.geonames.org\/2661886\/kingdom-of-sweden.html\">\u201eKingdom of Sweden\u201c bei GeoNames<\/a>. (Eine Auflistung vieler weiterer Ressourcen findet sich in <a href=\"https:\/\/ontologist.substack.com\/p\/using-public-taxonomies\">Using Public Taxonomies<\/a> von Kurt Cagle.)<\/p>\n\n\n\n\n<p class=\"wp-block-paragraph\">Ich m\u00f6chte die \u201eamtliche\u201c deutsche Schreibweise verwenden und entscheide mich deshalb f\u00fcr die <a href=\"https:\/\/sta.dnb.de\/doc\/GND-VW-LC\">L\u00e4ndercodes der GND<\/a>: \u201eSchweden\u201c hat dort z. B. die ID (URI) <code>https:\/\/d-nb.info\/gnd\/4077258-5<\/code>. Die ID bleibt stabil, auch falls die Schweden sich mal entscheiden sollten, ihr Land umzubenennen\u2026<\/p>\n\n\n\n\n<p class=\"wp-block-paragraph\">Leider enth\u00e4lt die GND au\u00dfer dem deutschen und englischen Namen keine alternativen Schreibweisen f\u00fcr ein Land. Die sind daf\u00fcr reichlich in Wikidata vorhanden: der Name in der Landessprache, ISO-L\u00e4ndercodes, Kfz-L\u00e4nderkennzeichen usw. Weil Wikidata auch die GND-ID kennt, lassen sich beide Datenquellen kombinieren.<\/p>\n\n\n\n\n<p class=\"wp-block-paragraph\">(Feinheiten ignoriere ich hier, sie sind aber auch interessant: Je nach Anwendungsfall meint \u201eL\u00e4nder\u201c vielleicht ausschlie\u00dflich \u201esouver\u00e4ne Staaten\u201c, was z. B. Puerto Rico ausschlie\u00dfen w\u00fcrde, oder sogar nicht mehr existierende wie die DDR.)<\/p>\n\n\n\n\n<h3 class=\"wp-block-heading\">Lokaler RDF Triplestore<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Bei \u201eLinked Open Data\u201c ist <a href=\"https:\/\/www.w3.org\/TR\/2014\/REC-rdf11-concepts-20140225\/Overview.html\">RDF<\/a> das Standard-Datenmodell. Sowohl GND als auch Wikidata ver\u00f6ffentlichen die Datens\u00e4tze als RDF, u. a. im <a href=\"https:\/\/www.w3.org\/TR\/turtle\/\">Turtle-Format<\/a>. Allerdings nutzen sie nicht dasselbe Vokabular (Klassen- und Feldnamen). Um die von mir gew\u00fcnschte Liste mit L\u00e4nderschreibweisen zu erstellen, muss ich die \u00f6ffentlichen Daten filtern, umwandeln und kombinieren.<\/p>\n\n\n\n\n<p class=\"wp-block-paragraph\">Daf\u00fcr ist keine Programmierung notwendig; am einfachsten geht das in einer RDF-Datenbank (\u201eTriplestore\u201c) mit der Abfragesprache <a href=\"https:\/\/www.w3.org\/TR\/2013\/REC-sparql11-query-20130321\/\">SPARQL<\/a>. Diese Datenbank brauche ich nur vor\u00fcbergehend: Sobald die statische Liste fertig ist, kann ich sie wieder abschalten. Ich verwende daf\u00fcr die kostenlose <a href=\"https:\/\/graphdb.ontotext.com\">GraphDB von Ontotext<\/a>.<\/p>\n\n\n\n\n<p class=\"wp-block-paragraph\">Und ich muss mich f\u00fcr ein Vokabular entscheiden. Die von Wikidata und GND scheinen mir nicht so passend, besser lesbar finde ich <a href=\"https:\/\/schema.org\/\">Schema.org<\/a>, dort gibt es bereits einen <a href=\"https:\/\/schema.org\/Country\">Typ <code>Country<\/code><\/a> mit den Properties <code>name<\/code> und <code>alternateName<\/code>.<\/p>\n\n\n\n\n<h3 class=\"wp-block-heading\">Daten importieren und umwandeln<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Bei GND und Wikidata funktioniert der Import unterschiedlich: <\/p>\n\n\n\n\n<p class=\"wp-block-paragraph\">Die \u201eGND Geographic Area Codes\u201c werden <a href=\"https:\/\/d-nb.info\/standards\/vocab\/gnd\/geographic-area-code.ttl\">als Turtle-Datei bereitgestellt<\/a>, die ich in GraphDB importiere. Mit einer SPARQL-Abfrage filtere und wandele ich sie ins Schema.org-Vokabular um (und importiere das Ergebnis wieder in GraphDB):<\/p>\n\n\n\n\n<pre class=\"wp-block-code\"><code>PREFIX areaCode: &lt;https:\/\/d-nb.info\/standards\/vocab\/gnd\/geographic-area-code#&gt;\nPREFIX foaf: &lt;http:\/\/xmlns.com\/foaf\/0.1\/&gt;\nPREFIX owl: &lt;http:\/\/www.w3.org\/2002\/07\/owl#&gt;\nPREFIX rdfs: &lt;http:\/\/www.w3.org\/2000\/01\/rdf-schema#&gt;\nPREFIX schema: &lt;https:\/\/schema.org\/&gt;\nPREFIX skos: &lt;http:\/\/www.w3.org\/2004\/02\/skos\/core#&gt;\n\nCONSTRUCT {\n    ?gndIri a schema:Country ;\n    \tschema:name ?label ;\n    \towl:sameAs ?id ;\n    \towl:sameAs ?geonamesIri ;\n    \towl:sameAs ?page .\n}\nWHERE {\n    ?id skos:inScheme &lt;https:\/\/d-nb.info\/standards\/vocab\/gnd\/geographic-area-code#&gt; .\n    VALUES ?continents { areaCode:XA areaCode:XB areaCode:XC areaCode:XD areaCode:XE areaCode:XH areaCode:XI areaCode:XK areaCode:XL areaCode:XM }\n    ?id skos:broader ?continents .\n    ?id skos:prefLabel ?label .\n    OPTIONAL {\n        ?id rdfs:seeAlso ?gndIri .\n        FILTER (SUBSTR(str(?gndIri), 1, 22) = \"https:\/\/d-nb.info\/gnd\/\")\n    }\n    OPTIONAL {\n        ?id rdfs:seeAlso ?geonamesIri .\n        FILTER (SUBSTR(str(?geonamesIri), 1, 24) = \"http:\/\/www.geonames.org\/\")\n    }\n    OPTIONAL { ?id foaf:page ?page }\n    FILTER regex(str(?id), \"#[A-Z]{2}-[A-Z]{2}$\")\n    FILTER (lang(?label) = \"de\")\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Wikidata l\u00e4sst mich dagegen direkt auf der Seite <a href=\"https:\/\/query.wikidata.org\/\">Wikidata Query Service<\/a> SPARQL-Abfragen ausf\u00fchren. Diese Abfrage liefert die Zusatzinformationen zu den oben importierten GND-L\u00e4ndern, das Ergebnis kann ich in GraphDB importieren:<\/p>\n\n\n\n\n<pre class=\"wp-block-code\"><code>PREFIX owl: &lt;http:\/\/www.w3.org\/2002\/07\/owl#&gt;\nPREFIX skos: &lt;http:\/\/www.w3.org\/2004\/02\/skos\/core#&gt;\nPREFIX schema: &lt;https:\/\/schema.org\/&gt;\nPREFIX exVocab: &lt;https:\/\/knowledgegraph.example.com\/vocab\/&gt;\n\nCONSTRUCT {\n  ?gndIri\n    schema:alternateName ?label ;\n    schema:alternateName ?nativeLabel ;\n    schema:alternateName ?altLabel ;\n    schema:url ?officialWebsite ;\n    owl:sameAs ?country ;\n    owl:sameAs ?wikipediaArticle ;\n    owl:sameAs ?geoNamesIri ;\n    exVocab:countryCodeAlpha2 ?iso3166Alpha2Code ;\n    exVocab:countryCodeAlpha3 ?iso3166Alpha3Code ;\n    exVocab:countryCodeLicencePlate ?licencePlateCode ;\n    schema:subjectOf ?spiegelTopicIri .\n}\nWHERE {\n  # instance of country\n  ?country wdt:P31 wd:Q6256 .\n  ?country wdt:P227 ?gndId .\n  OPTIONAL { ?country wdt:P1705 ?nativeLabel } .\n  OPTIONAL {\n    ?country rdfs:label ?label .\n    FILTER(LANG(?label) IN (\"en\", \"de\", \"fr\"))\n  } .\n  OPTIONAL {\n    ?country skos:altLabel ?altLabel .\n    FILTER(LANG(?altLabel) IN (\"en\", \"de\", \"fr\"))\n  } .\n  OPTIONAL { ?country wdt:P297 ?iso3166Alpha2Code } .\n  OPTIONAL { ?country wdt:P298 ?iso3166Alpha3Code } .\n  OPTIONAL { ?country wdt:P395 ?licencePlateCode } .\n  OPTIONAL { ?country wdt:P856 ?officialWebsite } .\n  OPTIONAL { ?country wdt:P1566 ?geoNamesId } .\n  OPTIONAL { ?country wdt:P10234 ?spiegelTopicId } .\n  OPTIONAL {\n    ?wikipediaArticle &lt;http:\/\/schema.org\/about&gt; ?country .\n    FILTER (SUBSTR(str(?wikipediaArticle), 1, 25) IN (\"https:\/\/en.wikipedia.org\/\", \"https:\/\/de.wikipedia.org\/\"))\n  } .\n  BIND (IRI(CONCAT(\"http:\/\/www.geonames.org\/\", ?geoNamesId)) AS ?geoNamesIri)\n  BIND (IRI(CONCAT(\"https:\/\/d-nb.info\/gnd\/\", ?gndId)) AS ?gndIri)\n  BIND (IRI(CONCAT(\"https:\/\/www.spiegel.de\/thema\/\", ?spiegelTopicId, \"\/\")) AS ?spiegelTopicIri)\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">M\u00f6chte ich Schreibweisen aus meiner eigenen Datensammlung erg\u00e4nzen, kann ich sie in einer Turtle-Datei definieren und diese in GraphDB importieren:<\/p>\n\n\n\n\n<pre class=\"wp-block-code\"><code>@prefix schema: &lt;https:\/\/schema.org\/&gt; .\n\n# Deutschland\n&lt;https:\/\/d-nb.info\/gnd\/4011882-4&gt;\n    schema:alternateName\n        \"BRD\"@de,\n        \"DEU, Deutschland\"@de,\n        \"DEU, Deutschland, Germany\",\n        \"Deutschland Germany\",\n        \"Deutschland, Germany\",\n        \"Deutschland \/ Germany\",\n        \"Deutschland (DE)\"@de,\n        \"Deutschland (DEU)\"@de,\n        \"GER\"@en,\n        \"Niemcy\"@pl .\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Liste f\u00fcr \u201eSuchen und Ersetzen\u201c erzeugen<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Sind die Daten alle in GraphDB angekommen, komme ich mit dieser Abfrage endlich an die gew\u00fcnschte Liste:<\/p>\n\n\n\n\n<pre class=\"wp-block-code\"><code>PREFIX exVocab: &lt;https:\/\/knowledgegraph.example.com\/vocab\/&gt;\nPREFIX schema: &lt;https:\/\/schema.org\/&gt;\n\nSELECT DISTINCT ?alternateLower ?name WHERE {\n    ?id a schema:Country .\n    ?id schema:name ?name .\n    { ?id schema:alternateName ?alternateName . }\n    UNION { ?id exVocab:countryCodeAlpha2 ?alternateName . }\n    UNION { ?id exVocab:countryCodeAlpha3 ?alternateName . }\n    UNION { ?id exVocab:countryCodeLicencePlate ?alternateName . }\n    BIND (LCASE(STR(?alternateName)) AS ?alternateLower)\n} ORDER BY ?alternateLower\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Das Ergebnis kann ich aus GraphDB im CSV-Format herunterladen und dann in meiner Anwendung f\u00fcrs \u201eSuchen und Ersetzen\u201c nutzen.<\/p>\n\n\n\n\n<p class=\"wp-block-paragraph\">Hier ein kleiner Auszug des Ergebnisses:<\/p>\n\n\n\n\n<pre class=\"wp-block-code\"><code>\u2026\ndeu,Deutschland\n\"deu, deutschland\",Deutschland\n\"deu, deutschland, germany\",Deutschland\ndeutschland,Deutschland\ndeutschland (de),Deutschland\ndeutschland (deu),Deutschland\ndeutschland \/ germany,Deutschland\ndeutschland germany,Deutschland\n\"deutschland, germany\",Deutschland\ndie niederlande,Niederlande\ndie philippinen,Philippinen\ndie staaten,USA\ndj,Dschibuti\ndjaza\u00efr,Algerien\ndji,Dschibuti\ndjibouti,Dschibuti\ndk,D\u00e4nemark\ndm,Dominica\ndma,Dominica\ndnk,D\u00e4nemark\n\u2026\n<\/code><\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Unsaubere Daten Wenn man Daten aus unterschiedlichen Quellen konsolidiert (oder sie frei eintippen l\u00e4sst, anstatt eine kontrollierte Liste vorzugeben), staunt man immer wieder, wie viele M\u00f6glichkeiten es gibt, etwas zu schreiben \u2013 bei L\u00e4ndern steht zum Beispiel statt \u201eDeutschland\u201c gerne mal \u201eDE\u201c, \u201eGermany\u201c oder \u201eAllemagne\u201c, dazu kommen Kombinationen (\u201eDeutschland \/ Germany\u201c), Abk\u00fcrzungen (\u201eDeutschl.\u201c) und Tippfehler. [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"footnotes":"","_share_on_mastodon":"0"},"categories":[1],"tags":[],"class_list":["post-2302","post","type-post","status-publish","format-standard","hentry","category-weblog"],"share_on_mastodon":{"url":"https:\/\/mastodon.social\/@tistre\/115848520102586599","error":""},"_links":{"self":[{"href":"https:\/\/www.strehle.de\/tim\/wp-json\/wp\/v2\/posts\/2302","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.strehle.de\/tim\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.strehle.de\/tim\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.strehle.de\/tim\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.strehle.de\/tim\/wp-json\/wp\/v2\/comments?post=2302"}],"version-history":[{"count":2,"href":"https:\/\/www.strehle.de\/tim\/wp-json\/wp\/v2\/posts\/2302\/revisions"}],"predecessor-version":[{"id":2304,"href":"https:\/\/www.strehle.de\/tim\/wp-json\/wp\/v2\/posts\/2302\/revisions\/2304"}],"wp:attachment":[{"href":"https:\/\/www.strehle.de\/tim\/wp-json\/wp\/v2\/media?parent=2302"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.strehle.de\/tim\/wp-json\/wp\/v2\/categories?post=2302"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.strehle.de\/tim\/wp-json\/wp\/v2\/tags?post=2302"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}