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