001// License: GPL. See LICENSE file for details. 002package org.openstreetmap.josm.data.validation.tests; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.util.HashSet; 007import java.util.Map.Entry; 008import java.util.regex.Pattern; 009 010import org.openstreetmap.josm.data.osm.OsmPrimitive; 011import org.openstreetmap.josm.data.validation.Severity; 012import org.openstreetmap.josm.data.validation.Test; 013import org.openstreetmap.josm.data.validation.TestError; 014 015/** 016 * Check for missing name:* translations. 017 * <p> 018 * This test finds multilingual objects whose 'name' attribute is not 019 * equal to any 'name:*' attribute and not a composition of some 020 * 'name:*' attributes separated by ' - '. 021 * <p> 022 * For example, a node with name=Europe, name:de=Europa should have 023 * name:en=Europe to avoid triggering this test. An object with 024 * name='Suomi - Finland' should have at least name:fi=Suomi and 025 * name:sv=Finland to avoid a warning (name:et=Soome would not 026 * matter). Also, complain if an object has some name:* attribute but 027 * no name. 028 * 029 * @author Skela 030 */ 031public class NameMismatch extends Test.TagTest { 032 protected static final int NAME_MISSING = 1501; 033 protected static final int NAME_TRANSLATION_MISSING = 1502; 034 private static final Pattern NAME_SPLIT_PATTERN = Pattern.compile(" - "); 035 036 /** 037 * Constructs a new {@code NameMismatch} test. 038 */ 039 public NameMismatch() { 040 super(tr("Missing name:* translation"), 041 tr("This test finds multilingual objects whose ''name'' attribute is not equal to some ''name:*'' attribute and not a composition of ''name:*'' attributes, e.g., Italia - Italien - Italy.")); 042 } 043 044 /** 045 * Report a missing translation. 046 * 047 * @param p The primitive whose translation is missing 048 */ 049 private void missingTranslation(OsmPrimitive p) { 050 errors.add(new TestError(this, Severity.OTHER, 051 tr("A name:* translation is missing."), 052 NAME_TRANSLATION_MISSING, p)); 053 } 054 055 /** 056 * Check a primitive for a name mismatch. 057 * 058 * @param p The primitive to be tested 059 */ 060 @Override 061 public void check(OsmPrimitive p) { 062 HashSet<String> names = new HashSet<>(); 063 064 for (Entry<String, String> entry : p.getKeys().entrySet()) { 065 if (entry.getKey().startsWith("name:")) { 066 String n = entry.getValue(); 067 if (n != null) { 068 names.add(n); 069 } 070 } 071 } 072 073 if (names.isEmpty()) return; 074 075 String name = p.get("name"); 076 077 if (name == null) { 078 errors.add(new TestError(this, Severity.OTHER, 079 tr("A name is missing, even though name:* exists."), 080 NAME_MISSING, p)); 081 return; 082 } 083 084 if (names.contains(name)) return; 085 /* If name is not equal to one of the name:*, it should be a 086 composition of some (not necessarily all) name:* labels. 087 Check if this is the case. */ 088 089 String[] splitNames = NAME_SPLIT_PATTERN.split(name); 090 if (splitNames.length == 1) { 091 /* The name is not composed of multiple parts. Complain. */ 092 missingTranslation(p); 093 return; 094 } 095 096 /* Check that each part corresponds to a translated name:*. */ 097 for (String n : splitNames) { 098 if (!names.contains(n)) { 099 missingTranslation(p); 100 return; 101 } 102 } 103 } 104}