From 6ba271d09466a5dca0ea2a197986b773cd1081d4 Mon Sep 17 00:00:00 2001 From: Bentley Breithaupt Date: Thu, 13 Sep 2018 17:33:14 -0700 Subject: [PATCH] Mixed Case Name Check (#66) * create MixedCaseNameCheck * added MixedCaseNameCheck config * Added unit test * bug fixes * Inverted function of iso configurable and added comments * change default config value * improved instruction * Added comments and UID * created mixedCaseNameCheck.md * update test rule * Added support for leading numbers, added test for articles, changed to split on config of chars, changed list of prepositions * added character to config, updated docs * added/fixed config values * added lower case, apostrophe, end of word combination handling. * corrected config error * added 'in' to prepositions * Changed unit test and rule names * removed comment * updated docs * updated docs * Change default config value; check for all lower case name * update config * update config * added mixed case units configurable * fix typo * change config names * code clean up * remove space * change code review structure * refactor long regex to methods * add comments and code cleanup * move comment * update docs * condense conditionals; clean up docs * add to comment and docs * fix comments * move regex compile to global * update docs --- config/configuration.json | 22 ++ docs/checks/mixedCaseNameCheck.md | 79 ++++ .../validation/tag/MixedCaseNameCheck.java | 277 +++++++++++++ .../tag/MixedCaseNameCheckTest.java | 273 +++++++++++++ .../tag/MixedCaseNameCheckTestRule.java | 367 ++++++++++++++++++ 5 files changed, 1018 insertions(+) create mode 100644 docs/checks/mixedCaseNameCheck.md create mode 100644 src/main/java/org/openstreetmap/atlas/checks/validation/tag/MixedCaseNameCheck.java create mode 100644 src/test/java/org/openstreetmap/atlas/checks/validation/tag/MixedCaseNameCheckTest.java create mode 100644 src/test/java/org/openstreetmap/atlas/checks/validation/tag/MixedCaseNameCheckTestRule.java diff --git a/config/configuration.json b/config/configuration.json index eb5e5656d..ab91f9af0 100644 --- a/config/configuration.json +++ b/config/configuration.json @@ -171,6 +171,28 @@ "difficulty": "MEDIUM" } }, + "MixedCaseNameCheck": { + "check_name.countries":["AIA", "ATG", "AUS", "BHS", "BRB", "BLZ", "BMU", "BWA", "VGB", + "CMR", "CAN", "CYM", "DMA", "FJI", "GMB", "GHA", "GIB", "GRD", "GUY", "IRL", "JAM", + "KEN", "LSO", "MWI", "MLT", "MUS", "MSR", "NAM", "NZL", "NGA", "PNG", "SYC", "SLE", + "SGP", "SLB", "ZAF", "SWZ", "TZA", "TON", "TTO", "TCA", "UGA", "GBR", "USA", "VUT", + "ZMB", "ZWE"], + "name":{ + "language.keys":["name:en"], + "affixes":["Mc", "Mac", "Mck","Mhic", "Mic"], + "articles": ["a", "an", "the"], + "prepositions": ["and", "from", "to", "of", "by", "upon", "on", "off", "at", "as", + "into", "like", "near", "onto", "per", "till", "up", "via", "with", "for", "in"], + "units":["kv"] + }, + "regex.split":" -/&@–", + "challenge": { + "description": "Tasks containing objects with mixed case names.", + "blurb": "Mixed Case Name", + "instruction": "Correct the listed names tags so they conform to capitalization standards", + "difficulty": "MEDIUM" + } + }, "OneMemberRelationCheck": { "challenge": { "description": "Tasks containing relations with only one member.", diff --git a/docs/checks/mixedCaseNameCheck.md b/docs/checks/mixedCaseNameCheck.md new file mode 100644 index 000000000..a2d6b273c --- /dev/null +++ b/docs/checks/mixedCaseNameCheck.md @@ -0,0 +1,79 @@ +# Mixed Case Name Check + +This check flags objects with name tags that improperly use mixed cases. + +Proper case use is defined by set standards and configurable exceptions. + +The standards are as follows: + +* Words must start with a capital unless: + * The first letter is preceded by a number (ex. 20th) + * All the words in the name are lower case (ex. ferry dock) +* All other letters must be lower case unless: + * They follow an apostrophe (ex. O'Flin) and, they are not the last letter of the word (ex. Smith's not Smith'S) + * The entire word is uppercase, except the last letter if it follows or is followed by an apostrophe (ex. MAX'S or MAX's) + +The standards are broken by the following configurable exceptions (with default values): + +* Articles that are capitalised only if they are the first word: + * a, an, the +* Prepositions that do not need to start with a capital: + * and, from, to, of, by, upon, on, off, at, as, into, like, near, onto, per, till, up, via, with, for, in +* Name affixes that may be followed by a capital: + * Mc, Mac, Mck, Mhic, Mic +* Mixed case units of measurement that are valid after a number: + * kV + +The above configurables allow this check to be adapted to test different languages. +The check should only test names in languages it is configured to handle. +OSM uses the `name` tag for the name in a locations primary language, and `name:[ISOcode]` for other languages. +This check uses two configurable to control what languages are checked. + +The first is a list of ISO codes for countries that should have there `name` tag checked. +The official language(s) of the countries in this list should be (a) language(s) that the check is configured to handle. +It has default values of: + +* AIA, ATG, AUS, BHS, BRB, BLZ, BMU, BWA, VGB, CMR, CAN, CYM, DMA, FJI, GMB, GHA, GIB, GRD, GUY, IRL, JAM, KEN, LSO, MWI, MLT, MUS, MSR, NAM, NZL, NGA, PNG, SYC, SLE, SGP, SLB, ZAF, SWZ, TZA, TON, TTO, TCA, UGA, GBR, USA, VUT, ZMB, ZWE + +The second is a list of `name:[ISOcode]` tags to check. These should be for the languages the check is configured to handle. + Default values are: + +* name:en + +A final configurable is a list of characters that names are split by, to form words. Its default values are: + +* SPACE, \-, /, &, @, – + +#### Live Examples + +1. Way [id:4780932622](https://www.openstreetmap.org/node/4780932622) has the name `NZ Convenience store`. It is flagged because the S in store should be capitalized. + +#### Code Review + +In [Atlas](https://github.com/osmlab/atlas), OSM elements are represented as Edges, Points, Lines, Nodes, Areas & Relations; in our case, we’re are looking at +[Edges](https://github.com/osmlab/atlas/blob/dev/src/main/java/org/openstreetmap/atlas/geography/atlas/items/Edge.java), +[Lines](https://github.com/osmlab/atlas/blob/dev/src/main/java/org/openstreetmap/atlas/geography/atlas/items/Line.java), +[Nodes](https://github.com/osmlab/atlas/blob/dev/src/main/java/org/openstreetmap/atlas/geography/atlas/items/Node.java), +[Points](https://github.com/osmlab/atlas/blob/dev/src/main/java/org/openstreetmap/atlas/geography/atlas/items/Point.java), and +[Areas](https://github.com/osmlab/atlas/blob/dev/src/main/java/org/openstreetmap/atlas/geography/atlas/items/Area.java). + +Our first goal is to validate the incoming Atlas object. Valid features for this check will satisfy the following conditions (see `validCheckForObject` method): + +* It is an Edge, Line, Node, Point, or Area +* It is a country where the `name` tag should be checked and it has a `name` tag, or it has a one of the `name:[ISOcode]` tags. +* It has not already been flagged + +Next the objects have each of their name tags, that are being checked, tested for proper use of case. +If the object's ISO code is in checkNameCountries its `name` tag is checked, else only the tags in `languageNameTags` are checked. + +The test for proper use of case uses multiple regular expressions to check both the entire name and each word. +The most complex expression checks that all letters are lowercase, with the exception of the first letter and letters following apostrophes at the end of the word. + +```java +return Pattern.compile(String.format( + "(\\p{L}.*(? FALLBACK_INSTRUCTIONS = Arrays.asList( + "{0} {1,number,#} has (an) invalid mixed-case value(s) for the following tag(s): {2}."); + private static final List CHECK_NAME_COUNTRIES_DEFAULT = Arrays.asList("AIA", "ATG", + "AUS", "BHS", "BRB", "BLZ", "BMU", "BWA", "VGB", "CMR", "CAN", "CYM", "DMA", "FJI", + "GMB", "GHA", "GIB", "GRD", "GUY", "IRL", "JAM", "KEN", "LSO", "MWI", "MLT", "MUS", + "MSR", "NAM", "NZL", "NGA", "PNG", "SYC", "SLE", "SGP", "SLB", "ZAF", "SWZ", "TZA", + "TON", "TTO", "TCA", "UGA", "GBR", "USA", "VUT", "ZMB", "ZWE"); + private static final List LANGUAGE_NAME_TAGS_DEFAULT = Arrays.asList("name:en"); + private static final List LOWER_CASE_PREPOSITIONS_DEFAULT = Arrays.asList("and", "from", + "to", "of", "by", "upon", "on", "off", "at", "as", "into", "like", "near", "onto", + "per", "till", "up", "via", "with", "for", "in"); + private static final List LOWER_CASE_ARTICLES_DEFAULT = Arrays.asList("a", "an", "the"); + private static final String SPLIT_CHARACTERS_DEFAULT = " -/&@–"; + private static final List NAME_AFFIXES_DEFAULT = Arrays.asList("Mc", "Mac", "Mck", + "Mhic", "Mic"); + private static final List MIXED_CASE_UNITS_DEFAULT = Arrays.asList("kV"); + + // A list of countries where the name tag should be checked + private final List checkNameCountries; + // A list of language specific name tags to check + private final List languageNameTags; + // A list of prepositions that are normally lower case in names + private final List lowerCasePrepositions; + // A list of articles that are normally lower case in names, unless at the start + private final List lowerCaseArticles; + // A string of characters that can proceed a capital letter + private final String splitCharacters; + // A list of name affixes that can proceed a capital letter + private final String nameAffixes; + // Know intentionally mixed case words + private final String mixedCaseUnits; + + // Regex Patterns + private final Pattern upperCasePattern; + private final Pattern anyLetterPattern; + private final Pattern lowerCasePattern; + private final Pattern mixedCaseUnitsPattern; + private final Pattern mixedCaseApostrophePattern; + private final Pattern nonFirstCapitalPattern; + + /** + * The default constructor that must be supplied. The Atlas Checks framework will generate the + * checks with this constructor, supplying a configuration that can be used to adjust any + * parameters that the check uses during operation. + * + * @param configuration + * the JSON configuration for this check + */ + public MixedCaseNameCheck(final Configuration configuration) + { + super(configuration); + this.checkNameCountries = (List) configurationValue(configuration, + "check_name.countries", CHECK_NAME_COUNTRIES_DEFAULT); + this.languageNameTags = (List) configurationValue(configuration, + "name.language.keys", LANGUAGE_NAME_TAGS_DEFAULT); + this.lowerCasePrepositions = (List) configurationValue(configuration, + "name.prepositions", LOWER_CASE_PREPOSITIONS_DEFAULT); + this.lowerCaseArticles = (List) configurationValue(configuration, "name.articles", + LOWER_CASE_ARTICLES_DEFAULT); + this.splitCharacters = (String) configurationValue(configuration, "regex.split", + SPLIT_CHARACTERS_DEFAULT); + this.nameAffixes = (String) configurationValue(configuration, "name.affixes", + NAME_AFFIXES_DEFAULT, value -> String.join("|", (List) value)); + this.mixedCaseUnits = (String) configurationValue(configuration, "name.units", + MIXED_CASE_UNITS_DEFAULT, value -> String.join("|", (List) value)); + + // Compile regex + this.upperCasePattern = Pattern.compile("\\p{Lu}"); + this.anyLetterPattern = Pattern.compile("\\p{L}"); + this.lowerCasePattern = Pattern.compile("\\p{Ll}"); + this.mixedCaseUnitsPattern = Pattern.compile( + String.format("[^\\p{L}]*\\p{Digit}[%1$s][^\\p{L}]*", this.mixedCaseUnits)); + this.mixedCaseApostrophePattern = Pattern + .compile("([^\\p{Ll}]+'\\p{Ll})|([^\\p{Ll}]+\\p{Ll}')"); + this.nonFirstCapitalPattern = Pattern.compile(String.format( + "(\\p{L}.*(? object.getOsmTags().containsKey(key))); + } + + /** + * This is the actual function that will check to see whether the object needs to be flagged. + * + * @param object + * the atlas object supplied by the Atlas-Checks framework for evaluation + * @return an optional {@link CheckFlag} object that + */ + @Override + protected Optional flag(final AtlasObject object) + { + final List mixedCaseNameTags = new ArrayList<>(); + final Map osmTags = object.getOsmTags(); + + // Check ISO against list of countries for testing name tag + if (this.checkNameCountries.contains(object.tag(ISOCountryTag.KEY).toUpperCase()) + && Validators.hasValuesFor(object, NameTag.class) + && isMixedCase(osmTags.get(NameTag.KEY))) + { + mixedCaseNameTags.add(NameTag.KEY); + } + // Check all language name tags + for (final String key : this.languageNameTags) + { + if (osmTags.containsKey(key) && isMixedCase(osmTags.get(key))) + { + mixedCaseNameTags.add(key); + } + } + + // If mix case id detected, flag + if (!mixedCaseNameTags.isEmpty()) + { + this.markAsFlagged(object.getOsmIdentifier()); + // Instruction includes type of OSM object and list of flagged tags + return Optional.of(this.createFlag(object, + this.getLocalizedInstruction(0, object instanceof LocationItem ? "Node" : "Way", + object.getOsmIdentifier(), String.join(", ", mixedCaseNameTags)))); + } + return Optional.empty(); + } + + @Override + protected List getFallbackInstructions() + { + return FALLBACK_INSTRUCTIONS; + } + + /** + * Tests each word in a string for proper use of case in a name. + * + * @param value + * String to check + * @return true when there is improper case in any of the words + */ + private boolean isMixedCase(final String value) + { + // Check if it is all lower case + if (this.upperCasePattern.matcher(value).find()) + { + // Split into words based on configurable characters + final String[] wordArray = value.split("[\\Q" + this.splitCharacters + "\\E]"); + boolean firstWord = true; + // Check each word + for (final String word : wordArray) + { + // Check if the word is intentionally mixed case + if (!isMixedCaseUnit(word)) + { + final Matcher firstLetterMatcher = this.anyLetterPattern.matcher(word); + // If the word is not in the list of prepositions, and the + // word is not both in the article list and not the first word: check that + // the first letter is a capital + if ((!this.lowerCasePrepositions.contains(word) + && !(!firstWord && this.lowerCaseArticles.contains(word)) + // If the first letter is lower case: return true if it is not preceded + // by a number + && firstLetterMatcher.find() + && Character.isLowerCase(firstLetterMatcher.group().charAt(0)) + && !(firstLetterMatcher.start() != 0 && Character + .isDigit(word.charAt(firstLetterMatcher.start() - 1)))) + // If the word is not all upper case: check if all the letters not + // following apostrophes, unless at the end of the word, are lower case + || (this.lowerCasePattern.matcher(word).find() + && !isMixedCaseApostrophe(word) + && isProperNonFirstCapital(word))) + { + return true; + } + } + firstWord = false; + } + } + return false; + } + + /** + * Tests a {@link String} against a configurable list of unit abbreviations. + * + * @param word + * {@link String} to test + * @return true if {@code word} contains a mixed case unit abbreviation preceded by a number, + * and it does not contain any other alphabetic characters. + */ + private boolean isMixedCaseUnit(final String word) + { + // This returns true if one of the items in this.mixedCaseUnits is preceded by a number - + // `\p{Digit}` + // There may be 0 or more non-alphabetic characters proceeding or following the + // digit+mixedCaseUnits - `[^\p{L}]*` + return this.mixedCaseUnitsPattern.matcher(word).find(); + } + + /** + * Tests a {@link String} for being all upper case, except the last letter which is adjacent to + * an apostrophe (ex. MAX's). + * + * @param word + * {@link String} to test + * @return true if a lower case letter is found preceding or following an apostrophe that is the + * last or second to last character in the string, and all other letters are upper case + */ + private boolean isMixedCaseApostrophe(final String word) + { + // This returns true if the last 2 characters are an apostrophe and a lower case letter, and + // all other letters are upper case. + return this.mixedCaseApostrophePattern.matcher(word).matches(); + } + + /** + * Tests a {@link String} for incorrect capitalization, excluding the first letter. + * + * @param word + * {@link String} to test + * @return true if a capital letter is incorrectly used + */ + private boolean isProperNonFirstCapital(final String word) + { + // This checks each capital letter for incorrect usage + // It does not check the first letter - `(\p{L}.*` + // To be incorrect usage a capital letter: + // Must not be preceded by an apostrophe or name affix - `(? Assert.assertEquals(1, flags.size())); + } + + @Test + public void invalidNameNodeTest() + { + this.verifier.actual(this.setup.invalidNameNodeAtlas(), + new MixedCaseNameCheck(inlineConfiguration)); + this.verifier.globallyVerify(flags -> Assert.assertEquals(1, flags.size())); + } + + @Test + public void invalidNameLineTest() + { + this.verifier.actual(this.setup.invalidNameLineAtlas(), + new MixedCaseNameCheck(inlineConfiguration)); + this.verifier.globallyVerify(flags -> Assert.assertEquals(1, flags.size())); + } + + @Test + public void invalidNameAreaTest() + { + this.verifier.actual(this.setup.invalidNameAreaAtlas(), + new MixedCaseNameCheck(inlineConfiguration)); + this.verifier.globallyVerify(flags -> Assert.assertEquals(1, flags.size())); + } + + @Test + public void invalidNameEdgeTest() + { + this.verifier.actual(this.setup.invalidNameEdgeAtlas(), + new MixedCaseNameCheck(inlineConfiguration)); + this.verifier.globallyVerify(flags -> Assert.assertEquals(1, flags.size())); + } + + @Test + public void invalidNamePointOneWordTest() + { + this.verifier.actual(this.setup.invalidNamePointOneWordAtlas(), + new MixedCaseNameCheck(inlineConfiguration)); + this.verifier.globallyVerify(flags -> Assert.assertEquals(1, flags.size())); + } + + @Test + public void validNamePointHyphenTest() + { + this.verifier.actual(this.setup.validNamePointHyphenAtlas(), + new MixedCaseNameCheck(inlineConfiguration)); + this.verifier.globallyVerify(flags -> Assert.assertEquals(0, flags.size())); + } + + @Test + public void validNamePointNumberTest() + { + this.verifier.actual(this.setup.validNamePointNumberAtlas(), + new MixedCaseNameCheck(inlineConfiguration)); + this.verifier.globallyVerify(flags -> Assert.assertEquals(0, flags.size())); + } + + @Test + public void invalidNamePointHyphenTest() + { + this.verifier.actual(this.setup.invalidNamePointHyphenAtlas(), + new MixedCaseNameCheck(inlineConfiguration)); + this.verifier.globallyVerify(flags -> Assert.assertEquals(1, flags.size())); + } + + @Test + public void validNamePointAffixTest() + { + this.verifier.actual(this.setup.validNamePointAffixAtlas(), + new MixedCaseNameCheck(inlineConfiguration)); + this.verifier.globallyVerify(flags -> Assert.assertEquals(0, flags.size())); + } + + @Test + public void invalidNamePointAffixTest() + { + this.verifier.actual(this.setup.invalidNamePointAffixAtlas(), + new MixedCaseNameCheck(inlineConfiguration)); + this.verifier.globallyVerify(flags -> Assert.assertEquals(1, flags.size())); + } + + @Test + public void validNamePointApostropheTest() + { + this.verifier.actual(this.setup.validNamePointApostropheAtlas(), + new MixedCaseNameCheck(inlineConfiguration)); + this.verifier.globallyVerify(flags -> Assert.assertEquals(0, flags.size())); + } + + @Test + public void validNamePointApostropheLowerTest() + { + this.verifier.actual(this.setup.validNamePointApostropheLowerAtlas(), + new MixedCaseNameCheck(inlineConfiguration)); + this.verifier.globallyVerify(flags -> Assert.assertEquals(0, flags.size())); + } + + @Test + public void validNamePointApostropheAllCapsTest() + { + this.verifier.actual(this.setup.validNamePointApostropheAllCapsAtlas(), + new MixedCaseNameCheck(inlineConfiguration)); + this.verifier.globallyVerify(flags -> Assert.assertEquals(0, flags.size())); + } + + @Test + public void validNamePointCapsApostropheTest() + { + this.verifier.actual(this.setup.validNamePointCapsApostropheAtlas(), + new MixedCaseNameCheck(inlineConfiguration)); + this.verifier.globallyVerify(flags -> Assert.assertEquals(0, flags.size())); + } + + @Test + public void validNamePointCapsLowerApostropheTest() + { + this.verifier.actual(this.setup.validNamePointCapsLowerApostropheAtlas(), + new MixedCaseNameCheck(inlineConfiguration)); + this.verifier.globallyVerify(flags -> Assert.assertEquals(0, flags.size())); + } + + @Test + public void invalidNamePointApostropheTest() + { + this.verifier.actual(this.setup.invalidNamePointApostropheAtlas(), + new MixedCaseNameCheck(inlineConfiguration)); + this.verifier.globallyVerify(flags -> Assert.assertEquals(1, flags.size())); + } + + @Test + public void validNamePointAllCapsTest() + { + this.verifier.actual(this.setup.validNamePointAllCapsAtlas(), + new MixedCaseNameCheck(inlineConfiguration)); + this.verifier.globallyVerify(flags -> Assert.assertEquals(0, flags.size())); + } + + @Test + public void validNamePointNoCapsTest() + { + this.verifier.actual(this.setup.validNamePointNoCapsAtlas(), + new MixedCaseNameCheck(inlineConfiguration)); + this.verifier.globallyVerify(flags -> Assert.assertEquals(0, flags.size())); + } + + @Test + public void validNamePointLowerCasePrepositionTest() + { + this.verifier.actual(this.setup.validNamePointLowerCasePrepositionAtlas(), + new MixedCaseNameCheck(inlineConfiguration)); + this.verifier.globallyVerify(flags -> Assert.assertEquals(0, flags.size())); + } + + @Test + public void validNamePointLowerCaseArticleTest() + { + this.verifier.actual(this.setup.validNamePointLowerCaseArticleAtlas(), + new MixedCaseNameCheck(inlineConfiguration)); + this.verifier.globallyVerify(flags -> Assert.assertEquals(0, flags.size())); + } + + @Test + public void validNamePointLowerCaseArticleStartTest() + { + this.verifier.actual(this.setup.validNamePointLowerCaseArticleStartAtlas(), + new MixedCaseNameCheck(inlineConfiguration)); + this.verifier.globallyVerify(flags -> Assert.assertEquals(0, flags.size())); + } + + @Test + public void invalidNamePointLowerCaseArticleStartTest() + { + this.verifier.actual(this.setup.invalidNamePointLowerCaseArticleStartAtlas(), + new MixedCaseNameCheck(inlineConfiguration)); + this.verifier.globallyVerify(flags -> Assert.assertEquals(1, flags.size())); + } + + @Test + public void validNamePointMixedCaseUnitTest() + { + this.verifier.actual(this.setup.validNamePointMixedCaseUnitAtlas(), + new MixedCaseNameCheck(inlineConfiguration)); + this.verifier.globallyVerify(flags -> Assert.assertEquals(0, flags.size())); + } + + @Test + public void invalidNamePointEnTest() + { + this.verifier.actual(this.setup.invalidNamePointEnAtlas(), + new MixedCaseNameCheck(inlineConfiguration)); + this.verifier.globallyVerify(flags -> Assert.assertEquals(1, flags.size())); + } + + @Test + public void invalidNamePointGreekTest() + { + this.verifier.actual(this.setup.invalidNamePointGreekAtlas(), + new MixedCaseNameCheck(inlineConfiguration)); + this.verifier.globallyVerify(flags -> Assert.assertEquals(1, flags.size())); + } + + @Test + public void validNamePointGreekTest() + { + this.verifier.actual(this.setup.validNamePointGreekAtlas(), + new MixedCaseNameCheck(inlineConfiguration)); + this.verifier.globallyVerify(flags -> Assert.assertEquals(0, flags.size())); + } + + @Test + public void invalidNamePointGreekElTest() + { + this.verifier.actual(this.setup.invalidNamePointGreekElAtlas(), + new MixedCaseNameCheck(inlineConfiguration)); + this.verifier.globallyVerify(flags -> Assert.assertEquals(1, flags.size())); + } + + @Test + public void validNamePointGreekElTest() + { + this.verifier.actual(this.setup.validNamePointGreekElAtlas(), + new MixedCaseNameCheck(inlineConfiguration)); + this.verifier.globallyVerify(flags -> Assert.assertEquals(0, flags.size())); + } + + @Test + public void invalidNamePointChnTest() + { + this.verifier.actual(this.setup.invalidNamePointChnAtlas(), + new MixedCaseNameCheck(inlineConfiguration)); + this.verifier.globallyVerify(flags -> Assert.assertEquals(1, flags.size())); + } + + @Test + public void validNamePointChnTest() + { + this.verifier.actual(this.setup.validNamePointChnAtlas(), + new MixedCaseNameCheck(inlineConfiguration)); + this.verifier.globallyVerify(flags -> Assert.assertEquals(0, flags.size())); + } +} diff --git a/src/test/java/org/openstreetmap/atlas/checks/validation/tag/MixedCaseNameCheckTestRule.java b/src/test/java/org/openstreetmap/atlas/checks/validation/tag/MixedCaseNameCheckTestRule.java new file mode 100644 index 000000000..8322ad5e0 --- /dev/null +++ b/src/test/java/org/openstreetmap/atlas/checks/validation/tag/MixedCaseNameCheckTestRule.java @@ -0,0 +1,367 @@ +package org.openstreetmap.atlas.checks.validation.tag; + +import org.openstreetmap.atlas.geography.atlas.Atlas; +import org.openstreetmap.atlas.utilities.testing.CoreTestRule; +import org.openstreetmap.atlas.utilities.testing.TestAtlas; +import org.openstreetmap.atlas.utilities.testing.TestAtlas.Area; +import org.openstreetmap.atlas.utilities.testing.TestAtlas.Edge; +import org.openstreetmap.atlas.utilities.testing.TestAtlas.Line; +import org.openstreetmap.atlas.utilities.testing.TestAtlas.Loc; +import org.openstreetmap.atlas.utilities.testing.TestAtlas.Node; +import org.openstreetmap.atlas.utilities.testing.TestAtlas.Point; + +/** + * Tests for {@link MixedCaseNameCheck} + * + * @author bbreithaupt + */ +public class MixedCaseNameCheckTestRule extends CoreTestRule +{ + private static final String TEST_1 = "47.2136626201459,-122.443275382856"; + private static final String TEST_2 = "47.2138327316739,-122.44258668766"; + private static final String TEST_3 = "47.2136626201459,-122.441897992465"; + + @TestAtlas( + // points + points = { @Point(coordinates = @Loc(value = TEST_1), tags = { "iso_country_code=USA", + "name=tEst TeSt" }) }) + private Atlas invalidNamePointAtlas; + + @TestAtlas( + // nodes + nodes = { @Node(coordinates = @Loc(value = TEST_1), tags = { "iso_country_code=USA", + "name=tEst TeSt" }) }) + private Atlas invalidNameNodeAtlas; + + @TestAtlas( + // Lines + lines = { @Line(coordinates = { @Loc(value = TEST_1), @Loc(value = TEST_2), + @Loc(value = TEST_3) }, tags = { "iso_country_code=USA", "name=tEst TeSt" }) }) + private Atlas invalidNameLineAtlas; + + @TestAtlas( + // Areas + areas = { @Area(coordinates = { @Loc(value = TEST_1), @Loc(value = TEST_2), + @Loc(value = TEST_3) }, tags = { "iso_country_code=USA", "name=tEst TeSt" }) }) + private Atlas invalidNameAreaAtlas; + + @TestAtlas( + // nodes + nodes = { @Node(coordinates = @Loc(value = TEST_1)), + @Node(coordinates = @Loc(value = TEST_3)) }, + // edges + edges = { @Edge(coordinates = { @Loc(value = TEST_1), @Loc(value = TEST_2), + @Loc(value = TEST_3) }, tags = { "iso_country_code=USA", "name=tEst TeSt" }) }) + private Atlas invalidNameEdgeAtlas; + + @TestAtlas( + // points + points = { @Point(coordinates = @Loc(value = TEST_1), tags = { "iso_country_code=USA", + "name=tEst" }) }) + private Atlas invalidNamePointOneWordAtlas; + + @TestAtlas( + // points + points = { @Point(coordinates = @Loc(value = TEST_1), tags = { "iso_country_code=USA", + "name=Test-Test" }) }) + private Atlas validNamePointHyphenAtlas; + + @TestAtlas( + // points + points = { @Point(coordinates = @Loc(value = TEST_1), tags = { "iso_country_code=USA", + "name=2test Test" }) }) + private Atlas validNamePointNumberAtlas; + + @TestAtlas( + // points + points = { @Point(coordinates = @Loc(value = TEST_1), tags = { "iso_country_code=USA", + "name=Test-TesT" }) }) + private Atlas invalidNamePointHyphenAtlas; + + @TestAtlas( + // points + points = { @Point(coordinates = @Loc(value = TEST_1), tags = { "iso_country_code=USA", + "name=McMan" }) }) + private Atlas validNamePointAffixAtlas; + + @TestAtlas( + // points + points = { @Point(coordinates = @Loc(value = TEST_1), tags = { "iso_country_code=USA", + "name=McMaN" }) }) + private Atlas invalidNamePointAffixAtlas; + + @TestAtlas( + // points + points = { @Point(coordinates = @Loc(value = TEST_1), tags = { "iso_country_code=USA", + "name=O'Flanagan" }) }) + private Atlas validNamePointApostropheAtlas; + + @TestAtlas( + // points + points = { @Point(coordinates = @Loc(value = TEST_1), tags = { "iso_country_code=USA", + "name=Scott's" }) }) + private Atlas validNamePointApostropheLowerAtlas; + + @TestAtlas( + // points + points = { @Point(coordinates = @Loc(value = TEST_1), tags = { "iso_country_code=USA", + "name=SCOTT'S" }) }) + private Atlas validNamePointApostropheAllCapsAtlas; + + @TestAtlas( + // points + points = { @Point(coordinates = @Loc(value = TEST_1), tags = { "iso_country_code=USA", + "name=SCOTT's" }) }) + private Atlas validNamePointCapsApostropheAtlas; + + @TestAtlas( + // points + points = { @Point(coordinates = @Loc(value = TEST_1), tags = { "iso_country_code=USA", + "name=SCOTTs'" }) }) + private Atlas validNamePointCapsLowerApostropheAtlas; + + @TestAtlas( + // points + points = { @Point(coordinates = @Loc(value = TEST_1), tags = { "iso_country_code=USA", + "name=Scott'S" }) }) + private Atlas invalidNamePointApostropheAtlas; + + @TestAtlas( + // points + points = { @Point(coordinates = @Loc(value = TEST_1), tags = { "iso_country_code=USA", + "name=TEST" }) }) + private Atlas validNamePointAllCapsAtlas; + + @TestAtlas( + // points + points = { @Point(coordinates = @Loc(value = TEST_1), tags = { "iso_country_code=USA", + "name=test" }) }) + private Atlas validNamePointNoCapsAtlas; + + @TestAtlas( + // points + points = { @Point(coordinates = @Loc(value = TEST_1), tags = { "iso_country_code=USA", + "name=Test of Test" }) }) + private Atlas validNamePointLowerCasePrepositionAtlas; + + @TestAtlas( + // points + points = { @Point(coordinates = @Loc(value = TEST_1), tags = { "iso_country_code=USA", + "name=Test a Test" }) }) + private Atlas validNamePointLowerCaseArticleAtlas; + + @TestAtlas( + // points + points = { @Point(coordinates = @Loc(value = TEST_1), tags = { "iso_country_code=USA", + "name=A Test" }) }) + private Atlas validNamePointLowerCaseArticleStartAtlas; + + @TestAtlas( + // points + points = { @Point(coordinates = @Loc(value = TEST_1), tags = { "iso_country_code=USA", + "name=a Test" }) }) + private Atlas invalidNamePointLowerCaseArticleStartAtlas; + + @TestAtlas( + // points + points = { @Point(coordinates = @Loc(value = TEST_1), tags = { "iso_country_code=USA", + "name=Test (20kV) Test" }) }) + private Atlas validNamePointMixedCaseUnitAtlas; + + @TestAtlas( + // points + points = { @Point(coordinates = @Loc(value = TEST_1), tags = { "iso_country_code=USA", + "name:en=tEst TeSt" }) }) + private Atlas invalidNamePointEnAtlas; + + @TestAtlas( + // points + points = { @Point(coordinates = @Loc(value = TEST_1), tags = { "iso_country_code=GRC", + "name=τΕστ ΤεΣτ" }) }) + private Atlas invalidNamePointGreekAtlas; + + @TestAtlas( + // points + points = { @Point(coordinates = @Loc(value = TEST_1), tags = { "iso_country_code=GRC", + "name=Τεστ Τεστ" }) }) + private Atlas validNamePointGreekAtlas; + + @TestAtlas( + // points + points = { @Point(coordinates = @Loc(value = TEST_1), tags = { "iso_country_code=GRC", + "name:el=τΕστ ΤεΣτ" }) }) + private Atlas invalidNamePointGreekElAtlas; + + @TestAtlas( + // points + points = { @Point(coordinates = @Loc(value = TEST_1), tags = { "iso_country_code=GRC", + "name:el=Τεστ Τεστ" }) }) + private Atlas validNamePointGreekElAtlas; + + @TestAtlas( + // points + points = { @Point(coordinates = @Loc(value = TEST_1), tags = { "iso_country_code=CHN", + "name:en=tEst TeSt", "name=Test of Test" }) }) + private Atlas invalidNamePointChnAtlas; + + @TestAtlas( + // points + points = { @Point(coordinates = @Loc(value = TEST_1), tags = { "iso_country_code=CHN", + "name:en=Test of Test", "name=tEst TeSt" }) }) + private Atlas validNamePointChnAtlas; + + public Atlas invalidNamePointAtlas() + { + return this.invalidNamePointAtlas; + } + + public Atlas invalidNameNodeAtlas() + { + return this.invalidNameNodeAtlas; + } + + public Atlas invalidNameLineAtlas() + { + return this.invalidNameLineAtlas; + } + + public Atlas invalidNameAreaAtlas() + { + return this.invalidNameAreaAtlas; + } + + public Atlas invalidNameEdgeAtlas() + { + return this.invalidNameEdgeAtlas; + } + + public Atlas invalidNamePointOneWordAtlas() + { + return this.invalidNamePointOneWordAtlas; + } + + public Atlas validNamePointHyphenAtlas() + { + return this.validNamePointHyphenAtlas; + } + + public Atlas validNamePointNumberAtlas() + { + return this.validNamePointNumberAtlas; + } + + public Atlas invalidNamePointHyphenAtlas() + { + return this.invalidNamePointHyphenAtlas; + } + + public Atlas validNamePointAffixAtlas() + { + return this.validNamePointAffixAtlas; + } + + public Atlas invalidNamePointAffixAtlas() + { + return this.invalidNamePointAffixAtlas; + } + + public Atlas validNamePointApostropheAtlas() + { + return this.validNamePointApostropheAtlas; + } + + public Atlas validNamePointApostropheLowerAtlas() + { + return this.validNamePointApostropheLowerAtlas; + } + + public Atlas validNamePointApostropheAllCapsAtlas() + { + return this.validNamePointApostropheAllCapsAtlas; + } + + public Atlas validNamePointCapsApostropheAtlas() + { + return this.validNamePointCapsApostropheAtlas; + } + + public Atlas validNamePointCapsLowerApostropheAtlas() + { + return this.validNamePointCapsLowerApostropheAtlas; + } + + public Atlas invalidNamePointApostropheAtlas() + { + return this.invalidNamePointApostropheAtlas; + } + + public Atlas validNamePointAllCapsAtlas() + { + return this.validNamePointAllCapsAtlas; + } + + public Atlas validNamePointNoCapsAtlas() + { + return this.validNamePointNoCapsAtlas; + } + + public Atlas validNamePointLowerCasePrepositionAtlas() + { + return this.validNamePointLowerCasePrepositionAtlas; + } + + public Atlas validNamePointLowerCaseArticleAtlas() + { + return this.validNamePointLowerCaseArticleAtlas; + } + + public Atlas validNamePointLowerCaseArticleStartAtlas() + { + return this.validNamePointLowerCaseArticleStartAtlas; + } + + public Atlas invalidNamePointLowerCaseArticleStartAtlas() + { + return this.invalidNamePointLowerCaseArticleStartAtlas; + } + + public Atlas validNamePointMixedCaseUnitAtlas() + { + return this.validNamePointMixedCaseUnitAtlas; + } + + public Atlas invalidNamePointEnAtlas() + { + return this.invalidNamePointEnAtlas; + } + + public Atlas invalidNamePointGreekAtlas() + { + return this.invalidNamePointGreekAtlas; + } + + public Atlas validNamePointGreekAtlas() + { + return this.validNamePointGreekAtlas; + } + + public Atlas invalidNamePointGreekElAtlas() + { + return this.invalidNamePointGreekElAtlas; + } + + public Atlas validNamePointGreekElAtlas() + { + return this.validNamePointGreekElAtlas; + } + + public Atlas invalidNamePointChnAtlas() + { + return this.invalidNamePointChnAtlas; + } + + public Atlas validNamePointChnAtlas() + { + return this.validNamePointChnAtlas; + } +}