001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.data.validation.tests; 003 004import static org.openstreetmap.josm.tools.I18n.marktr; 005import static org.openstreetmap.josm.tools.I18n.tr; 006 007import java.util.HashSet; 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(TestError.builder(this, Severity.OTHER, NAME_TRANSLATION_MISSING) 054 .message(tr("Missing name:* translation"), marktr("Missing name:*={0}. Add tag with correct language key."), name) 055 .primitives(p) 056 .build()); 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 p.getKeys().forEach((key, n) -> { 069 if (n != null && key.startsWith("name:") && !"name:etymology:wikidata".equals(key)) { 070 names.add(n); 071 } 072 }); 073 074 if (names.isEmpty()) return; 075 076 String name = p.get("name"); 077 078 if (name == null) { 079 errors.add(TestError.builder(this, Severity.OTHER, NAME_MISSING) 080 .message(tr("A name is missing, even though name:* exists.")) 081 .primitives(p) 082 .build()); 083 return; 084 } 085 086 if (names.contains(name)) return; 087 /* If name is not equal to one of the name:*, it should be a 088 composition of some (not necessarily all) name:* labels. 089 Check if this is the case. */ 090 091 String[] splitNames = NAME_SPLIT_PATTERN.split(name); 092 if (splitNames.length == 1) { 093 /* The name is not composed of multiple parts. Complain. */ 094 missingTranslation(p, splitNames[0]); 095 return; 096 } 097 098 /* Check that each part corresponds to a translated name:*. */ 099 for (String n : splitNames) { 100 if (!names.contains(n)) { 101 missingTranslation(p, n); 102 } 103 } 104 } 105}